Implementar sistema completo de trazabilidad de lotes
Some checks failed
build-and-deploy / build-and-deploy (push) Failing after 1m47s

- Agregar PostgreSQL 16 con esquema completo
- Crear API endpoints para lotes y operaciones
- Implementar UI con Nuxt UI (tablas, formularios, trazabilidad)
- Agregar datos de ejemplo del flujo completo
- Documentar sistema en PLAN_TRAZABILIDAD.md
This commit is contained in:
2025-11-21 18:39:04 -06:00
parent e5456bf522
commit ee3dffa38e
26 changed files with 4223 additions and 74 deletions

View File

@@ -0,0 +1,225 @@
-- =====================================================
-- SISTEMA DE TRAZABILIDAD DE LOTES - ESQUEMA PRINCIPAL
-- =====================================================
-- Este esquema implementa un modelo de grafo para trazabilidad
-- de café desde ingreso de uva hasta secado final.
-- Permite rastrear divisiones, combinaciones y transformaciones.
-- Extensiones necesarias
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- =====================================================
-- TABLA: lotes
-- =====================================================
-- Representa cualquier estado físico del café en un momento dado.
-- Ejemplos: uva ingresada, café despulpado, café oreado, café secado, etc.
CREATE TABLE lotes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
codigo TEXT UNIQUE, -- Código legible: UVA-001, SEC-042, etc.
tipo TEXT NOT NULL, -- uva, despulpado_primera, despulpado_segunda, despulpado_rechazos, oreado, presecado, reposo, secado
fecha_creado TIMESTAMPTZ NOT NULL DEFAULT NOW(),
lugar_id INTEGER, -- Referencia opcional a lugares (patio 1, pila 2, etc.)
cantidad_kg NUMERIC(10,2), -- Cantidad en kilogramos
meta JSONB, -- Información adicional (humedad, notas, etc.)
CONSTRAINT lotes_cantidad_positiva CHECK (cantidad_kg IS NULL OR cantidad_kg >= 0),
CONSTRAINT lotes_tipo_valido CHECK (tipo IN (
'uva',
'despulpado_primera',
'despulpado_segunda',
'despulpado_rechazos',
'oreado',
'presecado',
'reposo',
'secado'
))
);
-- Índices para búsquedas frecuentes
CREATE INDEX idx_lotes_tipo ON lotes(tipo);
CREATE INDEX idx_lotes_fecha_creado ON lotes(fecha_creado DESC);
CREATE INDEX idx_lotes_codigo ON lotes(codigo) WHERE codigo IS NOT NULL;
-- Comentarios
COMMENT ON TABLE lotes IS 'Representa cualquier estado físico del café en un momento dado';
COMMENT ON COLUMN lotes.codigo IS 'Código legible opcional para identificar el lote (ej: UVA-001, SEC-042)';
COMMENT ON COLUMN lotes.tipo IS 'Tipo de lote: uva, despulpado_*, oreado, presecado, reposo, secado';
COMMENT ON COLUMN lotes.meta IS 'Datos adicionales en formato JSON (ej: {humedad: 12.5, notas: "café especial"})';
-- =====================================================
-- TABLA: operaciones
-- =====================================================
-- Representa un evento donde lotes se transforman, combinan o dividen.
-- Ejemplos: ingreso de uva, despulpado, oreado, ajuste de merma, etc.
CREATE TABLE operaciones (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tipo TEXT NOT NULL, -- Tipo de operación
fecha TIMESTAMPTZ NOT NULL DEFAULT NOW(),
lugar_id INTEGER, -- Referencia opcional a lugares
meta JSONB, -- Información adicional específica del tipo
CONSTRAINT operaciones_tipo_valido CHECK (tipo IN (
-- Operaciones de proceso normal
'ingreso',
'despulpado',
'oreado',
'presecado',
'reposo',
'secado',
'traslado',
'mezcla',
-- Operaciones de ajuste/corrección
'ajuste_merma',
'ajuste_cantidad',
'ajuste_tipo',
'correccion_asignacion',
'fusion_manual',
'division_manual'
))
);
-- Índices para búsquedas frecuentes
CREATE INDEX idx_operaciones_tipo ON operaciones(tipo);
CREATE INDEX idx_operaciones_fecha ON operaciones(fecha DESC);
-- Comentarios
COMMENT ON TABLE operaciones IS 'Eventos donde lotes se transforman, combinan o dividen';
COMMENT ON COLUMN operaciones.tipo IS 'Tipo de operación: ingreso, despulpado, oreado, ajuste_merma, etc.';
COMMENT ON COLUMN operaciones.meta IS 'Datos adicionales específicos del tipo de operación en formato JSON';
-- =====================================================
-- TABLA: operacion_lotes
-- =====================================================
-- Relación muchos a muchos entre operaciones y lotes.
-- Define qué lotes entran (input) y salen (output) de cada operación.
CREATE TABLE operacion_lotes (
operacion_id UUID NOT NULL REFERENCES operaciones(id) ON DELETE CASCADE,
lote_id UUID NOT NULL REFERENCES lotes(id) ON DELETE CASCADE,
rol TEXT NOT NULL, -- 'input' o 'output'
cantidad_kg NUMERIC(10,2), -- Cantidad específica usada/producida
PRIMARY KEY (operacion_id, lote_id, rol),
CONSTRAINT operacion_lotes_rol_valido CHECK (rol IN ('input', 'output')),
CONSTRAINT operacion_lotes_cantidad_positiva CHECK (cantidad_kg IS NULL OR cantidad_kg > 0)
);
-- Índices para navegación del grafo
CREATE INDEX idx_operacion_lotes_operacion ON operacion_lotes(operacion_id);
CREATE INDEX idx_operacion_lotes_lote ON operacion_lotes(lote_id);
CREATE INDEX idx_operacion_lotes_rol ON operacion_lotes(rol);
-- Comentarios
COMMENT ON TABLE operacion_lotes IS 'Define qué lotes entran y salen de cada operación (grafo de trazabilidad)';
COMMENT ON COLUMN operacion_lotes.rol IS 'input: lote usado en la operación | output: lote producido por la operación';
COMMENT ON COLUMN operacion_lotes.cantidad_kg IS 'Cantidad en kg que participó en esta relación específica';
-- =====================================================
-- FUNCIÓN: get_trazabilidad
-- =====================================================
-- Obtiene el historial completo de un lote caminando el grafo hacia atrás.
-- Retorna todos los lotes ancestros hasta llegar a los ingresos iniciales.
CREATE OR REPLACE FUNCTION get_trazabilidad(lote_id_inicial UUID)
RETURNS TABLE (
lote_id UUID,
codigo TEXT,
tipo TEXT,
cantidad_kg NUMERIC,
operacion_id UUID,
operacion_tipo TEXT,
profundidad INTEGER
) AS $$
BEGIN
RETURN QUERY
WITH RECURSIVE trazabilidad AS (
-- Punto de partida: el lote final
SELECT
l.id AS lote_id,
l.codigo,
l.tipo,
l.cantidad_kg,
ol.operacion_id,
o.tipo AS operacion_tipo,
0 AS profundidad
FROM lotes l
LEFT JOIN operacion_lotes ol ON ol.lote_id = l.id AND ol.rol = 'output'
LEFT JOIN operaciones o ON o.id = ol.operacion_id
WHERE l.id = lote_id_inicial
UNION ALL
-- Caminar hacia atrás: buscar lotes que fueron inputs
SELECT
l2.id AS lote_id,
l2.codigo,
l2.tipo,
l2.cantidad_kg,
ol2.operacion_id,
o2.tipo AS operacion_tipo,
t.profundidad + 1
FROM trazabilidad t
JOIN operacion_lotes ol_in
ON ol_in.operacion_id = t.operacion_id
AND ol_in.rol = 'input'
JOIN lotes l2
ON l2.id = ol_in.lote_id
LEFT JOIN operacion_lotes ol2
ON ol2.lote_id = l2.id
AND ol2.rol = 'output'
LEFT JOIN operaciones o2
ON o2.id = ol2.operacion_id
WHERE t.operacion_id IS NOT NULL -- Solo continuar si hay operación
)
SELECT * FROM trazabilidad
ORDER BY profundidad, tipo, codigo;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION get_trazabilidad IS 'Obtiene el historial completo de un lote caminando el grafo hacia atrás';
-- =====================================================
-- VISTA: vista_lotes_con_origen
-- =====================================================
-- Vista útil que muestra cada lote con información de la operación que lo creó.
CREATE OR REPLACE VIEW vista_lotes_con_origen AS
SELECT
l.id,
l.codigo,
l.tipo,
l.fecha_creado,
l.cantidad_kg,
l.meta,
o.id AS operacion_id,
o.tipo AS operacion_tipo,
o.fecha AS operacion_fecha
FROM lotes l
LEFT JOIN operacion_lotes ol
ON ol.lote_id = l.id
AND ol.rol = 'output'
LEFT JOIN operaciones o
ON o.id = ol.operacion_id;
COMMENT ON VIEW vista_lotes_con_origen IS 'Muestra lotes con información de la operación que los creó';
-- =====================================================
-- MENSAJES DE ÉXITO
-- =====================================================
DO $$
BEGIN
RAISE NOTICE '✓ Esquema de trazabilidad creado exitosamente';
RAISE NOTICE ' - Tabla lotes creada';
RAISE NOTICE ' - Tabla operaciones creada';
RAISE NOTICE ' - Tabla operacion_lotes creada';
RAISE NOTICE ' - Función get_trazabilidad() creada';
RAISE NOTICE ' - Vista vista_lotes_con_origen creada';
END $$;