PostgreSQL — motor único
Zentto opera sobre un solo motor de base de datos: PostgreSQL 16, en desarrollo y en producción. La lógica de datos vive en funciones PL/pgSQL y los cambios de esquema se aplican con migraciones Goose. No hay SQL directo en TypeScript.
DOCTRINA VIGENTE (desde 2026-07-24)
El proyecto ya no es dual-database. Nunca crear ni actualizar SPs T-SQL,
aunque el módulo tenga equivalentes históricos en sqlweb/. Si un diff trae T-SQL,
sobra. No preguntar por SQL Server — la respuesta es siempre no.
Nota histórica: la arquitectura dual
Hasta julio de 2026 el ERP mantenía paridad entre SQL Server (T-SQL, sqlweb/) y
PostgreSQL (sqlweb-pg/), con la variable DB_TYPE como switch de motor.
Ese esquema se retiró: duplicaba el esfuerzo de cada cambio y el 100% de los despliegues
(producción, dev, tenants) ya corría en PostgreSQL. Lo que queda de aquella etapa:
| Directorio | Estado |
|---|---|
web/api/sqlweb-pg/ | Vivo — baseline + 368 funciones PL/pgSQL (includes/sp/) + seeds |
web/api/migrations/postgres/ | Vivo — 480+ migraciones Goose (fuente de verdad; la numeración va por 00572) |
web/api/sqlweb-mssql/ y web/api/migrations/sqlserver/ | Legacy congelado — T-SQL histórico, no se toca ni se borra |
DB_TYPE / driver mssql en el código de la API | Rama muerta conservada por compatibilidad — todos los despliegues corren con DB_TYPE=postgres; no escribir código nuevo contra la rama SQL Server |
El SQL Server DELLXEONE31545 (BD DatqBoxWeb) queda solo como referencia
de lectura del sistema legacy VB6 — hoy vive en la VM 102 del
centro de datos dev y es la fuente de la que migra
clientes el Zentto Migrador.
Flujo de un cambio de BD
Todo cambio de base de datos sigue el mismo camino, sin excepciones:
- Migración Goose en
web/api/migrations/postgres/NNNNN_descripcion.sql— DDL, ALTER, seeds y también elCREATE OR REPLACE FUNCTIONdel SP nuevo/modificado. - Espejo de la función en
web/api/sqlweb-pg/includes/sp/usp_*.sql— para que la definición completa quede versionada y las BDs nuevas se provisionen conrun-functions.sql. - Deploy: el CI ejecuta
goose upsobre la BD principal y después sobre cada BD dedicada de tenant (ver Backoffice y multi-tenant).
-- web/api/migrations/postgres/NNNNN_mi_cambio.sql
-- +goose Up
ALTER TABLE master."Product" ADD COLUMN IF NOT EXISTS "Brand" VARCHAR(100);
DROP FUNCTION IF EXISTS usp_master_product_list(INT, VARCHAR, INT, INT);
CREATE OR REPLACE FUNCTION usp_master_product_list(...)
RETURNS TABLE(...) LANGUAGE plpgsql AS $$ ... $$;
-- +goose Down
-- reverso mínimo (el mirror de sqlweb-pg NO debe tomarse del bloque Down) Numeración de migraciones
La numeración NNNNN es secuencial y compartida entre PRs: antes de crear una,
verificar la última en developer (dos PRs con el mismo número, o una migración de
número menor mergeada después de una mayor, hacen que goose la salte en silencio — por eso los
deploys usan goose -allow-missing up).
Ejecución local y en producción
# Local (BD datqboxweb)
cd web/api
goose -dir migrations/postgres postgres "host=localhost dbname=datqboxweb user=postgres" up
# Recrear funciones completas (idempotente)
psql -U postgres -d datqboxweb -f sqlweb-pg/run-functions.sql
# Producción: lo hace el CI (job migrate-pg del deploy) — nunca a mano Reglas y trampas de PL/pgSQL que ya nos costaron bugs
- Nombres en snake_case exacto al llamar:
callSp()solo pasa a minúsculas, no convierte camelCase — el nombre en la API debe coincidir con el de la función. - Literales con cast: en
RETURN QUERY SELECT, los strings van con'texto'::VARCHAR— sin cast, PG infiereTEXTy no coincide con elRETURNS TABLE. p_param IS NULLnecesita CAST cuando el driver manda el parámetro comoNULLsin tipo:(p_search IS NULL OR ...)falla con "could not determine data type" — castear en la llamada o en la condición.- Ambigüedad en
RETURNS TABLE: las columnas de salida son variables dentro del cuerpo; toda columna de tabla homónima debe llevar alias (p."Name", no"Name"). - Cambiar el tipo de retorno exige
DROP FUNCTIONantes:CREATE OR REPLACEno puede cambiar elRETURNS(error 42P13). - Upserts sobre tablas con soft-delete: el
ON CONFLICTdebe excluir (o manejar explícitamente) los registros conIsDeleted— un upsert ingenuo revive registros borrados.
Checklist para cambios de BD
- Migración goose nueva en
migrations/postgres/(número verificado contradeveloper) - Función espejo actualizada en
sqlweb-pg/includes/sp/ - Probada contra PostgreSQL local (o una BD desechable)
- Contrato OpenAPI actualizado si cambia un endpoint
- Tipos TypeScript de la API actualizados
- Cero T-SQL en el diff