Saltar a contenido

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