Fase 5 (migracion de datos): - api/scripts/migrate-sqlite-to-postgres.ts: ETL unico SQLite -> Postgres. Pre-flight (CURP/RFC duplicados case-insensitive, FKs huerfanas, tenant_id invalido, fechas mal formateadas) -> carga por base/esquema con credenciales _owner (setval de secuencias, OVERRIDING SYSTEM VALUE, ON CONFLICT DO NOTHING idempotente) -> verificacion de conteos y sumas de dinero con tolerancia. Probado end-to-end contra un dataset SQLite legacy sintetico y un Postgres limpio (solo schema+catalogos, sin contexto dev): 100% de las filas migradas, sumas de dinero exactas, snapshot de uploaded_by/created_by resuelto correctamente. - db/RUNBOOK-corte.md: checklist go/no-go para el corte por ambiente. - Fix de bug real encontrado al probar: sin --context-filter explicito, Liquibase corre TAMBIEN los changesets context=dev (comportamiento por defecto, no "modo seguro") -- documentado en db/README.md con el ejemplo correcto (--context-filter='!dev' para staging/produccion). Fase 5b: patron de compensacion para createTenant ya resuelto en el rewrite de saas.ts (fase 3/4). Fase 6 (backups): db/backups/ con plantillas pgBackRest por base, politica de retencion, nota de persistencia minima de Redis (RDB, sin retencion de negocio), checklist de simulacro de restauracion mensual, y la validacion pendiente de si Coolify permite WAL archiving antes de comprometerse a PITR real. Fase 7 (limpieza y CI): - Dockerfile.api simplificado (sin JRE/Liquibase/FFI). Dockerfile.migrate y Dockerfile.provision nuevos, de un solo uso, para el paso explicito de deploy (nunca sirven trafico). - docker-compose.yml: postgres+redis+provision+migrate para dev local end-to-end; api ya no arranca hasta que migrate termina bien. - docs/coolify.md y .env.example actualizados al modelo de 2 bases + Redis + Contabo. - api/schema.sql eliminado (desincronizado, competia con Liquibase como fuente de verdad). - .github/workflows/ci.yml: Postgres+Redis de servicio, aprovisiona roles/ACLs, dry-run de Liquibase (updateSQL) antes de aplicar, verify-isolation.sh, deno check + deno test, build de los dos frontends. Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
3.8 KiB
Runbook de corte SQLite → Postgres (Fase 5)
Checklist operativo para migrar UN ambiente (dev, luego staging, luego
producción -- nunca saltar directo a producción). Usa
api/scripts/migrate-sqlite-to-postgres.ts.
0. Antes de empezar
- Postgres del ambiente aprovisionado (
db/provision/) y migrado sin datos de demo:./db/update.sh all --context-filter='!dev'. - Backup fresco del volumen SQLite actual (
data/app.db,data/platform.db) guardado aparte, fuera del volumen que se va a apagar. - Credenciales
_ownerde los 3 módulos disponibles en el entorno (DATABASE_URL_PLATFORM_OWNER,DATABASE_URL_IAM_OWNER,DATABASE_URL_CORE_OWNER).
1. Pre-flight (sin ventana de mantenimiento, se puede correr en caliente)
cd api
deno run --allow-ffi --allow-net --allow-read --allow-write --allow-env \
scripts/migrate-sqlite-to-postgres.ts --dry-run \
--app-db=../data/app.db --platform-db=../data/platform.db
Si el pre-flight reporta [FAIL], no continuar -- corregir los datos
en SQLite (duplicados, huérfanas, tenant_id inválido, fechas mal
formateadas) y repetir hasta que todo salga [OK].
2. Ventana de mantenimiento (corte real)
Hoy no existe un "modo mantenimiento" en la app. Opciones, de menor a mayor invasividad:
- Parar el contenedor/proceso
api(nadie puede escribir mientras está abajo -- los fronts mostrarán error de conexión). - Responder 503 temporal en nginx para
/v1/*mientras se corre el ETL.
Elegir una, documentar la hora exacta de inicio.
3. Migración
cd api
set -a && source /ruta/al/.env.del.ambiente && set +a
deno run --allow-ffi --allow-net --allow-read --allow-write --allow-env \
scripts/migrate-sqlite-to-postgres.ts \
--app-db=/ruta/a/app.db --platform-db=/ruta/a/platform.db
El script hace: pre-flight → carga (una transacción por base/esquema,
todo o nada) → setval() de secuencias → verificación de conteos y sumas
de dinero. Si CUALQUIER paso falla, no queda un estado a medias en
Postgres (la transacción de esa base se revierte completa), pero SQLite
sigue siendo la fuente de verdad -- no se ha cortado nada todavía.
4. Criterios go/no-go
Antes de apuntar la app a Postgres y apagar SQLite:
- El script terminó con
Migración completa y verificada.(exit code 0). - Conteos de filas origen=destino en todas las tablas listadas
(no solo las 9 de ejemplo del script -- ampliar
verifyCounts()si el ambiente tiene datos en tablas no cubiertas ahí). - Diferencia de sumas de dinero dentro de la tolerancia ($0.05) -- si no cuadra, decidir explícitamente si se acepta el redondeo REAL→NUMERIC o se investiga antes de continuar.
- Spot-check manual de 2-3 registros conocidos (un trabajador, un preupuesto, un préstamo) comparando app vieja vs. Postgres.
Si algo no cuadra: no cortar. Volver a levantar la app contra SQLite (no se tocó), investigar, y repetir desde el paso 1 en otro intento.
5. Cutover
- Actualizar
DATABASE_URL_*/REDIS_URL_*del servicioapia los valores del ambiente Postgres/Redis recién migrado. - Levantar
api-- el fail-fast de arranque (/v1/health) debe responder{"ok":true,...}antes de reabrir tráfico. - Reabrir tráfico (quitar el 503/levantar el contenedor).
- Login de prueba con un usuario real del ambiente.
6. Después del corte
- Conservar el volumen SQLite (
data/) como respaldo frío por un período de retención definido (ej. 30 días) antes de borrarlo. - Correr
./db/provision/verify-isolation.shcontra el ambiente para confirmar que el aislamiento de roles/RLS/ACLs sigue intacto. - Repetir todo el runbook en el siguiente ambiente (dev → staging → producción), nunca en paralelo.