Implementar sistema completo de trazabilidad de lotes
Some checks failed
build-and-deploy / build-and-deploy (push) Failing after 1m47s
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:
501
PLAN_TRAZABILIDAD.md
Normal file
501
PLAN_TRAZABILIDAD.md
Normal file
@@ -0,0 +1,501 @@
|
||||
# Plan de Trazabilidad de Lotes - Seguidor de Lotes
|
||||
|
||||
## Descripción General
|
||||
|
||||
El **Sistema de Trazabilidad de Lotes** es una aplicación web diseñada para rastrear el flujo completo del café desde el ingreso de uva hasta el secado final. Implementa un **modelo de grafo** que permite representar operaciones complejas como:
|
||||
|
||||
- **División**: Un lote se divide en varios (ej: despulpado → primera, segunda, rechazos)
|
||||
- **Combinación**: Varios lotes se mezclan en uno (ej: varios reposos → un secado)
|
||||
- **Transformación**: Un lote cambia de estado (ej: oreado → presecado)
|
||||
- **Ajustes**: Correcciones de merma, cantidad o tipo sin alterar el historial
|
||||
|
||||
---
|
||||
|
||||
## Arquitectura del Sistema
|
||||
|
||||
### Stack Tecnológico
|
||||
|
||||
- **Frontend**: Nuxt 4 + Nuxt UI + Vue 3
|
||||
- **Backend**: Nuxt Server API Routes
|
||||
- **Base de Datos**: PostgreSQL 16
|
||||
- **Autenticación**: Authentik Proxy Outpost
|
||||
- **Despliegue**: Docker + Traefik + Gitea Actions
|
||||
|
||||
### Estructura del Proyecto
|
||||
|
||||
```
|
||||
/home/draganel/repos/seguidorDeLotes/
|
||||
├── nuxt4/
|
||||
│ ├── app/
|
||||
│ │ ├── components/
|
||||
│ │ │ ├── lotes/
|
||||
│ │ │ │ ├── LotesTable.vue
|
||||
│ │ │ │ ├── LoteForm.vue
|
||||
│ │ │ │ ├── LoteCard.vue
|
||||
│ │ │ │ └── TrazabilidadTree.vue
|
||||
│ │ │ └── operaciones/
|
||||
│ │ │ ├── OperacionesTable.vue
|
||||
│ │ │ └── OperacionForm.vue
|
||||
│ │ ├── composables/
|
||||
│ │ │ └── useLotes.ts
|
||||
│ │ └── app.vue
|
||||
│ ├── server/
|
||||
│ │ ├── api/
|
||||
│ │ │ ├── lotes/
|
||||
│ │ │ │ ├── index.get.ts
|
||||
│ │ │ │ ├── index.post.ts
|
||||
│ │ │ │ ├── [id].get.ts
|
||||
│ │ │ │ ├── [id].patch.ts
|
||||
│ │ │ │ ├── [id].delete.ts
|
||||
│ │ │ │ └── [id]/
|
||||
│ │ │ │ └── trazabilidad.get.ts
|
||||
│ │ │ └── operaciones/
|
||||
│ │ │ ├── index.get.ts
|
||||
│ │ │ ├── index.post.ts
|
||||
│ │ │ └── [id].get.ts
|
||||
│ │ ├── utils/
|
||||
│ │ │ ├── db.ts
|
||||
│ │ │ └── queries.ts
|
||||
│ │ └── database/
|
||||
│ │ ├── 01_schema.sql
|
||||
│ │ ├── 02_seed.sql
|
||||
│ │ └── README.md
|
||||
│ └── package.json
|
||||
├── docker-compose.yml
|
||||
└── PLAN_TRAZABILIDAD.md (este archivo)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modelo de Datos
|
||||
|
||||
### Concepto Central: Grafo de Lotes y Operaciones
|
||||
|
||||
El sistema representa la trazabilidad como un **grafo dirigido acíclico (DAG)**:
|
||||
|
||||
- **Nodos = Lotes**: Estados físicos del café
|
||||
- **Aristas = Operaciones**: Eventos que transforman lotes
|
||||
|
||||
### Tablas Principales
|
||||
|
||||
#### 1. `lotes`
|
||||
|
||||
Representa cualquier estado físico del café.
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| `id` | UUID | Identificador único |
|
||||
| `codigo` | TEXT | Código legible (ej: UVA-001, SEC-042) |
|
||||
| `tipo` | TEXT | Tipo de lote (uva, despulpado_primera, oreado, etc.) |
|
||||
| `fecha_creado` | TIMESTAMPTZ | Fecha de creación |
|
||||
| `lugar_id` | INTEGER | Lugar donde se encuentra (opcional) |
|
||||
| `cantidad_kg` | NUMERIC | Cantidad en kilogramos |
|
||||
| `meta` | JSONB | Datos adicionales (humedad, notas, etc.) |
|
||||
|
||||
**Tipos de lote válidos:**
|
||||
- `uva`
|
||||
- `despulpado_primera`
|
||||
- `despulpado_segunda`
|
||||
- `despulpado_rechazos`
|
||||
- `oreado`
|
||||
- `presecado`
|
||||
- `reposo`
|
||||
- `secado`
|
||||
|
||||
#### 2. `operaciones`
|
||||
|
||||
Representa eventos donde lotes se transforman, combinan o dividen.
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| `id` | UUID | Identificador único |
|
||||
| `tipo` | TEXT | Tipo de operación (despulpado, oreado, etc.) |
|
||||
| `fecha` | TIMESTAMPTZ | Fecha de la operación |
|
||||
| `lugar_id` | INTEGER | Lugar donde ocurrió (opcional) |
|
||||
| `meta` | JSONB | Datos adicionales |
|
||||
|
||||
**Tipos de operación válidos:**
|
||||
|
||||
*Operaciones de proceso normal:*
|
||||
- `ingreso` - Ingreso de café uva
|
||||
- `despulpado` - Despulpado del café
|
||||
- `oreado` - Proceso de oreado
|
||||
- `presecado` - Presecado
|
||||
- `reposo` - Reposo
|
||||
- `secado` - Secado final
|
||||
- `traslado` - Movimiento de lote
|
||||
- `mezcla` - Mezcla de lotes
|
||||
|
||||
*Operaciones de ajuste/corrección:*
|
||||
- `ajuste_merma` - Corrección por pérdida de peso
|
||||
- `ajuste_cantidad` - Corrección de cantidad registrada
|
||||
- `ajuste_tipo` - Corrección de tipo de lote
|
||||
- `correccion_asignacion` - Corrección de lote mal asignado
|
||||
- `fusion_manual` - Fusión manual de lotes
|
||||
- `division_manual` - División manual de lotes
|
||||
|
||||
#### 3. `operacion_lotes`
|
||||
|
||||
Relación muchos a muchos entre operaciones y lotes.
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| `operacion_id` | UUID | ID de la operación |
|
||||
| `lote_id` | UUID | ID del lote |
|
||||
| `rol` | TEXT | 'input' o 'output' |
|
||||
| `cantidad_kg` | NUMERIC | Cantidad específica utilizada/producida |
|
||||
|
||||
---
|
||||
|
||||
## Diagrama del Grafo de Ejemplo
|
||||
|
||||
Flujo completo incluido en los datos de ejemplo (`02_seed.sql`):
|
||||
|
||||
```
|
||||
┌───────────────────────────────┐
|
||||
│ INGRESO UVA │
|
||||
│ OP1: ingreso_uva │
|
||||
└─────────────┬─────────────────┘
|
||||
│
|
||||
▼
|
||||
[L_UVA1] 2086 kg
|
||||
│
|
||||
│
|
||||
┌─────────────┴──────────────────┐
|
||||
│ DESPULPADO │
|
||||
│ OP2: despulpado │
|
||||
└───┬─────────┬─────────┬────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
[L_PRIM1] [L_SEG1] [L_RECH1]
|
||||
1500 kg 400 kg 150 kg
|
||||
│
|
||||
│
|
||||
┌────────────┴───────────────────────────┐
|
||||
│ OREADO │
|
||||
│ OP3: oreado (registro mal) │
|
||||
└─────────────────────┬───────────────────┘
|
||||
│
|
||||
▼
|
||||
[L_ORE1] 1500 kg
|
||||
│
|
||||
│
|
||||
┌────────────────────────┴────────────────────────────┐
|
||||
│ AJUSTE DE MERMA │
|
||||
│ OP4: ajuste_merma (1500 → 1480 kg) │
|
||||
└───────────────────────────┬──────────────────────────┘
|
||||
│
|
||||
▼
|
||||
[L_ORE1A] 1480 kg
|
||||
│
|
||||
│
|
||||
┌───────────────────────┴───────────────────────────┐
|
||||
│ AJUSTE DE TIPO │
|
||||
│ OP5: ajuste_tipo (oreado → presecado) │
|
||||
└──────────────────────────┬─────────────────────────┘
|
||||
│
|
||||
▼
|
||||
[L_PRE1] 1480 kg
|
||||
│
|
||||
│
|
||||
┌────────────────────┴────────────────────────┐
|
||||
│ REPOSO │
|
||||
│ OP6: reposo │
|
||||
└───────────────────┬──────────────────────────┘
|
||||
│
|
||||
▼
|
||||
[L_REP1] 1480 kg
|
||||
│
|
||||
│ (+ [L_REP2] 520 kg)
|
||||
│ │
|
||||
┌──────────────────────────┴──────┴────────────────────┐
|
||||
│ SECADO │
|
||||
│ OP7: secado (mezcla) │
|
||||
└───────────────────────┬───────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
[L_SEC1] 2000 kg
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ejemplos de Uso de la API
|
||||
|
||||
### 1. Listar todos los lotes
|
||||
|
||||
```bash
|
||||
GET /api/lotes
|
||||
|
||||
# Con filtros
|
||||
GET /api/lotes?tipo=secado&limit=10
|
||||
```
|
||||
|
||||
**Respuesta:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": "uuid-here",
|
||||
"codigo": "SEC-001",
|
||||
"tipo": "secado",
|
||||
"fecha_creado": "2025-11-21T10:00:00Z",
|
||||
"cantidad_kg": 2000,
|
||||
"meta": { "humedad_final": 11.5 }
|
||||
}
|
||||
],
|
||||
"count": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Obtener trazabilidad completa de un lote
|
||||
|
||||
```bash
|
||||
GET /api/lotes/{id}/trazabilidad
|
||||
```
|
||||
|
||||
**Respuesta:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"historial": [
|
||||
{
|
||||
"lote_id": "uuid-sec-001",
|
||||
"codigo": "SEC-001",
|
||||
"tipo": "secado",
|
||||
"cantidad_kg": 2000,
|
||||
"operacion_id": "uuid-op-secado",
|
||||
"operacion_tipo": "secado",
|
||||
"profundidad": 0
|
||||
},
|
||||
{
|
||||
"lote_id": "uuid-rep-001",
|
||||
"codigo": "REP-001",
|
||||
"tipo": "reposo",
|
||||
"cantidad_kg": 1480,
|
||||
"operacion_id": "uuid-op-reposo",
|
||||
"operacion_tipo": "reposo",
|
||||
"profundidad": 1
|
||||
}
|
||||
// ... más ancestros
|
||||
],
|
||||
"estadisticas": {
|
||||
"total_ancestros": 7,
|
||||
"profundidad_maxima": 6,
|
||||
"kg_iniciales": 2086
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Crear una nueva operación (ejemplo: despulpado)
|
||||
|
||||
```bash
|
||||
POST /api/operaciones
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"tipo": "despulpado",
|
||||
"inputs": [
|
||||
{ "lote_id": "uuid-uva-001", "cantidad_kg": 2086 }
|
||||
],
|
||||
"outputs": [
|
||||
{ "codigo": "PRIM-001", "tipo": "despulpado_primera", "cantidad_kg": 1500 },
|
||||
{ "codigo": "SEG-001", "tipo": "despulpado_segunda", "cantidad_kg": 400 },
|
||||
{ "codigo": "RECH-001", "tipo": "despulpado_rechazos", "cantidad_kg": 150 }
|
||||
],
|
||||
"meta": {
|
||||
"pila": 2,
|
||||
"operador": "Juan Pérez"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Respuesta:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"operacion": {
|
||||
"id": "uuid-operacion",
|
||||
"tipo": "despulpado",
|
||||
"fecha": "2025-11-21T10:00:00Z",
|
||||
"meta": { "pila": 2, "operador": "Juan Pérez" }
|
||||
},
|
||||
"lotes_creados": [
|
||||
{ "id": "uuid-prim", "codigo": "PRIM-001", "tipo": "despulpado_primera" },
|
||||
{ "id": "uuid-seg", "codigo": "SEG-001", "tipo": "despulpado_segunda" },
|
||||
{ "id": "uuid-rech", "codigo": "RECH-001", "tipo": "despulpado_rechazos" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Componentes Frontend
|
||||
|
||||
### Componentes de Lotes
|
||||
|
||||
1. **LotesTable.vue**
|
||||
- Tabla con listado de lotes
|
||||
- Filtros por tipo
|
||||
- Acciones: Ver, Editar, Ver Trazabilidad
|
||||
|
||||
2. **LoteForm.vue**
|
||||
- Formulario para crear/editar lotes
|
||||
- Validación de campos
|
||||
- Soporte para metadata JSON
|
||||
|
||||
3. **LoteCard.vue**
|
||||
- Vista de detalle de un lote
|
||||
- Información completa del lote
|
||||
- Acciones rápidas
|
||||
|
||||
4. **TrazabilidadTree.vue**
|
||||
- Visualización del historial completo
|
||||
- Árbol indentado por profundidad
|
||||
- Estadísticas de trazabilidad
|
||||
|
||||
### Componentes de Operaciones
|
||||
|
||||
1. **OperacionesTable.vue**
|
||||
- Tabla con listado de operaciones
|
||||
- Filtros por tipo
|
||||
- Acciones: Ver detalle
|
||||
|
||||
2. **OperacionForm.vue**
|
||||
- Formulario multi-paso:
|
||||
1. Seleccionar tipo de operación
|
||||
2. Seleccionar lotes input
|
||||
3. Definir lotes output
|
||||
- Creación transaccional
|
||||
|
||||
---
|
||||
|
||||
## Consultas SQL Importantes
|
||||
|
||||
### Obtener trazabilidad completa (función recursiva)
|
||||
|
||||
```sql
|
||||
SELECT * FROM get_trazabilidad('uuid-del-lote-final');
|
||||
```
|
||||
|
||||
Esta función CTE recursiva camina el grafo hacia atrás desde el lote final hasta los ingresos iniciales.
|
||||
|
||||
### Ver lotes con su operación de origen
|
||||
|
||||
```sql
|
||||
SELECT * FROM vista_lotes_con_origen
|
||||
ORDER BY fecha_creado DESC;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Despliegue
|
||||
|
||||
### Requisitos
|
||||
|
||||
- Docker y Docker Compose
|
||||
- Acceso al servidor con Traefik y Authentik configurados
|
||||
- Gitea con Actions habilitado
|
||||
|
||||
### Variables de Entorno
|
||||
|
||||
```env
|
||||
# PostgreSQL
|
||||
POSTGRES_USER=seguidor
|
||||
POSTGRES_PASSWORD=seguidor_password
|
||||
POSTGRES_DB=seguidor_lotes
|
||||
|
||||
# Aplicación
|
||||
APP_NAME=seguidorDeLotes
|
||||
APP_DOMAIN=lotes.nucleoriofrio.com
|
||||
NUXT_PUBLIC_APP_URL=https://lotes.nucleoriofrio.com
|
||||
|
||||
# Registry
|
||||
REG=gitea.nucleoriofrio.com
|
||||
REPO_OWNER=nucleo000
|
||||
```
|
||||
|
||||
### Proceso de Deploy
|
||||
|
||||
1. **Push a main/master** → Gitea Actions se ejecuta automáticamente
|
||||
2. **Build**: Construye imagen Docker
|
||||
3. **Push**: Sube imagen al registry de Gitea
|
||||
4. **Deploy**: Ejecuta `docker-compose up -d` en el servidor
|
||||
|
||||
### Primer Inicio
|
||||
|
||||
Al iniciar PostgreSQL por primera vez, ejecutará automáticamente:
|
||||
|
||||
1. `01_schema.sql` - Crea tablas, índices, funciones
|
||||
2. `02_seed.sql` - Inserta datos de ejemplo
|
||||
|
||||
---
|
||||
|
||||
## Próximos Pasos
|
||||
|
||||
### Fase 2: Visualización Avanzada
|
||||
|
||||
- [ ] Integrar librería de grafos (Cytoscape.js o D3.js)
|
||||
- [ ] Vista de grafo interactivo
|
||||
- [ ] Zoom, pan y selección de nodos
|
||||
- [ ] Colores por tipo de lote
|
||||
|
||||
### Fase 3: Reportes y Análisis
|
||||
|
||||
- [ ] Reporte de trazabilidad en PDF
|
||||
- [ ] Estadísticas por período
|
||||
- [ ] Gráficos de volumen procesado
|
||||
- [ ] Análisis de mermas
|
||||
|
||||
### Fase 4: Características Avanzadas
|
||||
|
||||
- [ ] Gestión de lugares (patios, pilas, bodegas)
|
||||
- [ ] QR codes para lotes
|
||||
- [ ] Escáner móvil
|
||||
- [ ] Notificaciones de eventos
|
||||
- [ ] Integración con básculas
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### La base de datos no se inicializa
|
||||
|
||||
Verificar logs del contenedor postgres:
|
||||
```bash
|
||||
docker logs seguidorDeLotes-postgres
|
||||
```
|
||||
|
||||
### Los datos de ejemplo no se cargan
|
||||
|
||||
Eliminar el volumen y recrear:
|
||||
```bash
|
||||
docker-compose down -v
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Error de conexión a PostgreSQL
|
||||
|
||||
Verificar que el contenedor postgres esté saludable:
|
||||
```bash
|
||||
docker ps
|
||||
# Buscar "healthy" en la columna STATUS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Contacto y Soporte
|
||||
|
||||
- **Proyecto**: Nucleo Rio Frio
|
||||
- **Desarrollador**: Dario (draganel)
|
||||
- **Repositorio**: https://gitea.nucleoriofrio.com/nucleo000/seguidorDeLotes
|
||||
|
||||
---
|
||||
|
||||
## Licencia
|
||||
|
||||
Este proyecto es propiedad de **Nucleo Rio Frio** y está desarrollado para uso interno.
|
||||
Reference in New Issue
Block a user