Saltar al contenido
Volver a proyectos
En produccióncaudalwallet.com

Caudal

Gestor de finanzas personales, self-hosted y open source.

  • Python 3.12
  • Django 5.2
  • PostgreSQL 17
  • HTMX
  • Alpine.js
  • gunicorn
  • WhiteNoise
  • Docker
  • uv
  • pytest
  • ruff
Caudal abierto en el celular en caudalwallet.com, mostrando el panel de gastos hormiga con las categorías Viaje, Hogar, Salud y Supermercado, y un aumento del 46% respecto del mes pasado. Los importes están desenfocados.
Caudal abierto en el celular en caudalwallet.com, mostrando el panel de gastos hormiga con las categorías Viaje, Hogar, Salud y Supermercado, y un aumento del 46% respecto del mes pasado. Los importes están desenfocados.

El problema que solucionamos

Los gastos chicos y repetidos —el café, el delivery, la suscripción que nadie cancela— no aparecen como problema en ningún resumen bancario. Están diluidos entre cientos de líneas sin categoría. A fin de mes falta plata y no hay forma de señalar dónde se fue.

Qué hace

Caudal separa el sueldo de lo que queda, y hace visible el gasto hormiga como categoría propia. Está pensado para el celular: cargar un gasto son dos toques, porque un gestor que tarda no se usa.

Arquitectura

Todo corre en mi VPS, en contenedores. El único proceso que escucha en internet es el reverse proxy; la aplicación y la base se hablan por una red interna de Docker que no sale a ningún lado.

  1. Borde

    Caddy

    Único puerto expuesto (443). Emite y renueva el TLS solo.

  2. Aplicación

    gunicorn + Django

    Sin puertos publicados al host: Caddy lo alcanza por el nombre del servicio.

  3. Datos

    PostgreSQL 17

    En una red interna aparte. Verificado que no responde desde la red del proxy.

  • Ningún contenedor de aplicación o base publica puertos: Docker escribe reglas de iptables por debajo del firewall, así que un puerto publicado deja Postgres expuesto aunque ufw diga lo contrario.
  • Límites de memoria y CPU por contenedor, para que una aplicación con problemas no deje sin recursos a las demás del servidor.
  • Respaldo diario con un timer de systemd, y la restauración se prueba de verdad: se restaura en una base descartable y se comparan las filas contra el origen.

El desafío

Importar extractos bancarios que no se parecen entre sí

Cada banco y billetera exporta el CSV a su manera: distinto delimitador, distinto encoding, distintos nombres de columna y dos convenciones incompatibles para los montos (1.234,56 en Argentina, 1234.56 en Estados Unidos). El importador detecta todo eso solo, por alias de columna y por análisis del contenido, y deduplica por el identificador de la fuente —o por un hash de fecha, monto y descripción cuando la fuente no da identificador— para que reimportar el mismo archivo no duplique nada. Encima corre un motor de reglas editable que autocategoriza por palabra clave; lo que no matchea queda marcado para revisar, en vez de caer en una categoría equivocada en silencio.

Decisiones técnicas

  • HTMX y Alpineen vez deuna SPA con React

    Porque La app es formularios y listas. Una SPA habría sumado un toolchain de Node, un bundle al cliente y un estado duplicado, para resolver algo que el servidor ya resuelve. Con HTMX la carga en dos toques es un fragmento de HTML.

  • Postgres en mi propio VPSen vez deseguir en un Postgres gestionado

    Porque Bajaba la latencia y sacaba una dependencia externa, pero volvía bloqueante tener backups reales. Fue un intercambio consciente: la base propia no vale nada sin restauración probada.

  • Importación por CSVen vez deintegración en vivo con Mercado Pago

    Porque Evalué la API y no sirve para esto: está orientada a comercios y expone los cobros, no los gastos propios del usuario que paga. Descartarla temprano evitó construir sobre una expectativa falsa.

Qué aprendí

Que migrar datos no termina cuando el restore corre sin errores. Terminé comparando el conteo de filas de las 18 tablas contra el origen y corriendo el chequeo de migraciones pendientes, porque un restore que 'no falló' y una base correcta no son lo mismo.

Qué sigue

Gastos fijos recurrentes autogenerados y proyección del resto del sueldo a fin de mes.

En resumen

  • Open source con licencia MIT
  • Cada versión congelada en su rama y tag
  • Tests con pytest y linting con ruff
  • CHANGELOG mantenido a mano