# Base de datos (Postgres + Liquibase) PANELS usa **Postgres** organizado como monolito modular: dos bases de datos físicamente separadas en el mismo servidor/instancia por ambiente. | Base de datos | Esquema(s) | Changelog | Qué vive ahí | |---|---|---|---| | `panels_platform` | `public` | `db/platform/` | Control plane SaaS: `tenants`, `platform_users` (identidad de PANELS como operador), `smtp_settings`. Solo lo tocan las rutas `/v1/saas/*`. | | `panels_product` | `iam` | `db/iam/` | Identidad/roles/permisos **de cada tenant** (ej. usuarios de ARCTEC). Aislado de `core` -- sin JOIN/FK cruzado. | | `panels_product` | `core` | `db/core/` | Negocio: empresas, personal, obras, expedientes, presupuesto, nómina, gafetes. | No hay FK real entre `panels_platform` y `panels_product` (bases distintas), ni entre los esquemas `iam` y `core` (aislamiento a propósito). `tenant_id` es una referencia lógica validada en la capa de aplicación. Ver el plan de migración para el detalle de la arquitectura y las reglas del monolito modular. ## Aprovisionamiento (una vez por ambiente, antes de Liquibase) Ver [`db/provision/README.md`](provision/README.md): crea los 6 roles de Postgres (`panels_{platform,iam,core}_{owner,app}`), las 2 bases de datos, los esquemas `iam`/`core`, la extensión `citext`, y los usuarios ACL de Redis. Incluye `dev-local.sh` para reproducir todo esto en una máquina de desarrollo sin depender de Coolify, y `verify-isolation.sh` para confirmar que el aislamiento realmente se cumple. ## Aplicar migraciones Requiere Java 17+ y las credenciales del rol `_owner` de cada módulo (nunca `_app`, que es solo runtime): ```bash # Local, después de correr db/provision/dev-local.sh: set -a && source .env.dev-local && set +a ./db/update.sh all --context-filter=dev # dev: incluye datos de demo ./db/update.sh all --context-filter='!dev' # staging/producción: solo esquema + catálogos # Un solo módulo ./db/update.sh core --context-filter='!dev' ./db/update.sh iam --context-filter='!dev' ./db/update.sh platform --context-filter='!dev' ``` **Importante**: Liquibase, si NO se le pasa `--context-filter`, corre TODOS los changesets sin importar su `context` -- incluidos los de `context="dev"`. No pasar el filtro en staging/producción NO es "modo seguro por defecto", es lo contrario: cargaría el tenant/empresas/proyecto de demostración. Por eso `--context-filter` es obligatorio siempre, nunca opcional. `npm run db:migrate` corre `./db/update.sh all`. **Las migraciones ya NO corren automáticamente al arrancar la API** -- son un paso explícito de deploy (a diferencia del `runLiquibase()` que existía con SQLite). Contexts de Liquibase: - Sin `context`: changesets de esquema y catálogos requeridos (`risk_levels`, `document_types`, etc.) -- corren en **todos** los ambientes. - `context="dev"`: datos de demostración (tenant/empresas/proyecto ARCTEC) -- **nunca** en staging/producción. Siempre pasar `--context-filter` explícito; no confiar en el default de Liquibase. ## Nuevos cambios de schema Un changeset nuevo en `db/{platform,iam,core}/changesets/`, referenciado en el `changelog-master.xml` correspondiente. Reglas: - Nunca editar un changeset ya aplicado en un ambiente compartido -- crear uno nuevo. - Usar `dbms:postgresql` en vez de `context` para lógica específica de motor. - No añadir migraciones directamente en `api/*.ts`. - Las herramientas (Liquibase + driver JDBC de Postgres) viven en `db/tools/` (gitignored); `bootstrap-tools.sh` las descarga. ## Bootstrap del primer usuario administrador El `password_hash` (PBKDF2) no lo puede generar un changeset SQL. El primer `platform_admin` y el primer `tenant_admin` de un tenant nuevo se crean con `scripts/bootstrap-admin.ts` (ver Fase 4 del plan de migración), no con lógica de seed en el arranque de la API ni con datos hardcodeados en Liquibase.