Inicializar rioCata - Sistema de Catación de Café
- Base de datos PostgreSQL 16 con extensiones JSONB y arrays - Docker Compose para containerización - Scripts SQL de inicialización (schema, funciones, índices, datos de prueba) - Suite de tests de validación (18 tests) - Queries de ejemplo (17 queries) - Script helper para gestión (scripts/riocata.sh) - Documentación completa en README.md Estructura: - 4 tablas principales: sesion, auth.users, sesion_participante, muestra, evaluacion - Tipo ENUM para defectos - 2 triggers automáticos (updated_at, puntaje_final) - 19 índices de optimización (GIN, B-tree, funcionales) - Constraints de validación para arrays y JSONB - 2 funciones auxiliares para análisis
This commit is contained in:
123
postgres/init/01_schema.sql
Normal file
123
postgres/init/01_schema.sql
Normal file
@@ -0,0 +1,123 @@
|
||||
-- ============================================
|
||||
-- rioCata - Sistema de Catación de Café
|
||||
-- Schema de Base de Datos
|
||||
-- ============================================
|
||||
|
||||
-- Habilitar extensiones necesarias
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- ============================================
|
||||
-- TIPOS ENUMERADOS
|
||||
-- ============================================
|
||||
|
||||
-- Tipo de defecto en la catación
|
||||
CREATE TYPE defecto_tipo AS ENUM ('Mohoso', 'Fenólico', 'Papa');
|
||||
|
||||
-- ============================================
|
||||
-- TABLAS
|
||||
-- ============================================
|
||||
|
||||
-- Sesiones de catación
|
||||
CREATE TABLE sesion (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
codigo text UNIQUE, -- opcional: código visible (ej. "S-2025-10-17-01")
|
||||
fecha date NOT NULL,
|
||||
nombre text, -- ej. "Mesa #3 Lotes Lavados"
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE sesion IS 'Sesiones de catación de café';
|
||||
COMMENT ON COLUMN sesion.codigo IS 'Código único identificador de la sesión';
|
||||
COMMENT ON COLUMN sesion.fecha IS 'Fecha en que se realiza la catación';
|
||||
COMMENT ON COLUMN sesion.nombre IS 'Nombre descriptivo de la sesión';
|
||||
|
||||
-- Crear schema auth si no existe (antes de crear la tabla)
|
||||
CREATE SCHEMA IF NOT EXISTS auth;
|
||||
|
||||
-- Tabla de usuarios (simula auth.users de Supabase para desarrollo local)
|
||||
-- En producción se usaría directamente auth.users de Supabase
|
||||
CREATE TABLE auth.users (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
email text UNIQUE NOT NULL,
|
||||
nombre text,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE auth.users IS 'Tabla de usuarios (simula auth.users de Supabase para desarrollo)';
|
||||
|
||||
-- Participantes (catadores) por sesión
|
||||
CREATE TABLE sesion_participante (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
sesion_id uuid NOT NULL REFERENCES sesion(id) ON DELETE CASCADE,
|
||||
catador_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE RESTRICT,
|
||||
rol text DEFAULT 'catador', -- opcional: juez, invitado, etc.
|
||||
UNIQUE (sesion_id, catador_id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE sesion_participante IS 'Relación de participantes (catadores) en sesiones de catación';
|
||||
COMMENT ON COLUMN sesion_participante.rol IS 'Rol del participante: catador, juez, invitado, etc.';
|
||||
|
||||
-- Muestras pertenecen a una sesión
|
||||
CREATE TABLE muestra (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
sesion_id uuid NOT NULL REFERENCES sesion(id) ON DELETE CASCADE,
|
||||
codigo text NOT NULL, -- "Muestra 1", "A-101", etc.
|
||||
posicion int, -- ej. orden físico en mesa
|
||||
UNIQUE (sesion_id, codigo)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE muestra IS 'Muestras de café a ser evaluadas en una sesión';
|
||||
COMMENT ON COLUMN muestra.codigo IS 'Código identificador de la muestra dentro de la sesión';
|
||||
COMMENT ON COLUMN muestra.posicion IS 'Orden físico de la muestra en la mesa de catación';
|
||||
|
||||
-- Evaluación de una muestra hecha por un participante específico de esa sesión
|
||||
CREATE TABLE evaluacion (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
muestra_id uuid NOT NULL REFERENCES muestra(id) ON DELETE CASCADE,
|
||||
sesion_participante_id uuid NOT NULL REFERENCES sesion_participante(id) ON DELETE CASCADE,
|
||||
|
||||
-- JSONB según lo acordado
|
||||
intensidades jsonb NOT NULL, -- { fragancia: {descriptiva, afectiva}, ... }
|
||||
fragancia_aroma_notas jsonb DEFAULT '[]', -- array de objetos [{categoria, subcategoria, notaEspecifica}]
|
||||
sabor_notas jsonb DEFAULT '[]', -- array de objetos
|
||||
|
||||
-- Arrays según lo acordado
|
||||
tazas_no_uniformes smallint[] DEFAULT '{}', -- valores 1..5
|
||||
tazas_defectuosas smallint[] DEFAULT '{}', -- valores 1..5
|
||||
sensacion_en_boca text[] DEFAULT '{}', -- Áspero, Suave, Aceitoso, Metálico, Astringente
|
||||
gustos_predominantes text[] DEFAULT '{}', -- 1..2 elementos entre Salado, Amargo, Ácido, Dulce, Umami
|
||||
|
||||
defecto defecto_tipo, -- opcional
|
||||
otras_notas text,
|
||||
puntaje_final int, -- guardado por trigger
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
|
||||
-- Una evaluación por participante por muestra
|
||||
UNIQUE (muestra_id, sesion_participante_id),
|
||||
|
||||
-- Constraints de validez
|
||||
CHECK (tazas_no_uniformes <@ ARRAY[1,2,3,4,5]::smallint[]),
|
||||
CHECK (tazas_defectuosas <@ ARRAY[1,2,3,4,5]::smallint[]),
|
||||
|
||||
CHECK (sensacion_en_boca <@ ARRAY['Áspero','Suave','Aceitoso','Metálico','Astringente']::text[]),
|
||||
CHECK (gustos_predominantes <@ ARRAY['Salado','Amargo','Ácido','Dulce','Umami']::text[]),
|
||||
CHECK (COALESCE(array_length(gustos_predominantes,1),0) BETWEEN 0 AND 2),
|
||||
|
||||
-- Validación de rangos en intensidades (descriptiva 1..15, afectiva 1..10)
|
||||
CHECK (NOT jsonb_path_exists(intensidades, '$.*.descriptiva ? (@ < 1 || @ > 15)')),
|
||||
CHECK (NOT jsonb_path_exists(intensidades, '$.*.afectiva ? (@ < 1 || @ > 10)'))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE evaluacion IS 'Evaluaciones de muestras realizadas por catadores';
|
||||
COMMENT ON COLUMN evaluacion.intensidades IS 'Intensidades descriptivas y afectivas de los 8 parámetros evaluados';
|
||||
COMMENT ON COLUMN evaluacion.fragancia_aroma_notas IS 'Array de familias de fragancia/aroma (categoría, subcategoría, nota específica)';
|
||||
COMMENT ON COLUMN evaluacion.sabor_notas IS 'Array de familias de sabor (categoría, subcategoría, nota específica)';
|
||||
COMMENT ON COLUMN evaluacion.tazas_no_uniformes IS 'Números de taza (1-5) que no fueron uniformes';
|
||||
COMMENT ON COLUMN evaluacion.tazas_defectuosas IS 'Números de taza (1-5) con defectos graves';
|
||||
COMMENT ON COLUMN evaluacion.sensacion_en_boca IS 'Sensaciones táctiles: Áspero, Suave, Aceitoso, Metálico, Astringente';
|
||||
COMMENT ON COLUMN evaluacion.gustos_predominantes IS 'Gustos básicos predominantes (1-2): Salado, Amargo, Ácido, Dulce, Umami';
|
||||
COMMENT ON COLUMN evaluacion.defecto IS 'Tipo de defecto identificado si aplica';
|
||||
COMMENT ON COLUMN evaluacion.otras_notas IS 'Notas adicionales del catador en texto libre';
|
||||
COMMENT ON COLUMN evaluacion.puntaje_final IS 'Suma de los valores afectivos (calculado automáticamente por trigger)';
|
||||
Reference in New Issue
Block a user