# Base de datos local y migraciones ## Entornos [Resultados y límites de validación](./validacion-docker-y-migraciones-bd.md). Desarrollo, integración HTTP y E2E utilizan la imagen oficial MariaDB 11.8.3 basada en Ubuntu 24.04, fijada por digest en `infrastructure/database/compose.yaml`. Cambiar esa referencia requiere comprobar compatibilidad con producción y ejecutar las suites de BD. El código de la aplicación sigue ejecutándose en WSL Ubuntu-24.04, con Node.js 24 y PNPM 11.25.0. El checkout de esta adaptación está en `/home/david/dev/factuges/factuges-erp`; la ruta anterior documentada en AGENTS.md no existe en este entorno. Ejecutar los comandos desde la raíz Linux real. ## Desarrollo ```bash pnpm db:dev:up pnpm db:migrate pnpm db:status pnpm db:verify pnpm dev:server:local ``` La primera orden genera `infrastructure/database/.env` si falta, con contraseñas aleatorias y permisos 0600. No sobrescribe ficheros existentes. El archivo está excluido de Git; no compartirlo ni copiar sus valores en documentación o logs. Compose publica MariaDB en `127.0.0.1:3307`, usa la base `factuges_erp_development` y conserva los datos en un volumen nombrado. El puerto se puede cambiar en ese `.env`. Las órdenes de desarrollo leen exclusivamente ese fichero y rechazan otros nombres de base u hosts. Los comandos `dev` del servidor y `dev:server:local` leen el `.env` habitual de `apps/server` (si existe) y superponen únicamente la conexión Docker local, sin pasarle al proceso la contraseña administrativa ni reescribir archivos existentes. Para arrancar el servidor con otras herramientas, configurar la conexión local con `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER` y `DB_PASS` del archivo generado, y `DB_SYNC_MODE=none`. El `.env` de la aplicación necesita también su configuración habitual de HTTP, JWT y servicios. No se reescriben los `.env` locales existentes automáticamente. El usuario de aplicación tiene lectura del esquema y del historial, y DML por tabla activa. No puede escribir en `database_migrations`. Las órdenes locales de migración usan la contraseña administrativa en una conexión separada. La inicialización del volumen concede lectura; `db:migrate`, `db:adopt` y `db:recover` ajustan los permisos del usuario local después de verificar el esquema. Modificar las credenciales de un `.env` no cambia cuentas ya persistidas. ```bash pnpm db:dev:logs pnpm db:dev:down ``` Detener Compose conserva el volumen. Para eliminar exclusivamente los datos de este proyecto local existe una orden distinta: ```bash pnpm db:dev:reset --confirm-reset ``` El reset no forma parte del arranque ni de las migraciones. Después hay que volver a levantar la base y ejecutar `db:migrate`. No se cargan empresas ni cuentas de demostración automáticamente; se conserva el flujo funcional de bootstrap/onboarding. ## Migraciones El catálogo y el motor están en `modules/core/src/api/infrastructure/database`, compartidos por servidor y worker. Esta ubicación evita dependencias entre aplicaciones. La CLI está en `apps/server/src/infrastructure/database` y no necesita HTTP, JWT ni certificados. Se utiliza Umzug sobre una conexión SQL dedicada de `mysql2`. Se mantiene la misma sesión desde `GET_LOCK` hasta `RELEASE_LOCK`, incluidos los commits implícitos del DDL. La aplicación continúa utilizando Sequelize v6. La migración `202610170001-initial-schema` crea las 30 tablas del runtime activo. Es SQL congelado, ordenado por dependencias y sin imports de modelos. Las longitudes, fechas, índices, claves externas y colaciones reflejan los modelos registrados al preparar esta versión; no se han cambiado contratos de dominio para acomodar SQL antiguo. `database_migrations` registra nombre, checksum SHA-256, estado, fechas y si se adoptó un esquema existente. La aplicación no tiene permisos para modificar ese historial en desarrollo. Un checksum distinto, una versión desconocida, un hueco o una migración incompleta impiden continuar. Servidor y worker verifican historial y definiciones de tablas antes de arrancar. La comparación es deliberadamente estricta: usa `SHOW CREATE TABLE`, ignora el contador `AUTO_INCREMENT` y el formato de líneas, pero no diferencias en columnas, índices, restricciones, orden o nombres. Tablas adicionales ajenas al contrato, por ejemplo legacy, se conservan y no impiden adoptar una base compatible. `DB_SYNC_MODE` solo admite `none`. No se ejecuta `sequelize.sync()` al arrancar, preparar HTTP ni preparar E2E. ### Incorporar un cambio nuevo 1. Añadir un fichero de migración con SQL explícito y un identificador ordenable único al catálogo. No modificar migraciones ya aplicadas ni importar modelos. 2. Actualizar el contrato vigente en `schema-contract.ts`, conservando el snapshot inicial. Los cambios del esquema y los datos deben respetar tenants y referencias. 3. Comprobar precondiciones y prever ejecución parcial. Una segunda invocación del ejecutor debe dejar aplicadas las migraciones sin repetirlas. 4. Añadir una prueba de actualización desde la versión anterior con datos representativos, además de la instalación vacía y las verificaciones de contrato. 5. Ejecutar las suites dirigidas, build y comprobaciones del área afectada. Los seeds de escenarios pertenecen a los tests; los datos opcionales de desarrollo no son migraciones. No reutilizar UUIDs de empresas de los antiguos SQL de ejemplo. ## Bases existentes No ejecutar la migración inicial sobre una base que ya contiene tablas. El comando `migrate` lo rechaza antes de aplicar DDL. 1. Obtener un backup y comprobar su restauración en una instancia aislada. 2. Usar `status` para ver tablas ausentes o diferentes. Comparar sus definiciones con el SQL inicial y revisar los datos antes de cualquier reconciliación. 3. Preparar cambios específicos para las diferencias reales. La herramienta no elimina columnas, tablas legacy ni datos automáticamente. 4. Cuando el esquema activo coincida, ejecutar `adopt` con el ejecutable de esta versión inicial. Solo registra el baseline; no recrea tablas ni modifica sus datos. 5. Ejecutar `verify` y ensayar los flujos funcionales antes del cambio real. La adopción inicial se limita a esta versión de baseline. Si ya existen migraciones posteriores, usar las herramientas del baseline para adoptar y después actualizar con el ejecutor de la versión destino. No marcar todas las versiones como aplicadas. La herramienta verifica estructura, no garantiza por sí sola la validez de todos los datos históricos. Deben auditarse huérfanos, contadores y contratos fiscales en la copia del entorno concreto. No se ha inspeccionado ni adoptado producción. ## Fallos y recuperación MariaDB hace commits implícitos con DDL: una migración de varios pasos puede quedar parcialmente aplicada. El ejecutor registra `running` antes del SQL, `failed` si puede registrar el fallo, y `applied` únicamente tras completarla. Un proceso interrumpido puede dejar `running`; nunca se reintenta automáticamente ese SQL. `status` es de lectura y muestra estados incompletos y diferencias. Corregir o restaurar la base mediante un procedimiento revisado. `recover` solo permite registrar como aplicada una migración final incompleta cuando el esquema completo coincide con el contrato y no hay migraciones posteriores pendientes. No ejecuta SQL correctivo ni comprueba backfills de datos: estos deben comprobarse por separado antes de recuperar. No se ofrece rollback destructivo automático. ## Tests y CI ```bash pnpm test:unit pnpm test:db pnpm test:http pnpm test:e2e ``` Cada launcher crea su propio proyecto Compose con nombre aleatorio, puerto local dinámico y credenciales generadas. No utiliza la MariaDB de desarrollo ni la de producción. El contenedor y sus volúmenes se eliminan al terminar, también ante fallos. `test:unit` excluye las suites HTTP y los ficheros `*.integration.test.ts`; no necesita Docker. El paralelismo se limita a cuatro procesos para evitar crear cientos de workers simultáneos. La suite HTTP usa una base con nombre único por runtime sobre su instancia exclusiva. La contraseña administrativa se transmite solo al runtime de test para crear esas bases y conceder acceso al usuario temporal. No toma su destino de `.env.test`: el launcher lo sustituye y el runtime verifica su prefijo de ejecución. La configuración no relacionada con BD puede proceder de `.env.test` o, si falta, de `.env.test.example`. Los E2E conservan sus ficheros locales de configuración de aplicación y sus fixtures; la preparación del esquema ahora ejecuta las mismas migraciones que desarrollo. Ver [infraestructura E2E](../tests/e2e/README.md). La suite de migraciones comprueba instalación vacía, reejecución conservando datos, adopción compatible/incompatible, checksum, diferencias de esquema, bloqueo y recuperación. También comprueba la CLI compilada. `pnpm test:database:ci` agrupa las validaciones de base de datos; requiere Docker y los `.env.e2e` configurados. Puede integrarse en el proveedor de CI existente sin imponer uno nuevo. ## Ejecución desde el artefacto desplegable ```bash pnpm --filter @erp/factuges-server build node --env-file=/ruta/segura/migraciones.env apps/server/dist/database-cli.js status node --env-file=/ruta/segura/migraciones.env apps/server/dist/database-cli.js migrate node --env-file=/ruta/segura/migraciones.env apps/server/dist/database-cli.js verify ``` En un contenedor de aplicación se ejecuta la misma CLI incluida en `dist` como proceso independiente. Su configuración mínima es `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASS` y, si corresponde, `DB_SSL=true`. Para producción, suministrar credenciales de migración separadas y verificar el backup y la compatibilidad de aplicación antes del despliegue. El usuario ordinario requiere DML y lectura de metadatos/historial; revisar cualquier permiso adicional que necesite la instalación. Las órdenes PNPM de la raíz `db:*` están restringidas a desarrollo. Para otros destinos usar la CLI compilada con un archivo de configuración explícito. Los antiguos SQL en `docs/*/sql` y `modules/identity/sql` quedan como referencia histórica, diagnóstico o procedimiento específico. No se ejecutan automáticamente ni constituyen una segunda fuente del esquema vigente.