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,55 @@
import { Component, OnInit, inject, signal } from '@angular/core';
import { Router } from '@angular/router';
import { ApiService } from '../../core/api.service';
import { I18nService } from '../../core/i18n.service';
import { KioskService } from '../../core/kiosk.service';
import { ChildSummary } from '../../core/models';
/**
* Pantalla de entrada del kiosko: "¿QUIÉN ENTRA HOY?". Tarjetas grandes por niño
* con mascota, nombre y monedero. Al elegir, entra a su día (Home).
*
* Nota: el selector de edad ± del prototipo era un control de demo. La edad es un
* dato que gestionan los padres, así que aquí se muestra como chip de solo lectura;
* su edición vive en el panel de padres (Fase 5).
*/
@Component({
selector: 'app-profile-select',
imports: [],
templateUrl: './profile-select.component.html',
styleUrl: './profile-select.component.scss',
})
export class ProfileSelectComponent implements OnInit {
private readonly api = inject(ApiService);
private readonly router = inject(Router);
private readonly kiosk = inject(KioskService);
protected readonly i18n = inject(I18nService);
protected readonly children = signal<ChildSummary[]>([]);
protected readonly loading = signal(true);
protected readonly error = signal(false);
ngOnInit(): void {
this.api.getChildren().subscribe({
next: (list) => {
this.children.set(list);
this.loading.set(false);
},
error: () => {
this.error.set(true);
this.loading.set(false);
},
});
}
/** Entra al día del niño. Aprovecha el gesto para pedir pantalla completa. */
enter(child: ChildSummary): void {
this.i18n.setLang(child.language);
this.kiosk.enterFullscreen();
this.router.navigate(['/home', child.id]);
}
openParents(): void {
this.router.navigate(['/parents']);
}
}