Skip to content
GitHub

Arquitectura del Sistema

Esta página proporciona una visión integral de toda la arquitectura del sistema, incluyendo el diseño de servicios, patrones de comunicación, stack tecnológico y decisiones arquitectónicas clave.

ServicioTecnologíaPuertoPropósito
SevastopolAstro 5.x + SolidJS4321Capa de presentación, enrutamiento de vistas, componentes UI
OrchestratorNode.js 20+ / Express 5.x8000Lógica de negocio, acceso a datos, servicios API

flowchart TD
    A[Cliente Navegador<br/>Agente de Usuario]

    A -->|HTTP GET /command| B

    subgraph FE["Servicio Frontend :4321"]
        B[Páginas Astro<br/>src/pages/*.astro]
        B --> C[Renderiza SSR<br/>view router / Carga Dinámica de Vistas]
        C --> D[Carga<br/>Islands SolidJS<br/>src/components/islands/*]
        D --> E[Llamadas API]
        E --> F[Proxy API<br/>src/pages/api/*]
    end

    F -->|Proxy con cookie sid| G

    subgraph BE["Servicio Orchestrator :8000"]
        G[App Express<br/>src/app.ts]
        G --> H[authenticateToken<br/>src/middleware/auth.ts]
        H --> I[authorizeRoute<br/>src/lib/rbac.ts]
        I --> J[Manejadores de Rutas<br/>src/routes/*]
        J --> K[Servicios de Dominio<br/>src/domain/*]
    end

    K -->|centralPool| DB1[(nostromo_command<br/>Central)]
    K -->|commonPool| DB2[(nostromo_common<br/>Parámetros)]
    K -->|getTenantPool| DB3[(nostromo_*<br/>DBs Tenant)]

El siguiente diagrama muestra cómo fluye una petición API autenticada típica a través del sistema:

sequenceDiagram
    participant N as 🌐 Navegador
    participant V as Sevastopol
    participant P as Proxy API
    participant A as Orchestrator
    participant M as Auth Middleware
    participant R as RBAC
    participant D as PostgreSQL

    N->>V: Click en navegación
    V->>V: Dispara sidebar:navigate
    V->>P: authenticatedFetch('/api/employees')
    Note right of V: Cookie sid adjunta

    P->>A: HTTP con cookie sid
    A->>M: Verifica JWT + sesión
    M->>D: Consulta sessions
    D-->>M: Sesión válida ✅

    M->>R: Verifica permisos
    R->>R: rbac.canAccess(role, route)

    alt ✅ Autorizado
        R->>A: Ejecuta handler
        A->>D: Query SQL
        D-->>A: Datos
        A-->>P: JSON (snake_case)
        P-->>V: Respuesta
        V->>N: Re-renderiza UI
    else ❌ No autorizado
        R-->>A: 403 Forbidden
        A-->>P: Error
        P-->>V: Error
        V->>N: Mensaje de error
    end

Sevastopol usa Astro con Islands Architecture de SolidJS para hidratación selectiva e interactividad.

ComponenteTecnologíaVersión
FrameworkAstro (SSR)5.x
UI LibrarySolidJS (Islands)1.x
EstilosTailwindCSS3.x
Build ToolVite6.x
flowchart TD
    A[command.astro<br/>Contenedor Principal]

    A -->|Inicializa| B

    subgraph GV["Gestión de Vistas"]
        B[view-router.ts<br/>Cargador dinámico]
        M[menu.config.ts<br/>Estructura de navegación]
        B -->|Lee| M
    end

    B -->|import dinámico| I1
    B -->|import dinámico| I2
    B -->|import dinámico| I3

    subgraph IS["Islands SolidJS"]
        I1[Remuneraciones<br/>PayrollViewIsland<br/>EmployeesViewIsland]
        I2[Administración<br/>TenantsViewIsland<br/>CompanyViewIsland]
        I3[Contabilidad<br/>OperationsViewIsland]
    end

    I1 & I2 & I3 -->|Envuelve en| C

    subgraph BC["Biblioteca de Componentes"]
        C[IslandBase.tsx]
        C --> O[Organismos]
        O --> MO[Moléculas]
        MO --> AT[Átomos]
    end
  1. Usuario hace clic en elemento de navegación en Sidebar.astro
  2. Sidebar dispara CustomEvent("sidebar:navigate", detail: viewKey)
  3. View router escucha el evento en view-router.ts
  4. Router realiza import() dinámico del módulo island correspondiente
  5. Función render() de SolidJS monta el island en #command-view
  6. Router emite CustomEvent("view:state") para actualizar estado activo

Orchestrator implementa Diseño Orientado al Dominio (DDD) con contextos delimitados claros y una arquitectura por capas.

ComponenteTecnología
RuntimeNode.js 20+
FrameworkExpress 5.x
LenguajeTypeScript 5.x
Base de DatosPostgreSQL (node-postgres)
DocumentosPuppeteer, docxtemplater
flowchart TD
    subgraph PE["Punto de Entrada"]
        S["server.ts"] --> A["app.ts"]
    end

    subgraph R["Capa de Rutas"]
        R1[command/]
        R2[remuneraciones/]
        R3[common/]
        R4[admin/]
    end

    subgraph MW["Middleware"]
        M1["auth.ts<br/>authenticateToken()"]
        M2["rbac.ts<br/>canAccess()"]
    end

    subgraph D["Capa de Dominio"]
        D1[PayrollService<br/>PayrollRepository]
        D2[CommonDataService<br/>PdfService]
        D3[ChartOfAccountsService]
    end

    subgraph DB["Base de Datos"]
        DB1["db.ts<br/>centralPool / commonPool / getTenantPool()"]
    end

    A --> R1 & R2 & R3 & R4
    A --> M1 & M2
    R1 & R2 --> D1
    R3 --> D2
    R4 --> D3
    D1 & D2 & D3 --> DB1
  1. Middleware de Seguridad: helmet() para encabezados HTTP seguros
  2. CORS: Configurado para puertos 4320-4322 con credenciales
  3. Logging: morgan('combined') para registro de peticiones
  4. Parsing de Body: express.json() con límite de 2MB
  5. Parsing de Cookies: cookieParser() para cookies de sesión
PrefijoMóduloPropósito
/api/tenantcommand/tenant.tsGestión de tenants (SUPER_ADMIN)
/api/admincommand/users.tsGestión de usuarios
/api/sessionscommand/sessions.tsMonitoreo de sesiones
/api/parameterscommon/parameters.tsIndicadores económicos
/api/remuneraciones/payrollremuneraciones/payroll.tsLiquidaciones
/api/employeesremuneraciones/employees.tsCRUD empleados
/api/admin/chart-of-accountsadmin/chart-of-accounts.tsPlan de cuentas

Clases Clave:

  • PayrollService: Orquesta el flujo de generación de liquidaciones
  • PayrollRepository: Recopilación de contexto y persistencia
  • SocialLawsCalculator: Cálculos de AFP, salud, seguro de cesantía
  • HealthPlanCalculator: Lógica FONASA vs ISAPRE
  • TaxCalculator: Impuesto progresivo (Impuesto 2da Categoría)

Patrón de Recopilación de Contexto:

PayrollRepository.ts
// Consultas paralelas para optimizar rendimiento
const [attendanceRes, indicatorsRes, afpCode, taxRes] = await Promise.all([
this.getAttendanceData(db, contractRes.employeeId, periodStr),
CommonDataService.getIndicators(periodStr),
this.getAfpCode(db, contractId),
CommonDataService.getTaxBrackets(periodStr)
]);

Proporciona parámetros regulatorios compartidos:

  • CommonDataService: Consultas de parámetros temporales
  • PdfService: Generación de documentos usando Puppeteer

  • Query params: snake_case (ej: ?empleado_id=123)
  • Body: JSON en snake_case
  • Auth: Cookie sid con token JWT
MétodoPatrónPropósito
GET/api/resourceListar todos (con filtros opcionales)
GET/api/resource/:idObtener por ID
POST/api/resourceCrear nuevo
PUT/PATCH/api/resource/:idActualizar existente
DELETE/api/resource/:idEliminar (con ?force=true opcional)
Ejemplo: Rutas de Remuneraciones
router.get("/", async (req, res) => { /* Listar */ });
router.get("/vista/:id", async (req, res) => { /* Obtener por ID */ });
router.get("/:id/details", async (req, res) => { /* Obtener detalles */ });
router.post("/generar", async (req, res) => { /* Crear/Generar */ });
router.patch("/:id", async (req, res) => { /* Actualizar */ });
router.delete("/:id", async (req, res) => { /* Eliminar */ });

