← Volver a proyectos

GrowTogether

Una app de hábitos con componente social, inspirada en Atomic Habits de James Clear: construye hábitos consistentes, visualiza tu progreso y compite con amigos en desafíos.

Cuándo
2025/2026 · Trabajo Final de Grado de DAM
Rol
Todo el proyecto, en solitario
Stack
  • Java 17
  • Spring Boot 3.3
  • PostgreSQL
  • JWT
  • Flutter 3.38
  • Dart 3.10
  • Provider
  • Docker
Enlaces

El proyecto ya no está desplegado: no hay demo en línea ni APK que descargar. El código completo está en los cuatro repositorios, y la memoria explica cómo funcionaba por dentro.

Qué es

Arquitectura

Por dentro son cuatro repositorios: la app y el panel de administración se apoyan en una API propia y un paquete de datos compartido.

APP Flutter · Android ADMIN Flutter Web DATA paquete Dart API Spring Boot
Un mismo paquete de datos alimenta la app y el panel; solo él habla con la API.
APP
App Flutter que usan los usuarios finales: hábitos con rachas y un heatmap de actividad hecho a mano, desafíos entre amigos y consejos diarios, en español, inglés y catalán. Sigue funcionando sin cobertura leyendo de una caché local. Por qué el heatmap es propio · Por qué no se cae sin conexión
ADMIN
Panel de administración en Flutter Web, de uso interno, con auditoría de las acciones del equipo. Comparte toda su lógica de datos con la app a través de DATA.
DATA
Paquete Dart compartido entre la app y el panel: es el único que habla con la API, así ambos consumen hábitos, desafíos y datos de usuario de la misma forma, sin duplicar lógica.
API
Backend en Spring Boot: autenticación, hábitos, desafíos y lo que necesita el panel de administración. Un job programado a medianoche rellena el historial para que las estadísticas del día siguiente estén listas. Por qué no hay blacklist de tokens · Por qué no hay huecos en el historial

Capturas

Pantalla de inicio de la app: progreso del día y lista de hábitos, cada uno con su racha.
Los hábitos del día, con la racha de cada uno.
Formulario de nuevo hábito: selector de hábito positivo o negativo, nombre, descripción y una rejilla de iconos.
Crear un hábito, positivo o negativo, con su icono.
Pantalla de análisis: mapa de calor del año, totales de hábitos completados y ranking de mejores rachas.
Mapa de calor del año y récords de racha.
Pantalla de desafíos: retos activos con sus participantes, días restantes y barra de progreso.
Desafíos en grupo, con progreso y participantes.
Detalle de un desafío: botón de hecho hoy con la racha y un podio con los tres participantes que más puntos llevan.
El podio del desafío, con los puntos de cada participante.
Pantalla de perfil con el selector de idioma abierto: castellano, inglés y valenciano.
La app está en castellano, inglés y valenciano.
La misma pantalla de inicio con un aviso rojo de sin conexión, mostrando los datos guardados en el dispositivo.
Sin conexión: la app avisa y sigue mostrando los datos del dispositivo.
Panel de administración web: tarjetas con usuarios activos, hábitos creados y desafíos, y un gráfico de altas por mes.
El panel de administración, en web: métricas de uso y altas por mes.
Panel de administración: tabla de consejos diarios con fecha, título, descripción y estado, y pestañas de lista y calendario.
Los consejos diarios se programan desde el panel, en lista o en calendario.

Decisiones técnicas

Cada decisión de arquitectura quedó documentada con las alternativas que descarté y por qué.

Revocar sesión sin tabla extra
Un campo tokenVersion en vez de una blacklist en base de datos o Redis: revocación inmediata sin romper el modelo stateless de JWT.
Heatmap de actividad hecho desde cero
En vez de un paquete de terceros, construí el calendario de calor a mano para controlar colores, tamaños y la diferencia entre «no completado» y «día futuro».
Historial sin huecos, sin filas de sobra
Los días sin interacción no generan registro; una tarea programada a medianoche rellena los huecos para que las estadísticas del día siguiente estén listas.
La app no se cae sin cobertura
Lectura tolerante a red con caché local; las acciones que sí necesitan conexión avisan con claridad. Sin la complejidad de una cola de sincronización offline completa.
Romper un ciclo de dependencias
Separé el PasswordEncoder de la configuración de seguridad para evitar un ciclo real entre Spring Security y el servicio de usuarios.

Más detalles del proyecto

Todo lo que hay en esta página sale de la memoria que presenté: los requisitos, los casos de uso, los diagramas de clases y de base de datos, las pruebas y el despliegue, con el porqué de cada decisión.