Files
seguidorDeLotes/nuxt4/server/database/README.md
josedario87 f8045abb5b
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m4s
Documentar peculiaridades de implementación de PostgreSQL
- Agregar sección completa sobre autenticación md5 vs scram-sha-256
- Documentar race condition entre app y PostgreSQL con retry logic
- Explicar idempotencia de scripts (schema vs seed)
- Documentar persistencia de datos y volúmenes Docker
- Incluir troubleshooting detallado de errores comunes
- Agregar diagramas de arquitectura de inicialización
- Documentar endpoints de debug temporales
- Tabla de decisiones técnicas con alternativas descartadas
- Queries útiles y comandos de backup/restore
2025-11-21 20:18:07 -06:00

618 lines
18 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Database - Documentación Completa
Este directorio contiene los scripts SQL para inicializar y gestionar la base de datos PostgreSQL del sistema de trazabilidad.
## Tabla de Contenidos
- [Archivos SQL](#archivos-sql)
- [⚠️ Peculiaridades de Implementación](#-peculiaridades-de-implementación)
- [Autenticación PostgreSQL](#autenticación-postgresql)
- [Inicialización de la Base de Datos](#inicialización-de-la-base-de-datos)
- [Persistencia de Datos](#persistencia-de-datos)
- [Conexión desde Node.js](#conexión-desde-nodejs)
- [Endpoints de Debug](#endpoints-de-debug)
- [Ejecución de Scripts](#ejecución-de-scripts)
- [Troubleshooting](#troubleshooting)
- [Queries Útiles](#queries-útiles)
---
## Archivos SQL
### `00_configure_auth.sh`
Script Bash que configura la autenticación de PostgreSQL:
- Elimina configuración `scram-sha-256` (incompatible con driver `pg` de Node.js)
- Configura método `md5` en `pg_hba.conf`
- Se ejecuta automáticamente durante inicialización de PostgreSQL
### `01_schema.sql`
Crea el esquema completo de la base de datos:
- Tablas: `lotes`, `operaciones`, `operacion_lotes`
- Índices para optimización
- Función recursiva `get_trazabilidad()` para queries de trazabilidad completa
- Vista `vista_lotes_con_origen`
- Constraints y validaciones
- **Idempotente**: Usa `CREATE TABLE IF NOT EXISTS`
### `02_seed.sql`
Datos de ejemplo que representan un flujo completo de trazabilidad:
- Ingreso de uva (2086 kg)
- Despulpado → primera, segunda, rechazos
- Oreado (con error de registro intencional)
- Ajuste de merma (1500 → 1480 kg)
- Ajuste de tipo (oreado → presecado)
- Reposo
- Secado final (mezcla de 2 lotes = 2000 kg)
- **Total**: 10 lotes, 7 operaciones, 16 relaciones
- **Idempotente con TRUNCATE**: Limpia datos antes de insertar
---
## ⚠️ Peculiaridades de Implementación
Esta sección documenta los desafíos técnicos encontrados y las soluciones implementadas durante el desarrollo.
### Autenticación PostgreSQL
#### ❌ Problema: Incompatibilidad scram-sha-256
**Contexto**: PostgreSQL 16 usa por defecto el método de autenticación `scram-sha-256`, pero el driver `pg` de Node.js tiene problemas de compatibilidad que resultan en:
```
Error code: 28P01
FATAL: password authentication failed for user "seguidor"
```
#### ✅ Solución: Script de configuración automática
**Archivo**: `00_configure_auth.sh`
```bash
#!/bin/bash
set -e
echo "Configurando autenticación de PostgreSQL..."
# Eliminar configuración scram-sha-256
sed -i '/scram-sha-256/d' "$PGDATA/pg_hba.conf"
# Agregar configuración md5
if ! grep -q "host all all all md5" "$PGDATA/pg_hba.conf"; then
echo "host all all all md5" >> "$PGDATA/pg_hba.conf"
fi
echo "✓ Configuración de autenticación aplicada (md5)"
```
**Notas importantes**:
- El script se ejecuta **antes** de que PostgreSQL termine su inicialización
- Los cambios en `pg_hba.conf` se aplican automáticamente al finalizar el inicio
- No es necesario ejecutar `pg_ctl reload` manualmente
- El método `md5` es compatible con el driver `pg` de Node.js
---
### Inicialización de la Base de Datos
#### 📂 Scripts en `/docker-entrypoint-initdb.d/`
Los scripts se ejecutan **solo si el volumen de datos está vacío** (primera vez):
1. **`00_configure_auth.sh`** - Configura autenticación md5
2. **`01_schema.sql`** - Crea estructura (tablas, funciones, vistas)
3. **`02_seed.sql`** - Inserta datos de ejemplo
**Orden de ejecución**: Los archivos se ejecutan en orden alfabético/numérico.
#### 🔄 Idempotencia de Scripts
##### `01_schema.sql` - Totalmente Idempotente ✅
```sql
CREATE TABLE IF NOT EXISTS lotes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- ...
);
CREATE INDEX IF NOT EXISTS idx_lotes_tipo ON lotes(tipo);
```
- ✅ Puede ejecutarse múltiples veces sin errores
- ✅ No duplica tablas ni índices
- ✅ Seguro para re-ejecución
##### `02_seed.sql` - Idempotente con TRUNCATE ⚠️
```sql
-- Línea 17: Limpia datos antes de insertar
TRUNCATE TABLE operacion_lotes, operaciones, lotes CASCADE;
-- Luego inserta datos de ejemplo
INSERT INTO lotes (codigo, tipo, ...) VALUES (...);
```
**IMPORTANTE**:
- ⚠️ Si las tablas no existen, `TRUNCATE` falla
- ✅ Por eso el endpoint `seed-database` ejecuta **primero** el schema
- ✅ Puede ejecutarse múltiples veces si las tablas existen
#### 🔍 Verificación en Workflow de Gitea Actions
El workflow verifica si es necesario inicializar:
```bash
TABLE_EXISTS=$(docker exec lotes-postgres psql -U seguidor -d seguidor_lotes \
-tAc "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'lotes');")
if [ "$TABLE_EXISTS" = "f" ]; then
echo "📝 Ejecutando scripts de inicialización..."
docker exec -i lotes-postgres psql -U seguidor -d seguidor_lotes < nuxt4/server/database/01_schema.sql
docker exec -i lotes-postgres psql -U seguidor -d seguidor_lotes < nuxt4/server/database/02_seed.sql
fi
```
**Comportamiento**:
- ✅ Primera vez: Ejecuta schema + seed
- ✅ Redeploys: Solo actualiza contenedor de app, **datos persisten**
- ✅ Después de `docker-compose down -v`: Reinicializa todo
---
### Persistencia de Datos
#### 💾 Volumen Docker
```yaml
# docker-compose.yml
volumes:
postgres_data: # Volumen nombrado
services:
postgres:
volumes:
- postgres_data:/var/lib/postgresql/data
```
**Comportamiento**:
- ✅ Datos persisten entre `docker-compose down` y `docker-compose up`
- ✅ Datos persisten entre redespliegues del workflow
- ❌ Datos se pierden con `docker-compose down -v` (elimina volúmenes)
#### 🔄 Resetear a Estado Inicial
**Opción 1: Eliminar volumen (desde servidor)**
```bash
docker-compose --project-name lotes down -v
# Próximo deploy reinicializará automáticamente
```
**Opción 2: Usar botones de debug (desde web)**
1. Click en "🗑️ BORRAR TODA LA BD" → Elimina todas las tablas
2. Click en "🌱 CARGAR DATOS DE EJEMPLO" → Recrea schema + seed
3. Recarga la página
**Opción 3: Trigger redeploy después de borrar**
```bash
# 1. Click "🗑️ BORRAR TODA LA BD" en la web
# 2. Trigger redeploy
git commit --allow-empty -m "Trigger reinit DB" && git push
# El workflow detectará tablas faltantes y ejecutará schema + seed
```
---
### Conexión desde Node.js
#### 🔌 Pool de Conexiones
**Configuración en `server/utils/db.ts`**:
```typescript
const config = {
user: process.env.POSTGRES_USER || 'seguidor',
password: process.env.POSTGRES_PASSWORD || 'seguidor_password',
database: process.env.POSTGRES_DB || 'seguidor_lotes',
host: process.env.POSTGRES_HOST || 'postgres',
port: parseInt(process.env.POSTGRES_PORT || '5432'),
max: 20, // Máximo de conexiones en el pool
idleTimeoutMillis: 30000, // 30s timeout para conexiones idle
connectionTimeoutMillis: 10000, // 10s timeout para establecer conexión
}
```
**Peculiaridades**:
1. **Timeout de 10 segundos**: Necesario porque PostgreSQL puede tardar en aplicar configuración de autenticación
2. **Retry Logic Automático**:
```typescript
// Verifica conexión inicial con 5 reintentos
let retries = 5
const tryConnect = async () => {
try {
const client = await pool!.connect()
console.log('✅ Conexión inicial a PostgreSQL exitosa')
client.release()
} catch (err: any) {
retries--
if (retries > 0) {
console.log(`⚠️ Conexión falló, reintentando... (${retries} intentos)`)
setTimeout(tryConnect, 2000) // Reintentar en 2s
}
}
}
```
3. **Logs solo en desarrollo**:
```typescript
if (process.env.NODE_ENV !== 'production') {
pool.on('connect', () => {
console.log('Nueva conexión establecida con PostgreSQL')
})
}
```
#### ⚡ Race Condition: App vs PostgreSQL
**Problema**: Aunque docker-compose tiene `depends_on` con `service_healthy`, el healthcheck solo verifica que PostgreSQL **responde**, no que terminó de ejecutar los scripts de inicialización.
**Timeline Real**:
```
T+0s: PostgreSQL inicia
T+2s: PostgreSQL responde a pg_isready → healthcheck ✅
T+2s: App Nuxt inicia e intenta conectarse
T+3s: PostgreSQL ejecuta 00_configure_auth.sh (cambia auth)
T+4s: Primera conexión de app FALLA (todavía usa scram-sha-256)
T+5s: Script termina, auth está configurado
T+7s: Retry logic de app conecta exitosamente ✅
```
**Solución**: El retry logic compensa esta race condition automáticamente.
---
### Endpoints de Debug
#### ⚠️ Endpoints Temporales
Estos endpoints están marcados como **TEMPORALES** y **NO DEBEN ELIMINARSE** sin consultar a Dario/Draganel/nucleo000.
#### `POST /api/debug/reset-database`
**Función**: Elimina completamente todas las tablas de la base de datos.
**Implementación**:
```typescript
await client.query('DROP TABLE IF EXISTS operacion_lotes CASCADE')
await client.query('DROP TABLE IF EXISTS operaciones CASCADE')
await client.query('DROP TABLE IF EXISTS lotes CASCADE')
await client.query('DROP FUNCTION IF EXISTS get_trazabilidad CASCADE')
await client.query('DROP VIEW IF EXISTS vista_lotes_con_origen CASCADE')
```
**Notas**:
- Usa `DROP TABLE` (no `TRUNCATE`) para que el workflow detecte la ausencia de tablas
- Después de usar, el próximo deploy reinicializará automáticamente
- Requiere confirmación del usuario en el frontend
#### `POST /api/debug/seed-database`
**Función**: Recrea schema y carga datos de ejemplo.
**Implementación**:
```typescript
// 1. Ejecutar schema (crea tablas si no existen)
const schemaSQL = await readFile('server/database/01_schema.sql', 'utf-8')
await client.query(schemaSQL)
// 2. Ejecutar seed (truncate + insert)
const seedSQL = await readFile('server/database/02_seed.sql', 'utf-8')
await client.query(seedSQL)
```
**Peculiaridad**: Los archivos SQL deben estar en el contenedor de la app
**Dockerfile**:
```dockerfile
# Copy SQL files for seed endpoint
COPY --from=builder /app/server/database /app/server/database
```
Sin esta línea, el endpoint devuelve `ENOENT: no such file or directory`.
---
## Ejecución de Scripts
### Automática (Docker Compose)
```yaml
# docker-compose.yml
volumes:
- ./nuxt4/server/database:/docker-entrypoint-initdb.d:ro
```
PostgreSQL ejecuta todos los archivos `.sql` y `.sh` en orden alfabético dentro de `/docker-entrypoint-initdb.d/` **solo la primera vez**.
### Manual - Desde el contenedor Docker
```bash
# Conectarse al contenedor
docker exec -it lotes-postgres psql -U seguidor -d seguidor_lotes
# Dentro de psql, ejecutar:
\i /docker-entrypoint-initdb.d/01_schema.sql
\i /docker-entrypoint-initdb.d/02_seed.sql
```
### Manual - Desde tu máquina local
```bash
# Asegúrate de tener psql instalado
psql -h localhost -U seguidor -d seguidor_lotes -f 01_schema.sql
psql -h localhost -U seguidor -d seguidor_lotes -f 02_seed.sql
```
### Manual - Vía API (endpoints de debug)
```bash
# Resetear BD
curl -X POST https://lotes.nucleoriofrio.com/api/debug/reset-database
# Cargar datos de ejemplo
curl -X POST https://lotes.nucleoriofrio.com/api/debug/seed-database
```
---
## Troubleshooting
### ❌ Error: "password authentication failed for user seguidor"
**Causas posibles**:
1. **Primera carga del sistema** (normal)
- PostgreSQL todavía está configurando autenticación
- Solución: Esperar ~5 segundos, el retry logic se encarga
- Logs: `⚠️ Conexión falló, reintentando...` seguido de `✅ Conexión exitosa`
2. **Volumen de postgres de un deploy anterior con scram-sha-256**
- Solución: Eliminar volumen y redeploy
```bash
docker-compose --project-name lotes down -v
git commit --allow-empty -m "Reinit DB" && git push
```
3. **Variables de entorno incorrectas**
- Verificar en Gitea > Settings > Actions > Secrets/Variables
- Variables requeridas: `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`
### ❌ Error: "relation 'lotes' does not exist"
**Causas posibles**:
1. **Base de datos no inicializada**
- Verificar logs del workflow: ¿se ejecutó la inicialización?
- Solución: Click en "🌱 CARGAR DATOS DE EJEMPLO"
2. **Múltiples contenedores PostgreSQL**
- App conectándose al contenedor equivocado
- Solución: Limpiar contenedores huérfanos
```bash
docker ps -a | grep postgres
docker rm -f [contenedores antiguos]
```
3. **Usaste TRUNCATE en lugar de DROP**
- Si usaste el botón de borrar BD pero las tablas quedaron vacías
- Solución: Click en "🌱 CARGAR DATOS DE EJEMPLO" (ahora ejecuta schema primero)
### ❌ Error: "ENOENT: no such file or directory, open '/app/server/database/02_seed.sql'"
**Causa**: Archivos SQL no copiados al contenedor de la app
**Solución**: Verificar que Dockerfile incluya:
```dockerfile
COPY --from=builder /app/server/database /app/server/database
```
Luego rebuild:
```bash
git commit -am "Fix Dockerfile" && git push
```
### 📊 Logs: Múltiples "Nueva conexión establecida con PostgreSQL"
**Comportamiento**: Es **normal y correcto** en desarrollo
- El pool crea múltiples conexiones (configurado para max 20)
- Cada endpoint que hace query puede obtener una conexión del pool
**Si quieres reducir logs**: Ya está configurado para no mostrar en producción
```typescript
if (process.env.NODE_ENV !== 'production') {
pool.on('connect', () => console.log('Nueva conexión...'))
}
```
---
## Queries Útiles
### Verificar que todo está correcto
```sql
-- 1. Listar tablas
\dt
-- 2. Contar registros
SELECT
(SELECT COUNT(*) FROM lotes) as total_lotes,
(SELECT COUNT(*) FROM operaciones) as total_operaciones,
(SELECT COUNT(*) FROM operacion_lotes) as total_relaciones;
-- 3. Ver lotes creados
SELECT codigo, tipo, cantidad_kg FROM lotes ORDER BY fecha_creado;
-- 4. Probar función de trazabilidad
SELECT * FROM get_trazabilidad(
(SELECT id FROM lotes WHERE codigo = 'SEC-001')
);
```
### Ver todas las operaciones de un lote
```sql
SELECT
o.tipo AS operacion,
o.fecha,
ol.rol,
ol.cantidad_kg
FROM operacion_lotes ol
JOIN operaciones o ON o.id = ol.operacion_id
WHERE ol.lote_id = (SELECT id FROM lotes WHERE codigo = 'SEC-001')
ORDER BY o.fecha;
```
### Ver inputs directos de un lote
```sql
SELECT
l.codigo,
l.tipo,
l.cantidad_kg,
o.tipo AS operacion_tipo
FROM lotes l
JOIN operacion_lotes ol_in ON ol_in.lote_id = l.id
JOIN operacion_lotes ol_out ON ol_out.operacion_id = ol_in.operacion_id
JOIN operaciones o ON o.id = ol_out.operacion_id
WHERE ol_out.lote_id = (SELECT id FROM lotes WHERE codigo = 'SEC-001')
AND ol_out.rol = 'output'
AND ol_in.rol = 'input';
```
### Estadísticas de operaciones
```sql
SELECT
tipo,
COUNT(*) as total,
SUM(cantidad_kg) as kg_totales
FROM operaciones
WHERE fecha >= NOW() - INTERVAL '30 days'
GROUP BY tipo
ORDER BY total DESC;
```
---
## Arquitectura de Inicialización
```
┌─────────────────────────────────────────────────────────────────┐
│ INICIALIZACIÓN COMPLETA (solo primera vez) │
└─────────────────────────────────────────────────────────────────┘
1. docker-compose up
2. PostgreSQL container inicia
3. Volumen postgres_data vacío? → SÍ
4. Ejecuta /docker-entrypoint-initdb.d/ en orden:
├─ 00_configure_auth.sh → Cambia pg_hba.conf a md5
├─ 01_schema.sql → Crea tablas, funciones, vistas
└─ 02_seed.sql → Inserta 10 lotes, 7 ops, 16 rels
5. PostgreSQL termina inicio → healthcheck ✅
6. App container inicia
7. App intenta conectar (puede fallar 1-2 veces)
8. Retry logic espera y reconecta
9. ✅ Sistema funcionando
┌─────────────────────────────────────────────────────────────────┐
│ REDESPLIEGUE (datos persisten) │
└─────────────────────────────────────────────────────────────────┘
1. git push → Workflow inicia
2. Build nueva imagen Docker
3. docker-compose down (sin -v) → PostgreSQL se detiene
4. docker-compose up → PostgreSQL reinicia
5. Volumen postgres_data existe? → SÍ
6. PostgreSQL lee datos existentes
7. NO ejecuta scripts de /docker-entrypoint-initdb.d/
8. App container inicia con nueva imagen
9. ✅ Sistema funcionando (con datos previos)
```
---
## Backup y Restore
### Hacer backup
```bash
# Backup completo
docker exec lotes-postgres pg_dump -U seguidor seguidor_lotes > backup_$(date +%Y%m%d).sql
# Backup solo schema (sin datos)
docker exec lotes-postgres pg_dump -U seguidor -s seguidor_lotes > schema_$(date +%Y%m%d).sql
# Backup solo datos
docker exec lotes-postgres pg_dump -U seguidor -a seguidor_lotes > data_$(date +%Y%m%d).sql
```
### Restaurar backup
```bash
# Restaurar desde archivo
cat backup_20251121.sql | docker exec -i lotes-postgres psql -U seguidor -d seguidor_lotes
# Restaurar directo desde servidor
docker exec -i lotes-postgres psql -U seguidor -d seguidor_lotes < backup.sql
```
---
## Resumen de Decisiones Técnicas
| Decisión | Razón | Alternativa Descartada |
|----------|-------|------------------------|
| md5 en lugar de scram-sha-256 | Compatibilidad con driver pg de Node.js | Actualizar driver (requiere cambios mayores) |
| Retry logic en pool | Compensa race condition de inicialización | depends_on custom healthcheck (complejo) |
| Timeout de 10s | PostgreSQL puede tardar en configurar auth | 2s (insuficiente) |
| DROP TABLE en reset | Workflow detecta ausencia y reinicializa | TRUNCATE (deja tablas vacías) |
| seed ejecuta schema primero | Funciona después de borrar BD | Solo seed (falla si no hay tablas) |
| Scripts SQL en app container | Permite endpoint de seed | Solo en postgres (no accesible desde app) |
| Volumen nombrado | Persistencia entre redeploys | Volumen bind mount (menos portable) |
---
## Referencias
- **PostgreSQL Authentication**: https://www.postgresql.org/docs/16/auth-pg-hba-conf.html
- **node-postgres (pg)**: https://node-postgres.com/
- **Docker Init Scripts**: https://hub.docker.com/_/postgres (sección "Initialization scripts")
- **Pool de Conexiones**: https://node-postgres.com/apis/pool
- **Recursive Queries (CTE)**: https://www.postgresql.org/docs/current/queries-with.html
---
**Última actualización**: 2025-11-21
**Autor**: Claude Code (claudeCode0@nucleoriofrio.com)
**Proyecto**: Seguidor de Lotes - Sistema de Trazabilidad de Café