El sistema usa tres tipos de connection pools:

flowchart LR
    subgraph PS["Pools Singleton"]
        CP[centralPool]
        CMP[commonPool]
    end

    subgraph PD["Pools Dinámicos"]
        F["getTenantPool(dbName)"]
        M[Map en caché]
        F --> M
    end

    subgraph DB["Bases de Datos"]
        DB1[(nostromo_command)]
        DB2[(nostromo_common)]
        DB3[(nostromo_6000431)]
        DB4[(nostromo_7000123)]
    end

    CP --> DB1
    CMP --> DB2
    M --> DB3
    M --> DB4
  1. Extraer tenant_id de los parámetros de consulta
  2. Mapear a nombre de base de datos: nostromo_${tenant_id}
  3. Recuperar o crear connection pool
  4. Ejecutar consulta en base de datos específica del tenant
lib/db.ts
const tenantDb = await getDatabaseNameForUser(req.user, req);
const pool = getTenantPool(tenantDb);
const result = await pool.query(sql, params);

Sevastopol usa un patrón de proxy para comunicarse con Orchestrator:

  1. Proxy de Desarrollo: Vite hace proxy de /api/* a http://localhost:8000
  2. Páginas Proxy API: sevastopol/src/pages/api/* implementan proxies simples
  3. Fetch Autenticado: La utilidad authenticatedFetch() adjunta la cookie sid
Ejemplo de Proxy
// sevastopol/src/pages/api/admin/chart-of-accounts.ts
import { createProxy } from '@/lib/proxyUtils';
export const { GET, POST, PUT, DELETE } = createProxy('/api/admin/chart-of-accounts');
  1. Usuario inicia sesión vía /api/auth/login
  2. Orchestrator valida credenciales contra nostromo_command.users
  3. Crea sesión en tabla sessions
  4. Retorna cookie sid con token JWT (HttpOnly)
  5. Todas las peticiones subsecuentes incluyen la cookie
  6. Middleware authenticateToken valida token y sesión

CapaResponsabilidadEjemplo
PresentaciónRenderizado UI, interacciónview-router.ts, Islands SolidJS
API GatewayEnrutamiento, auth, RBACapp.ts, rbac.ts
ServicioOrquestación de lógicaPayrollService.generatePayroll()
RepositorioAcceso a datosPayrollRepository.getPayrollContext()
CalculadorLógica de negocio puraSocialLawsCalculator.calculate()

Cada dominio es autocontenido con:

  • Directorysrc/domain/chart-of-accounts/
    • types.ts (Account, CreateAccountDTO)
    • ChartOfAccountsService.ts (Lógica de negocio)
  • Directorysrc/routes/admin/
    • chart-of-accounts.ts (Rutas HTTP)
Mapeo DB → Dominio
private static mapRow(row: any): LegalRep {
return {
fecha_desde: row.fecha_nombramiento, // Mapea DB a dominio
fecha_hasta: row.fecha_termino,
vigente: row.activo,
// ... otros campos
};
}

VariableServicioPropósito
PORTOrchestratorPuerto HTTP (8000)
DB_HOSTOrchestratorHost PostgreSQL
DB_PORTOrchestratorPuerto PostgreSQL (5432)
DB_USEROrchestratorUsuario de BD
DB_PASSWORDOrchestratorContraseña de BD
JWT_SECRETOrchestratorClave de firma de token (256+ bits)
LIQUIDACIONES_DIROrchestratorRuta de almacenamiento PDF

localhost:4321 ← Sevastopol (Vite dev server)
↓ (proxy /api/*)
localhost:8000 ← Orchestrator (nodemon)
localhost:5432 ← PostgreSQL (Docker)