en el medio del kilombo de la db

This commit is contained in:
markosbenitez
2026-06-28 01:36:45 -03:00
parent 6a09ce9ee5
commit 0fdee073a6
7 changed files with 396 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
# 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=false` y `type != '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=N` en 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:
```ts
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=N` en 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:**
```ts
// 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_id` y `?page` anteriores
(pasar como searchParam o usar `referrer`).
- 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 `id` no 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=false` ni `type != '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_number` o `section_title` no 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.
```

View File

@@ -0,0 +1,89 @@
# ETAPA E2E — Tests Playwright: verificación deploy Sprint 5
## Antes de tocar código, leé:
1. `CLAUDE.md`
2. `docs/estado/ESTADO_PROYECTO_26JUN2026.md` — estado general del proyecto
Confirmame el plan antes de implementar.
## Credenciales de test (usuario dedicado — nunca las de Marcos)
- URL: `https://economia-cuidado.inqualityhq.com`
- Email: `test-e2e@inquality.com.py`
- Password: `TestE2E2026!#Cuidado`
- Rol: `platform_admin`, org: `InQuality`
Estas credenciales van en `.env.test` en la raíz (agregar a `.gitignore` si no está).
**Nunca hardcodeadas en los specs.**
## Verificar Playwright antes de instalar nada
```bash
npx playwright --version 2>/dev/null && echo "instalado" || echo "no instalado"
ls ~/.cache/ms-playwright 2>/dev/null && echo "browsers presentes" || echo "browsers ausentes"
```
Reportar resultado antes de continuar. Si ya está instalado con browsers, agregar solo
`@playwright/test` como devDependency sin correr `npx playwright install`.
Si no está: `npx playwright install --with-deps chromium` (solo Chromium).
## Los 5 tests a implementar
**Archivo:** `tests/e2e/sprint5-verificacion.spec.ts`
### Test 1 — Login y redirect
- Abrir `/admin/responses` sin sesión
- Verificar redirect a `/login`
- El DOM de `/login` contiene el campo `email`, el campo `password`, y el botón de toggle
de contraseña (buscar `aria-label` que contenga "contraseña")
### Test 2 — Login funcional
- Hacer login con las credenciales de test en `/login`
- Verificar redirect a `/admin/responses`
- El DOM contiene `<select>` para filtro de survey (no links hardcodeados)
- El DOM contiene al menos una fila de respuesta (hay 50 sembradas)
### Test 3 — Vista detallada de respuesta
- Desde `/admin/responses`, hacer click en el primer link "Ver" disponible
- Verificar que la URL cambia a `/admin/responses/[uuid]`
- El DOM contiene al menos una sección (buscar texto que indique agrupación por sección)
- No hay error 404 ni texto "Respuesta no encontrada"
### Test 4 — Dashboard con datos
- Navegar a `/dashboard` (o la ruta del dashboard — verificar cuál es)
- Verificar que al menos un widget muestra un porcentaje (buscar `%` en el DOM)
- No aparece "Datos insuficientes" en todos los widgets (puede aparecer en algunos
segmentos con k<5, pero no en todos)
### Test 5 — Formulario público accesible sin auth
- Navegar a `/` o `/encuesta` (verificar ruta correcta del formulario)
- Verificar que NO redirige a `/login` (el formulario es público)
- El DOM contiene el wizard o la pantalla de consentimiento
## Decisiones ya tomadas
- Playwright en local apuntando a producción, no en CI (backlog)
- `playwright.config.ts` en raíz, `testDir: 'tests/e2e'`
- `.env.test` para credenciales, en `.gitignore`
- Si un test falla, reportar el error exacto y consultarme — no intentar workarounds
## Archivos autorizados
`playwright.config.ts`, `tests/e2e/sprint5-verificacion.spec.ts`, `.env.test`
(no se commitea), `package.json` (devDependency), `package-lock.json`.
Cualquier otro en `git diff --name-only HEAD` → revertir y reportar.
## DoD antes de reportar
```bash
npx playwright test tests/e2e/sprint5-verificacion.spec.ts --reporter=list
git log --name-only -1
git diff --name-only HEAD # solo archivos autorizados, sin .env.test
grep -r "TestE2E2026" . # debe dar vacío — contraseña no commiteada
```
## Qué reportar
- Resultado de checks de Playwright previos (versión, browsers)
- Resultado de cada test: pass/fail con mensaje exacto si falla
- Ruta real del dashboard y del formulario público (las que encontró en el código)
- `git log --name-only -1` y `git diff --name-only HEAD`
- Hallazgos no esperados
**No hacer deploy ni modificar nada en producción. No continuar sin mi OK si algo falla.**