De un reto técnico a un SPA full-stack: Angular 19, Express y Firebase
Caso de estudio: cómo resolví un reto técnico full-stack —una app de tareas con login, CRUD y estado— con Angular 19, un API Express + TypeScript en Cloud Functions y Firestore, aplicando arquitectura hexagonal, SOLID y buenas prácticas.
Una startup de mensajería en tiempo real me lanzó un reto técnico full-stack: construir una aplicación de tareas completa, con login, CRUD y estado, en pocos días. El detalle divertido es que el frontend pedía Angular, un framework que hasta ese momento nunca había tocado. Aun así, no bajé el listón: entregué un SPA con arquitectura limpia, un API desacoplado y buenas prácticas de punta a punta. Este es el detrás de escena —las decisiones técnicas, no solo el resultado— y lo que revela de cómo trabajo.
El reto
Sobre el papel sonaba a un «Todo app» más. En la práctica, el enunciado estaba diseñado para medir criterio de arquitectura, no para ver si sabías pintar una lista. Lo que pedían:
- Dos páginas. Un inicio de sesión y un tablero principal con las tareas del usuario ordenadas por fecha de creación.
- Login solo con correo. Si el usuario existe, entra directo; si no, aparece un diálogo que confirma su creación y luego pasa al tablero.
- CRUD de tareas. Cada tarea con título, descripción, fecha de creación y estado; crear, editar, eliminar y marcar como completada o pendiente con una casilla.
- Responsive y usable en cualquier dispositivo.
- Un API propio con Express y TypeScript, desplegado en Cloud Functions, con Firestore como base de datos, y seis endpoints: listar, crear, actualizar y eliminar tareas, más buscar y crear usuario.
Cómo se evaluaba (el listón real)
El enunciado era explícito: se prioriza la calidad del código y las buenas prácticas por encima de la cantidad de funcionalidades. En concreto miraban arquitectura limpia o hexagonal, principios SOLID, patrones de diseño (repositorios, factories, singletons), observables y servicios bien estructurados en el frontend, trackBy en las listas, guards y lazy loading, seguridad con tokens y CORS, pruebas unitarias, y un README útil. En otras palabras: no querían ver qué construyes, sino cómo decides.
Esa última frase fue mi brújula. Preferí entregar menos superficie, pero cada pieza en su sitio.
Arquitectura de un vistazo
Antes de escribir una línea, dibujé el flujo completo: un cliente Angular que habla por HTTP con un API Express desplegado como función serverless, que a su vez persiste en Firestore. Cada petición viaja autenticada con un token; cada capa tiene una única responsabilidad.
La idea de fondo es simple: el cliente no sabe nada de Firestore y el dominio no sabe nada de Express. Cada frontera está ahí para que una decisión de infraestructura (cambiar la base de datos, mover el hosting) no se filtre al resto.
Las decisiones técnicas
Aquí está el corazón del reto. Separé el trabajo en tres frentes y en cada uno tomé decisiones deliberadas.
Angular 19, aprendido sobre la marcha, pero con criterio.
- Componentes standalone y funcionalidades cargadas con lazy loading, para que el navegador solo descargue lo que la ruta necesita.
- Estado con Angular Signals —el enfoque moderno del framework— y RxJS para las llamadas al API, que la plantilla consume con el
asyncpipe: nada de suscripciones colgadas que limpiar a mano. - Dos guards: uno de autenticación protege el tablero, y otro de habilidad (CASL) decide qué puede hacer cada rol antes de entrar a una ruta.
- Un interceptor HTTP adjunta el token a cada petición de forma centralizada.
- Formularios reactivos con validación tipada, y
trackByen la lista para que Angular no repinte el DOM entero en cada cambio. - Angular Material + TailwindCSS, con una arquitectura modular
core / features / sharedy una interfaz responsive y accesible.
Por qué la arquitectura hexagonal aquí
Para una lista de tareas puede sonar a sobreingeniería. No lo era: el reto medía justamente esto. Separar dominio, aplicación e infraestructura convierte «funciona» en «funciona y se puede mantener», y demuestra que sé dónde poner cada responsabilidad aunque el dominio sea pequeño.
El flujo de usuario, paso a paso
Con la arquitectura resuelta, la experiencia quedó exactamente como pedía el enunciado:
Inicia sesión con el correo
El usuario escribe solo su email. El frontend pregunta al API si ya existe.
Confirma la creación si es nuevo
Si el correo no está registrado, un diálogo pregunta si quiere crear la cuenta. Al aceptar, se da de alta y entra directo, sin fricción extra.
Gestiona sus tareas
En el tablero ve sus tareas ordenadas por fecha. Puede agregar una nueva desde un formulario, editarla o eliminarla.
Marca el progreso
Una casilla alterna cada tarea entre completada y pendiente, con la interfaz respondiendo al instante gracias a los observables.
Qué demuestra esto
El reto no era la lista de tareas; era la excusa. Lo que realmente entregué fue una forma de trabajar:
- Aprendí Angular sobre la marcha sin sacrificar la calidad. Enfrentar un framework nuevo bajo presión y aun así aplicar lazy loading, guards y estado con signals como se debe dice más que cualquier certificación.
- Fui más allá de lo pedido donde aportaba valor. El enunciado invitaba a sumar extras; añadí permisos por roles con CASL —con su guard de habilidad y su página de acceso denegado— para mostrar cómo modelo autorización de verdad, no solo un login.
- Prioricé criterio sobre cantidad, tal como pedía la nota del enunciado: cada pieza con una arquitectura que puedo defender.
- Pensé en el mantenimiento desde el día uno. Un dominio desacoplado de la infraestructura es lo que separa un prototipo de algo que un equipo puede hacer crecer.
Esa es, al final, la mentalidad senior: no solo resolver el problema de hoy, sino dejar el código listo para el de mañana.
El código completo está en los repositorios del frontend en Angular y del API en Express.
Artículos relacionados
Playground de Live Helper Chat con Docker, Traefik, MySQL y Redis
Guía paso a paso para levantar Live Helper Chat —el chat de soporte en vivo de código abierto— con Docker, Traefik, MySQL y Redis, en sus versiones PHP y NodeJS.
Cómo preparar un plan de despliegue con Docker, Traefik y Let’s Encrypt para un Microservicio y una PWA
Plan de despliegue con Docker Compose y Traefik: proxy inverso, SSL automático con Let's Encrypt y middlewares para un microservicio Laravel y una PWA.
