Ir al contenido

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.


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

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.

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:

  1. Contenedor DI en Backend (apps/backend/.../di/container.ts): Inicializa conexiones (SQLite, pool de DuckDB) e inyecta los adaptadores en servicios como FifoMaterializerService. Las rutas de Hono consumen dependencias de este contenedor.
  2. 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 mediante inject() sin acoplar la vista al transporte de red.

🛡️ Reglas Arquitectónicas Innegociables

Sección titulada «🛡️ Reglas Arquitectónicas Innegociables»
  1. 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.
  2. Prohibición de any: Todo el código compila bajo modo estricto sin tolerar bypasses de tipos.
  3. Desacoplamiento del Cliente BFF: Los componentes jamás importan bffClient de forma directa; interactúan siempre mediante composables ligados a puertos.