6.1 KiB
ETAPA ADMIN — Vista de respuestas mejorada + vista detallada
1. Objetivo
Mejorar /admin/responses para que sea una interfaz de administración real,
y crear /admin/responses/[id] como vista detallada de una respuesta individual.
Todo detrás del middleware de auth existente.
2. Contexto
- Estado actual:
app/admin/responses/page.tsx(Server Component, 249 líneas) +app/admin/responses/responses-table.tsx(Client Component, 98 líneas). - Auth: ya implementada — getSupabaseSession() con anon key + cookies, respeta RLS.
- Exclusión de sensibles:
is_sensitive=falseytype != 'text_long'en query de questions — NO tocar, mantener exactamente igual. - SURVEY_OPTIONS hardcodeadas con UUIDs literales — reemplazar por query dinámica.
- LIMIT 500 sin paginación real — reemplazar por paginación con page size de 50.
3. DECISIONES YA TOMADAS — no consultar
- Paginación: page size 50, parámetro
?page=Nen la URL (Server Component, recarga completa — no client-side infinite scroll). - Filtro de survey:
<select>que carga surveys desde la DB (solo las que el usuario puede ver según RLS), no hardcodeado. - Vista detallada: ruta
/admin/responses/[id]— Server Component, muestra todas las respuestas no sensibles de esa submission, agrupadas por sección. - Diseño: usar clases Tailwind o clases
.admin-*existentes — sin nuevo sistema. - Sin librería de tablas externa.
- El expand/collapse por submission en la tabla se conserva.
4. Tareas
4.1 Vista lista mejorada — app/admin/responses/page.tsx
Filtro de survey dinámico:
Reemplazar SURVEY_OPTIONS hardcodeadas por query a surveys table:
const { data: surveys } = await supabase
.from('surveys')
.select('id, title')
.order('title')
Renderizar como <select> con onChange que navega a ?survey_id=<id>.
Opción "Todos los surveys" con value=''.
Paginación:
- Leer
searchParams.page(default 1). - Query de responses:
.range((page-1)*50, page*50-1)en vez de.limit(500). - Mostrar: "Página N de M · X respuestas totales".
- Links Anterior / Siguiente que modifican
?page=Nen la URL. - Si hay menos de 50 resultados, no mostrar paginación.
Columna de acciones:
Agregar columna "Ver" en la fila cabecera de cada submission (isFirstInGroup=true)
con link a /admin/responses/[response_id].
Header informativo: Mostrar claramente: total de submissions, total de answers, survey seleccionado.
4.2 Vista detallada — app/admin/responses/[id]/page.tsx
Server Component. Ruta: /admin/responses/[id] donde id = response_id (uuid).
Query principal:
// 1. Metadata de la respuesta
const { data: response } = await supabase
.from('responses')
.select('id, submitted_at, qi_zone, qi_sector, qi_age_bucket, survey_id')
.eq('id', id)
.single()
// 2. Preguntas permitidas (mismo filtro que la lista)
const { data: questions } = await supabase
.from('questions')
.select('id, code, prompt, type, position, section_number, section_title')
.eq('is_sensitive', false)
.neq('type', 'text_long')
.eq('survey_id', response.survey_id)
.order('position')
// 3. Answers de esta respuesta
const { data: answers } = await supabase
.from('answers')
.select('question_id, value, instance_id')
.eq('response_id', id)
.in('question_id', questions.map(q => q.id))
Layout:
- Header: fecha, zona, sector, edad — datos de qi_* de la respuesta.
- Link "← Volver a la lista" que preserva el
?survey_idy?pageanteriores (pasar como searchParam o usarreferrer). - Respuestas agrupadas por sección (section_number + section_title).
- Dentro de cada sección: tabla código | pregunta | valor.
- Instancias (instance_id != 'default'): mostrar agrupadas bajo "Familiar N" con badge visual.
- Valores nulos: badge "sin respuesta" (igual que la lista).
- Si
idno existe o RLS bloquea: mostrar "Respuesta no encontrada" — no crashear.
4.3 Tabla de respuestas — app/admin/responses/responses-table.tsx
Agregar la columna "Ver" en las filas isFirstInGroup con <Link> a
/admin/responses/[submissionId]. Pasar surveyId y page como props
para construir el link de "← Volver" en la vista detallada.
5. DoD antes de reportar
git log --name-only -1— listar todos los archivos tocados- Filtro de survey carga desde DB, no hardcodeado
- Paginación funciona: page=1 muestra primeros 50, page=2 los siguientes
- Link "Ver" en cada submission lleva a
/admin/responses/[id] - Vista detallada muestra respuestas agrupadas por sección
- Instancias agrupadas bajo "Familiar N"
- RLS respetado: misma exclusión de sensibles que la lista
- "Respuesta no encontrada" si el id no existe
- Build sin errores:
npm run build git log --name-only -1- Commit + push a Gitea
6. Qué reportar
Verificado:
- Filtro survey: ✓ — query a tabla surveys, no hardcodeado
- Paginación: ✓ — page size 50, range correcto
- Link "Ver" → /admin/responses/[id]: ✓
- Vista detallada: ✓ — agrupada por sección, instancias bajo "Familiar N"
- RLS/exclusión sensibles: ✓ — mismo filtro que antes
- "No encontrada" si id inválido: ✓
- Build: ✓
- git log --name-only -1: [lista completa]
- Commit: [hash]
Decisiones de diseño tomadas:
- [lo que haya]
Hallazgos:
- [lo que aparezca, especialmente sobre questions.section_number/section_title
— si esas columnas no existen, reportarlo antes de inventar una solución]
7. Qué NO hacer
- ❌ No tocar el filtro
is_sensitive=falsenitype != 'text_long' - ❌ No usar service_role — solo getSupabaseSession() con RLS
- ❌ No agregar librerías de tablas o UI externas
- ❌ No implementar búsqueda por texto (fuera de alcance)
- ❌ No hacer deploy — solo commit y push a Gitea
- ❌ Si
section_numberosection_titleno existen en questions, reportarlo y NO inventar columnas — esperar instrucción
8. Versionado
feat(admin): vista respuestas mejorada + vista detallada [admin]
/admin/responses: filtro survey dinámico, paginación 50/página,
link a vista detallada. /admin/responses/[id]: respuestas por sección,
instancias bajo "Familiar N". RLS y exclusión de sensibles intactos.