feat: app completa recordaLexia (fases 1-5)

App web familiar de rutinas visuales para niños con TDAH: muestra cada día el
material del cole y las rutinas de tarde, con gamificación por monedas y tienda
de recompensas. Multi-niño y bilingüe ES/CA. Uso doméstico/homelab.

Backend (Spring Boot 3.5 / Java 21 / Gradle):
- Dominio por capas, PostgreSQL + Liquibase, datos semilla.
- API REST con DTOs: /today, toggle con monedas y bonos de bloque/día, monedero,
  tienda/canje, ajustes y CRUD del panel de padres.
- Seguridad ligera por PIN (BCrypt + sesion en memoria), sin Keycloak.
- Tests JUnit: generacion del dia, monedas/bonos con reversion, canje, seguridad.

Frontend (Angular 19, standalone + signals):
- Perfiles, Home (Tablero y Foco), Tienda y panel de padres (5 pestañas).
- Tipografia OpenDyslexic conmutable (accesibilidad), i18n ES/CA, TTS y sonido.
- Tokens de diseño fieles al handoff (paleta, animaciones, monedas voladoras).

Empaquetado:
- Docker multi-stage + docker-compose (PostgreSQL + backend + Nginx).
- Decisiones de arquitectura documentadas en docs/adr.
This commit is contained in:
Jaume Garriga Maestre
2026-06-21 10:48:57 +02:00
commit 52e559a159
160 changed files with 29022 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
# ADR 0001 — Fijar Spring Boot 3.5.x (no Spring Boot 4)
- **Estado:** aceptada
- **Fecha:** 2026-06-21
- **Fase:** 1 (esqueleto)
## Contexto
Al generar el backend con Spring Initializr, la versión por defecto ofrecida es
**Spring Boot 4.1.0** (arrastra Spring Framework 7 y un layout de *starters*
modular nuevo: `spring-boot-starter-webmvc`, starters de test por módulo).
El contrato del proyecto (CLAUDE.md y prompt director) fija explícitamente
**Spring Boot 3.x / Java 21**.
## Decisión
Se fija el backend a **Spring Boot 3.5.15** (última 3.x disponible en Initializr),
con `spring-boot-starter-web`, `liquibase-core` y `spring-boot-starter-test`.
## Motivación
- Cumplir el stack contratado sin introducir un salto de versión mayor no aprobado.
- Boot 4 / Spring 7 es muy reciente; el ecosistema y los ejemplos del equipo
siguen sobre 3.x.
## Consecuencias
- Cuando se quiera adoptar Boot 4 deberá hacerse mediante un ADR propio que
contemple la migración de starters y `jakarta`.
- El wrapper de Gradle queda fijado por Initializr; no se requiere Gradle en host.

View File

@@ -0,0 +1,39 @@
# ADR 0002 — OpenDyslexic como tipografía por defecto (conmutable)
- **Estado:** aceptada
- **Fecha:** 2026-06-21
- **Fase:** 1 (esqueleto / preparación de tipografías)
## Contexto
El handoff de diseño fija como tokens tipográficos **Fredoka** (títulos/labels) y
**Nunito** (texto). Para una app dirigida a peques con TDAH (y con vocación de
lectura accesible) se solicita usar **OpenDyslexic**, fuente diseñada para mejorar
la legibilidad. Esto entra en tensión con los tokens fijos del handoff, por lo que
se decide cómo conviven.
## Decisión
1. **OpenDyslexic es la tipografía por defecto y se aplica a TODO el texto**
(títulos, etiquetas y cuerpo).
2. **Es una preferencia conmutable por niño, activada de serie.** Al desactivarla,
la UI cae a las tipografías de marca del handoff (Fredoka/Nunito).
3. **Las tres familias se empaquetan en local** vía `@fontsource` (sin CDN): la
app funciona en un kiosko sin internet garantizado.
## Implementación
- Fichero único de tokens `frontend/src/styles/_theme.scss`: define
`--font-display` y `--font-body`, que por defecto apuntan a OpenDyslexic.
- Interruptor: atributo `data-dyslexia-font` en `<html>`. Con valor `off`, los
tokens caen a las familias de marca. Se inicializa a `on` en `index.html`.
- `FontPreferenceService` aplica/persiste la preferencia (hoy en `localStorage`).
## Consecuencias
- La UI nunca referencia una familia concreta, solo `var(--font-display/-body)`:
el cambio de tipografía no toca componentes.
- **Pendiente Fase 5:** la preferencia se conectará a los ajustes por niño del
backend, sustituyendo el almacenamiento en `localStorage`.
- Se cargan solo los subsets `latin` y `latin-ext` (suficientes para ES/CA) para
no inflar el bundle.

View File

@@ -0,0 +1,51 @@
# ADR 0003 — Decisiones de dominio y seguridad (Fase 2 backend)
- **Estado:** aceptada
- **Fecha:** 2026-06-21
- **Fase:** 2 (dominio + API + seguridad)
## Contexto
El contrato de backend deja varias decisiones "a tu criterio". Se documentan aquí
las tomadas al implementar el dominio, la lógica de negocio y la seguridad.
## Decisiones
1. **i18n por columnas embebidas** `label_es` / `label_ca` (no tabla de
traducciones). Encaja con el shape de los DTOs y simplifica las consultas.
2. **Capas, sin hexagonal completa.** Entidad JPA como modelo de dominio (con
comportamiento donde aporta: monedero en `Child`, marcado en `DailyTask`), DTOs
tipo `record` en el borde. **Nunca se exponen entidades**.
3. **Mañana vs. tarde.** La mañana se modela con `Activity` (catálogo) +
`WeeklyTemplateEntry` (asignación por día). La tarde con `AfternoonRoutine`
directa por día. Se evita una FK polimórfica.
4. **Eventos = banner, no tareas.** Los `SpecialEvent` (examen/deberes) se muestran
en el banner informativo del día (`specialEvents[]`), NO se materializan como
`DailyTask` marcables (coherente con el prototipo). El enum `TaskOrigin.EVENT`
queda disponible por si se decide lo contrario más adelante.
5. **Bonos reconciliables.** El bono de bloque y el de día se modelan como
transacciones de monedas con el mismo motivo: +importe al otorgar, -importe al
revertir. "Suma neta > 0" indica bono activo. Hace el marcar/desmarcar robusto
ante cualquier secuencia.
6. **Seguridad ligera.** PIN de padres con hash BCrypt; al validarlo se abre una
sesión en memoria identificada por un valor opaco (cabecera `X-Parent-Session`).
`/api/parents/**` exige rol PARENT; el resto (kiosko del niño) es abierto. Todo
encapsulado en el paquete `security` para poder sustituirlo por Keycloak.
7. **Semilla por `DataSeeder`** (ApplicationRunner, solo si la BD está vacía), no
por Liquibase. Más mantenible y tipado. Desactivable con
`recordalexia.seed.enabled=false` (lo usan los tests).
8. **Tests sobre H2** (modo PostgreSQL) con Liquibase. Se valorará Testcontainers
en una iteración posterior para fidelidad total con PostgreSQL.
## Consecuencias
- Los textos visibles siempre viajan en ES y CA; el frontend elige idioma.
- Cambiar a Keycloak no afecta a controladores ni servicios de negocio.
- La zona horaria del negocio (Europe/Madrid) se centraliza en un `Clock` inyectable.