# Contract: Herramienta `agregar_contratos_unicliente` (agent_database)

**Feature**: `006-contratos-conteo-agrupado`
**Consumer**: `AgentDatabaseQueryCapabilityHandler` → `AgentCommercialQueryService`
**Connection**: `agent_db_secondary`

## Protocol (sin cambios respecto a la 002/005)

LLM turno 1 devuelve JSON:

```json
{
  "query_name": "agregar_contratos_unicliente",
  "parameters": {
    "operacion": "count",
    "agrupar_por": ["localidad_suministro", "mes_contrato"]
  }
}
```

Turno 2: el LLM interpreta los **agregados** devueltos en lenguaje natural (español, Markdown), comunicando el total general y, si aplica, que hay más grupos (Top-N). **No** debe hablar de "lista truncada".

## Tool: `agregar_contratos_unicliente`

| Property | Value |
|----------|-------|
| Description | Cuenta y/o calcula operaciones matemáticas (suma, promedio, mínimo, máximo) sobre contratos Unicliente (`TipProCom ∈ {1,2}`), agrupando por hasta dos columnas de una lista blanca. Considera TODOS los registros (no se trunca). Usar para "¿cuántos contratos por …?", "distribución por …", "consumo/potencia total/medio por …". |
| Eloquent path | `PropuestaComercialCups` → `whereHas(propuestaComercialCliente.propuestaComercial, TipProCom IN [1,2])` + `selectRaw(<agg>)` + `groupBy(<dims>)` + joins/`with` según dims/medidas |
| Agg COUNT | `COUNT(DISTINCT CodProCom)` (cuenta contratos, no filas de CUPS) |
| FR | FR-001..FR-021 |

### Parameters

| Param | Tipo | Default | Descripción |
|-------|------|---------|-------------|
| `operacion` | string | `count` | `count` \| `suma` \| `promedio` \| `minimo` \| `maximo`. |
| `medida` | string | — | Requerida si `operacion ≠ count`. Una de: `consumo_electrico`, `consumo_gas`, `potencia_p1`..`potencia_p6`. |
| `metricas` | array | — | Opcional. Lista de `{ "operacion": "...", "medida": "..." }` para varias medidas en una sola consulta. |
| `agrupar_por` | array(string) | `[]` | 0–2 dimensiones de la lista blanca (ver abajo). Vacío = agregado global. |
| `tipo` | string | `ambos` | `unicliente_unipunto` (1) \| `unicliente_multipuntos` (2) \| `ambos`. |
| `tipo_energia` | string | — | `electrico` (TipCups=1) \| `gas` (TipCups=2). |
| `termino` | string | — | Filtro libre por cliente (nombre/NIF/correo/teléfono/dirección). |
| `localidad` | string | — | Filtra por localidad de suministro o de cliente antes de agregar. |
| `fec_desde` | string (YYYY-MM-DD) | — | `FecProCom >=`. |
| `fec_hasta` | string (YYYY-MM-DD) | — | `FecProCom <=`. |
| `orden` | string | `total_desc` | `total_desc` \| `total_asc` \| `dim_asc` (por etiqueta de dimensión). |
| `limite` | int | — | Top-N de **presentación** (alta cardinalidad). NO limita el cálculo. |

### Dimensiones permitidas (`agrupar_por`)

`localidad_suministro`, `localidad_cliente`, `provincia_suministro`, `provincia_cliente`,
`direccion_suministro` (alta cardinalidad), `mes_contrato`, `anio_contrato`,
`tarifa_electrica`, `tarifa_gas`, `tipo_energia`, `tipo_contrato`,
`cliente` (alta cardinalidad), `estado`,
`bucket_consumo_electrico`, `bucket_consumo_gas`.

> Cualquier otra clave → error `agent_query_invalid_params` con sugerencia de dimensiones válidas.

### Medidas numéricas permitidas (`medida`)

