Skip to content
GitHub

Sistema Contable Nostromo

Este documento proporciona una introducción de alto nivel al sistema Accounting, una plataforma de gestión de remuneraciones, RRHH y contabilidad chilena construida como una aplicación SaaS multi-tenant. El sistema implementa el cumplimiento de la legislación laboral chilena, incluyendo cálculo de remuneraciones, seguridad social (AFP, ISAPRE, AFC), retención de impuestos y operaciones contables.

El sistema consiste en dos aplicaciones separadas que trabajan en conjunto:

  • Sevastopol: Un frontend Astro + SolidJS que sirve como Backend-for-Frontend (BFF)
  • Orchestrator: Un backend Node.js/Express que actúa como fuente de verdad para toda la lógica de negocio

Visión General de la Arquitectura del Sistema

Section titled “Visión General de la Arquitectura del Sistema”

El sistema emplea una clara separación de responsabilidades entre dos aplicaciones distintas:

flowchart TB
    %% =====================
    %% Cliente / Navegador
    %% =====================
    subgraph Navegador
        Browser[Navegador del Usuario]
    end
    %% =====================
    %% Frontend (Astro + SolidJS)
    %% =====================
    subgraph Frontend["Sevastopol (Frontend BFF)"]
        AstroPages[Páginas Astro SSR + Estático]
        SolidIslands[Islands SolidJS<br/>Hidratación Cliente]
        ViewRouter[view-router.ts<br/>Navegación Dinámica]
        ApiProxy[authenticatedFetch<br/>Rutas Proxy API /api/*]
        ClientAuth[authenticatedFetch Cliente Auth]
    end

    Browser -->|HTTPS| AstroPages
    Browser <-->|Fetch + Eventos| SolidIslands

    AstroPages -->|Eventos Personalizados| ViewRouter
    SolidIslands -->|Importación Dinámica| ViewRouter
    SolidIslands -->|authenticatedFetch| ApiProxy


    %% =====================
    %% Proxy Backend
    %% =====================
    ApiProxy -->|Reenvío Proxy<br/>Puerto 8000| Factory

    %% =====================
    %% Backend / Orchestrator
    %% =====================
    subgraph Backend["Orchestrator (Backend)"]
        Factory["createApp() Factory Express"]
        AuthToken[authenticateToken<br/>Validación JWT]
        Authorize[authorizeRoute<br/>Verificación Permisos]
        DomainRoutes[Rutas de Dominio<br/>/api/tenant<br/>/api/employees<br/>/api/payroll]

        Factory --> AuthToken
        AuthToken --> Authorize
        Authorize --> DomainRoutes
    end

    %% =====================
    %% Servicios de Negocio
    %% =====================
    subgraph Services["Servicios de Negocio"]
        Payroll[PayrollEngine PdfService<br/>AuditLogger]
    end

    DomainRoutes -->|Consultas de Sesión| Payroll
    AuthToken -.->|Consultas de Sesión| DBSystem

    %% =====================
    %% Bases de Datos
    %% =====================
    subgraph DB["Bases de Datos PostgreSQL"]
        DBSystem[nostromo_command<br/>BD Sistema]
        DBCommon[nostromo_common<br/>Parámetros Compartidos]
        DBTenant[nostromo_600031<br/>nostromo_7654321<br/>Datos Específicos Tenant]
    end

    Payroll -->|Auditoría y Métricas| DBSystem
    DomainRoutes -->|Consultas de Parámetros| DBCommon
    DomainRoutes -->|Datos de Tenant| DBTenant

CapaTecnologíaPropósito
Framework FrontendAstro 5.xSSR, generación estática, arquitectura islands
Biblioteca UISolidJS 5.xIslands reactivos del lado del cliente con hidratación
EstilosTailwind CSSCSS utility-first con soporte para modo oscuro
Runtime BackendNode.js 20+Runtime de servidor para Orchestrator
Framework BackendExpress 5.xServidor API HTTP con pipeline de middleware
Base de DatosPostgreSQLPersistencia de datos multi-tenant
AutenticaciónJWT + SesionesTokens sin estado con seguimiento de sesiones
AutorizaciónRBACSistema de Control de Acceso Basado en Roles
MonitoreoPersonalizadoLogs de auditoría + métricas estilo Prometheus
Generación PDFPuppeteerRenderizado de PDF del lado del servidor
TestingJest + PlaywrightPruebas unitarias (backend) + pruebas E2E (frontend)

Arquitectura de Base de Datos Multi-Tenant

Section titled “Arquitectura de Base de Datos Multi-Tenant”

El sistema implementa multi-tenancy a nivel de base de datos con tres tipos distintos de bases de datos, garantizando estricta aislación de datos entre tenants:

flowchart LR
    %% =====================
    %% Flujo de Autenticación
    %% =====================
    subgraph Auth["Flujo de Autenticación"]
        Req[Solicitud Entrante<br/>Cookie: sid=JWT]
        AuthToken[authenticateToken<br/>Decodificar JWT]
        TenantResolver[tenantResolver<br/>Validar tenant_id]

        Req --> AuthToken --> TenantResolver
    end

    %% =====================
    %% Pools de Conexión
    %% =====================
    subgraph Pools["Pools de Conexión"]
        CentralPool[centralPool<br/>Instancia Singleton]
        CommonPool[commonPool<br/>Instancia Singleton]
        TenantFactory["getTenantPool(db)<br/>Factory Pool Cacheado"]
    end

    TenantResolver -->|Consultas de Sistema| CentralPool
    TenantResolver -->|Consultas de Parámetros| CommonPool
    TenantResolver -->|Consultas de Tenant| TenantFactory

    %% =====================
    %% Servidor PostgreSQL
    %% =====================
    subgraph Postgres["Servidor PostgreSQL"]
        CentralDB[nostromo_command<br/>Base de Datos Central]
        CommonDB[nostromo_common<br/>Parámetros Comunes]
        TenantDB1[nostromo_6000431<br/>Datos Tenant 1]
        TenantDB2[nostromo_7654321<br/>Datos Tenant 2]
    end

    CentralPool --> CentralDB
    CommonPool --> CommonDB
    TenantFactory -->|Pool para Tenant 1| TenantDB1
    TenantFactory -->|Pool para Tenant 2| TenantDB2

    %% =====================
    %% Esquemas BD Central
    %% =====================
    subgraph SchemasCentral["Esquemas BD Central"]
        CentralSchemas[
            command.tenants<br/>
            command.users<br/>
            auth.user_sessions
        ]
        MonitoringSchemas[
            monitoring.audit_log<br/>
            monitoring.system_metrics
        ]
    end

    CentralDB --> CentralSchemas
    CentralDB --> MonitoringSchemas

    %% =====================
    %% Esquemas BD Común
    %% =====================
    subgraph SchemasCommon["Esquemas BD Común"]
        CommonSchemas[
            parametros.afp_tasas<br/>
            parametros.afc<br/>
            parametros.impuesto_2cat<br/>
            parametros.indicadores
        ]
    end

    CommonDB --> CommonSchemas

    %% =====================
    %% Esquemas BD Tenant
    %% =====================
    subgraph SchemasTenant["Esquemas BD Tenant"]
        TenantSchemas1[
            remuneraciones.*<br/>
            RRHH y Remuneraciones
        ]
        TenantSchemas2[
            operaciones.*<br/>
            Ventas y Compras
        ]
        TenantSchemas3[
            administracion.*<br/>
            Configuración Empresa
        ]
    end

    TenantDB1 --> TenantSchemas1
    TenantDB1 --> TenantSchemas2
    TenantDB1 --> TenantSchemas3
Nombre Base de DatosPropósitoPatrón de Acceso
nostromo_commandDatos del sistema: tenants, usuarios, sesiones, logs de auditoría, métricasTodas las solicitudes autenticadas
nostromo_commonParámetros compartidos: tablas de impuestos, tasas AFP, indicadores económicos (UF, UTM)Solo lectura, consultas temporales
nostromo_NNNNNNDatos de negocio específicos del tenant: RRHH, remuneraciones, contabilidadAislación por tenant vía getTenantPool()

El sistema implementa autenticación basada en JWT con seguimiento de sesiones y RBAC de tres niveles:

sequenceDiagram
    autonumber

    participant B as Navegador
    participant S as "Sevastopol /api/auth/*"
    participant O as "Orchestrator authRoutes"
    participant DB as "nostromo_command"
    participant RBAC as "rbac.canAccess()"

    %% =========================
    %% LOGIN
    %% =========================
    B->>S: POST /api/auth/login (username, password)
    S->>O: Reenvío Proxy
    O->>DB: SELECT * FROM users WHERE username = $1
    DB-->>O: Registro usuario + password_hash

    alt Contraseña válida
        O->>O: bcrypt.compare(password, hash)
        O->>O: jwt.sign(userId, username, role, sessionId)
        O->>DB: INSERT INTO auth.user_sessions
        O->>DB: auditLog.login(userId) ok=true
        O-->>S: 200 OK
        Note over S,B: Set-Cookie sid=JWT HttpOnly Secure
        S-->>B: 200 OK + Cookie
    else Contraseña inválida
        O->>DB: auditLog.login(userId?) ok=false
        O-->>S: 401 Unauthorized
        S-->>B: 401 Error
    end

    %% =========================
    %% REQUEST AUTENTICADA
    %% =========================
    B->>S: GET /api/employees Cookie sid
    S->>O: Reenvío Proxy + Cookie

    O->>O: authenticateToken(req)
    O->>O: Decodificar JWT
    O->>O: Verificar JWT
    O->>O: set req.user = decoded

    %% =========================
    %% RBAC
    %% =========================
    O->>RBAC: canAccess(role, "/api/employees")

    alt Autorizado
        RBAC-->>O: true
        O->>O: Ejecutar manejador de ruta
        O->>DB: SELECT employees
        O-->>S: 200 OK + Datos
        S-->>B: 200 OK + Datos
    else Prohibido
        RBAC-->>O: false
        O->>DB: auditLog.accessDenied(resource)
        O-->>S: 403 Forbidden
        S-->>B: 403 Error
    end
RolAlcanceRutas Típicas
SUPER_ADMINAcceso completo al sistema, puede gestionar todos los tenants y usuarios/api/tenant/*, /api/admin/users/*, /api/monitoring/*
ADMINAdministración de tenant, puede gestionar los datos de su tenant/api/accounting/*, /api/employees/*, /api/payroll/*
USERAcceso de solo lectura a reportes y operaciones/api/reports/*, /api/operations/operations (lectura)

El sistema está organizado en cuatro dominios de negocio principales, cada uno con rutas dedicadas y vistas UI:

flowchart TB

    %% =========================
    %% Dominio Operaciones
    %% =========================
    subgraph Ops["Dominio Operaciones"]
        OpsRoutes["
            /api/operations/operations
            /api/operations/sales
            /api/accounting"
        ]
        OpsView[OperationsViewIsland]
        OpsRoutes --> OpsView
    end

    %% =========================
    %% Dominio Remuneraciones
    %% =========================
    subgraph Remu["Dominio Remuneraciones"]
        RemuRoutes[
            /api/employees
            /api/contracts
            /api/attendance
            /api/remuneraciones/payroll
            /api/vacations
            /api/permissions
            /api/spare_contracts
            /api/pay_contracts
        ]

        RemuViews[
            EmployeesViewIsland
            ContractsViewIsland
            AttendanceViewIsland
            PayrollViewIsland
            VacationsViewIsland
            PermissionsViewIsland
        ]

        RemuServices[
            PayrollEngine.calculate
            SocialLawsCalculator
            TaxCalculator
            HealthPlanCalculator
        ]

        RemuRoutes --> RemuViews
        RemuRoutes --> RemuServices
    end

    %% =========================
    %% Dominio Admin
    %% =========================
    subgraph Admin["Dominio Admin"]
        AdminRoutes[
            /api/admin/company
            /api/admin/capital
            /api/admin/representatives
            /api/admin/chart-of-accounts
            /api/admin/system-config
        ]

        AdminViews[
            CompanyViewIsland
            CapitalViewIsland
            LegalRepresentativesViewIsland
            ChartOfAccountsViewIsland
            SystemConfigViewIsland
        ]

        AdminRoutes --> AdminViews
    end

    %% =========================
    %% Dominio Command
    %% =========================
    subgraph Command["Dominio Command"]
        CommandRoutes[
            /api/tenant/api/tenant-db
            /api/admin/users
            /api/sessions
            /api/monitoring
        ]

        CommandViews[
            TenantsViewIsland
            SessionsViewIsland
            MonitoringViewIsland
        ]

        CommandRoutes --> CommandViews
    end
DominioPropósitoEntidades Clave
CommandAdministración del sistema, gestión de tenants, gestión de usuarios, monitoreoTenant, TenantDatabase, User, Session, AuditLog, SystemMetrics
AdminConfiguración de empresa, estructura legal, plan de cuentasCompanyConfig, LegalRep, CapitalEntry, Account, SystemConfig
RemuneracionesGestión de RRHH, cálculo de remuneraciones, cumplimiento legislación laboral chilenaEmployee, Contract, Attendance, Payroll, Vacation, IsapreContract, APVContract
OperacionesOperaciones contables, integración SII, ventas y comprasOperation, Sale, Purchase, ResumenOperaciones

Sevastopol actúa como una capa BFF pura sin lógica de negocio. Todas las rutas API en Sevastopol son proxies simples:

sevastopol/src/pages/api/tenant.ts
// sevastopol/src/pages/api/tenant.ts
import { createProxy } from "@/lib/proxyUtils";
export const { GET, POST, PUT, DELETE } = createProxy("/api/tenant");

La utilidad createProxy reenvía todas las solicitudes al Orchestrator en localhost:8000 con credenciales incluidas.


El Orchestrator sigue un patrón Hybrid Core para la lógica de negocio:

Tipo de OperaciónImplementaciónEjemplo
Escrituras y Lógica ComplejaTypeScript en serviciosCálculos de PayrollEngine, cómputo de impuestos, reglas de validación
Lecturas y ReportesVistas SQL en PostgreSQLVistas inteligentes como v_resumen_ventas, v_employee_balance

Este patrón asegura:

  • Cálculos complejos (remuneraciones, impuestos) son testeables en Jest con completa seguridad de tipos
  • Datasets grandes (reportes, dashboards) son consultados eficientemente vía vistas SQL optimizadas
  • Reglas de negocio permanecen en código de aplicación, no dispersas en procedimientos almacenados

El sistema incluye monitoreo comprensivo integrado en el Orchestrator:

flowchart TB
%% =========================
%% Pipeline de Solicitudes
%% =========================
subgraph Pipeline["Pipeline de Solicitudes"]
Req[Solicitud Entrante]
MetricsMW[metricsMiddleware]
AuditMW[auditMiddleware]
Handler[Manejador de Ruta]
Req --> MetricsMW
MetricsMW --> AuditMW
AuditMW --> Handler
end
%% =========================
%% Recolección de Métricas
%% =========================
subgraph Metrics["Recolección de Métricas"]
Collector[MetricsCollector<br/>Singleton]
M1[http_requests_total<br/>http_errors_total]
M2[http_active_requests<br/>nodejs_heap_used_bytes]
M3[http_request_duration_ms]
M4[monitoring.system_metrics]
Collector --> M1
Collector --> M2
Collector --> M3
Collector --> M4
end
%% =========================
%% Registro de Auditoría
%% =========================
subgraph Audit["Registro de Auditoría"]
AuditLogger[AuditLogger<br/>Singleton]
Buffer[Cola en Memoria<br/>Buffer por Lotes]
AuditDB[monitoring.audit_log]
AuditLogger --> Buffer
Buffer -->|Flush cada 5s| AuditDB
end
%% =========================
%% Conexiones entre dominios
%% =========================
MetricsMW -->|Rastrear métricas| Collector
Collector -->|Flush cada 60s| M4
Handler -->|Registrar operaciones| AuditLogger
  • Rastreo Automático de Solicitudes: Tiempos de respuesta, códigos de estado, tasas de error
  • Métricas de Salud del Sistema: Uso de memoria, lag del event loop, estadísticas de pool de BD
  • Pista de Auditoría: Todas las operaciones de modificación (INSERT/UPDATE/DELETE) registradas con contexto de usuario
  • Endpoint en Tiempo Real: GET /metrics expone métricas actuales como JSON