-- ============================================ -- 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)';