Flujo generate → migrate de Drizzle, la migración automática en el arranque de producción, la función NCF de numeración fiscal, el riesgo de drift y la regla de aplicar todo cambio a ambas bases.
Las migraciones de ixiclinic son archivos SQL versionados generados por Drizzle Kit y guardados
en apps/api/src/db/migrations/. Cada migración queda registrada en
migrations/meta/_journal.json (tag, índice y timestamp).
Drizzle compara el esquema con el estado anterior y escribe un nuevo NNNN_<nombre>.sql
en src/db/migrations/, añadiendo la entrada correspondiente a meta/_journal.json.
Revisar el SQL generado antes de aplicarlo (ver el aviso de drift más abajo).
Aplicar la migración a la base de desarrollo (Neon):
Si migrate.js sale con código distinto de cero, el API no arranca — se prefiere fallar
rápido a correr sobre un esquema roto. Las migraciones SQL se copian junto al migrate.js
compilado dentro de la imagen (COPY ... src/db/migrations -> dist/db/migrations).
No todo el esquema vive en migraciones. apps/api/src/db/ensure-ncf-function.ts aplica DDL
idempotente en el arranque para piezas de cumplimiento fiscal dominicano que conviene
mantener convergentes entre desarrollo y producción sin una migración manual. Crea, entre
otras cosas:
La función PostgreSQL next_fiscal_number(tenant_id, ncf_type), que incrementa de forma
atómica (con FOR UPDATE) el contador de la secuencia fiscal activa y devuelve el NCF/eNCF
formateado. Autoprovisiona la secuencia B02 (Consumidor Final) si el tenant no tiene una;
el resto de tipos (B01, B15, B16…) requieren autorización DGII y deben configurarse en Ajustes.
Un índice único parcial uq_fiscal_seq_active sobre (tenant_id, ncf_type) para secuencias activas.
La función next_generic_number(tenant_id) y la tabla generic_sequences para documentos
no fiscales (p. ej. FAC-000001).
Las tablas e-CF (ecf_configurations, ecf_submissions) y el soporte de facturación
agrupada de empresas (columnas invoices.company_id, orders.grouped_invoice_id, etc.).
Todas estas sentencias usan CREATE ... IF NOT EXISTS / ADD COLUMN IF NOT EXISTS /
CREATE OR REPLACE, por lo que se pueden ejecutar repetidamente sin efectos secundarios.
pnpm db:generate compara el esquema TypeScript con el estado del journal, pero el DDL
idempotente de ensure-ncf-function.ts y otros ajustes aplicados fuera de las migraciones
no están reflejados en ese estado. El resultado es que db:generate a veces arrastra
drift: incluye en el .sql cambios que en realidad ya existen en la base.
Importante: revisa siempre el .sql recién generado y recórtalo para que contenga
únicamente lo nuevo. Si dejas DDL redundante o conflictivo, el migrate.js del arranque
de producción puede fallar y, como el API falla rápido, el contenedor no levantará.
Si una base ya tiene el esquema (creado fuera del migrador, p. ej. con db:push), hay que
marcar las migraciones existentes como ya aplicadas para que el migrador solo aplique las
nuevas de ahí en adelante. Para eso está apps/api/scripts/baseline-prod-migrations.mjs:
El script es genérico: lee DATABASE_URL del entorno (no trae credenciales), recorre
meta/_journal.json, calcula el hash SHA-256 de cada .sql —tal como hace Drizzle— e inserta
las entradas faltantes en drizzle.__drizzle_migrations. Es idempotente: las migraciones ya
registradas se omiten.