04 — Flujos de usuario — RELE
1. Mapa de flujos
| ID | Flujo | Actor | Éxito |
|---|
| F0 | Descubrimiento home | Visitante | Entiende valor y va a login |
| F1 | Login | RESIDENT / ADVISOR | JWT + redirect /lecturas |
| F2 | Consultar lecturas + stats | Ambos | Lista y strip stats |
| F3 | Crear lectura | RESIDENT | Reading SUBMITTED con código |
| F4 | Revisar y cambiar estado | ADVISOR | REVIEWED o FLAGGED + nota |
| F5 | Empty state | RESIDENT (0 items) | CTA a nueva lectura |
| F6 | Error de red | Ambos | Mensaje + reintento |
| F7 | Logout | Ambos | Limpia token |
2. F0 — Home → Login
[/] → CTA "Soy residente" | "Soy asesor" | "Abrir panel"
→ [/login]
| Paso | UI | Notas |
|---|
| 1 | Hero + media + 3 pasos | Copy: sin Excel / WhatsApp |
| 2 | Click CTA | Ambos CTAs a /login (mismo form; [DECISIÓN]) |
| 3 | Login prefill residente | casa@rele.energy por defecto en demo |
3. F1 — Login JWT
[/login] → email + password → POST /api/auth/login
OK → localStorage (rele_token, rele_user) → [/lecturas]
KO → mensaje "Credenciales inválidas"
| Campo | Validación cliente | Server |
|---|
| email | required, type email | @IsEmail() |
| password | required, min práctico | @MinLength(6) + bcrypt |
Errores
| Caso | Respuesta | UI |
|---|
| Password incorrecta | 401 | coral “Credenciales inválidas” |
| API caída | network error | mismo mensaje genérico o red |
4. F2 — Lista + stats
[/lecturas] (token required)
→ GET /api/readings/stats/summary
→ GET /api/readings
→ render cards + badges
| Rol | Datos |
|---|
| RESIDENT | Solo residentId = me; include home + advisor |
| ADVISOR | Todas; include home + resident |
Soft auth UI: si no hay token → navigateByUrl('/login').
Variantes
| Estado | Condición | UI |
|---|
| Loading | petición en curso | lista vacía / sin empty (loading flag) |
| Empty | 200 + [] | “Sin lecturas todavía” + CTA si RESIDENT |
| Error | HTTP error | panel coral + Reintentar |
| Success | items ≥ 1 | lista ordenada createdAt desc |
5. F3 — Crear lectura (RESIDENT)
[/lecturas] → "Nueva lectura" → [/lecturas/nueva]
→ period, kwh, costEur?, notes?
→ POST /api/readings
→ OK → [/lecturas] (o detalle según impl.)
→ ADVISOR que intenta POST → 403
| Campo | Req | Ejemplo |
|---|
| period | Sí | 2026-08 |
| kwh | Sí, ≥ 0 | 245 |
| costEur | No | 56.1 |
| notes | No | Lectura contador 14 ago |
Server [COMPROBADO en código]:
- Solo
role === RESIDENT.
- Resuelve
Home del residente (primera o crea default).
status = SUBMITTED, code = RE-MMDD-XXX.
6. F4 — Detalle + status (ADVISOR)
[/lecturas] → click card → [/lecturas/:id]
→ GET /api/readings/:id
→ (si ADVISOR) textarea nota + botones REVIEWED | FLAGGED
→ PATCH /api/readings/:id/status { status, advisorNote? }
→ UI actualiza item
| Transición típica | Desde | Hacia |
|---|
| Validar OK | SUBMITTED | REVIEWED |
| Anomalía / tarifa | SUBMITTED o REVIEWED | FLAGGED |
| Re-validar | FLAGGED | REVIEWED |
Permisos
| Actor | Puede ver detalle | Puede PATCH |
|---|
| RESIDENT dueño | Sí | No |
| RESIDENT ajeno | 403 | No |
| ADVISOR | Sí | Sí |
7. F5 — Empty
| Condición | Mensaje | CTA |
|---|
| RESIDENT 0 lecturas | “Sin lecturas todavía…” | Registrar lectura |
| ADVISOR 0 lecturas | Mismo empty sin CTA create | — |
8. F6 — Error de red (lista)
GET fallan → error string → panel "No se pudieron cargar las lecturas"
→ [Reintentar] → load()
9. F7 — Logout
click Salir → ApiService.logout() → limpia localStorage → login/home
10. Diagrama de estados Reading
create (RESIDENT)
│
▼
SUBMITTED ◄──────────────┐
/ \ │
/ \ │
▼ ▼ │
REVIEWED FLAGGED ──────────┘
\ / (PATCH ADVISOR)
\ /
▼ ▼
(ambos pueden re-etiquetarse vía PATCH libre en v1)
Nota [DECISIÓN]: la API acepta cualquier ReadingStatus en PATCH (enum); no hay máquina de estados rígida server-side más allá de rol ADVISOR. DRAFT existe en enum para evolución, no se emite en create v1.
11. Criterios de aceptación por flujo
| Flujo | AC |
|---|
| F1 | Login demo 200; mal password mensaje visible |
| F2 | Stats total/open/kwhTotal coherentes con seed |
| F3 | POST crea SUBMITTED con code RE- |
| F4 | PATCH REVIEWED asigna advisorId |
| F5 | Empty sin crash |
| F6 | Reintentar re-lanza GETs |