---
description: "Task list for feature 006-contratos-conteo-agrupado"
---

# Tasks: Agregación de contratos por cualquier columna (conteo + operaciones matemáticas)

**Input**: Design documents from `/specs/006-contratos-conteo-agrupado/`

**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/agregar-contratos-tool.md, quickstart.md; **feature 005 implementada** (modelos y `listar_contratos_unicliente`)

**Tests**: Incluidos en fase Polish (plan Phase D); no TDD estricto — la spec no lo exige explícitamente.

**Organization**: Tareas agrupadas por user story para implementación y prueba incremental independiente.

## Format: `[ID] [P?] [Story] Description`

- **[P]**: Paralelizable (archivos distintos, sin dependencias entre sí)
- **[Story]**: User story de spec.md (US1–US6)
- Rutas concretas en cada descripción

## Phase 1: Setup (Shared Infrastructure)

**Purpose**: Prerrequisitos de entorno y dependencia con la feature 005

- [x] T001 Verificar que la feature 005 está implementada (`listar_contratos_unicliente`, modelos en `app/Models/Commercial/`, relaciones en `PropuestaComercialCups` y `Cliente.localidadSocial`) antes de continuar (dependencia en `specs/006-contratos-conteo-agrupado/plan.md`)
- [x] T002 [P] Documentar en `specs/006-contratos-conteo-agrupado/quickstart.md` el comando de invalidación de cache y prerequisito de BD secundaria (ya presente; revisar coherencia con contrato)
- [x] T003 [P] Verificar que la capacidad `agent_database` está habilitada (`AGENT_CAPABILITY_AGENT_DATABASE=true`) en `config/agent.php` / `.env`

---

## Phase 2: Foundational (Blocking Prerequisites)

**Purpose**: Whitelists, consulta base de agregación y esqueleto del método — **bloquea todas las user stories**

**⚠️ CRITICAL**: Ninguna user story puede completarse hasta terminar esta fase

- [x] T004 Verificar o añadir relación `provincia()` (`CodPro` → `T_Provincia`) en `app/Models/Commercial/Localidad.php` para dimensiones `provincia_suministro` / `provincia_cliente` (FR-003, plan Phase A)
- [x] T005 Implementar mapa privado `dimensionesContratoUnicliente(): array` (clave → expresión SQL/relación) con las 15 dimensiones de `specs/006-contratos-conteo-agrupado/research.md` R3 en `app/Services/Agent/AgentCommercialQueryService.php` (FR-003, FR-004)
- [x] T006 [P] Implementar mapa privado `medidasNumericasContratoUnicliente(): array` (`consumo_electrico`→`ConCup`, `consumo_gas`→`CauDiaGas`, `potencia_p1`…`p6`) en `app/Services/Agent/AgentCommercialQueryService.php` (FR-016)
- [x] T007 [P] Implementar helper `bucketConsumoElectrico()` / `bucketConsumoGas()` con rangos de `research.md` R6 en `app/Services/Agent/AgentCommercialQueryService.php` (FR-021)
- [x] T008 Añadir caso `'agregar_contratos_unicliente' => $this->agregarContratosUnicliente($parameters)` al `match()` de `execute()` y esqueleto del método en `app/Services/Agent/AgentCommercialQueryService.php` (FR-001)
- [x] T009 Implementar consulta base en `agregarContratosUnicliente()`: `PropuestaComercialCups` + `whereHas(propuestaComercialCliente.propuestaComercial, TipProCom IN [1,2])` + joins/`with` dinámicos según dimensiones pedidas en `app/Services/Agent/AgentCommercialQueryService.php` (FR-001, FR-013)
- [x] T010 Implementar `operacion=count` con `COUNT(DISTINCT CodProCom)` (no `COUNT(*)`) y respuesta con `truncated: false` siempre vía `wrapAggregateResult()` en `app/Services/Agent/AgentCommercialQueryService.php` (FR-001, FR-002, research R2)
- [x] T011 Implementar `total_general` en la respuesta de agregación (agregado sin `GROUP BY`) en `app/Services/Agent/AgentCommercialQueryService.php` (FR-015)

**Checkpoint**: Whitelists, consulta base y COUNT por contrato listos — pueden empezar US1/US2/US6

---

## Phase 3: User Story 1 — Contar contratos agrupados por una columna (Priority: P1) 🎯 MVP

**Goal**: El operador obtiene totales exactos por una dimensión, sin "lista truncada", sobre todos los registros

**Independent Test**: "¿Cuántos contratos por localidad?" → tabla localidad/total + `total_general`; `truncated: false`; sin datos inventados

### Implementation for User Story 1

