Arquitectura
Topología por colegio
Cada colegio corre su propia instancia dockerizada (fork + VPS con Dokploy). Solo nginx expone puertos; el core y la base de datos viven en la red interna.
flowchart LR
subgraph Internet
W[Panel web SPA<br/>staff]
M[App de consulta<br/>familias/alumnos]
EXT[Sistemas externos<br/>x-api-key]
end
subgraph VPS [Docker — red interna]
N[nginx :8080<br/>SPA + proxy + TLS]
B[BFF NestJS<br/>cookies httpOnly · rate limit]
C[Core NestJS<br/>dominio · Prisma · workers]
P[(PostgreSQL<br/>datos + colas pg-boss)]
BK[backup<br/>pg_dump diario]
end
MD[Moodle del colegio]
W --> N
M --> N
EXT --> N
N --> B --> C --> P
BK --> P
C -. web services .-> MD
- BFF: única cara pública del backend. Sesión web por cookies httpOnly
(el navegador nunca ve los JWT), passthrough Bearer para la app, rate
limiting, y proxy 1:1 al core propagando
x-correlation-id. - Core: todo el dominio en vertical slices (NestJS + Prisma). Nunca expuesto a internet.
- Solo PostgreSQL: las colas corren sobre la misma base con pg-boss — sin Redis ni brokers que operar.
Consistencia: el patrón outbox
Los efectos críticos (notificar a la familia, aprovisionar Moodle, generar boletas) nunca pueden perderse ni ejecutarse a medias:
sequenceDiagram
participant D as Docente
participant C as Core
participant DB as PostgreSQL
participant W as Worker/Dispatcher
participant F as FCM / SMTP / Moodle
D->>C: registrar falta
C->>DB: BEGIN — falta + evento outbox (misma transacción)
DB-->>C: COMMIT
Note over DB: si algo falla, se revierte TODO:<br/>ni falta sin alerta ni alerta sin falta
W->>DB: reclama pendientes (SKIP LOCKED)
W->>F: envía push/email · llama Moodle
alt éxito
W->>DB: COMPLETADO
else error
W->>DB: reintento con backoff (5×) → FALLIDO visible en Configuración → Sistema
end
Decisiones clave
| Decisión | Por qué |
|---|---|
| Fork + instancia por colegio | Aislamiento total de datos; personalización sin multi-tenancy |
| UUID v7 como id público | Time-ordered (indexa bien) y apto para interconexión externa |
Optimistic locking (version) |
Ediciones concurrentes de notas/matrícula sin pisarse |
| Excel todo-o-nada | Nunca una importación a medias silenciosa |
| Migraciones al boot + admin bootstrap | Un colegio nuevo arranca con docker compose up |
| Backups con restore drill | Un backup que nunca se restauró no es un backup (backend/scripts/restore.sh) |
Stack de pruebas
| Suite | Qué cubre |
|---|---|
| Unit (Jest) | Reglas de dominio puras (promedios, supletorios, logger) |
| E2E backend (22) | Flujos completos contra Postgres real, volumen 600+ |
| Vitest + Playwright smoke | Front: componentes, flujos clave y regresión responsive (390px) |
journey (13) + sad-paths (13) |
La simulación de este manual: colegio desde cero + errores esperados |