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
502 lines
15 KiB
Markdown
502 lines
15 KiB
Markdown
# 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.
|