# ETAPA 3V-B — Vista de control: tabla plana de respuestas individuales

## 1. Objetivo
Reemplazar la vista `/admin/responses` actual (una fila por submission) por una tabla plana
con **una fila por respuesta individual** (una fila por cada pregunta respondida), mostrando
todos los campos explícitamente incluyendo nulos marcados visualmente.

---

## 2. Contexto previo
- La Etapa 3V-A implementó la vista de metadatos de submissions (una fila por response).
- El director quiere ver las respuestas individuales a las preguntas, no solo los metadatos.
- El esquema relevante: `responses` → `answers` (via response_id) → `questions` (via question_id).
- `answers.value` es JSONB. Los tipos de preguntas existentes en el instrumento son:
  - `single_choice` → value es string: `"Asunción (Capital)"`
  - `multiple_choice` → value es array: `["opción1","opción2"]`
  - `rating` → value es número: `3`
  - `text_short` → value es string corto
  - `text_long` → **NUNCA mostrar** — regla no negociable (RLS + privacidad)

---

## 3. DECISIONES YA TOMADAS — no consultar de nuevo
- Ruta: `/admin/responses` — reemplaza la página anterior completamente.
- Sin auth (herramienta interna de control).
- Sin paginación por ahora — máximo 500 filas (limit en la query).
- Ordenado por `responses.submitted_at DESC`, luego por `questions.position ASC` dentro de cada submission.
- Valores nulos mostrados con badge visual "sin dato" — fondo `--color-re-light` (#E7E6E5), texto `--color-re-dark` (#494F4F) opacidad 0.6.
- text_long excluido completamente — ni la columna ni el valor aparecen.
- Convención CSS: `.admin-*` en `app/globals.css`, igual que la etapa anterior.
- Cliente service-role inline en el Server Component, igual que en la versión anterior.

---

## 4. Estructura de la tabla

### 4.1 Encabezado de página
- Título: "Control de respuestas"
- Subtítulo: total de submissions + total de respuestas individuales + fecha/hora actualización.
- Botón "Actualizar" (`<a href="/admin/responses">`).

### 4.2 Columnas de la tabla (una fila por answer)

| Columna | Fuente | Notas |
|---|---|---|
| # submission | índice de la submission (1, 2, 3...) | mismo número para todas las filas de esa submission |
| Fecha | `responses.submitted_at` | DD/MM/YYYY HH:mm — solo en la primera fila de cada submission |
| Código | `questions.code` | A1, A2, 1.1, 1.6, etc. |
| Pregunta | `questions.prompt` | texto de la pregunta, truncado a 60 chars con "..." |
| Tipo | `questions.type` | badge con el tipo |
| Valor | `answers.value` | renderizado según tipo (ver 4.3) |
| Departamento | `responses.qi_zone` | solo en primera fila de cada submission |
| Sector | `responses.qi_sector` | solo en primera fila de cada submission |

### 4.3 Renderizado del valor según tipo

```typescript
function renderValue(value: unknown, type: string): string {
  if (value === null || value === undefined) return null // → badge "sin dato"
  
  switch(type) {
    case 'single_choice':
    case 'text_short':
      return String(value).replace(/^"|"$/g, '') // quitar comillas del JSONB string
    
    case 'multiple_choice':
      if (Array.isArray(value)) return value.join(', ')
      return String(value)
    
    case 'rating':
      return String(value) // número, opcionalmente con ★ repetidas
    
    case 'text_long':
      return null // NUNCA mostrar — excluir de la query directamente
    
    default:
      return String(value)
  }
}
```

### 4.4 Agrupación visual de submissions
Las filas de una misma submission deben estar visualmente agrupadas:
- Separador visual entre submissions (border-top más pronunciado o fondo alternado por submission).
- Fecha, Departamento y Sector solo se muestran en la primera fila de cada submission.
- Las filas siguientes de la misma submission muestran esas celdas vacías (o con colspan si es HTML puro).

---

## 5. Query recomendada

```typescript
const { data } = await supabase
  .from('responses')
  .select(`
    id,
    submitted_at,
    qi_zone,
    qi_sector,
    qi_age_bucket,
    ip_hash,
    surveys ( title ),
    answers (
      value,
      questions (
        code,
        prompt,
        type,
        position,
        is_sensitive
      )
    )
  `)
  .order('submitted_at', { ascending: false })
  .limit(500)
```

Después en el componente, aplanar el resultado:
```typescript
// Para cada response, para cada answer (excluir text_long e is_sensitive):
// generar una fila con los datos de la response + los datos de la answer/question
const rows = responses.flatMap((response, responseIdx) =>
  response.answers
    .filter(a => a.questions?.type !== 'text_long' && !a.questions?.is_sensitive)
    .sort((a, b) => (a.questions?.position ?? 0) - (b.questions?.position ?? 0))
    .map((answer, answerIdx) => ({
      responseIdx: responseIdx + 1,
      isFirstInGroup: answerIdx === 0,
      submitted_at: response.submitted_at,
      qi_zone: response.qi_zone,
      qi_sector: response.qi_sector,
      ip_hash: response.ip_hash,
      survey_title: response.surveys?.title,
      code: answer.questions?.code,
      prompt: answer.questions?.prompt,
      type: answer.questions?.type,
      value: answer.value,
    }))
)
```

---

## 6. Entregables (DoD)

- [ ] Ruta `/admin/responses` renderiza con tabla plana (HTTP 200).
- [ ] Una fila por answer, agrupadas visualmente por submission.
- [ ] Columnas: # submission, Fecha, Código, Pregunta (truncada), Tipo, Valor, Departamento, Sector.
- [ ] `text_long` e `is_sensitive=true` excluidos.
- [ ] Valores nulos muestran badge "sin dato" con estilo tenue.
- [ ] Fecha, Departamento, Sector solo en primera fila de cada grupo.
- [ ] Build exitoso.
- [ ] `git diff --name-only HEAD` solo en `app/admin/` y `app/globals.css`.

## 7. Qué reportar
```
Verificado:
- Ruta /admin/responses accesible ✓
- Total submissions: [N] · Total filas en tabla: [N]
- Tipos de preguntas visibles: [lista]
- text_long excluido ✓ / is_sensitive excluido ✓
- Badge "sin dato" visible en campos nulos ✓ / no hay nulos en los datos actuales
- Build exitoso ✓
- Diff en scope ✓

Asumido:
- [decisiones cosméticas]
```

## 8. Qué NO hacer
- ❌ No mostrar text_long ni is_sensitive bajo ningún concepto.
- ❌ No exponer SUPABASE_SERVICE_ROLE_KEY al cliente.
- ❌ No tocar el formulario ni el dashboard.
- ❌ No agregar dependencias npm.
- ❌ No paginar todavía — limit 500 es suficiente para el piloto.