# TECH_DEBT.md — Deuda técnica registrada

> Ítems identificados durante el desarrollo. Cada ítem documenta qué falta, por qué
> no se hizo ahora, y qué trigger habilitaría su resolución.

---

## [TECH-DEBT-001] Ranking widget para 6.2

- **Estado:** 6.2 es actualmente `multiple_choice` max_select=3. El widget muestra
  frecuencia simple (top 8).
- **Deuda:** cuando el tipo pase a `ranking` con JSONB `{rank1, rank2, rank3}`, el
  widget debe reescribirse para mostrar ponderación por posición.
- **Trigger:** decisión del director sobre el tipo de 6.2 en próxima versión del
  instrumento (anotada en SEED_SPEC.md § Decisiones pendientes).

---

## [TECH-DEBT-002] KPI NPS Cuidador

- **Estado:** el instrumento actual no tiene pregunta NPS. El KPI fue eliminado del
  dashboard en Etapa 2E.
- **Para implementar:** agregar 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?"
- **Trigger:** decisión de incluir NPS en el instrumento v2.

---

## [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. W6 muestra solo demanda.
- **Para implementar:** agregar en próxima versión del instrumento:
  "¿Qué apoyos para el cuidado ofrece tu empresa hoy?" (multi-select con las mismas
  opciones de 6.2 + "Ninguno" + "No lo sé").
- **Trigger:** decisión de agregar la pregunta de oferta al instrumento.

---

## [TECH-DEBT-004] Intención directa de salida laboral

- **Estado:** el instrumento no pregunta directamente por intención de dejar el
  empleo. W8 usa indicador compuesto (4.3 + 5.1 + 5.2) renombrado a "Saturación".
- **Para implementar:** agregar en próxima versión:
  "En los últimos 6 meses, ¿pensaste en dejar este empleo por razones relacionadas
  al cuidado?" (Sí/Frecuentemente · Sí/A veces · No · Prefiero no decir).
- **Trigger:** validación metodológica de que el indicador compuesto no es suficiente
  para medir intención de salida.

---

## [TECH-DEBT-005] qi_city / qi_country — migración pendiente

- **Estado:** A1 (Departamento) y A2 (Ciudad) se guardan en `answers`. Las columnas
  `qi_city` y `qi_country` en `responses` son NULL. Sprint 3 las necesita para
  segmentación avanzada.
- **Para implementar:** migration futura que agregue extracción de A1/A2 en el insert
  y extienda la whitelist de `agg_segment`.
- **Trigger:** inicio de Sprint 3 (dashboard avanzado con filtros de ciudad).

---

## [TECH-DEBT-006] Ranking widget para 2.1

