Arquitectura Hexagonal
Arquitectura Hexagonal del Sistema Contable
Section titled “Arquitectura Hexagonal del Sistema Contable”Mapa de la arquitectura hexagonal del sistema contable que muestra el flujo desde el frontend Sevastopol a través del proxy BFF hasta el backend Orchestrator, incluyendo autenticación JWT, gestión multi-tenant con pools de conexión PostgreSQL, y la capa de dominio con servicios de negocio. Destacan el patrón de proxy inverso [1b], el sistema RBAC [2c], la gestión de pools por tenant [3b], y el motor de cálculo de nómina [5c].
Capas de Arquitectura
Section titled “Capas de Arquitectura”El sistema sigue los principios de arquitectura hexagonal, aislando la lógica de negocio central de las preocupaciones externas a través de puertos y adaptadores bien definidos.
Independencia del Dominio Central
Section titled “Independencia del Dominio Central”El dominio central (Servicios, Motores, Modelos) no tiene dependencias directas de la infraestructura:
- Sin importaciones de tipos express en la lógica de dominio
- Sin consultas directas a base de datos en los motores de cálculo
- Sin operaciones del sistema de archivos en las reglas de negocio
flowchart %% ========================= %% Sistemas Externos %% ========================= subgraph EX["Sistemas Externos"] WEB["Navegador Web"] DB["Base de Datos PostgreSQL"] FS["Sistema de Archivos<br/>(Almacenamiento PDF)"] end %% ========================= %% Adaptadores %% ========================= subgraph AD["Adaptadores"] FE["Adaptador Frontend (Proxy)<br/>Sevastopol"] API["Adaptador API (Rutas)<br/>Express"] DB_AD["Adaptador Base de Datos<br/>(Patrón Repository)"] FILE_AD["Adaptador de Archivos<br/>(PdfService)"] end %% ========================= %% Puertos %% ========================= subgraph PORTS["Puertos (Interfaces)"] HTTP_PORT["Puerto de Petición HTTP"] DATA_PORT["Puerto de Acceso a Datos"] STORAGE_PORT["Puerto de Almacenamiento"] end %% ========================= %% Dominio Central %% ========================= subgraph DOMAIN["Dominio Central"] SERVICES["Servicios de Dominio<br/>PayrollService<br/>EmployeeService<br/>ContractService"] CALC["Motores de Cálculo<br/>PayrollEngine<br/>TaxCalculator<br/>SocialLawsCalculator"] MODELS["Modelos de Dominio<br/>Employee<br/>Contract<br/>Payroll"] end %% ========================= %% Flujos %% ========================= WEB -->|HTTP| FE FE -->|Proxy| API API -->|Transformar| HTTP_PORT HTTP_PORT --> SERVICES SERVICES -->|Usa| CALC SERVICES -->|Usa| MODELS SERVICES -->|Consultar / Persistir| DATA_PORT SERVICES -->|Generar PDF| STORAGE_PORT DATA_PORT --> DB_AD STORAGE_PORT --> FILE_AD DB_AD --> DB FILE_AD --> FS
1 Flujo de Petición del Frontend al Backend
Section titled “1 Flujo de Petición del Frontend al Backend”El patrón BFF (Backend-for-Frontend) que protege la lógica de negocio en Sevastopol.
Sevastopol Frontend │ └── 1a Proxy API en Sevastopol │ └── Proxy utils │ └── createProxy() genera handlres │ └── 1b Reenvío a OrchestratorOrchestrator Backend │ └── App Factory | │ | └── 1c Registro de Ruta en backend │ └── Routes Handler │ └── authenticateToken() middleware │ └── 1d Middleware de Autenticación2 Autenticación y Autorización Multi-Tenant
Section titled “2 Autenticación y Autorización Multi-Tenant”El sistema de seguridad basado en JWT y RBAC que protege los endpoints.
Middleware de Autenticación │ └── 2a Extracción de Token desde Cookie │ └── 2b Verificación del JWT │ └── Decodificación de user payloadSistema RBAC │ └── 2c Verificación RBAC │ └── 2d Configuración de Permisos │ └── Mapeo de rutas permitidasFlujo de Autorización │ └── authenticateToken() middleware │ └── autorizeRoute() validation │ └── requireRole() helper3 Gestión de Conexiones Multi-Tenant
Section titled “3 Gestión de Conexiones Multi-Tenant”El sistema de pools de conexión qeu aísla datos por tenant.
Configuración Inicial de DB │ └── buildPoolConfug() | │ | └── Parámetros por pool type | │ | └── Configuración de conexión │ └── 3a Pool Central de ConexionesGestión de Pools por Tenant │ └── 3b Obtención de Pool por tenant | │ | └── Verificar cache existente | │ | └── 3c Creación de Pool Específico │ | │ │ | └── buildPoolConfig(database) │ | │ │ | └── 3c Creación de Pool Específico │ │ │ └── 3d Cache de Pools │ └── Retornar pool al solicitanteSistema de Cache │ └── Map<string, Pool> tenantPools │ └── Reutilización de conexiones4 Capa de Dominio y Servicios de Negocio
Section titled “4 Capa de Dominio y Servicios de Negocio”La implementación del patrón Service con lógica de negocio encapsulada.
Ruta HTTP Express │ └── 4a Instanciacion ded Servicio │ └── 4b Llamada a Servicio de NegocioServicio de Dominio │ └── getAllTenants(): Promise<Tenant[]> | │ | └── 4c Servicio de Dominio │ └── Repositorio de Datos │ └── 4d Acceso a Datos5 Procesamiento de Nómina con Cálculos Complejos
Section titled “5 Procesamiento de Nómina con Cálculos Complejos”El motor de cálculo de nómina que desmuestra la separación de responsabilidades.
5a Transacción de Negocio │ └── Obtener Contexto del Contrato | │ | └── Datos del empleado | │ | └── Asistencia y horas extras │ └── Mapeo a Input de Cálculo │ | │ └── Conversión de UF a CLP │ └── 5c Motor de Cálculo Puro | │ | └── Cálculo de imponibles | │ | └── Descuentos legales (AFP, salud) | │ | └── Cálculo de impuestos | │ | └── Líquido final │ └── 5d Persistencia de Resultados │ └── Guardar liquidación │ └── Actualizar estados │ └── Generar PDF (opcional)Manejo de Errores │ └── Rollback automático │ └── Logging de auditoría6 Protección de Rutas en el Frontend
Section titled “6 Protección de Rutas en el Frontend”El middleware de Astro que protege las páginas HTML y endpoints API.
6a Middleware de Astro │ └── 6b Verificación de Rutas Protegidas | │ | └── /dashboard,/settings,/registry | │ | └── Extraer token de cookie 'sid' │ └── 6c Validación con Backend │ | │ └── POST /api/auth/validate │ | │ └── Headers: Cookie + Content-Type │ | │ └── Respuesta OK/No autorizada │ └── 6d Redirección su no AutorizadoAplicación de headers de seguridad │ └── CSP, X-Frame-Options │ └── Headers a todas las respuestas1a Proxy API en Sevastopol
Section titled “1a Proxy API en Sevastopol”El frontend crea un proxy que reenvía todas las peticiones al backend
export const { GET, POST, PUT, DELETE } = createProxy("/api/tenant");1b Reenvío a Orchestrator
Section titled “1b Reenvío a Orchestrator”Se reenvía la petición con cookies y headers al backend principal
const orchestratorRes = await fetch(targetUrl, { method: request.method, headers: getProxyHeaders(request), body: body, credentials: "include",});1c Registro de Ruta en backend
Section titled “1c Registro de Ruta en backend”El backend registra la ruta del tenant en Express
app.use("/api/tenant", tenantRouter);1d Middleware de Autenticación
Section titled “1d Middleware de Autenticación”Se aplica autenticación JWT antes de procesar la petición
router.get('/', authenticateToken, async (req: AuthenticatedRequest, res) => {...})2a Extracción de Token desde Cookie
Section titled “2a Extracción de Token desde Cookie”Se extrae el JWT de la cookie ‘sid’ para autenticación
if (!token && req.headers.cookie) { const parsed = cookie.parse(req.headers.cookie); token = parsed.sid;}2b Verificación del JWT
Section titled “2b Verificación del JWT”Se verifica y decodifica el token con la clave secreta
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any;2c Verificación RBAC
Section titled “2c Verificación RBAC”Se verifica si el rol tiene acceso a la ruta específica
if (!rbac.canAccess(role, route)) {...}2d Configuración de Permisos
Section titled “2d Configuración de Permisos”Se definen las rutas accesibles por cada rol de usuario
this.routes.set('SUPER_ADMIN', [...])3a Pool Central de Conexiones
Section titled “3a Pool Central de Conexiones”Pool para base de datos central con metadata de tenants
export const centralPool = new Pool(buildPoolConfig(undefined, "central"));3b Obtención de Pool por tenant
Section titled “3b Obtención de Pool por tenant”Función que retorna pool específico para cada tenant.
export function getTenantPool(database: string): Pool {...};3c Creación de Pool Específico
Section titled “3c Creación de Pool Específico”Se crea nuevo pool con configuaración optimizada para tenant
const pool = new Pool(buildPoolConfig(database, "tenant"));3d Cache de Pools
Section titled “3d Cache de Pools”Se almacena el pool en cache para reutilizarlo
tenantPools.set(database, pool);4a Servicio de Dominio
Section titled “4a Servicio de Dominio”Se crea instacia del servicio de dominio en la ruta
const tenantService = new TenantService();4b Llamada a Servicio de Negocio
Section titled “4b Llamada a Servicio de Negocio”El controller delega la lógica al servicio de dominio
const tenants = await tenantService.getAllTenants();4c Servicio de Dominio
Section titled “4c Servicio de Dominio”El servicio encapsula la lógica y delega persistencia al repositorio.
async getAllTenants(): Promise<Tenant[]> {...};4d Repositorio de Datos
Section titled “4d Repositorio de Datos”El repositorio ejecuta la consulta SQL contra la base de datos.
const { rows } = await centralPool.query<Tenant>(...);5a Transacción de Negocio
Section titled “5a Transacción de Negocio”El servicio inicia una transacción para generar nómina.
return this.withTransaction(ctx, async (client) => {...});5c Motor de Cálculo Puro
Section titled “5c Motor de Cálculo Puro”Se delega a motor funcional sin efectos secundarios.
const result = PayrollEngine.calculate(input);5d Persistencia de Resultados
Section titled “5d Persistencia de Resultados”Se guardan los resultadios calculados en la base de datos.
const persistedId = await PayrollRepository.savePayroll(...);6a Middleware de Astro
Section titled “6a Middleware de Astro”se define middleware oara todas las peticiones del frontend
export const onRequest = defineMiddleware(async (ctx, next) => {...});6b Verificación de Rutas Protegidas
Section titled “6b Verificación de Rutas Protegidas”Se identifican rutas que requieren autenticación
if ( url.pathname.startsWith('/dashboard') || url.pathname.startsWith('/settings') || url.pathname.startsWith('/registry') )6c Verificación de Token
Section titled “6c Verificación de Token”Se valida la sesión contra el backend orchestrator
const validateRes = await fetch('http://localhost:8000/api/auth/validate', {...});6d Redirección si no Autorizado
Section titled “6d Redirección si no Autorizado”Se redirige al login si la validación falla
if (!validateRes.ok) { return ctx.redirect("/", 302);}