- [x] T012 [US1] Implementar `agrupar_por` con **una** dimensión (`GROUP BY` + etiquetas de dimensión en filas) en `agregarContratosUnicliente()` en `app/Services/Agent/AgentCommercialQueryService.php` (FR-003)
- [x] T013 [US1] Implementar orden por defecto `orden=total_desc` (total descendente) en `app/Services/Agent/AgentCommercialQueryService.php` (FR-006)
- [x] T014 [US1] Manejar resultado vacío (`rows` vacío, `total_general` coherente) sin inventar totales en `app/Services/Agent/AgentCommercialQueryService.php` (FR-010)
- [x] T015 [US1] Registrar herramienta `agregar_contratos_unicliente` (description + parameters, sin SQL; enumerar dimensiones y operaciones) en `allowed_queries` de `config/agent.php` según `specs/006-contratos-conteo-agrupado/contracts/agregar-contratos-tool.md` (FR-001)
- [x] T016 [US1] Añadir pistas de enrutado ("cuántos contratos por", "número de contratos por", "distribución de contratos por") en `app/Services/Agent/IntentClassifierService.php` (FR-012)

**Checkpoint**: MVP — conteo agrupado por una columna vía chat, sin truncado

---

## Phase 4: User Story 2 — Contar por cualquier columna del resultado (Priority: P1)

**Goal**: Todas las dimensiones de la lista blanca funcionan; rechazo seguro de columnas inválidas; nulos como "Sin dato"

**Independent Test**: "Por tarifa", "por provincia", "por tipo de energía" devuelven totales; "por color" rechaza con sugerencia de columnas válidas

### Implementation for User Story 2

- [x] T017 [US2] Completar resolución de **todas** las claves de `dimensionesContratoUnicliente()` en el `GROUP BY` (localidad, provincia, tarifas, mes/año, tipo energía/contrato, cliente, estado, buckets) en `app/Services/Agent/AgentCommercialQueryService.php` (FR-003)
- [x] T018 [US2] Rechazar dimensión no permitida con `agent_query_invalid_params` y lista de dimensiones válidas en el mensaje en `app/Services/Agent/AgentCommercialQueryService.php` (FR-004, SC-004)
- [x] T019 [US2] Agrupar valores nulos/vacíos de dimensión bajo etiqueta `"Sin dato"` en `app/Services/Agent/AgentCommercialQueryService.php` (FR-008)
- [x] T020 [US2] Actualizar descripción de `agrupar_por` en `config/agent.php` con la lista completa de dimensiones permitidas (FR-003)

**Checkpoint**: US1 + US2 — conteo por cualquier columna categórica/temporal de la whitelist

---

## Phase 5: User Story 6 — Operaciones matemáticas sobre columnas numéricas (Priority: P1)

**Goal**: SUM/AVG/MIN/MAX sobre consumos y potencias, con o sin agrupación, varias métricas y total general

**Independent Test**: "Consumo eléctrico total por localidad" (suma); "potencia P1 media por tarifa" (promedio); rechazo de "suma de localidad"

### Implementation for User Story 6

- [x] T021 [US6] Implementar `operacion` `suma|promedio|minimo|maximo` con `SUM/AVG/MIN/MAX` sobre medida validada en `app/Services/Agent/AgentCommercialQueryService.php` (FR-016)
- [x] T022 [P] [US6] Implementar medidas `consumo_electrico` y `consumo_gas` en agregación numérica en `app/Services/Agent/AgentCommercialQueryService.php` (FR-016)
- [x] T023 [P] [US6] Implementar medidas `potencia_p1`…`potencia_p6` en agregación numérica en `app/Services/Agent/AgentCommercialQueryService.php` (FR-016)
- [x] T024 [US6] Excluir nulos del cálculo numérico (no como cero en promedios) en `app/Services/Agent/AgentCommercialQueryService.php` (FR-020)
- [x] T025 [US6] Rechazar operación numérica sin `medida` válida o sobre dimensión no numérica, con sugerencia de alternativas en `app/Services/Agent/AgentCommercialQueryService.php` (FR-018, SC-008)
- [x] T026 [US6] Implementar parámetro `metricas[]` (varias `{operacion, medida}` en una consulta) en `app/Services/Agent/AgentCommercialQueryService.php` (FR-019)
- [x] T027 [US6] Incluir `total_general` para cada métrica numérica solicitada en `app/Services/Agent/AgentCommercialQueryService.php` (FR-015, FR-017)
- [x] T028 [US6] Actualizar descripción de `medida`, `operacion` y `metricas` en `config/agent.php` (FR-016)
- [x] T029 [US6] Añadir pistas de enrutado ("consumo total/medio por", "potencia P1 media por", "suma/mínimo/máximo de consumo") en `app/Services/Agent/IntentClassifierService.php` (FR-012)

**Checkpoint**: Conteo + operaciones matemáticas sobre columnas numéricas

---

## Phase 6: User Story 3 — Combinar dos dimensiones (Priority: P2)