- **Estado:** Etapa 3I2 (Cambio 5) cambió 2.1 ("¿Quién es el/la responsable principal
  del cuidado?") de `single_choice` a `multiple_choice`. Permite selección múltiple
  pero sin orden de prioridad — pierde la noción de "responsable principal" cuando
  el respondente marca más de una opción.
- **Deuda:** implementar widget de ranking para 2.1 con el orden:
  Principal cuidador soy yo > secundario instituto > principal pagado > secundario
  familiar (mismo patrón que TECH-DEBT-001 para 6.2).
- **Trigger:** decisión del director sobre el tipo de 2.1 en próxima versión del
  instrumento.

---

## [TECH-DEBT-008] /admin/responses sin autenticación

- **Estado:** RESUELTO (27 jun 2026, ETAPA_AUTH).
- **Descripción:** la vista de control `/admin/responses` no tenía auth. Se implementó
  login con Supabase Auth (`@supabase/ssr`), `middleware.ts` protegiendo
  `/admin/:path*` y `/dashboard/:path*` con redirect a `/login` sin sesión, y la página
  migrada de `service_role` a la sesión del usuario (`anon` key + RLS). Sigue
  excluyendo preguntas `is_sensitive=true` y `text_long` — ese diseño no cambió.
- **Nota:** la vista no distingue roles dentro de staff todavía (todo usuario con fila
  en `user_roles` ve lo mismo). Diferenciar `platform_admin` de `org_admin` ahí sería
  una etapa nueva, no parte de este cierre.

---

## [TECH-DEBT-010] E9 lee modalidad por JOIN a `answers` en vez de `qi_modalidad`

- **Estado:** ABIERTO — decisión consciente, no un descuido.
- **Descripción:** `rpc_me_segmentacion` (E9, Mirada Empresa) segmenta por nivel
  organizacional y por modalidad de trabajo. El nivel (`2.6`) sí se promovió a
  `responses.qi_sector` en el submit (Etapa 4G) — E9 lee de ahí directo. La
  modalidad (`2.7`) NO tiene una columna `qi_modalidad` equivalente — promoverla
  hubiera significado reabrir `rpc_submit_response`, ya verificada contra
  Postgres real en 4G. Para no reabrirla, E9 resuelve modalidad con un JOIN a
  `answers`/`questions` por `code='2.7'` en tiempo de consulta.
- **Costo del JOIN vs. la columna:** una pasada extra por `answers`/`questions`
  en cada llamada a E9 — aceptable para el volumen actual (decenas/cientos de
  respuestas por encuesta), pero más lento que leer una columna denormalizada
  si el dataset crece mucho o si la segmentación por modalidad se usa con
  frecuencia en varios widgets.
- **Acción si se decide promover:** agregar `qi_modalidad text` a `responses`,
  poblarla en `rpc_submit_response` (requiere re-verificar esa RPC contra
  Postgres real, igual que se hizo en 4G), y migrar E9 a leerla directo —
  mismo patrón que se usó para `qi_sector`.
- **Trigger:** evaluar si la Mirada País/Humana también necesitan segmentar por
  modalidad con frecuencia — si varias RPCs repiten el mismo JOIN, conviene
  promoverla de una vez en lugar de cada RPC reimplementando el JOIN.

---

## [TECH-DEBT-011] Sección de pregunta inferida por posición, no modelada como dato

- **Estado:** ABIERTO — decisión consciente, no un descuido. Surgió en
  ETAPA_ADMIN (vista detallada `/admin/responses/[id]`, 27 jun 2026): el prompt
  de etapa asumía columnas `questions.section_number`/`section_title` que NO
  existen. Verificado contra el esquema real (`supabase/migrations/
  20260531000001_create_tables.sql` — única definición de `questions`,
  `20260620000002_form_schema_v3.sql` solo agrega comentarios, ninguna
  migración agrega esas columnas).
- **Descripción:** hoy `questions` no tiene ningún campo que declare "esta
  pregunta pertenece a la sección X". La sección es una fila más en la misma
  tabla, con `type='section'`, intercalada por `position` entre las preguntas
  reales — el código (`lib/form-engine.ts:146-148` en el wizard, y la vista
  detallada de admin) infiere el agrupamiento recorriendo las filas en orden
  de `position` y "abriendo" una sección nueva cada vez que encuentra una fila
  `type='section'`. El título de la sección es el `prompt` de esa fila — no
  hay un campo `title` separado.
- **Por qué es deuda, no solo una curiosidad de modelado:** el motor está
  pensado para ser un "motor de formularios de encuestas" genérico — no toda
  encuesta futura va a tener secciones, y las que las tengan no necesariamente
  van a querer modelarlas como filas-marcador en la misma tabla que las
  preguntas. Hoy CUALQUIER consumidor que necesite agrupar por sección
  (wizard, admin, y a futuro cualquier export/reporte) tiene que reimplementar
  el mismo recorrido secuencial con el mismo supuesto implícito ("la fila
  `type='section'` más cercana hacia atrás en `position` es la sección de esta
  pregunta"). Si esa lógica se necesita en un tercer lugar, ya es momento de
  promoverla a una función compartida — y si el motor crece a encuestas con
  estructuras más ricas (subsecciones, secciones condicionales, secciones que
  no son "preguntas" en absoluto), el modelo actual no escala sin reescribir
  cada consumidor.
- **Alternativa más genérica y escalable (no implementada, propuesta para
  evaluar cuando se priorice):** sacar la sección de `questions.type` y
  modelarla como entidad propia — opción simple: `questions.section_id uuid
  NULL REFERENCES sections(id)` + tabla `sections(id, survey_id, title,
  position)`; opción más liviana sin tabla nueva: `questions.section_title
  text NULL` + `questions.section_position int NULL` poblados directamente en
  cada pregunta (denormalizado, sin fila-marcador `type='section'` separada).
  Cualquiera de las dos deja "pertenece a la sección X" como un hecho
  consultable con una columna, no como un cálculo derivado de recorrer
  `position` — y ninguna asume que toda encuesta tiene secciones (el campo es
  nullable). Migrar implicaría: (1) nueva(s) columna(s)/tabla, (2) backfill
  desde las filas `type='section'` existentes, (3) actualizar
  `lib/form-engine.ts` y la vista detallada de admin para leer el dato en vez
  de inferirlo, (4) decidir si `type='section'` se deprecia o convive.
- **Acción tomada mientras tanto:** la vista detallada de admin
  (`app/admin/responses/[id]/page.tsx`) replica la misma lógica de
  `lib/form-engine.ts` (recorrido secuencial por `position`, abre sección en
  cada `type='section'`) — no inventa columnas nuevas, no diverge del modelo
  actual.
- **Trigger:** si una tercera feature necesita el mismo agrupamiento, extraer
  la lógica a un helper compartido en `lib/` en vez de triplicarla. Si el
  producto efectivamente evoluciona a soportar surveys con estructuras de
  sección más complejas que el caso actual, evaluar la migración a columna/
  tabla explícita antes de seguir apilando consumidores sobre el supuesto
  implícito.