`consumo_electrico` (`ConCup`), `consumo_gas` (`CauDiaGas`), `potencia_p1`…`potencia_p6` (`PotEleConP1`…`PotEleConP6`).

> Operación numérica sin medida válida, o medida sobre dimensión no numérica → `agent_query_invalid_params`.

### Response shape (internal, hacia el LLM)

**Ejemplo: COUNT por dos dimensiones**

```json
{
  "query_name": "agregar_contratos_unicliente",
  "operacion": "count",
  "medida": null,
  "agrupar_por": ["localidad_suministro", "mes_contrato"],
  "rows": [
    { "localidad_suministro": "Madrid", "mes_contrato": "2026-01", "total": 315 },
    { "localidad_suministro": "Guadalajara", "mes_contrato": "2026-01", "total": 180 },
    { "localidad_suministro": "Sin dato", "mes_contrato": "2026-02", "total": 4 }
  ],
  "total_general": [{ "metrica": "total", "valor": 1234 }],
  "filtros_aplicados": null,
  "truncated": false,
  "count": 3
}
```

**Ejemplo: SUMA de consumo eléctrico por localidad**

```json
{
  "query_name": "agregar_contratos_unicliente",
  "operacion": "suma",
  "medida": "consumo_electrico",
  "agrupar_por": ["localidad_suministro"],
  "rows": [
    { "localidad_suministro": "Madrid", "suma_consumo_electrico": 1875400 },
    { "localidad_suministro": "Ávila", "suma_consumo_electrico": 210350 }
  ],
  "total_general": [{ "metrica": "suma_consumo_electrico", "valor": 2085750 }],
  "filtros_aplicados": null,
  "truncated": false,
  "count": 2
}
```

**Ejemplo: varias métricas (suma + promedio) sin agrupar**

```json
{
  "operacion": "suma",
  "agrupar_por": [],
  "metricas": [
    { "operacion": "suma", "medida": "consumo_electrico" },
    { "operacion": "promedio", "medida": "potencia_p1" }
  ],
  "rows": [
    { "suma_consumo_electrico": 2085750, "promedio_potencia_p1": 5.42 }
  ],
  "total_general": [
    { "metrica": "suma_consumo_electrico", "valor": 2085750 },
    { "metrica": "promedio_potencia_p1", "valor": 5.42 }
  ],
  "truncated": false,
  "count": 1
}
```

### Reglas de cálculo

- **COUNT** = `COUNT(DISTINCT CodProCom)` (contratos, no filas de CUPS).
- **Medidas numéricas** (`SUM/AVG/MIN/MAX`) se calculan a nivel de fila de CUPS; nulos excluidos del cálculo (no como cero).
- **Nulos en dimensión** → etiqueta `"Sin dato"`.
- **`truncated` SIEMPRE `false`**: el cálculo cubre todos los registros; `limite` solo acota la presentación (Top-N), añadiendo aviso de "hay más".
- **Dimensión numérica** (`bucket_consumo_*`) usa rangos predefinidos, no el valor crudo.
- En COUNT por **dimensiones de suministro**, un contrato MultiPuntos puede contar en varios grupos; el total general de COUNT reporta contratos distintos globales.

## Error codes (runtime)

| Code | Meaning |
|------|---------|
| `agent_db_unreachable` | Fallo de conexión a la BD secundaria |
| `agent_query_unknown` | `query_name` inválido |
| `agent_query_invalid_params` | Dimensión/medida no permitida, operación numérica sin/medida inválida, o >2 dimensiones |
| `agent_query_no_results` | Sin registros para el criterio (el LLM lo explica) |

## Routing hints (IntentClassifier)

Palabras clave que MUST enrutar a `agent_database` y, dentro del handler, a esta herramienta:
"cuántos contratos por …", "número/cantidad de contratos por …", "distribución de contratos por …",
"contratos agrupados por …", "consumo (eléctrico/gas) total/medio/máximo/mínimo por …",
"potencia P1..P6 media/total por …".
