finanze: Arquitectura y retos de un gestor de portafolios privado y autoalojado
En los últimos años, la gestión de activos financieros se ha vuelto cada vez más compleja. Muchos de nosotros tenemos cuentas y productos dispersos entre bancos tradicionales, brokers, plataformas de crowdfunding, wallets de criptomonedas, y más. Las soluciones existentes suelen ser SaaS de terceros, con poca flexibilidad, problemas de privacidad y escasa personalización.
Esta necesidad fue el punto de partida de finanze, un gestor de portafolios privado, autoalojado y extensible, que centraliza tus datos financieros en una única interfaz segura, acercando la visión global y el control total sobre tus activos. En este post, repaso las decisiones arquitectónicas, los principales retos técnicos y aprendizajes clave tras su desarrollo.
¿Por qué crear finanze?
La motivación fue doble:
- Centralización y visibilidad: Los dashboards bancarios y de inversión suelen ser verticales y limitados a sus propios productos. La visión global de patrimonio requiere extraer, transformar y agrupar datos dispersos en múltiples fuentes.
- Privacidad y control: No quería depender de servicios externos que almacenan mis datos financieros sensibles en la nube. Buscaba una solución autoalojada y cifrada localmente.
Además, necesitaba soportar activos poco convencionales (crowdfunding inmobiliario, cripto, commodities), multi-moneda, y una API flexible para importar/exportar datos.
Arquitectura: Decisiones y enfoque
Stack tecnológico
El stack elegido tenía que ser:
- Full-stack TypeScript: para robustez tipada de frontend a backend
- Python: para módulos de integración y scraping financiero, gracias a su ecosistema de librerías (Requests, BeautifulSoup, Pandas)
- Docker: para facilitar el despliegue autoalojado y la portabilidad
- CSS/HTML/JS: para una interfaz web moderna y responsiva
- Shell scripting: para tareas de automatización y backups
Repositorio: github.com/ESJavadex/finanze
Arquitectura general
La arquitectura sigue una aproximación modular y desacoplada. Los principales componentes son:
- Frontend SPA (Single Page Application)
- Desarrollado en TypeScript con React y Vite.
- Proporciona la interfaz de usuario para visualizar dashboards, KPIs, activos, movimientos y simulaciones.
- Gestiona autenticación local y mantiene la sesión cifrada en el navegador.
- API backend
- Node.js + TypeScript, expuesto como servidor Express.
- Expone endpoints RESTful para operaciones CRUD sobre activos, movimientos y entidades financieras.
- Interactúa con la base de datos local cifrada.
- Motor de integración financiera (Python)
- Módulos independientes para scraping, fetching de APIs y normalización de datos desde bancos, brokers, exchanges y otras fuentes.
- Se ejecutan como scripts programados (cron/pm2) y comunican los datos al backend vía API interna o archivos temporales.
- Base de datos cifrada local (SQLite + SQLCipher)
- Almacenamiento seguro de todas las transacciones, configuraciones y credenciales de integraciones.
- Solo accesible localmente, con clave maestra para descifrado.
- Infraestructura y despliegue (Docker)
- Imagen única que orquesta frontend, backend y servicios auxiliares.
- Volúmenes persistentes para la base de datos y configuraciones.
Diagrama de alto nivel
1+-----------+ +-------------+ +------------------+ +---------------------+2| Frontend |<---->| Backend |<---->| DB cifrada | | Integradores Python |3| (React) | | (Node/TS) | | (SQLite+SQLCipher)|<--->| (APIs, Scraping) |4+-----------+ +-------------+ +------------------+ +---------------------+5 ^ ^ ^ ^6 | | | |7 Usuario REST API/WS Acceso SQL Cron jobs/scripts
Componentes clave y decisiones técnicas
1. Multi-fuente y multi-activo: Normalización flexible
Los datos financieros provienen en formatos y estructuras radicalmente distintos. Para abstraer fuentes heterogéneas (APIs bancarias, CSV de brokers, web scraping, etc.), diseñé un schema intermedio con estos principios:
- Entidades financieras (bancos, brokers, exchanges)
- Cuentas y productos: acciones, fondos, cuentas, cripto, inmuebles, préstamos, etc.
- Movimientos/Transacciones: ingresos, gastos, aportaciones, dividendos, compras/ventas
- Atributos flexibles: custom fields para soportar activos no convencionales
Esto permitió diseñar integradores independientes en Python que mapean cualquier fuente al modelo común antes de persistir en la base de datos.
Ejemplo de parser para movimientos bancarios:
1def parse_bank_statement(csv_file):2 df = pd.read_csv(csv_file)3 transactions = []4 for _, row in df.iterrows():5 tx = {6 "date": row['Fecha'],7 "amount": float(row['Importe']),8 "currency": row['Moneda'],9 "entity": "MiBanco",10 "type": "income" if row['Importe'] > 0 else "expense",11 "description": row['Concepto'],12 }13 transactions.append(tx)14 return transactions
2. Seguridad y privacidad: cifrado local, no cloud
Uno de los mayores retos fue asegurar la privacidad total:
- Base de datos cifrada (SQLCipher): Todos los datos se almacenan cifrados en disco, y sólo se descifran en memoria durante la sesión autenticada.
- Sin llamadas a servidores externos: Salvo integraciones explícitas (ej: APIs de cotizaciones), el sistema nunca envía datos fuera de la máquina local.
- Autenticación local fuerte: Login protegido por contraseña (hash+salt), con posibilidad de añadir 2FA.
Configuración de SQLCipher en Node.js:
1import sqlite3 from 'sqlite3';2const db = new sqlite3.Database('finanze.db');3db.run("PRAGMA key = 'mi_clave_secreta_robusta';");
3. Multi-moneda y tipos de cambio automáticos
Muchos activos están denominados en divisas distintas (EUR, USD, BTC, etc.). Para mantener el dashboard coherente:
- Se integró una capa de conversión automática usando APIs públicas (ECB, CoinGecko).
- Se almacenan los valores originales y el histórico de tipos de cambio para simulaciones y reporting.
Ejemplo de fetch de tipo de cambio:
1async function getExchangeRate(from: string, to: string): Promise<number> {2 const res = await fetch(`https://api.exchangerate.host/latest?base=${from}&symbols=${to}`);3 const data = await res.json();4 return data.rates[to];5}
4. Exportación dinámica a Google Sheets y plantillas
La exportación a hojas de cálculo era clave para reporting avanzado o trabajar con asesores. Implementé:
- Plantillas personalizables para definir el formato y contenido de las exportaciones.
- Integración con la API de Google Sheets usando OAuth2 local, sin exponer credenciales.
- Exportación manual y programada.
Retos técnicos y cómo los resolví
1. Integración heterogénea y scraping
Cada banco/broker tiene formatos distintos, APIs poco documentadas o requerimientos de scraping. Para evitar que cambios en las webs rompieran el sistema:
- Separé cada integrador como un módulo Python independiente: así, actualizar uno no afecta al resto.
- Uso de parsers robustos (regex, heurísticas) y fallback manual (importación desde CSV/Excel).
- Tests automáticos por entidad/activo para detectar cambios en los formatos.
2. Escalabilidad y rendimiento local
A pesar de ejecutarse en máquina personal (Raspberry Pi, miniPC…), había que optimizar:
- Cargas diferidas y paginación en frontend para grandes volúmenes de movimientos.
- Uso de índices en la base de datos para búsquedas rápidas.
- Sincronización incremental para integraciones (fetch sólo de movimientos nuevos).
3. UX: Flexibilidad sin abrumar
Un dashboard financiero puede volverse complejo rápidamente. Para mantener una buena experiencia:
- UI modular, con widgets customizables y dashboards por tipo de activo.
- Wizards y formularios guiados para importar datos y configurar entidades.
Resultados: Impacto y métricas
Desde su despliegue personal y en pruebas con otros usuarios:
- Más de 15 entidades financieras soportadas, entre bancos, brokers tradicionales y cripto.
- >30 tipos de activos: desde acciones y fondos hasta préstamos p2p, inmuebles y commodities.
- Más de 100.000 movimientos procesados en total, con tiempos de carga <1s.
- Cero incidentes de pérdida o fuga de datos gracias al cifrado local.
Las funcionalidades más usadas han sido el dashboard consolidado, la simulación de escenarios (ahorro/jubilación) y la exportación flexible.
Aprendizajes y mejoras futuras
Lo que funcionó bien:
- El enfoque modular (integradores Python + backend TypeScript) facilita añadir nuevas fuentes o activos.
- El cifrado local y la autoalojabilidad han dado tranquilidad y control total a los usuarios.
- La capa de normalización ha simplificado mucho la agregación de datos heterogéneos.
Posibles mejoras:
- Mejorar la integración de APIs bancarias abiertas (PSD2/OpenBanking) para reducir el scraping.
- UI más personalizable y soporte para notificaciones/alertas automáticas.
- Mobile-friendly: adaptar la SPA a dispositivos móviles.
- Plugins/extensiones de terceros para permitir a la comunidad añadir nuevas integraciones.
Conclusión
finanze nació de una necesidad muy real: tener control sobre el patrimonio global sin sacrificar privacidad ni flexibilidad. El viaje de diseño y desarrollo ha sido una oportunidad para aplicar conceptos de arquitectura modular, seguridad y automatización, y el resultado es una herramienta potente y extensible que demuestra el valor de unir Python, TypeScript y buenas prácticas de software.
¿Te interesa contribuir o probarlo?
¡Siempre abierto a feedback, ideas y colaboraciones! 🚀