- 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
124 lines
6.2 KiB
SQL
124 lines
6.2 KiB
SQL
-- ============================================
|
|
-- 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)';
|