# Data Model: Agregación de contratos por cualquier columna

**Feature**: `006-contratos-conteo-agrupado` | **Date**: 2026-05-28

> Esta feature **no crea entidades nuevas**: reutiliza los modelos de la feature 005. Define el **modelo conceptual de agregación** (dimensiones, medidas, operaciones) y el mapeo a columnas/relaciones existentes.

## Dominio

«contrato» = propuesta comercial Unicliente (`TipProCom ∈ {1,2}`). La unidad física de la consulta es el **punto de suministro** (`PropuestaComercialCups`), que se agrega a nivel de contrato (`COUNT(DISTINCT CodProCom)`) o se usa directamente para medidas numéricas.

## Diagrama (reutilizado de la 005)

```mermaid
erDiagram
    PropuestaComercial ||--o{ PropuestaComercialCliente : tiene
    PropuestaComercialCliente ||--o{ PropuestaComercialCups : tiene
    PropuestaComercialCliente }o--|| Cliente : titular
    Cliente }o--|| Localidad : localidadSocial
    Localidad }o--|| Provincia : provincia
    PropuestaComercialCups }o--|| PuntoSuministro : en
    PuntoSuministro }o--|| Localidad : localidad
    PropuestaComercialCups }o--o| CupsElectrico : cupsElectrico
    PropuestaComercialCups }o--o| CupsGas : cupsGas
    PropuestaComercialCups }o--o| TarifaElectrica : tarifaElectrica
    PropuestaComercialCups }o--o| TarifaGas : tarifaGas
```

## Entidades de agregación (conceptuales)

### Dimensión

Eje categórico/temporal por el que se agrupan los contratos. Lista blanca cerrada (ver `research.md` R3). Atributos: `clave`, `expresión` (columna/relación o expresión de fecha), `cardinalidad`, `nivel` (contrato | suministro).

### Medida numérica

Columna numérica sobre la que aplicar operaciones. Lista blanca (R4): `consumo_electrico` (`ConCup`), `consumo_gas` (`CauDiaGas`), `potencia_p1`–`potencia_p6` (`PotEleConP1`–`PotEleConP6`).

### Operación

`count` (→ `COUNT(DISTINCT CodProCom)`), `suma` (`SUM`), `promedio` (`AVG`), `minimo` (`MIN`), `maximo` (`MAX`).

### Resultado de agregación

```text
AggregateResult {
  rows: GroupRow[]            # una por combinación de dimensión(es)
  total_general: Metric[]     # agregado sin agrupar (verificación)
  operacion: string
  medida: string | null
  agrupar_por: string[]       # 0–2 claves de dimensión
  filtros_aplicados: object | null
  truncated: false            # SIEMPRE false en agregados
  count: int                  # nº de grupos devueltos
}

GroupRow {
  <dim1>: string              # etiqueta del valor (o "Sin dato")
  <dim2>?: string
  total?: int                 # COUNT(DISTINCT CodProCom) si operacion=count
  <metricas>?: number         # p. ej. suma_consumo_electrico, promedio_potencia_p1
}
```

## Cambios en entidades existentes

| Entidad | Cambio | Motivo |
|---------|--------|--------|
| `Localidad` | Verificar/añadir relación `provincia()` (→ `T_Provincia` por `CodPro`) | Dimensiones `provincia_suministro` / `provincia_cliente` (FR-003). Ya usada en rankings; confirmar disponibilidad desde `puntoSuministro.localidad` y `cliente.localidadSocial`. |

> Resto de modelos (`PropuestaComercialCups`, `PuntoSuministro`, `CupsElectrico/Gas`, `TarifaElectrica/Gas`, `Cliente.localidadSocial`) ya existen tras la feature 005. **Sin migraciones**.

## Mapa dimensión → origen

| Dimensión | Expresión / columna | Nivel |
|-----------|---------------------|-------|
| `localidad_suministro` | `puntoSuministro.localidad.DesLoc` | suministro |
| `localidad_cliente` | `cliente.localidadSocial.DesLoc` | contrato |
| `provincia_suministro` | `puntoSuministro.localidad.provincia.DesPro` | suministro |
| `provincia_cliente` | `cliente.localidadSocial.provincia.DesPro` | contrato |
| `direccion_suministro` | `puntoSuministro.NomViaPunSum` | suministro |
| `mes_contrato` | `YEAR(FecProCom)` + `MONTH(FecProCom)` | contrato |
| `anio_contrato` | `YEAR(FecProCom)` | contrato |
| `tarifa_electrica` | `tarifaElectrica.NomTarEle` | suministro |
| `tarifa_gas` | `tarifaGas.NomTarGas` | suministro |
| `tipo_energia` | `TipCups` (1=eléctrico, 2=gas) | suministro |
| `tipo_contrato` | `TipProCom` (1/2) | contrato |
| `cliente` | `cliente.NumCifCli` (+ `NomComCli`) | contrato |
| `estado` | `propuesta.EstProCom` | contrato |
| `bucket_consumo_electrico` | rangos de `ConCup` (research R6) | suministro |
| `bucket_consumo_gas` | rangos de `CauDiaGas` (research R6) | suministro |

## Mapa medida → origen

| Medida | Columna | Tabla |
|--------|---------|-------|
| `consumo_electrico` | `ConCup` | `T_Propuesta_Comercial_CUPs` |
| `consumo_gas` | `CauDiaGas` | `T_Propuesta_Comercial_CUPs` |
| `potencia_p1`…`p6` | `PotEleConP1`…`PotEleConP6` | `T_Propuesta_Comercial_CUPs` |

## Reglas de validación

- `agrupar_por`: 0–2 claves, todas en la lista blanca; si vacía → agregado global único.
- `operacion ∈ {count, suma, promedio, minimo, maximo}`; si ≠ `count` requiere `medida` válida.
- `medida` solo aplicable a operaciones numéricas; combinada con `count` se ignora o se rechaza con aviso.
- Operación numérica sobre dimensión/columna no numérica → rechazo (`agent_query_invalid_params`).
- Nulos: excluidos del cálculo numérico; etiqueta "Sin dato" en dimensiones.
- `truncated` SIEMPRE `false`; `limite` solo acota presentación de grupos (Top-N).
