# 05 · Datos y contratos

Qué necesita cada pantalla para pintarse. Los nombres de entidad del backend (`Authorizations`, `Connectors`, `Transactions`, `MeterValues`, `Locations`, `Tariffs`) vienen del documento original y de CitrineOS; los contratos de abajo son la forma en que la interfaz los consume, no el esquema de la base de datos.

Los tipos van en notación libre a propósito: el kit es agnóstico de stack. Todo importe es **MXN**; toda fecha, **ISO 8601 con zona horaria**.

---

## 1 · Entidades

### Site (sitio / estación)

```
Site {
  id            string
  nombre        string          // "Plaza Río Tijuana"
  direccion     string          // "Paseo de los Héroes 96, Zona Río"
  lat, lng      number          // coordenadas reales, geocodificadas
  horario       string          // "24 hrs" | "10:00–20:30"
  abierto       boolean         // calculado contra el horario y la hora local
  distanciaKm   number          // calculada contra la ubicación del usuario
  fotos         string[]        // URLs — hoy no existen, ver 07-assets.md
  favorito      boolean
  conectores    Connector[]
  resumen       [{ tipo: "AC"|"HPC"|"DC", libres: number, total: number }]
  tarifaDesde   number          // el precio que se muestra en la lista
}
```

### Connector

```
Connector {
  id            string
  cargadorId    string          // "S003002" — se muestra en C1 y G3
  numero        number          // "conector 1"
  tipo          string          // "CCS1" | "GB/T DC" | "Tipo 1" | "Tipo 2" | "CHAdeMO"
  corriente     "AC" | "DC"
  potenciaKw    number          // 150
  tarifaKwh     number          // 7.70
  estado        ConnectorEstado
  reservable    boolean
}

ConnectorEstado = "disponible" | "ocupado" | "reservado" | "fuera_de_servicio" | "sin_conexion"
```

> El diseño sólo dibujó **dos** de los cinco estados (disponible / ocupado). Los otros tres necesitan su insignia antes de implementar. Ver `08-pendientes.md` §2.

### ChargingSession (sesión en vivo · C2)

```
ChargingSession {
  id             string
  estado         "autorizando" | "cargando" | "pausada_auto" | "pausada_cargador" | "terminando" | "terminada"
  sitio          { id, nombre }
  conector       { tipo, potenciaKw, numero }
  inicio         datetime
  energiaKwh     number          // acumulada, de MeterValues deduplicadas
  potenciaKw     number          // instantánea
  costo          number          // energiaKwh × tarifaKwh
  saldoRestante  number
  socEstimado    number?         // % — sólo si hay vehículo con capacidad guardada
  etaOchentaMin  number?         // minutos para llegar al 80 %
}
```

**Cálculo del anillo (C2):** `soc = socInicial + (energiaKwh / capacidadBateriaKwh) × 100`, tope 100.
Requiere `capacidadBateriaKwh` del vehículo —que la app ya no captura, F3 se retiró— y una lectura del SOC inicial. Si falta cualquiera de los dos, **el anillo debe mostrar kWh entregados en vez de porcentaje** — no un porcentaje inventado.

**Frecuencia de actualización:** el mockup refresca cada segundo. Con `MeterValues` reales, cada 5–10 s es suficiente; interpola en pantalla para que el número no salte.

### Receipt (recibo · D2)

```
Receipt {
  sesionId       string
  sitio          { nombre, direccion }
  conector       { tipo, potenciaKw }
  importe        number
  inicio, fin    datetime
  duracionMin    number
  energiaKwh     number
  tarifaKwh      number
  comoTermino    "cable_retirado" | "detenida_por_usuario" | "detenida_por_cargador" | "saldo_agotado"
  saldoRestante  number
  estadoCobro    "calculando" | "liquidado" | "no_confiable"
  pdfUrl         string?         // para D3
  facturable     boolean         // habilita el botón Factura (CFDI)
}
```

### Reservation (C3)

```
Reservation {
  id             string
  conectorId     string
  sitio          { id, nombre, distanciaKm, etaMin }
  minutos        10 | 20
  expiraEn       datetime        // fuente de verdad de la cuenta regresiva
  estado         "activa" | "vencida" | "cancelada" | "consumida"
}
```

**La cuenta regresiva se calcula contra `expiraEn` del servidor, nunca contra un contador local**: si la app se cierra y vuelve, el tiempo tiene que ser el correcto.

### Wallet (E1)

```
Wallet {
  saldo          number          // Authorizations.prepaidBalance
  movimientos    Movimiento[]
}

Movimiento {
  id             string
  tipo           "abono" | "cargo" | "promocion" | "reembolso"
  concepto       string          // "Abono con Visa ···· 4421" | "Carga · Plaza Río Tijuana"
  fecha          datetime
  importe        number          // siempre positivo; el signo lo pone `tipo`
  sesionId       string?         // si es un cargo, lleva al recibo
}
```

### PaymentMethod (E3/E4)

