Arquitectura Hexagonal (Puertos y Adaptadores)
🏛️ Arquitectura Hexagonal (Puertos y Adaptadores)
Sección titulada «🏛️ Arquitectura Hexagonal (Puertos y Adaptadores)»Criptofolio aplica de manera rigurosa los principios de la Arquitectura Hexagonal (conocida como Puertos y Adaptadores) tanto en su capa visual frontend como en su servicio backend de cálculo analítico.
El principio fundacional es la Independencia del Dominio: la lógica de negocio ignora por completo si está ejecutándose dentro de Vue 3, una interfaz CLI por terminal o una batería de tests automatizados.
🏗️ Las Tres Capas Concéntricas
Sección titulada «🏗️ Las Tres Capas Concéntricas»flowchart TD
subgraph Infrastructure ["3. Capa de Infraestructura (Adaptadores)"]
RestAdapter["RestTaxAdapter"]
SqliteVault["SqliteVaultAdapter"]
DuckDbEngine["DuckDbMetricsAdapter"]
end
subgraph Application ["2. Capa de Aplicación (Casos de Uso)"]
UploadUseCase["UploadTaxFileUseCase"]
UnlockUseCase["UnlockVaultUseCase"]
SummaryUseCase["GetPortfolioSummaryUseCase"]
end
subgraph Domain ["1. Capa de Dominio (Lógica Pura)"]
Models["Entidades y Value Objects\n(PreciseAmount, LotId)"]
Ports["Puertos de Dominio (Interfaces)\n(ITaxPort, IVaultCredentialsPort)"]
end
Infrastructure --> Application
Application --> Domain
1. Capa de Dominio (packages/core-domain/)
Sección titulada «1. Capa de Dominio (packages/core-domain/)»El corazón del sistema. Posee cero dependencias de frameworks externos (sin Vue, sin Hono, sin Axios, sin drivers de bases de datos).
- Entidades y Value Objects: Interfaces TypeScript puras y tipos con marca nominal o Branded Types (
PreciseAmount,LotId,AssetId). - Puertos: Interfaces que definen los contratos para persistencia, consulta de mercado y criptografía (
ITaxPort,ILedgerPort,IVaultCredentialsPort). El dominio dicta qué datos requiere, nunca cómo se obtienen.
2. Capa de Aplicación (src/core/application/)
Sección titulada «2. Capa de Aplicación (src/core/application/)»Coordina las interacciones del dominio para resolver los flujos de trabajo del usuario:
- Casos de Uso: Clases TypeScript puras (ej.
UploadTaxFileUseCase,UnlockVaultUseCase). - Cualquier mutación de estado debe pasar obligatoriamente a través de un Caso de Uso.
- Cero código de presentación o reactividad (sin
ref(),reactive()ni imports de Pinia).
3. Capa de Infraestructura (src/core/infrastructure/)
Sección titulada «3. Capa de Infraestructura (src/core/infrastructure/)»La frontera exterior que interactúa con la red, el almacenamiento y librerías externas:
- Adaptadores: Implementaciones concretas de los Puertos de Dominio (
BffTaxAdapter,AesGcmCryptographyAdapter,DuckDbMetricsAdapter). - Capa Anti-Corrupción (ACL): Esquemas de validación Zod que sanean y transforman las respuestas externas antes de construir entidades del dominio.
- Composition Root: El punto central donde se ensambla la Inyección de Dependencias.
🔄 Flujo de Datos Extremo a Extremo
Sección titulada «🔄 Flujo de Datos Extremo a Extremo»Ciclo de vida exacto de una acción iniciada por el usuario (como la subida de un extracto de operaciones):
sequenceDiagram
autonumber
participant UI as Componente Vue 3
participant Colada as Composable (useMutation)
participant UseCase as UploadTaxFileUseCase
participant Port as ITaxPort (Puerto Dominio)
participant Adapter as BffTaxAdapter (Infraestructura)
participant RPC as Cliente Hono RPC (hc)
participant Backend as Servidor Backend Hono
UI->>Colada: Dispara upload(file, sourceProfile)
Colada->>UseCase: execute({ file, sourceProfile })
Note over UseCase: Valida restricciones de negocio<br/>y prepara payload de ingesta
UseCase->>Port: uploadFile(payload)
Port->>Adapter: Invoca implementación concreta
Adapter->>RPC: bffClient.api.tax.upload.$post()
RPC->>Backend: HTTP POST /api/tax/upload
Backend-->>RPC: 200 OK (Lotes procesados)
RPC-->>Adapter: Respuesta JSON fuertemente tipada
Adapter-->>Port: Mapea DTO a Entidad de Dominio
Port-->>UseCase: Retorna resultado del Dominio
UseCase-->>Colada: Resuelve la promesa de mutación
Colada-->>UI: Actualiza estado reactivo (isSuccess)
🔌 Inyección de Dependencias y Composition Root
Sección titulada «🔌 Inyección de Dependencias y Composition Root»Para evitar acoplar componentes de vista a clases concretas:
- Contenedor DI en Backend (
apps/backend/.../di/container.ts): Inicializa conexiones (SQLite, pool de DuckDB) e inyecta los adaptadores en servicios comoFifoMaterializerService. Las rutas de Hono consumen dependencias de este contenedor. - Inyección en Frontend: Los adaptadores se declaran en la raíz de Vue mediante símbolos fuertemente tipados de tipo
InjectionKey(ej.VAULT_PORT_KEY). Los composables los consumen medianteinject()sin acoplar la vista al transporte de red.
🛡️ Reglas Arquitectónicas Innegociables
Sección titulada «🛡️ Reglas Arquitectónicas Innegociables»- Principios CQRS: Las lecturas simples (Queries) pueden invocar directamente el Puerto inyectado desde el composable de Pinia Colada. Las escrituras (Mutaciones) deben canalizarse obligatoriamente a través de un
UseCase. - Prohibición de
any: Todo el código compila bajo modo estricto sin tolerar bypasses de tipos. - Desacoplamiento del Cliente BFF: Los componentes jamás importan
bffClientde forma directa; interactúan siempre mediante composables ligados a puertos.