05 — Modelo de datos — RELE
1. Visión general
Dominio mínimo de lecturas energéticas L2:
| Entidad | Propósito |
|---|
| User | Identidad RESIDENT o ADVISOR |
| Home | Vivienda del residente (label, address, CUPS) |
| Reading | Lectura de contador por periodo con estado y notas |
Base: PostgreSQL (Neon) · ORM: Prisma · IDs: cuid().
2. Enums
Role
| Valor | Descripción |
|---|
RESIDENT | Titular / habitante que registra lecturas |
ADVISOR | Asesor que revisa y anota |
ReadingStatus
| Valor | Descripción |
|---|
DRAFT | Borrador (reservado; no usado en create v1) |
SUBMITTED | Enviada por residente (default create) |
REVIEWED | Revisada OK por asesor |
FLAGGED | Marcada para atención (tarifa / anomalía) |
3. Diagrama ER (texto)
User
id, email, passwordHash, name, role
homes[] (RESIDENT)
readings[] (como resident)
reviews[] (como advisor)
Home
id, label, address, cups
residentId → User
readings[]
Reading
id, code (unique)
period, kwh, costEur?, notes, advisorNote
status (default SUBMITTED)
homeId → Home
residentId → User
advisorId? → User
createdAt, updatedAt
4. Tablas / modelos Prisma
User
| Campo | Tipo | Constraints |
|---|
| id | String | PK, cuid |
| email | String | unique |
| passwordHash | String | bcrypt |
| name | String | |
| role | Role | RESIDENT | ADVISOR |
| createdAt | DateTime | default now |
| updatedAt | DateTime | updatedAt |
Home
| Campo | Tipo | Constraints |
|---|
| id | String | PK, cuid |
| label | String | ej. “Piso Ruzafa” |
| address | String | |
| cups | String | código punto suministro (demo) |
| residentId | String | FK User |
| createdAt | DateTime | |
Reading
| Campo | Tipo | Constraints |
|---|
| id | String | PK, cuid |
| code | String | unique, formato RE-MMDD-XXX |
| period | String | ej. 2026-08 |
| kwh | Float | ≥ 0 |
| costEur | Float? | opcional |
| notes | String | default "" |
| advisorNote | String | default "" |
| status | ReadingStatus | default SUBMITTED |
| homeId | String | FK Home |
| residentId | String | FK User |
| advisorId | String? | FK User (advisor) |
| createdAt / updatedAt | DateTime | |
5. Reglas de integridad y negocio
| Regla | Implementación |
|---|
| Auth lecturas | JwtAuthGuard en controller readings → 401 sin token |
| Create solo RESIDENT | ForbiddenException si no RESIDENT |
| Status solo ADVISOR | ForbiddenException si no ADVISOR |
| Get RESIDENT | Solo si reading.residentId === userId |
| Código único | code unique; generación RE- + MMDD + random 100–999 |
| Home en create | Primera home del residente o create default |
| Notes | Coalesce a "" si omitidas |
| Orden listado | createdAt desc |
| Password | Nunca en claro; solo passwordHash |
6. Contratos API (resumen)
POST /api/auth/login
Body: { email, password }
Response 200:
{
"accessToken": "<jwt>",
"user": { "id": "...", "email": "...", "name": "...", "role": "RESIDENT" }
}
GET /api/readings (JWT)
Array Reading + includes (home, resident|advisor según rol).
GET /api/readings/stats/summary (JWT)
{
"total": 5,
"open": 3,
"byStatus": {
"DRAFT": 0,
"SUBMITTED": 2,
"REVIEWED": 2,
"FLAGGED": 1
},
"kwhTotal": 921
}
open = SUBMITTED + FLAGGED.
kwhTotal = suma de kWh del scope del rol.
GET /api/readings/:id (JWT)
Reading con home + resident + advisor, o 404/403.
POST /api/readings (JWT RESIDENT)
Body
| Campo | Tipo | Req |
|---|
| period | string | sí (min 4) |
| kwh | number | sí ≥ 0 |
| costEur | number | no |
| notes | string | no |
| homeLabel | string | no (solo si se crea home) |
Response: Reading + home; status SUBMITTED.
PATCH /api/readings/:id/status (JWT ADVISOR)
Body: { status: ReadingStatus, advisorNote?: string }
Efecto: actualiza status, advisorId = me, advisorNote si se envía.
7. Seed de referencia
| Entidad | Datos |
|---|
| RESIDENT | Elena Marín · casa@rele.energy · password123 |
| ADVISOR | Toni Gil · asesor@rele.energy · password123 |
| Home | Piso Ruzafa · C/ Sueca 18, 3º · València · CUPS ES0021000000000001AB |
| RE-0809-01 | 2026-07 · 212 kWh · 48.6 € · REVIEWED |
| RE-0809-02 | 2026-06 · 168 kWh · 39.2 € · REVIEWED |
| RE-0809-03 | 2026-08 · 245 kWh · 56.1 € · SUBMITTED |
| RE-0809-04 | 2026-05 · 141 kWh · 33.4 € · FLAGGED |
| RE-0809-05 | 2026-04 · 155 kWh · 36.0 € · SUBMITTED |
8. Índices y rendimiento (v1)
| Necesidad | Enfoque v1 |
|---|
| List by resident | where residentId (volumen demo bajo) |
| Unique code | constraint unique Prisma |
| Stats | groupBy status + aggregate kwh |
Índices adicionales (residentId, status) = L2+ si crece volumen.
9. Privacidad de campos
| Campo | Sensibilidad | Quién lo ve |
|---|
| email | PII | self + advisor en list |
| cups | identificador suministro | ambos roles en scope |
| passwordHash | secreto | nunca en API response |
| advisorNote | semi-sensible | ambos en detalle |