```
PaymentMethod {
  id             string          // token del procesador
  marca          "visa" | "mastercard" | "amex" | "apple_pay" | "google_pay"
  ultimos4       string
  vencimiento    string          // "09/29"
  predeterminado boolean
}
```

**Nunca** se guarda ni se transmite el número completo, el CVV ni la fecha por el servidor propio.

### Vehicle (F3 — retirada el 12 sep 2026; se deja el contrato por si vuelve)

```
Vehicle {
  id                  string
  marca, modelo, anio
  conectores          string[]   // ["Tipo 1", "CHAdeMO"] — filtra sitios compatibles
  capacidadBateriaKwh number     // necesaria para el anillo de C2
  predeterminado      boolean
}
```

### User (F1/F2)

```
User {
  id            string          // "81434", visible en el perfil
  nombre        string
  correo        string          // identificador de cuenta
  correoConfirmado boolean
  celular       string?         // opcional
  idVirtual     string          // identificador ante el cargador
  legalAceptado { version: string, fecha: datetime }
  preferencias  { idioma, tema, unidades, notificaciones: { inicioFin, saldoBajo, promociones, conectorLiberado } }
}
```

### LegalDocument (A5/A6, G4)

```
LegalDocument {
  tipo      "terminos" | "privacidad"
  version   string             // "1.0"
  vigenteDesde date
  cuerpo    Seccion[]          // { titulo, texto }
}
```

El consentimiento se registra con **fecha, hora y versión** en la cuenta. Sin ese registro no hay forma de defender una disputa.

---

## 2 · Qué consume cada pantalla

| Pantalla | Datos | Origen indicado en el documento |
|---|---|---|
| A3 Registro | crea `User` | `Authorizations` (id virtual derivado de la cuenta) |
| A5/A6 Legales | `LegalDocument` | contenido editorial versionado |
| B1 Mapa | `Site[]` con coordenadas | `Locations`, `ChargingStations`, `Connectors.status` |
| B2 Lista | `Site[]` + filtros | igual que el mapa |
| B3 Detalle | `Site` + `Connector[]` + `Tariffs` | `Locations`, `Connectors`, `Tariffs` |
| B5 Favoritos | `Site[]` filtrado | preferencia por usuario |
| C1 Cargar aquí | `Connector` + `Wallet.saldo` | tarifa del conector + saldo |
| C2 Sesión en vivo | `ChargingSession` | `Transactions` + `MeterValues` deduplicadas |
| C3 Reservar | `Connector[]` + `Reservation` | `ReserveNow` / `CancelReservation` |
| C4 Calificar | crea reseña ligada a sesión y sitio | — |
| D1 Historial | `Receipt[]` agrupados por mes | `Transactions` por cuenta |
| D2 Recibo | `Receipt` | `Transactions` + `customData.billing` |
| D4 Actividad | agregados del periodo | derivado de `Transactions` |
| E1 Wallet | `Wallet` | `Authorizations.prepaidBalance` y su ledger |
| E2 Abonar | `PaymentMethod` predeterminado | procesador de pago |
| E3/E4 Pagos | `PaymentMethod[]` | tokens del procesador |
| F1–F5 Cuenta | `User`, `Vehicle[]` | `Authorizations` + `customData` |
| G2/G3 Soporte | tickets ligados a la cuenta | — |

---

## 3 · Estados de red

El documento marca **ocho pantallas con datos en vivo**: B1, B2, B3, C1, C2, D1, D2, E1. Cada una necesita tres variantes además del estado normal:

| Estado | Qué se muestra | Nota |
|---|---|---|
| Cargando | esqueleto con la forma del contenido real (no spinner centrado, salvo en procesos que bloquean) | mantener alturas para que no salte el layout |
| Error | mensaje corto + acción "Reintentar" | nunca un código de error crudo |
| Sin conexión | aviso persistente + último dato conocido con su marca de tiempo | crítico en C2: una sesión en curso no puede quedarse en blanco |
| Vacío | explicación + salida a la acción que lo resuelve | B5, D1, E1, E3 |

**Ninguna está diseñada todavía.** Es el hueco más grande del kit y hay que resolverlo antes de empezar: son unas 20 láminas, no pantallas nuevas. Ver `08-pendientes.md` §2.

---

## 4 · Reglas de negocio que la interfaz da por hechas

1. **Se cobra la energía entregada, nunca un estimado.** El importe del recibo sale de los `MeterValues` reales al cerrar.
2. **El precio no cambia durante una sesión ya iniciada** (Términos §5), aunque cambie la tarifa del conector.
3. **Sólo la cuenta que inició una sesión puede detenerla.**
4. **Si el saldo no alcanza para una carga mínima, el inicio se bloquea** con salida directa a abonar.
5. **Sólo se puede reservar un conector libre**, y la reserva vencida se libera sola y sin costo.
6. **El saldo no genera intereses** y debe resolverse antes de eliminar la cuenta (F6).
7. **Las finalidades secundarias son opcionales**: la casilla del Aviso de Privacidad nunca viene premarcada, y rechazarlas no quita acceso al servicio.