**Goal**: Agrupación cruzada por hasta dos columnas (p. ej. localidad + mes)

**Independent Test**: "¿Cuántos contratos por localidad y mes?" devuelve filas (localidad, mes, total) exactas

### Implementation for User Story 3

- [x] T030 [US3] Permitir `agrupar_por` con **hasta 2** dimensiones y `GROUP BY` combinado en `app/Services/Agent/AgentCommercialQueryService.php` (FR-005)
- [x] T031 [US3] Rechazar o acotar solicitudes con más de 2 dimensiones (`agent_query_invalid_params` o mensaje al usuario) en `app/Services/Agent/AgentCommercialQueryService.php` (FR-005)
- [x] T032 [US3] Reforzar prompt de interpretación (turno 2) para tablas cruzadas de dos dimensiones en `app/Services/Agent/Handlers/AgentDatabaseQueryCapabilityHandler.php` (FR-014)

**Checkpoint**: Consulta que originó el truncado (localidad × mes) resuelta

---

## Phase 7: User Story 5 — Aplicar filtros antes de contar (Priority: P2)

**Goal**: Filtros coherentes con la 005 aplicados antes de agregar

**Independent Test**: "¿Cuántos contratos de gas por provincia en 2026?" aplica filtros y agrupa correctamente

### Implementation for User Story 5

- [x] T033 [US5] Reutilizar/adaptar filtro `tipo` (`unicliente_unipunto`|`unicliente_multipuntos`|`ambos`) en la consulta base de agregación en `app/Services/Agent/AgentCommercialQueryService.php` (FR-007)
- [x] T034 [P] [US5] Implementar filtros `termino`, `localidad` y `tipo_energia` (coherentes con `listarContratosUnicliente`) en `app/Services/Agent/AgentCommercialQueryService.php` (FR-007)
- [x] T035 [P] [US5] Implementar filtros `fec_desde` / `fec_hasta` sobre `FecProCom` en `app/Services/Agent/AgentCommercialQueryService.php` (FR-007)
- [x] T036 [US5] Incluir `filtros_aplicados` en la respuesta de agregación en `app/Services/Agent/AgentCommercialQueryService.php` (FR-007, contrato)

**Checkpoint**: Agregaciones con subconjunto filtrado y totales exactos del criterio

---

## Phase 8: User Story 4 — Manejo de columnas de alta cardinalidad (Priority: P2)

**Goal**: Top-N en presentación para `direccion_suministro` y `cliente`; cálculo sobre todos los registros; aviso "hay más"

**Independent Test**: "¿Cuántos contratos por dirección de suministro?" → Top-N ordenado + indicación de más grupos; `truncated: false`

### Implementation for User Story 4

- [x] T037 [US4] Aplicar `limite` (Top-N) solo a la **presentación** de grupos para dimensiones de alta cardinalidad (`direccion_suministro`, `cliente`) en `app/Services/Agent/AgentCommercialQueryService.php` (FR-006, SC-005)
- [x] T038 [US4] Indicar en la respuesta cuando existen más grupos de los mostrados (`hay_mas` o equivalente en metadata) manteniendo `truncated: false` en `app/Services/Agent/AgentCommercialQueryService.php` (FR-006)
- [x] T039 [US4] Reforzar que `interpretResultsAndRespond()` comunica Top-N y total general **sin** lenguaje de "lista truncada" en `app/Services/Agent/Handlers/AgentDatabaseQueryCapabilityHandler.php` (FR-002, FR-014, SC-002)

**Checkpoint**: Alta cardinalidad manejable sin falsos truncados

---

## Phase 9: Polish & Cross-Cutting Concerns

**Purpose**: Tests, cache, enrutado y validación final

- [x] T040 [P] Crear `tests/Feature/Agent/ContratosAgregadoQueryTest.php`: whitelists, rechazo dimensión inválida, rechazo op numérica inválida, COUNT DISTINCT `CodProCom`, operaciones suma/promedio/min/max, 1 y 2 dimensiones, buckets, `truncated` siempre false, "Sin dato" (SC-001–SC-008)
- [x] T041 [P] Añadir regla en `systemPrompt` de `app/Services/Agent/Handlers/AgentDatabaseQueryCapabilityHandler.php` para usar `agregar_contratos_unicliente` en preguntas de conteo/distribución/agregación numérica (no `listar_contratos_unicliente`) (FR-012)
- [x] T042 Invalidar cache del catálogo: `php artisan cache:forget agent_database_queries_catalog`
- [ ] T043 Ejecutar checklist manual de `specs/006-contratos-conteo-agrupado/quickstart.md` (requiere BD operativa — validación manual)
- [x] T044 Ejecutar `php artisan test --filter=ContratosAgregado` y `vendor/bin/pint --dirty`

---

