MyDebts
Proyecto personalUna demo completa de una aplicación para gestionar deudas entre personas, con deudas marcadas automáticamente como vencidas y enlaces para compartir deudas sin necesidad de registro.

Recordar quién debe qué no debería depender de la memoria
Las deudas entre amigos y familiares suelen llevarse de forma informal: una nota mental, un hilo de mensajes, una hoja en una libreta... hasta que alguien lo olvida y sacar el tema se vuelve incómodo. MyDebts es una aplicación simple y clara para registrar deudas individuales, cada una con su propia fecha de vencimiento, para que nada dependa de la memoria.
- Desarrollado en solitario, en tardes y fines de semana, durante mi tiempo libre. La arquitectura tenía que ser sencilla para facilitar el desarrolo tras largos periodos sin actividad.
- Alojamiento en niveles gratuitos durante todo el proyecto
- Una demo completa: autenticación real, recuperación de contraseña y protección contra bots.
- Un despliegue real en vivo, accesible para cualquiera con quien lo compartiera, no solo una build local.
Un frontend en Angular 20 zoneless respaldado por una pequeña API en Express.
Angular 20 con componentes standalone en todo el proyecto, usando detección de cambios zoneless y signals para el estado local en lugar de una librería de estado. El backend es Express 5 con MongoDB a través de Mongoose, validado con Joi, además de un flujo completo de autenticación JWT: registro, inicio de sesión, recuperación de contraseña y protegido con Cloudflare Turnstile.
Cinco decisiones, y lo que costó cada una.
Angular zoneless, no la detección de cambios por defecto
Angular 20 lanzó la detección de cambios zoneless como una opción real, no solo un experimento, y quise construir directamente sobre ella en lugar del zone.js por defecto que la mayoría de tutoriales todavía enseña. Las llamadas a signal() locales sustituyen a NgRx: la aplicación no necesitaba una librería de gestión de estado, así que no añadí una. YAGNI aplicado a una decisión de arquitectura, no solo a una función suelta.
Un arranque en frío visible, no oculto
El backend se duerme en el nivel gratuito de Render, así que la primera petición tras un periodo de inactividad puede tardar varios segundos. Un interceptor de peticiones inicia un temporizador y, pasados tres segundos, muestra un aviso de 'despertando el servidor' y reintenta automáticamente, en lugar de dejar la interfaz con aspecto de estar bloqueada.
Instantáneas de solo lectura para los enlaces compartidos, no acceso en vivo
Compartir una deuda tenía que funcionar sin crear una cuenta para la otra persona. Un enlace compartido genera un token que apunta a una instantánea de la deuda en ese momento, no a una referencia en vivo, y caduca automáticamente a las 48 horas mediante un índice TTL de MongoDB.
Cálculo bajo demanda como red de seguridad, junto al cron job
Un cron job marca las deudas vencidas una vez al día, pero el backend se duerme en el nivel gratuito de Render y ese cron job puede no ejecutarse si el servidor está inactivo en ese momento. Como red de seguridad, getDebts() también recalcula el estado de vencimiento bajo demanda en cada llamada, así que aunque el cron job falle, la lectura nunca devuelve un estado desactualizado.
Migración de Karma a Vitest para compatibilidad con zoneless
Zoneless requiere patrones explícitos que Karma no siempre gestionaba bien en este proyecto: detección de cambios manual en tests, imports concretos como BrowserTestingModule y platformBrowserTesting. Migrar a Vitest resolvió esos problemas de compatibilidad y de paso redujo el tiempo de ejecución de la suite.
Sin cliente, ni ingresos. Sólo un proyecto personal
Zoneless, de forma deliberada
Signals y detección de cambios zoneless usados como modelo de estado principal en lugar de NgRx, no añadidos a posteriori.
Una suite de tests migrada
Karma sustituido por Vitest ya habiendo empezado el proyecto, para resolver problemas reales de compatibilidad con zoneless.
Un sistema completo
Autenticación JWT completa, recuperación de contraseña y protección contra bots, no una demo que se salta las partes menos vistosas.
Vencimiento que no depende de un solo mecanismo
Cron job diario respaldado por cálculo bajo demanda en getDebts(), para que un servidor dormido no deje datos desactualizados.
Este proyecto no tiene cifras de conversión que reportar, porque no tiene cliente. Lo que tiene son dos repositorios públicos disponibles para consultar y un despliegue completo. Siempre dispuesto a aceptar feedback!