Ir al contenido

Arquitectura Backend y Hono RPC

El backend de Criptofolio (apps/backend) es un servicio centralizado, ligero y ultrarrápido construido sobre Hono.js.

Actúa como la única fuente de verdad para:

  1. Las rutas de la API REST consumidas por la aplicación frontend en Vue 3.
  2. La seguridad de tipos extremo a extremo mediante Hono RPC sin generadores de código intermedios.
  3. El almacenamiento seguro de credenciales cifradas (Bóveda).
  4. La coordinación analítica entre SQLite y el motor DuckDB.

🏛️ Estructura del Servidor y Separación de Responsabilidades

Sección titulada «🏛️ Estructura del Servidor y Separación de Responsabilidades»

El backend se divide en dos puntos de entrada bien diferenciados:

apps/backend/src/
├── app.ts # Aplicación pura Hono: middlewares, enrutado y exportación de AppType
├── index.ts # Bootstrapper: pool de bases de datos, tareas programadas, puerto HTTP
├── core/
│ ├── domain/ # Modelos e interfaces del dominio
│ ├── application/ # Casos de Uso (ej. UnlockVaultUseCase, IngestTransactionsUseCase)
│ └── infrastructure/
│ ├── di/container.ts# Contenedor central de Inyección de Dependencias (Composition Root)
│ ├── adapters/ # Adaptadores para los motores SQLite y DuckDB
│ ├── routes/ # Definiciones modulares de rutas Hono
│ └── jobs/ # Tareas en segundo plano (ej. ExchangeRateSyncJob)
  • app.ts (Aplicación Pura): Configura middlewares de logging, cabeceras CORS, registra las rutas y exporta el tipo AppType. No incluye escucha activa en sockets, lo que facilita enormemente los tests con app.request().
  • index.ts (Arrancador en Runtime): Abre los archivos de base de datos, ejecuta migraciones pendientes, valida variables de entorno y vincula el servidor HTTP de Node.js al puerto configurado.

🔗 Seguridad de Tipos Extremo a Extremo (Hono RPC)

Sección titulada «🔗 Seguridad de Tipos Extremo a Extremo (Hono RPC)»

Las arquitecturas habituales requieren generadores pesados de código cliente basados en OpenAPI/Swagger o GraphQL cada vez que cambia un endpoint.

Criptofolio utiliza Hono RPC, compartiendo los tipos de TypeScript directamente entre los paquetes del monorepo sin pasos previos de compilación:

1. Definición y Exportación de Rutas (apps/backend/src/app.ts)

Sección titulada «1. Definición y Exportación de Rutas (apps/backend/src/app.ts)»
import { Hono } from 'hono';
import { metricsRoutes } from './core/infrastructure/routes/metrics';
import { taxRoutes } from './core/infrastructure/routes/tax';
import { vaultRoutes } from './core/infrastructure/routes/vault';
const routes = new Hono()
.route('/api/metrics', metricsRoutes)
.route('/api/tax', taxRoutes)
.route('/api/credentials', vaultRoutes);
export const app = new Hono().route('/', routes);
export type AppType = typeof routes;

2. Consumo en el Frontend (apps/frontend/.../BffClient.ts)

Sección titulada «2. Consumo en el Frontend (apps/frontend/.../BffClient.ts)»

El frontend inicializa el cliente RPC importando exclusivamente la definición de tipo AppType:

import { hc } from 'hono/client';
import type { AppType } from '@criptofolio/backend';
export const bffClient = hc<AppType>(import.meta.env.VITE_API_URL || 'http://localhost:3001');
// Autocompletado completo, verificación de argumentos y tipo de retorno estricto:
const response = await bffClient.api.metrics.kpis.$get();
if (response.ok) {
const data = await response.json(); // ¡Datos 100% tipados!
console.log(data.total_equity);
}

🔌 Inyección de Dependencias Centralizada (container.ts)

Sección titulada «🔌 Inyección de Dependencias Centralizada (container.ts)»

Los controladores de ruta de Hono no instancian servicios directamente. Un contenedor DI resuelve adaptadores concretos y los inyecta en los Casos de Uso:

apps/backend/src/core/infrastructure/di/container.ts
export interface Container {
sqliteLedgerAdapter: SqliteLedgerAdapter;
duckDbAdapter: DuckDbAdapter;
fifoMaterializerService: FifoMaterializerService;
vaultCredentialsPort: IVaultCredentialsPort;
ingestAndMaterializeUseCase: IngestAndMaterializeUseCase;
}

Esto garantiza:

  • Testabilidad: En suites de pruebas, el contenedor intercambia bases de datos en archivo por instancias :memory:.
  • Gestión Eficiente del Pool: DuckDB y SQLite mantienen un único pool de conexiones compartido sin fugas de descriptores.

Prefijo de Ruta Método Función
/api/metrics/kpis GET Resumen patrimonial (equity, coste base, PnL realizado/no realizado, ratio de acierto).
/api/metrics/risk GET Métricas de riesgo (Sharpe, volatilidad 30d, caída ATH, Alpha y Beta).
/api/metrics/performance GET Serie temporal de valoración diaria del portfolio en la currency solicitada (por defecto EUR), convertida en cada punto con la tasa de su fecha; los puntos sin tasa son null.
/api/tax/import POST Procesamiento del pipeline de ingesta con perfil de formato de origen.
/api/tax/lots GET Consulta de lotes fiscales calculados con desglose de custodia.
/api/settings/accounts GET Lista las cuentas visibles al usuario (id como value, nombre como label); ListAccountsUseCase excluye las contrapartes sintéticas de custodia.
/api/tax/transactions/spot GET Transacciones spot de la vista efectiva (editada), con filtro opcional por accountId, mediante SearchSpotTransactionsUseCase. Orden del libro, la más antigua primero.
/api/credentials/vault/* Varios Gestión de claves API cifradas y estado de desbloqueo de la bóveda.
/api/advisor/* Varios Asesor de Cartera con IA de solo lectura: chat con streaming y configuración de cadena de modelos y perfiles de ejecución. Ver Asesor de IA.