## Dependencies & Execution Order

### Phase Dependencies

- **Setup (Phase 1)**: Sin dependencias — inicio inmediato
- **Foundational (Phase 2)**: Depende de Phase 1 + **feature 005** — **BLOQUEA** US1–US6
- **US1 (Phase 3)**: Depende de Phase 2 — **MVP**
- **US2 (Phase 4)**: Depende de T012 (GROUP BY una dimensión)
- **US6 (Phase 5)**: Depende de Phase 2 (whitelists medidas); puede paralelizarse tras US1 con cuidado en el mismo archivo
- **US3 (Phase 6)**: Depende de T017 (todas las dimensiones)
- **US5 (Phase 7)**: Depende de T009 (consulta base); recomendable antes de pruebas end-to-end de US3/US4
- **US4 (Phase 8)**: Depende de T012/T017 (agrupación operativa)
- **Polish (Phase 9)**: Depende de las user stories deseadas completadas

### User Story Dependencies

| Story | Depende de | Independiente para probar |
|-------|------------|---------------------------|
| US1 | Phase 2 | ✅ Chat: conteo por una columna |
| US2 | US1 (GROUP BY) | ✅ Chat: cualquier dimensión whitelist |
| US6 | Phase 2 | ✅ Chat: suma/promedio consumo o potencia |
| US3 | US2 | ✅ Chat: localidad + mes |
| US5 | Phase 2 | ✅ Chat: filtros + agrupación |
| US4 | US1/US2 | ✅ Chat: Top-N dirección/cliente |

### Within Each User Story

- Whitelists y consulta base (Phase 2) antes que agrupaciones y operaciones numéricas
- Registro en `config/agent.php` (T015/T020/T028) antes de pruebas end-to-end en chat
- Handler prompts (T032, T039, T041) tras formato de respuesta estable en el servicio

### Parallel Opportunities

- **Phase 2**: T006 y T007 en paralelo (mapas distintos); T004 independiente de T005
- **Phase 5 (US6)**: T022 y T023 en paralelo (medidas distintas, mismo archivo — coordinar)
- **Phase 7 (US5)**: T034 y T035 en paralelo (filtros independientes)
- **Phase 9**: T040 y T041 en paralelo

---

## Parallel Example: Phase 2 (whitelists)

```bash
# En paralelo (métodos/helpers distintos, mismo archivo — secuencial si un solo dev):
T006: medidasNumericasContratoUnicliente()
T007: bucketConsumoElectrico() / bucketConsumoGas()
# Luego:
T005: dimensionesContratoUnicliente()
T008–T011: esqueleto + COUNT + total_general
```

## Parallel Example: User Story 6 (medidas)

```bash
# Tras T021 (resolver operacion numérica):
T022: consumo_electrico + consumo_gas
T023: potencia_p1 … potencia_p6
```

---

## Implementation Strategy

### MVP First (User Story 1)

1. Phase 1: Setup (T001–T003)
2. Phase 2: Foundational (T004–T011) — **crítico**
3. Phase 3: User Story 1 (T012–T016)
4. **STOP y VALIDAR** con quickstart pregunta 1: "¿Cuántos contratos hay por localidad?"
5. Demo si listo

### Incremental Delivery

1. Setup + Foundational → base de agregación lista
2. US1 → MVP conteo sin truncado
3. US2 → todas las dimensiones + rechazos
4. US6 → operaciones matemáticas (valor de negocio "cuánto")
5. US3 → cruce de dos dimensiones (caso original)
6. US5 → filtros previos
7. US4 → Top-N alta cardinalidad
8. Polish → tests + SC-001..SC-008

### Parallel Team Strategy

1. Equipo completa Phase 1–2 (un dev en `AgentCommercialQueryService.php`)
2. Tras Phase 2:
   - Dev A: US1 + US3 (agrupación)
   - Dev B: US2 + US4 (dimensiones + Top-N)
   - Dev C: US6 + US5 (medidas + filtros)
3. Polish al final (T040–T044)

---

## Notes

- Reutiliza modelos y filtros de la feature **005**; no crear tablas ni modelos nuevos salvo `Localidad.provincia()` si falta.
- Sin cambios en `ChatFront/` (feature backend-only).
- Solo lectura (FR-011): el método MUST NOT usar `insert`/`update`/`delete`.
- `TipProCom=3` fuera de alcance.
- **COUNT** = `COUNT(DISTINCT CodProCom)`; medidas numéricas a nivel CUPS (research R2).
- `truncated` **siempre** `false` en agregados; `limite` solo presentación (US4).
- Referencia contrato: `specs/006-contratos-conteo-agrupado/contracts/agregar-contratos-tool.md`
- Para conteos verificables (suma grupos = total), usar dimensión por-contrato (`localidad_cliente`) — ver quickstart.
