refactor completo + truncate de base de datos + reingenieria dashboard

This commit is contained in:
markosbenitez
2026-06-05 17:13:45 -03:00
parent a17e810ea8
commit 307a54dabc
43 changed files with 1320 additions and 92 deletions

View File

@@ -0,0 +1,292 @@
# ETAPA 2E — Refactorización del dashboard alineada al instrumento real
## 1. Objetivo
Corregir la deuda semántica de Etapa 2D: las RPC, KPIs y subtítulos del dashboard se
construyeron a partir de códigos de pregunta inventados en el prompt original, no de los
códigos reales de `sprints/sprint-1/SEED_SPEC.md`. Esto generó métricas mal etiquetadas
que el dashboard mostraría como verdaderas cuando lleguen datos reales.
**Esta etapa toca la capa de datos (Nivel 3, autorizado por el director).** Reescribe
RPCs, agrega una pregunta nueva al instrumento, elimina un KPI sin sustento, redefine
un widget y renombra otro.
---
## 2. Contexto previo
- Etapa 2D entregada con subtítulos basados en códigos inventados.
- El director auditó subtítulo por subtítulo contra `SEED_SPEC.md` y encontró que
11 de 12 widgets/KPIs tenían discrepancias.
- El instrumento real (`SEED_SPEC.md` v2) tiene 44 preguntas. Está en el repo.
- En base de Cloud hay respuestas de testing — se truncan en esta etapa.
---
## 3. DECISIONES YA TOMADAS — no consultar de nuevo
### 3.1 Truncado de datos de testing
Antes de aplicar migraciones nuevas, **borrar todas las respuestas de testing** del
survey piloto. Mantener `surveys`, `questions`, `consent_versions`. Borrar:
- `responses` del survey `30000000-0000-0000-0000-000000000001`
- `answers` asociadas
- `consent_records` asociados
### 3.2 Nueva pregunta del instrumento — A1b Departamento
Agregar al seed, **antes** de A2 (Ciudad):
```
A1b · single_choice · "¿En qué departamento de Paraguay vivís?" *
Opciones (18 departamentos):
Asunción · Central · Alto Paraná · Itapúa · Caaguazú · San Pedro ·
Cordillera · Paraguarí · Concepción · Guairá · Caazapá · Misiones ·
Ñeembucú · Amambay · Canindeyú · Presidente Hayes · Boquerón · Alto Paraguay
```
- `qi_zone` ← A1b (cuasi-identificador para el k-anonimato).
- A1 (País) **se mantiene** por ahora (lo decidiremos en BACKLOG).
### 3.3 KPI 4 (NPS) — ELIMINADO
El instrumento no tiene pregunta NPS. Eliminar el KPI completamente del layout. El
grid de KPIs queda en **3 cards**, no 4. Quitar `rpc_nps_*` si existe.
### 3.4 Widget 6 (Políticas) — REDEFINIDO
El instrumento no captura "políticas que la empresa ya ofrece". Eliminar el componente
de comparación. El widget muestra **solo demanda** desde 6.2 (apoyos que ayudarían más,
multi-select, top 8).
### 3.5 Widget 8 — RENOMBRADO
"Tabla de riesgo de retención" → **"Saturación del cuidador"**. El instrumento no
captura intención de salida directa; el indicador compuesto mide saturación, no
intención de irse. Cambiar título y subtítulo.
### 3.6 Mapeo final widget → códigos del seed real
**KPI Cards (3, sin NPS)**
| # | Título | Definición y código real | Subtítulo |
|---|---|---|---|
| K1 | CUIDADORES ACTIVOS | % con `1.6='Sí'` OR `1.13='Sí'` | `1.6 + 1.13 · % de la plantilla con responsabilidades de cuidado de otros` |
| K2 | FRECUENCIA DE AUSENTISMO | % con `5.2 IN ('Varias veces (410 veces al año)', 'Con frecuencia (más de 10 veces al año)')` | `5.2 · % con ausencias laborales recurrentes (4 o más veces al año)` |
| K3 | BIENESTAR | Promedio del rating `4.3` (escala 15, mostrar como /5 o como %) | `4.3 · autorreporte de bienestar general · escala 15` |
**Widgets (9, con W6 redefinido y W8 renombrado)**
| # | Título | Códigos reales | Subtítulo |
|---|---|---|---|
| W1 | Prevalencia de cuidadores por departamento | `A1b` (nueva) cruzado con `1.6 1.13` | `A1b · % cuidadores activos por departamento` |
| W2 | Perfil de la población | `1.1 + 1.6 + 1.13` (no excluyentes) | `1.1 + 1.6 + 1.13 · discapacidad propia, cuidador familiar y de adulto mayor (no excluyentes)` |
| W3 | Madurez organizacional | Score = 0.7 × `6.1_norm` + 0.3 × `6.5_norm`, ambas normalizadas 0100 | `6.1 + 6.5 · comprensión percibida y expectativa hacia la empresa · score 0100` |
| W4 | Horas de cuidado × Frecuencia de ausentismo | `2.2 × 5.2` | `2.2 × 5.2 · correlación entre intensidad de cuidado y ausentismo recurrente` |
| W5 | Pirámide demográfica | `A5 × A6` | `A5 × A6 · rango etario por género (celdas con k ≥ 5)` |
| W6 | Apoyos más demandados | `6.2` (multi-select, max 3) | `6.2 · apoyos de la empresa que los respondentes consideran que más los ayudarían` |
| W7 | Intensidad de cuidado vs. Bienestar | `2.2 × 4.3` | `2.2 × 4.3 · cruce agregado intensidad de cuidado × bienestar (bins con k ≥ 5)` |
| W8 | **Saturación del cuidador** | Compuesto: `4.3 ≤ 2` AND `5.1 = "Casi todos los días"` AND `5.2 ≥ "Varias veces (410 veces al año)"` | `4.3 + 5.1 + 5.2 · indicador compuesto de saturación (bienestar bajo + rendimiento afectado + ausentismo)` |
**Definición de segmentos para W8 (Saturación):**
- **Alta saturación:** los 3 criterios se cumplen
- **Saturación media:** 2 de los 3 criterios
- **Baja saturación:** 1 o ninguno
Columnas de la tabla: Segmento · N · % del total · Apoyo más demandado del segmento
(de 6.2). Cada segmento con N < 5 se muestra como "—" (suprimido).
### 3.7 Normalización para Madurez (W3)
`6.1` tiene 4 opciones, normalizar:
- "Sí, me siento acompañado/a" = 100
- "Parcialmente, hay cierta comprensión" = 66
- "No, es algo bastante invisible" = 33
- "No, nunca se ha hablado del tema" = 0
`6.5` tiene 4 opciones, normalizar:
- "Sí, definitivamente" = 100
- "Tal vez, dependería del caso" = 66
- "No lo considero necesario" = 33
- "No lo sé" = 0 (o NULL — decidir en RPC, ver tarea 4.3)
Score final = 0.7 × promedio(6.1_norm) + 0.3 × promedio(6.5_norm)
---
## 4. Tareas
### 4.1 Migración 009 — Truncado + nueva pregunta A1b
Crear `supabase/migrations/0YYYYMMDDHHMMSS_009_add_departamento_paraguay.sql`:
1. `BEGIN;`
2. `TRUNCATE answers, responses, consent_records CASCADE;` (de Cloud)
3. INSERT en `questions` la pregunta A1b con sus 18 opciones (`question_options`).
Posición: entre A1 y A2.
4. Actualizar el orden de las preguntas de la sección A si es necesario.
5. `COMMIT;`
Validación post-migración:
- `SELECT COUNT(*) FROM answers;` → 0
- `SELECT code, text FROM questions WHERE code='A1b';` → 1 fila
- `SELECT COUNT(*) FROM question_options qo JOIN questions q ON qo.question_id=q.id WHERE q.code='A1b';` → 18
### 4.2 Migración 010 — Refactor de RPCs del dashboard
Crear `supabase/migrations/0YYYYMMDDHHMMSS_010_dashboard_rpc_v2.sql`.
DROP y recreate todas las funciones del dashboard con los nuevos mapeos. Lista:
- `rpc_dashboard_kpis` — devuelve 3 KPIs (sin NPS), con los códigos correctos
- `rpc_prevalencia_por_depto` — usa A1b, NO A1
- `rpc_perfil_poblacion` (antes `rpc_distribucion_cuidado`) — 1.1 + 1.6 + 1.13
- `rpc_score_organizacional` — nueva fórmula 70/30
- `rpc_heatmap_intensidad` — 2.2 × 5.2
- `rpc_piramide_demografica` — sin cambios estructurales
- `rpc_apoyos_demandados` (antes `rpc_politicas_gap`) — solo 6.2, sin comparación
- `rpc_intensidad_vs_bienestar` (antes `rpc_intensidad_vs_productividad`) — 2.2 × 4.3
- `rpc_saturacion_cuidador` (antes `rpc_riesgo_retencion`) — 4.3 + 5.1 + 5.2
**Todas las RPC mantienen:**
- `SECURITY DEFINER` con `search_path` fijado
- Supresión `k ≥ 5` por construcción (devuelven `status='suppressed'` si N<5)
- Casteo seguro de TEXT a numérico cuando aplique
- Filtros globales (departamento ahora viene de A1b, no A1)
**El sidebar de filtros también cambia:**
- "Departamento" ahora hace dropdown contra las 18 opciones reales de A1b
- "Género" sigue contra A6
- "Modalidad" sigue contra A7
- "Etario" sigue contra A5
### 4.3 Decisión local: cómo tratar "No lo sé" en 6.5
Si el agente lo decide localmente, **debe reportar la decisión** como Nivel 2.
Opciones razonables:
- Tratar como NULL → no entra al promedio
- Tratar como 0 → baja el score
**Recomendación:** tratar como NULL (no entra al promedio) — "no lo sé" no es opinión.
### 4.4 Cliente — eliminar K4 (NPS)
- Quitar el componente del grid de KPIs
- Reajustar el layout del grid (de 4 cols a 3 cols, o 3 cards en 4 cols con la cuarta vacía)
- Eliminar tipo TypeScript asociado y query del cliente
- `grep -r "NPS\|nps" components/dashboard/ app/dashboard/ lib/dashboard*` debe
retornar vacío después
### 4.5 Cliente — redefinir Widget 6 (Apoyos)
- Renombrar archivo `politicas-bar.tsx``apoyos-bar.tsx` (o equivalente)
- Eliminar la lógica de comparación "demandadas vs ofrecidas"
- Mostrar solo barras horizontales con top 8 de 6.2 (multi-select, max 3 por respondente)
- Color único: `#43BBC8`. Eliminar el color secundario `#F8AD13` para "ofrecidas".
### 4.6 Cliente — renombrar Widget 8 (Saturación)
- Renombrar archivo `tabla-riesgo.tsx``tabla-saturacion.tsx`
- Título: "Saturación del cuidador" (no "Riesgo de retención")
- Etiquetas de segmentos: "Alta saturación" / "Saturación media" / "Baja saturación"
- Columnas: Segmento · N · % del total · Apoyo más demandado
### 4.7 Cliente — aplicar todos los subtítulos correctos
Reemplazar los 11 subtítulos con los textos exactos de la tabla en sección 3.6.
### 4.8 Documentar deuda técnica
Agregar a `TECH_DEBT.md` (o crearlo si no existe) los siguientes ítems:
```markdown
[TECH-DEBT-002] KPI NPS Cuidador
- Estado: el instrumento actual no tiene pregunta NPS.
- Si en el futuro se quiere medir NPS hacia la empresa de cuidadores, agregar
pregunta en próxima versión del instrumento: "En una escala de 0 a 10,
¿qué tan probable es que recomiendes esta empresa a otra persona cuidadora?"
- Hasta entonces, el KPI no existe en el dashboard.
[TECH-DEBT-003] Brecha de políticas (oferta vs demanda)
- Estado: el instrumento captura solo demanda (6.2 = apoyos que ayudarían).
- No captura qué políticas la empresa ya ofrece.
- Para construir el widget de brecha, agregar pregunta en próxima versión:
"¿Qué apoyos para el cuidado ofrece tu empresa hoy?" (multi-select con las
mismas opciones de 6.2 + "Ninguno" + "No lo sé").
- Hasta entonces, W6 muestra solo demanda.
[TECH-DEBT-004] Intención directa de salida laboral
- Estado: el instrumento no pregunta directamente "¿considerás dejar este trabajo?".
- El widget W8 usa indicador compuesto (4.3 + 5.1 + 5.2) renombrado a "Saturación".
- Para construir un indicador de intención de salida real, agregar pregunta:
"En los últimos 6 meses, ¿pensaste en dejar este empleo por razones relacionadas
al cuidado?" (Sí/Frecuentemente · Sí/A veces · No · Prefiero no decir).
[TECH-DEBT-005] A1 (País) vs A1b (Departamento)
- A1 sigue presente en el seed después de Etapa 2E.
- En contexto del piloto (Paraguay únicamente), A1 es redundante: 100% responde Paraguay.
- Decidir antes de Sprint 3: eliminar A1 del instrumento, o mantenerlo si la
plataforma se va a regionalizar (Uruguay, Bolivia, etc.) en el corto plazo.
```
### 4.9 Verificación cruzada — patrón nuevo C.8
Antes de declarar terminado, el agente ejecuta este check (Patrón C.8 propuesto):
```bash
# Cada subtítulo referenciado en el cliente debe tener su código documentado en SEED_SPEC.md
grep -oE '"[0-9A-Z]+(\.[0-9]+)?b?\s*[·+×]' components/dashboard/*.tsx | \
awk '{print $1}' | sort -u
# El agente verifica esta lista contra los códigos de SEED_SPEC.md
```
Si algún código del cliente no está en `SEED_SPEC.md`, **DETENERSE y reportar**
antes de declarar terminado. No ejecutar literalmente y delegar al director
(eso sería AP.10 — propagación silenciosa de inconsistencia).
---
## 5. Entregables (DoD)
- [ ] Migración 009 aplicada en Cloud (truncado + A1b con 18 opciones)
- [ ] Migración 010 aplicada en Cloud (9 RPCs refactorizadas)
- [ ] Test post-migración: con N=0 (truncado) todas las RPCs devuelven `suppressed`
- [ ] Test con N≥5 de datos sintéticos (insertados ad-hoc) — al menos 1 RPC devuelve `ok`
- [ ] `grep -r "NPS\|nps" components/dashboard/ app/dashboard/ lib/dashboard*` → vacío
- [ ] Subtítulos del cliente match exactamente con la tabla 3.6 del prompt
- [ ] Sidebar de filtros: dropdown de Departamento usa A1b, no A1
- [ ] Verificación cruzada (4.9) ejecutada sin discrepancias
- [ ] `TECH_DEBT.md` actualizado con TECH-DEBT-002, 003, 004, 005
- [ ] Build sin errores (`next build`)
- [ ] `git diff --name-only HEAD` solo en scope (migrations, components/dashboard,
lib/dashboard*, TECH_DEBT.md)
- [ ] Captura del dashboard en estado `suppressed` compartida (Principio 2)
## 6. Qué reportar
```
Verificado:
- Migración 009 aplicada: rows truncados=X, A1b insertada con 18 opciones ✓
- Migración 010 aplicada: 9 RPCs DROPped y CREATEd ✓
- Test N=0: todas las RPCs → suppressed ✓
- Test con datos sintéticos: [lista de RPCs con ok / suppressed]
- grep NPS: vacío ✓
- Subtítulos: 13 textos exactos verificados contra tabla 3.6 ✓
- Sidebar Departamento usa A1b ✓
- Verificación cruzada C.8: [resultado]
- TECH_DEBT.md: ítems 002005 agregados ✓
- Build exitoso · diff en scope ✓
Asumido:
- Decisión local "No lo sé" en 6.5 → [NULL / 0 / otra]
- [lo que no se pudo verificar]
Decisiones proactivas (Nivel 2):
- [lista]
Conflictos de scope detectados (Patrón C.8):
- [si los hubo, listarlos AQUÍ y DETENER antes de ejecutar]
```
## 7. Qué NO hacer
- ❌ No tocar el formulario (`/app/(survey)/`, `app/actions/submit-survey.ts`).
- ❌ No tocar RLS de write existente (la migración 011 anterior es intocable).
- ❌ No agregar dependencias npm.
- ❌ No inventar códigos de pregunta. Si un código nombrado en este prompt no
coincide con `SEED_SPEC.md`, DETENERSE y reportar (Patrón C.8). NO ejecutar
"literalmente" y delegar al director.
- ❌ No tocar la estructura de capas (sigue siendo cliente → Route Handler → RPC).
- ❌ No agregar widgets nuevos. Esta etapa es refactor, no extensión.
- ❌ No resolver ítems del backlog que no sean los 4 listados en sección 4.8.
---
## Apéndice — Patrón nuevo propuesto C.8
Si esta etapa cierra limpia, agregar a `method/PATTERNS.md`:
> **C.8 — Verificación de códigos contra fuente única · *deriva de P3, P4 · origen:
> 2do proyecto*.** Cuando un prompt menciona códigos de un instrumento, esquema o
> contrato (preguntas, columnas, campos), el agente verifica cada código contra la
> fuente única (`SEED_SPEC.md`, `schema.sql`, etc.) **antes** de ejecutar. Si
> detecta una discrepancia, DETIENE la ejecución y reporta el conflicto. Ejecutar
> "literalmente" y delegar la verificación semántica al director es AP.10
> (propagación silenciosa de inconsistencia). Respuesta directa al caso Etapa 2D del
> proyecto Datos País.