Compare commits
10 Commits
92a7923a26
...
v2.1.0-sta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31172cc91d | ||
|
|
5c17a9846a | ||
|
|
b9a3118c84 | ||
|
|
cfd2eb0624 | ||
|
|
edf82bf7fd | ||
|
|
60c9c81224 | ||
|
|
1bab6874a9 | ||
|
|
933d00ad58 | ||
|
|
059dfa071e | ||
|
|
2d13982d92 |
@@ -1,4 +1,5 @@
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { ResponsesTable, type DisplayRow } from './responses-table'
|
||||
|
||||
// Server Component sin auth — herramienta interna de diagnóstico (Etapa 3V-B).
|
||||
// Usa SUPABASE_SERVICE_ROLE_KEY: vive solo acá, server-side, nunca al cliente.
|
||||
@@ -7,6 +8,13 @@ export const dynamic = 'force-dynamic'
|
||||
const MAX_SUBMISSIONS = 500
|
||||
const PROMPT_MAX_LEN = 60
|
||||
|
||||
const SURVEY_OPTIONS = [
|
||||
{ label: 'Todos', id: null },
|
||||
{ label: 'InQuality', id: '30000000-0000-0000-0000-000000000002' },
|
||||
{ label: 'Fundación Solidaridad', id: '30000000-0000-0000-0000-000000000003' },
|
||||
{ label: 'Demo', id: '30000000-0000-0000-0000-000000000001' },
|
||||
] as const
|
||||
|
||||
interface AllowedQuestion {
|
||||
id: string
|
||||
code: string
|
||||
@@ -28,19 +36,6 @@ interface ResponseRow {
|
||||
answers: AnswerRow[]
|
||||
}
|
||||
|
||||
interface FlatRow {
|
||||
key: string
|
||||
submissionNum: number
|
||||
isFirstInGroup: boolean
|
||||
submitted_at: string
|
||||
qi_zone: string | null
|
||||
qi_sector: string | null
|
||||
code: string
|
||||
prompt: string
|
||||
type: string
|
||||
value: unknown
|
||||
}
|
||||
|
||||
function getSupabaseAdmin() {
|
||||
const url = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://placeholder.supabase.co'
|
||||
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY || 'placeholder'
|
||||
@@ -75,7 +70,12 @@ function renderValue(value: unknown, type: string): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
export default async function AdminResponsesPage() {
|
||||
export default async function AdminResponsesPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ survey_id?: string }>
|
||||
}) {
|
||||
const { survey_id: surveyId } = await searchParams
|
||||
const supabase = getSupabaseAdmin()
|
||||
|
||||
// 1. Preguntas permitidas — text_long e is_sensitive=true se excluyen ACÁ,
|
||||
@@ -103,22 +103,26 @@ export default async function AdminResponsesPage() {
|
||||
|
||||
// 2. Submissions con sus respuestas — answers!inner + .in() filtra el ARRAY
|
||||
// embebido a solo question_id permitidos (consulta, no post-filtro).
|
||||
let responsesQuery = supabase
|
||||
.from('responses')
|
||||
.select(
|
||||
`
|
||||
id,
|
||||
submitted_at,
|
||||
qi_zone,
|
||||
qi_sector,
|
||||
answers!inner ( value, question_id )
|
||||
`
|
||||
)
|
||||
.in('answers.question_id', allowedIds)
|
||||
|
||||
if (surveyId) {
|
||||
responsesQuery = responsesQuery.eq('survey_id', surveyId)
|
||||
}
|
||||
|
||||
const { data: responsesData, error: responsesError } =
|
||||
allowedIds.length > 0
|
||||
? await supabase
|
||||
.from('responses')
|
||||
.select(
|
||||
`
|
||||
id,
|
||||
submitted_at,
|
||||
qi_zone,
|
||||
qi_sector,
|
||||
answers!inner ( value, question_id )
|
||||
`
|
||||
)
|
||||
.in('answers.question_id', allowedIds)
|
||||
.order('submitted_at', { ascending: false })
|
||||
.limit(MAX_SUBMISSIONS)
|
||||
? await responsesQuery.order('submitted_at', { ascending: false }).limit(MAX_SUBMISSIONS)
|
||||
: { data: [] as ResponseRow[], error: null }
|
||||
|
||||
if (responsesError) {
|
||||
@@ -133,21 +137,32 @@ export default async function AdminResponsesPage() {
|
||||
|
||||
const responses = (responsesData ?? []) as unknown as ResponseRow[]
|
||||
|
||||
// 3. Contadores totales (independientes del límite de 500 submissions)
|
||||
const { count: totalSubmissions } = await supabase
|
||||
// 3. Contadores totales (independientes del límite de 500 submissions),
|
||||
// filtrados por survey_id cuando hay filtro activo.
|
||||
let submissionsCountQuery = supabase
|
||||
.from('responses')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
|
||||
if (surveyId) {
|
||||
submissionsCountQuery = submissionsCountQuery.eq('survey_id', surveyId)
|
||||
}
|
||||
|
||||
const { count: totalSubmissions } = await submissionsCountQuery
|
||||
|
||||
let answersCountQuery = supabase
|
||||
.from('answers')
|
||||
.select('*, responses!inner(survey_id)', { count: 'exact', head: true })
|
||||
.in('question_id', allowedIds)
|
||||
|
||||
if (surveyId) {
|
||||
answersCountQuery = answersCountQuery.eq('responses.survey_id', surveyId)
|
||||
}
|
||||
|
||||
const { count: totalAnswers } =
|
||||
allowedIds.length > 0
|
||||
? await supabase
|
||||
.from('answers')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.in('question_id', allowedIds)
|
||||
: { count: 0 }
|
||||
allowedIds.length > 0 ? await answersCountQuery : { count: 0 }
|
||||
|
||||
// 4. Aplanar: una fila por answer, ordenadas por position dentro de cada submission
|
||||
const rows: FlatRow[] = []
|
||||
const rows: DisplayRow[] = []
|
||||
responses.forEach((response, idx) => {
|
||||
const answers = response.answers
|
||||
.filter((a) => questionMap.has(a.question_id))
|
||||
@@ -158,17 +173,17 @@ export default async function AdminResponsesPage() {
|
||||
|
||||
answers.forEach((a, ai) => {
|
||||
const q = questionMap.get(a.question_id)!
|
||||
const isFirstInGroup = ai === 0
|
||||
rows.push({
|
||||
key: `${response.id}-${a.question_id}`,
|
||||
submissionNum: idx + 1,
|
||||
isFirstInGroup: ai === 0,
|
||||
submitted_at: response.submitted_at,
|
||||
qi_zone: response.qi_zone,
|
||||
qi_sector: response.qi_sector,
|
||||
isFirstInGroup,
|
||||
date: isFirstInGroup ? formatDate(response.submitted_at) : '',
|
||||
code: q.code,
|
||||
prompt: q.prompt,
|
||||
type: q.type,
|
||||
value: a.value,
|
||||
prompt: truncate(q.prompt),
|
||||
rendered: renderValue(a.value, q.type),
|
||||
zone: isFirstInGroup ? (response.qi_zone ?? '—') : '',
|
||||
sector: isFirstInGroup ? (response.qi_sector ?? '—') : '',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -182,51 +197,28 @@ export default async function AdminResponsesPage() {
|
||||
{totalSubmissions ?? 0} submissions · {totalAnswers ?? 0} respuestas individuales ·
|
||||
actualizado {formatDate(new Date().toISOString())}
|
||||
</p>
|
||||
<nav className="admin-survey-nav" aria-label="Selección de empresa">
|
||||
{SURVEY_OPTIONS.map((opt) => (
|
||||
<a
|
||||
key={opt.label}
|
||||
href={opt.id ? `/admin/responses?survey_id=${opt.id}` : '/admin/responses'}
|
||||
className={
|
||||
opt.id === (surveyId ?? null)
|
||||
? 'admin-survey-link admin-survey-link-active'
|
||||
: 'admin-survey-link'
|
||||
}
|
||||
aria-current={opt.id === (surveyId ?? null) ? 'page' : undefined}
|
||||
>
|
||||
{opt.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
<a href="/admin/responses" className="admin-refresh">Actualizar</a>
|
||||
</header>
|
||||
|
||||
<div className="admin-table-wrapper">
|
||||
<table className="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Fecha</th>
|
||||
<th>Código</th>
|
||||
<th>Pregunta</th>
|
||||
<th>Tipo</th>
|
||||
<th>Valor</th>
|
||||
<th className="admin-col-optional">Departamento</th>
|
||||
<th className="admin-col-optional">Sector</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row) => {
|
||||
const groupClass =
|
||||
row.submissionNum % 2 === 0 ? 'admin-group-even' : 'admin-group-odd'
|
||||
const trClass = row.isFirstInGroup ? `${groupClass} admin-group-start` : groupClass
|
||||
const rendered = renderValue(row.value, row.type)
|
||||
return (
|
||||
<tr key={row.key} className={trClass}>
|
||||
<td>{row.submissionNum}</td>
|
||||
<td>{row.isFirstInGroup ? formatDate(row.submitted_at) : ''}</td>
|
||||
<td>{row.code}</td>
|
||||
<td className="admin-prompt">{truncate(row.prompt)}</td>
|
||||
<td><span className="admin-type-badge">{row.type}</span></td>
|
||||
<td>
|
||||
{rendered === null ? <span className="admin-nodata">sin dato</span> : rendered}
|
||||
</td>
|
||||
<td className="admin-col-optional">
|
||||
{row.isFirstInGroup ? (row.qi_zone ?? '—') : ''}
|
||||
</td>
|
||||
<td className="admin-col-optional">
|
||||
{row.isFirstInGroup ? (row.qi_sector ?? '—') : ''}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<ResponsesTable rows={rows} />
|
||||
{rows.length === 0 && (
|
||||
<p className="admin-empty">Sin respuestas registradas todavía.</p>
|
||||
)}
|
||||
|
||||
98
app/admin/responses/responses-table.tsx
Normal file
98
app/admin/responses/responses-table.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
export interface DisplayRow {
|
||||
key: string
|
||||
submissionNum: number
|
||||
isFirstInGroup: boolean
|
||||
date: string
|
||||
code: string
|
||||
prompt: string
|
||||
rendered: string | null
|
||||
zone: string
|
||||
sector: string
|
||||
}
|
||||
|
||||
export function ResponsesTable({ rows }: { rows: DisplayRow[] }) {
|
||||
const [expanded, setExpanded] = useState<Set<number>>(new Set())
|
||||
|
||||
function toggle(submissionNum: number) {
|
||||
setExpanded((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(submissionNum)) next.delete(submissionNum)
|
||||
else next.add(submissionNum)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<table className="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Fecha</th>
|
||||
<th>Código</th>
|
||||
<th>Pregunta</th>
|
||||
<th>Valor</th>
|
||||
<th className="admin-col-optional">Departamento</th>
|
||||
<th className="admin-col-optional">Sector</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows
|
||||
.filter((row) => row.isFirstInGroup || expanded.has(row.submissionNum))
|
||||
.map((row) => {
|
||||
const groupClass =
|
||||
row.submissionNum % 2 === 0 ? 'admin-group-even' : 'admin-group-odd'
|
||||
const isExpanded = expanded.has(row.submissionNum)
|
||||
const trClass = row.isFirstInGroup
|
||||
? `${groupClass} admin-group-start admin-row-toggle`
|
||||
: groupClass
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={row.key}
|
||||
className={trClass}
|
||||
onClick={row.isFirstInGroup ? () => toggle(row.submissionNum) : undefined}
|
||||
onKeyDown={
|
||||
row.isFirstInGroup
|
||||
? (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault()
|
||||
toggle(row.submissionNum)
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
role={row.isFirstInGroup ? 'button' : undefined}
|
||||
tabIndex={row.isFirstInGroup ? 0 : undefined}
|
||||
aria-expanded={row.isFirstInGroup ? isExpanded : undefined}
|
||||
>
|
||||
<td>
|
||||
{row.isFirstInGroup && (
|
||||
<span className="admin-toggle-icon" aria-hidden="true">
|
||||
{isExpanded ? '▼' : '▶'}
|
||||
</span>
|
||||
)}
|
||||
{row.submissionNum}
|
||||
</td>
|
||||
<td>{row.date}</td>
|
||||
<td>{row.code}</td>
|
||||
<td className="admin-prompt">{row.prompt}</td>
|
||||
<td>
|
||||
{row.rendered === null ? (
|
||||
<span className="admin-nodata">sin dato</span>
|
||||
) : (
|
||||
row.rendered
|
||||
)}
|
||||
</td>
|
||||
<td className="admin-col-optional">{row.zone}</td>
|
||||
<td className="admin-col-optional">{row.sector}</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
@@ -14,6 +14,14 @@ import { FilterSidebar } from '@/components/dashboard/filter-sidebar'
|
||||
import type { DashboardFilters } from '@/lib/dashboard-types'
|
||||
|
||||
// El survey_id se pasa como query param: /dashboard?survey_id=UUID
|
||||
// '00000000-0000-0000-0000-000000000000' = flag p_all (vista agregada de todos los surveys)
|
||||
const SURVEY_OPTIONS = [
|
||||
{ label: 'Todos', id: '00000000-0000-0000-0000-000000000000' },
|
||||
{ label: 'InQuality', id: '30000000-0000-0000-0000-000000000002' },
|
||||
{ label: 'Fundación Solidaridad', id: '30000000-0000-0000-0000-000000000003' },
|
||||
{ label: 'Demo', id: '30000000-0000-0000-0000-000000000001' },
|
||||
]
|
||||
|
||||
export default function DashboardPage() {
|
||||
const searchParams = useSearchParams()
|
||||
const surveyId = searchParams.get('survey_id') ?? '30000000-0000-0000-0000-000000000001'
|
||||
@@ -40,6 +48,22 @@ export default function DashboardPage() {
|
||||
Datos agregados · k-anonimato k≥5 · Encuesta Mapeo de Cuidadores 2026
|
||||
{filters.depto && <span className="db-active-filter"> · Filtro: {filters.depto}</span>}
|
||||
</p>
|
||||
<nav className="db-survey-nav" aria-label="Selección de empresa">
|
||||
{SURVEY_OPTIONS.map((opt) => (
|
||||
<a
|
||||
key={opt.id}
|
||||
href={`/dashboard?survey_id=${opt.id}`}
|
||||
className={
|
||||
opt.id === surveyId
|
||||
? 'db-survey-link db-survey-link-active'
|
||||
: 'db-survey-link'
|
||||
}
|
||||
aria-current={opt.id === surveyId ? 'page' : undefined}
|
||||
>
|
||||
{opt.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
{/* Fila 1: KPIs (span 12) */}
|
||||
|
||||
@@ -559,6 +559,14 @@ body {
|
||||
.db-page-title { font-size: 1.375rem; font-weight: 700; color: #fff; margin: 0 0 4px; }
|
||||
.db-page-subtitle { font-size: 0.8125rem; color: #9ca3af; margin: 0; }
|
||||
.db-active-filter { color: var(--color-re-teal); font-weight: 600; }
|
||||
.db-survey-nav { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
|
||||
.db-survey-link {
|
||||
font-size: 0.75rem; font-weight: 600; padding: 4px 12px; border-radius: 999px;
|
||||
border: 1px solid rgba(67,187,200,0.3); color: #9ca3af; text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.db-survey-link:hover { color: #E7E6E5; border-color: var(--color-re-teal); }
|
||||
.db-survey-link-active { background: var(--color-re-teal); color: #0F1923; border-color: var(--color-re-teal); }
|
||||
.db-section { margin-bottom: 16px; }
|
||||
.db-grid {
|
||||
display: grid;
|
||||
@@ -750,6 +758,14 @@ body {
|
||||
}
|
||||
.admin-title { font-size: 1.375rem; font-weight: 700; color: var(--color-text); margin: 0 0 4px; }
|
||||
.admin-subtitle { font-size: 0.8125rem; color: var(--color-text-muted); margin: 0; }
|
||||
.admin-survey-nav { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
|
||||
.admin-survey-link {
|
||||
font-size: 0.75rem; font-weight: 600; padding: 4px 12px; border-radius: 999px;
|
||||
border: 1px solid var(--color-border); color: var(--color-text-muted); text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.admin-survey-link:hover { color: var(--color-text); border-color: var(--color-primary); }
|
||||
.admin-survey-link-active { background: var(--color-primary); color: #fff; border-color: var(--color-primary); }
|
||||
.admin-refresh {
|
||||
display: inline-block; padding: 8px 16px; border-radius: var(--radius);
|
||||
background: var(--color-primary); color: #fff; font-size: 0.8125rem;
|
||||
@@ -771,10 +787,10 @@ body {
|
||||
.admin-table tbody tr.admin-group-start td { border-top: 2px solid var(--color-re-dark); }
|
||||
.admin-table tbody tr:hover { background: var(--color-primary-light); }
|
||||
.admin-prompt { color: var(--color-text); }
|
||||
.admin-type-badge {
|
||||
display: inline-block; padding: 1px 8px; border-radius: 10px;
|
||||
background: var(--color-primary-light); color: var(--color-primary-hover);
|
||||
font-size: 0.7rem; font-weight: 600; white-space: nowrap;
|
||||
.admin-row-toggle { cursor: pointer; }
|
||||
.admin-toggle-icon {
|
||||
display: inline-block; width: 14px; margin-right: 4px;
|
||||
color: var(--color-text-muted); font-size: 0.7rem;
|
||||
}
|
||||
.admin-nodata {
|
||||
display: inline-block; padding: 1px 8px; border-radius: 10px;
|
||||
|
||||
BIN
db-snapshots/dump_v2.1.0_20260622.sql.gz
Normal file
BIN
db-snapshots/dump_v2.1.0_20260622.sql.gz
Normal file
Binary file not shown.
155
docs/estado/ESTADO_PROYECTO_JUNIO_2026.md
Normal file
155
docs/estado/ESTADO_PROYECTO_JUNIO_2026.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# ESTADO DEL PROYECTO — Datos País sobre el Cuidado
|
||||
## Documento de continuidad — 11 junio 2026
|
||||
|
||||
> Director: Marcos · Metodología: Maria Mersan · Contacto Fundación: Valeria Arce
|
||||
|
||||
---
|
||||
|
||||
## 1. URLs operativas
|
||||
|
||||
| Recurso | URL |
|
||||
|---|---|
|
||||
| Formulario Demo | https://economia-cuidado.inqualityhq.com/?s=30000000-0000-0000-0000-000000000001 |
|
||||
| Formulario InQuality | https://economia-cuidado.inqualityhq.com/?s=30000000-0000-0000-0000-000000000002 |
|
||||
| Formulario Fundación Solidaridad | https://economia-cuidado.inqualityhq.com/?s=30000000-0000-0000-0000-000000000003 |
|
||||
| Dashboard — Todos | https://economia-cuidado.inqualityhq.com/dashboard?survey_id=00000000-0000-0000-0000-000000000000 |
|
||||
| Dashboard — InQuality | https://economia-cuidado.inqualityhq.com/dashboard?survey_id=30000000-0000-0000-0000-000000000002 |
|
||||
| Dashboard — Fundación Solidaridad | https://economia-cuidado.inqualityhq.com/dashboard?survey_id=30000000-0000-0000-0000-000000000003 |
|
||||
| Dashboard — Demo | https://economia-cuidado.inqualityhq.com/dashboard?survey_id=30000000-0000-0000-0000-000000000001 |
|
||||
| Control de respuestas | https://economia-cuidado.inqualityhq.com/admin/responses |
|
||||
|
||||
---
|
||||
|
||||
## 2. Infraestructura — OPERATIVA ✅
|
||||
|
||||
- **App:** Next.js 15.3.8 (CVE-2025-66478 resuelto ✅)
|
||||
- **DB:** Supabase self-hosted en Dokploy v0.29.4
|
||||
- **VPS:** inq-mart-2, IP VPN: 172.20.5.11
|
||||
- **Repos:**
|
||||
- App: `git.inquality.com.py/marcos/motor-de-encuestas` (rama main)
|
||||
- Supabase stack: `git.inquality.com.py/marcos/supabase-motor-fomularios` (rama master)
|
||||
- **API Supabase:** https://db.inqualityhq.com (Kong + SSL)
|
||||
- **Backups:** `/var/backups/supabase-prod/`, cron 3AM, GFS (3 días diarios + domingos 14 días)
|
||||
- **Studio:** solo vía SSH tunnel → `ssh -L 3001:localhost:3001 administrator@172.20.5.11`
|
||||
|
||||
---
|
||||
|
||||
## 3. Instrumento — v2.1 ACTIVO
|
||||
|
||||
- **44 preguntas**, 8 secciones
|
||||
- **Salto triple:** 1.1=No AND 1.6=No AND 1.13=No → salta a 6.4 (validado con datos reales ✅)
|
||||
- **Nueva pregunta 6.6:** voluntariado en redes de apoyo (Sí/No/Tal vez)
|
||||
- **2.4 eliminada**, **2.1 → multiple_choice**
|
||||
- "Prefiero no decir" eliminado de todas las preguntas
|
||||
- Hint "Podés marcar más de una opción" en todas las multiple_choice
|
||||
- "Tecnología asistiva" en 1.3 y 1.10
|
||||
|
||||
---
|
||||
|
||||
## 4. DB — estado actual
|
||||
|
||||
| Tabla | Filas |
|
||||
|---|---|
|
||||
| organizations | 3 (Empresa Demo, InQuality, Fundación Solidaridad) |
|
||||
| surveys | 3 (una por organización) |
|
||||
| questions | 132 (44 × 3 surveys) |
|
||||
| responses | 15 (8 InQuality + 7 Demo — todas de prueba) |
|
||||
| answers | ~500 (estimado) |
|
||||
|
||||
---
|
||||
|
||||
## 5. Dashboard — multitenante operativo ✅
|
||||
|
||||
- Selector de empresa en el header: Todos / InQuality / Fundación Solidaridad / Demo
|
||||
- "Todos" usa UUID especial `00000000-0000-0000-0000-000000000000` → pool agregado
|
||||
- Implementado via flag en `_dash_responses` (migración `20260611000001`)
|
||||
- Validado visualmente con n=15: KPIs, heatmap departamental, perfil población ✅
|
||||
|
||||
---
|
||||
|
||||
## 6. Vista de control /admin/responses
|
||||
|
||||
- Tabla plana: una fila por answer, agrupada por submission
|
||||
- Colapsar/expandir por submission (Client Component island)
|
||||
- Excluye: `text_long`, `is_sensitive=true` (TECH-DEBT-008 — sin auth)
|
||||
- Sin columna "Tipo"
|
||||
|
||||
---
|
||||
|
||||
## 7. Método — Sprint 2 cerrado ✅
|
||||
|
||||
- `method/PATTERNS.md` y `method/ANTI_PATTERNS.md` creados en el repo
|
||||
- C.8 promovido a estable con casos reales del proyecto
|
||||
- AP.10 y AP.11 documentados con casos reales
|
||||
|
||||
---
|
||||
|
||||
## 8. Deuda técnica abierta
|
||||
|
||||
| ID | Descripción | Bloqueante |
|
||||
|---|---|---|
|
||||
| TECH-DEBT-006 | Backup remoto S3 | SÍ — antes del primer dato real |
|
||||
| TECH-DEBT-007 | auth.jwt() deprecado en GoTrue v2.189+ | No (piloto) |
|
||||
| TECH-DEBT-008 | /admin/responses sin auth | No (URL no publicada) |
|
||||
| TECH-DEBT-009 | Rotar secrets Supabase post-CVE-2025-66478 | SÍ — antes del primer dato real |
|
||||
|
||||
---
|
||||
|
||||
## 9. Pendientes próxima sesión
|
||||
|
||||
1. TECH-DEBT-006 — backup remoto S3
|
||||
2. TECH-DEBT-009 — rotar secrets Supabase
|
||||
3. RE Index — confirmar mapeo G1/G2/G4/G5 → códigos seed con Marcos
|
||||
4. Informe descargable — prometido en deck comercial
|
||||
5. Onboarding empresas reales — cuando validación esté lista
|
||||
|
||||
---
|
||||
|
||||
## 10. Decisiones tomadas — no reabrir
|
||||
|
||||
1. Supabase en Dokploy siempre via modo Git
|
||||
2. POSTGRES_PASSWORD siempre con `openssl rand -hex`
|
||||
3. kong.yml: una sola entrada por bloque keyauth_credentials
|
||||
4. SMTP_PORT=587 siempre explícito aunque SMTP esté desactivado
|
||||
5. GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=false siempre explícito
|
||||
6. LUA_AUTH_EXPR con prefijo "Bearer " en kong-entrypoint.sh
|
||||
7. NEXT_PUBLIC_SUPABASE_URL via Build Argument en Dokploy
|
||||
8. App conecta a https://db.inqualityhq.com
|
||||
9. /admin/responses no muestra is_sensitive=true sin auth (TECH-DEBT-008)
|
||||
10. brand_config.org_name alimenta el nombre de empresa en el formulario
|
||||
11. Organizaciones separadas para cada encuesta
|
||||
12. conditional_rules referencian códigos de pregunta, no UUIDs
|
||||
13. UUID cero `00000000-...` es el flag p_all del dashboard
|
||||
14. Next.js pinneado a versión exacta `15.3.8` (sin caret)
|
||||
|
||||
---
|
||||
|
||||
## 11. Contexto comercial
|
||||
|
||||
- 2 empresas comprometidas para piloto (InQuality y Fundación Solidaridad en espacio de pruebas)
|
||||
- Cronograma: JUN inicio · JUL-AGO relevamiento · SEP análisis · OCT informe + evento
|
||||
- Meta: 20 empresas pioneras
|
||||
- Precios: Gs. 4.5M (≤50) · 7.5M (≤200) · 12M (≤400)
|
||||
- GAPs deck vs producto: RE Index no implementado, informe descargable no existe
|
||||
- Contacto Fundación: Valeria Arce — ddff@fundacionsolidaridad.org.py · +595 991 854333
|
||||
|
||||
---
|
||||
|
||||
## 12. Prompt para nueva sesión
|
||||
|
||||
Pegá al inicio de la nueva conversación:
|
||||
|
||||
---
|
||||
|
||||
Sos el copiloto técnico del proyecto "Datos País sobre el Cuidado" — plataforma soberana
|
||||
de encuestas sobre economía del cuidado en Paraguay. Tenemos múltiples sesiones de trabajo.
|
||||
|
||||
Stack: Next.js 15.3.8 + Supabase self-hosted + Dokploy v0.29.4.
|
||||
URL: https://economia-cuidado.inqualityhq.com
|
||||
Repos en Gitea: git.inquality.com.py/marcos/
|
||||
|
||||
El estado completo está en `docs/estado/ESTADO_PROYECTO_JUNIO_2026.md` en el repo.
|
||||
Los prompts de etapas pasadas están en `sprints/`.
|
||||
Las reglas no negociables están en `CLAUDE.md`.
|
||||
|
||||
Próximos pasos: [COMPLETAR CON LO QUE VAS A HACER]
|
||||
246
method/ANTI_PATTERNS.md
Normal file
246
method/ANTI_PATTERNS.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# Anti-patrones
|
||||
|
||||
> Comportamientos que producen daño concreto y deben evitarse. Cada uno está documentado con el caso real donde apareció.
|
||||
>
|
||||
> A diferencia de los patrones (que son propuestos hasta validación), los anti-patrones se documentan estables porque el daño ya está observado.
|
||||
|
||||
---
|
||||
|
||||
## AP.1 — Tests verdes como aprobación final
|
||||
|
||||
**Comportamiento:** declarar una etapa terminada porque los tests pasaron, sin abrir el artefacto final con los ojos.
|
||||
|
||||
**Daño concreto:** llegás a producción con outputs visuales rotos. Caso real (InQ ROI MVP): los tests E2E verificaban que el PDF se generaba y tenía las páginas esperadas, pero nadie abrió el PDF. El heatmap embebido estaba en blanco por un bug de timing en html2canvas. Se descubrió 2 días después cuando un consultor lo abrió.
|
||||
|
||||
**Cómo detectarlo:** el reporte del agente dice "tests verdes, build limpio, PDF generado en X path" pero no incluye observaciones visuales del PDF.
|
||||
|
||||
**Cómo evitarlo:** aplicar **Principio 2** (validación humana es ojo) y **Patrón C.1** (validación visual mandatoria).
|
||||
|
||||
---
|
||||
|
||||
## AP.2 — Pasamanos invertido (humano como operador)
|
||||
|
||||
**Comportamiento:** el humano traduce manualmente entre dos agentes o entre agente y código. Copia outputs, formatea, transporta contexto entre sesiones.
|
||||
|
||||
**Daño concreto:** el humano se vuelve cuello de botella del sistema. Lo peor: deja de pensar en arquitectura/producto y se concentra en operación. Caso real (InQ ROI Sprint 1.5, Etapa 5): el director del proyecto sintió "soy pasamanos" después de varios checkpoints transportando reportes de Claude Code al arquitecto.
|
||||
|
||||
**Cómo detectarlo:** preguntate si lo que estás haciendo lo podría hacer un script o un prompt mejor estructurado. Si la respuesta es sí, estás en este anti-patrón.
|
||||
|
||||
**Cómo evitarlo:** aplicar **Principio 5** (humano director). Mover lo operativo a archivos (prompts pre-armados, templates) y reservar el chat para decisiones reales.
|
||||
|
||||
---
|
||||
|
||||
## AP.3 — Racionalización de output anómalo
|
||||
|
||||
**Comportamiento:** cuando un output del agente tiene una métrica anómala (PDF inusualmente liviano, número muy redondo, payback "0.0 meses"), el agente o el humano racionalizan ("numéricamente correcto", "es el caso esperado") en lugar de investigar la causa raíz.
|
||||
|
||||
**Daño concreto:** bugs latentes que se descubren en producción. Caso real (InQ ROI Sprint 1.5, Etapa 4): el PDF de ROI pesaba 36 KB con 4 páginas (9 KB/página), comparado con 78 KB/3 páginas (26 KB/página) de los otros PDFs. El agente reportó "PDF generado". El humano paró y pidió investigar — había secciones que no se renderizaban.
|
||||
|
||||
**Cómo detectarlo:** cualquier número que parezca "anómalo pero técnicamente válido". Especialmente: tamaños de archivo, payback inmediato, ROI extremo, métricas con muchos ceros.
|
||||
|
||||
**Cómo evitarlo:** aplicar **Principio 3** (auditoría adversarial) y **Patrón E.3** (anti-racionalización explícita).
|
||||
|
||||
---
|
||||
|
||||
## AP.4 — Decisión recurrente no registrada
|
||||
|
||||
**Comportamiento:** el agente vuelve a preguntar lo mismo en cada sesión nueva, o miembros del equipo asumen distinto porque la decisión no está documentada.
|
||||
|
||||
**Daño concreto:** outputs inconsistentes entre etapas, tiempo perdido re-debatiendo, deriva del producto. Caso real (InQ ROI Sprint 1.5, varias etapas): cada vez que Claude Code iba a aplicar formato de cifras, había que recordarle si usaba separador europeo o americano. Hasta que se documentó en `CLAUDE.md`.
|
||||
|
||||
**Cómo detectarlo:** el agente te hace una pregunta que ya respondiste en alguna sesión anterior.
|
||||
|
||||
**Cómo evitarlo:** aplicar **Principio 4** (decisiones como contratos) y **Patrón B.3** (decisiones tomadas explícitas en cada prompt).
|
||||
|
||||
---
|
||||
|
||||
## AP.5 — Improvisación sin gobernanza
|
||||
|
||||
**Comportamiento:** un equipo (o miembros individuales) usa agentes de IA sin método compartido. Cada uno improvisa con su herramienta preferida, sin briefs, sin validación estructurada, sin documentar decisiones.
|
||||
|
||||
**Daño concreto:**
|
||||
1. Deuda técnica invisible: artefactos producidos por IA que nadie puede auditar después
|
||||
2. Aprendizaje no se acumula: descubrimientos individuales no se transfieren
|
||||
3. Calidad inconsistente y no atribuible
|
||||
4. Imposibilidad de mejorar sistemáticamente
|
||||
|
||||
**Cómo detectarlo:** preguntá al equipo "¿cómo hicieron este artefacto con IA?" — si las respuestas son distintas y vagas, estás acá.
|
||||
|
||||
**Cómo evitarlo:** este método entero está diseñado para evitarlo. Específicamente: empezar con **Principios 1 y 4**.
|
||||
|
||||
---
|
||||
|
||||
## AP.6 — Brief grande pegado en chat
|
||||
|
||||
**Comportamiento:** pegar un brief de 800+ líneas en el chat del agente como mensaje inicial.
|
||||
|
||||
**Daño concreto:**
|
||||
- Consume contexto innecesariamente
|
||||
- No es auditable después
|
||||
- Se pierde si la sesión se corta
|
||||
- Cuesta tokens repetirlo
|
||||
|
||||
Caso real (InQ ROI Sprint 1.5): el primer intento fue pegar el BRIEF.md completo en chat. Se reemplazó por la estructura `sprints/sprint-N/` con archivos versionados, mensaje inicial corto referenciando paths.
|
||||
|
||||
**Cómo detectarlo:** si tu mensaje inicial al agente tiene más de 30 líneas, probablemente estás en este anti-patrón.
|
||||
|
||||
**Cómo evitarlo:** aplicar **Patrón B.1** (prompt como artefacto del repo) y **Patrón B.2** (mensaje inicial corto).
|
||||
|
||||
---
|
||||
|
||||
## AP.7 — Bypass total de permisos en desarrollo local
|
||||
|
||||
**Comportamiento:** usar `--dangerously-skip-permissions` (o equivalente) rutinariamente en máquinas de desarrollo donde corren proyectos reales. El motivo declarado suele ser "evitar la fricción de aprobar cada comando".
|
||||
|
||||
**Daño concreto:**
|
||||
|
||||
1. **Pérdida de auditoría en tiempo real:** cuando un agente ejecuta una acción no intencionada (borrar archivos, modificar git history, instalar dependencias rotas), el humano lo descubre por consecuencia, no por la pausa de aprobación.
|
||||
|
||||
2. **Datos perdidos en repos en evolución:** un `rm -rf` accidental sobre el directorio equivocado puede borrar trabajo no comiteado, configuraciones locales, o assets generados que no están en git.
|
||||
|
||||
3. **Confianza falsa en el agente:** el dev se acostumbra a "todo funciona" y deja de auditar. Cuando aparece un comando raro, el reflejo de pausar ya no existe.
|
||||
|
||||
4. **Imposibilidad de saber cuándo empezó un problema:** sin puntos de control, debuggear "¿cuándo pasó X?" requiere reconstruir desde logs en vez de tener una traza de aprobaciones.
|
||||
|
||||
**Caso real (no en este equipo, observado en otros proyectos):** un desarrollador con bypass total dejó al agente refactorizar carpetas con `mv` masivos. Una iteración movió archivos a una ruta equivocada por un typo del agente en la ruta destino. Sin permisos, los `mv` se ejecutaron en cadena. Recuperar tomó 4 horas porque los archivos no estaban en stash ni en commits.
|
||||
|
||||
**Cómo detectarlo:**
|
||||
- El dev menciona "skipeo todos los permisos para ir más rápido"
|
||||
- No existe `.claude/settings.json` ni equivalente en el repo
|
||||
- El dev no sabe explicar qué hace `--dangerously-skip-permissions`
|
||||
|
||||
**Cómo evitarlo:**
|
||||
- Aplicar **Patrón D.4** (permisos versionados en `.claude/`)
|
||||
- Configurar `allow` para comandos rutinarios (que es lo que daba fricción)
|
||||
- Configurar `deny` para comandos críticos (`rm -rf` con paths absolutos, `git push --force`, `npm publish`, `git reset --hard`)
|
||||
- **Aceptar la fricción ocasional como precio de la gobernanza**
|
||||
|
||||
**Cuándo SÍ es aceptable usar bypass total:**
|
||||
- Sandboxes efímeros (contenedores Docker que se destruyen post-ejecución)
|
||||
- CI/CD donde no hay datos del dev
|
||||
- Máquinas virtuales dedicadas para experimentación
|
||||
|
||||
**Cuándo NO es aceptable:**
|
||||
- Máquina personal del dev con repos reales
|
||||
- Cualquier entorno donde haya datos no replicables
|
||||
- Cualquier entorno con credenciales o secretos
|
||||
|
||||
---
|
||||
|
||||
## Cómo agregar un anti-patrón
|
||||
|
||||
Solo se agregan anti-patrones con daño concreto observado, no especulativo. Estructura obligatoria:
|
||||
|
||||
1. **Nombre breve** (forma de "AP.N — Descripción")
|
||||
2. **Comportamiento:** descripción de la acción problemática
|
||||
3. **Daño concreto:** qué pasa cuando ocurre, con caso real citado
|
||||
4. **Cómo detectarlo:** señal observable
|
||||
5. **Cómo evitarlo:** principio o patrón que lo previene
|
||||
|
||||
No agregar anti-patrones de "esto sería malo si pasara" — solo de cosas que **pasaron en proyectos reales** del equipo.
|
||||
|
||||
---
|
||||
|
||||
## AP.8 — Adelanto silencioso de deuda técnica
|
||||
|
||||
**Comportamiento:** el agente adelanta ítems explícitos de `TECH_DEBT.md`, `BACKLOG.md`, u otros archivos de planificación sin autorización del director, argumentando "mejora de legibilidad", "consistencia con etapa anterior", "resolver bug menor", o similar.
|
||||
|
||||
El cambio puede ser técnicamente correcto y producir un output mejor, pero **desestabiliza la planificación** del sprint y la priorización del director.
|
||||
|
||||
**Daño concreto:**
|
||||
|
||||
1. **Erosión de la planificación:** ítems planificados para sprints futuros se ejecutan en el sprint actual sin que el director lo decida. La capacidad estimada del sprint se desborda silenciosamente.
|
||||
|
||||
2. **Pérdida de control de scope:** el sprint termina con más cambios que los autorizados. La trazabilidad de "qué se hizo y cuándo" se rompe.
|
||||
|
||||
3. **Mensaje al agente:** "los Nivel 3 mal clasificados a veces se aprueban retroactivamente, podés seguir intentando". La política de iniciativa proactiva se debilita por aceptación implícita.
|
||||
|
||||
4. **Decisiones arquitectónicas no tomadas:** el cambio puede tener implicaciones que el agente no anticipó pero el director hubiera detectado. Por ejemplo: cambiar Σ a "SUM" se ve bien aisladamente, pero puede entrar en conflicto con políticas de tipografía financiera o internacionalización.
|
||||
|
||||
**Caso real (InQ ROI Sprint 1.5):**
|
||||
|
||||
- **Etapa 7:** Claude Code reescribió fórmulas matemáticas de la nota metodológica del PDF ROI a ASCII puro (Σ → "SUM", × → "x", − → "-") sin reportar el cambio en el reporte de la etapa. El bug Unicode estaba documentado en `TECH_DEBT.md` con plan "embebido de fuente Unicode en Sprint 2". El cambio era Nivel 3 (afecta contenido del entregable, choca con plan de TECH_DEBT) pero pasó sin reporte.
|
||||
|
||||
- **Etapa 8:** Claude Code extendió el cambio a los PDFs Actual y Automatizado, esta vez clasificándolo como Nivel 1 con el argumento "mismo criterio que ROI Etapa 7". Detectado en el reporte y discutido. El director decidió aceptar retroactivamente la solución porque resultó ser correcta para el dominio (terminología financiera/Excel), pero el patrón quedó documentado como anti-patrón.
|
||||
|
||||
**Cómo detectarlo:**
|
||||
|
||||
- El agente reporta un cambio justificado como "mejora", "consistencia", "resuelve bug", "limpia código" o similar.
|
||||
- Verificá si ese cambio resuelve un ítem listado en `TECH_DEBT.md`, `BACKLOG.md`, u observaciones diferidas. **Si está listado, era Nivel 3 sin importar el tamaño técnico.**
|
||||
- El agente invoca precedente ("se hizo lo mismo en etapa anterior") como justificación. Verificá si el precedente fue autorizado o si también es un adelanto silencioso.
|
||||
|
||||
**Cómo evitarlo:**
|
||||
|
||||
- Aplicar **Patrón C.6 refinado** (ver `methodology/PATTERNS.md`): cualquier cambio que resuelve ítem listado en archivos de planificación es Nivel 3, consulta previa.
|
||||
- En `CLAUDE.md` del proyecto, agregar regla: "Antes de aplicar un cambio fuera del scope del prompt actual, verificar si está listado en TECH_DEBT.md, BACKLOG.md, o equivalentes. Si está listado, consultar antes de proceder."
|
||||
- Auditoría adversarial al reporte (Patrón C.3): cuando el agente reporta cambios "consistentes" o "menores", verificar contra archivos de planificación, no solo contra el prompt de la etapa.
|
||||
|
||||
**Distinción importante:**
|
||||
|
||||
AP.8 NO es lo mismo que AP.3 (Racionalización de output anómalo). AP.3 es minimizar un problema observado. AP.8 es ejecutar trabajo no autorizado argumentando que es mejora.
|
||||
|
||||
AP.8 también difiere del Patrón C.6 mal aplicado. C.6 mal aplicado es clasificar mal el nivel de impacto. AP.8 es específicamente sobre adelantar deuda técnica planificada — un subconjunto que merece nombre propio.
|
||||
|
||||
---
|
||||
|
||||
## AP.9 — Resolución unilateral de restricciones contradictorias
|
||||
|
||||
**Comportamiento:** el agente enfrenta dos requisitos del prompt que colisionan (ej: "no tocar archivo X" + "build limpio sin errores TypeScript") y resuelve la contradicción actuando mínimamente en lugar de detenerse y reportar el conflicto.
|
||||
|
||||
**Daño concreto:** el agente produce código funcionalmente correcto en el área autorizada pero roto en el área restringida, y describe el resultado como "comportamiento esperado" minimizando la severidad real. Caso real (InQ ROI Sprint 2, Etapa 1): el prompt restringía `ActivityPanel.tsx` explícitamente (`❌`), pero también exigía build sin errores TypeScript. Cambiar `types.ts` inevitablemente generaba errores TypeScript en `ActivityPanel.tsx`. El agente lo corrigió mínimamente (clasificó como Nivel 2) en lugar de detenerse. Resultado: slider roto que requirió toda la Etapa 2 para corregir.
|
||||
|
||||
**Cómo detectarlo:** el reporte declara build limpio pero hay un archivo restringido en el diff. O el agente menciona "ajuste mínimo para compatibilidad de tipos" en un archivo que debería estar intacto.
|
||||
|
||||
**Cómo evitarlo:**
|
||||
1. En el prompt: cuando haya restricción `❌` sobre un archivo, agregar explícitamente: *"Si este cambio produce errores TypeScript en archivos restringidos, DETENTE, no los corrijas, reportá el conflicto al director antes de continuar."*
|
||||
2. Control preventivo: `git diff --name-only HEAD` al final — si aparece un archivo restringido, revertir y reportar.
|
||||
3. No incluir simultáneamente restricciones absolutas (`❌ no tocar X`) y requisitos que inevitablemente afectan X (cambios de tipo transitivos). Si el conflicto es inevitable, resolverlo en el prompt antes de dárselo al agente.
|
||||
|
||||
**Relación con otros patrones:**
|
||||
- Requiere el control preventivo de **C.7** (git diff como control de scope) para detección temprana.
|
||||
- Distinto de **AP.5** (improvisación sin gobernanza): AP.9 ocurre dentro de un prompt estructurado, no por ausencia de método.
|
||||
|
||||
---
|
||||
|
||||
## AP.10 — Propagación silenciosa de inconsistencia entre fuentes
|
||||
**Comportamiento:** un prompt contiene una referencia (código, ruta, esquema) que el agente
|
||||
podría verificar contra otra fuente del repo. En lugar de hacerlo, el agente ejecuta
|
||||
literalmente y traslada la responsabilidad al director con frases como "la validación
|
||||
semántica la hace el director" o "los textos son los solicitados literalmente".
|
||||
**Daño:** las inconsistencias se propagan a producción con apariencia de precisión técnica.
|
||||
*Caso real:* en Etapa 2D del proyecto Datos País, 11 de 12 subtítulos del dashboard
|
||||
quedaron con códigos de pregunta discrepantes del seed real. El dashboard mostraba
|
||||
métricas mal etiquetadas: el KPI "Ausencia promedio · 3.1 · días por mes" referenciaba
|
||||
una pregunta que en el seed real era *gasto en cuidado*, no ausencias. Detectado en la
|
||||
auditoría adversarial posterior, no antes. Si hubieran cargado datos reales, RRHH habría
|
||||
visto guaraníes presentados como días.
|
||||
**Detectar:** el reporte del agente declara "los textos son los solicitados literalmente";
|
||||
hay códigos, rutas o nombres que el agente pudo verificar contra otra fuente y no lo hizo;
|
||||
el agente delega la verificación semántica al humano sin haber verificado lo verificable.
|
||||
**Evitar:** Patrón C.8 + cláusula explícita en el prompt: *"si detectás una discrepancia
|
||||
entre el prompt y la fuente única, DETENÉ la ejecución y reportá antes de avanzar"*.
|
||||
**Distinción:** ≠ AP.3 — AP.3 minimiza un problema observado; AP.10 evita verificar lo
|
||||
verificable. ≠ AP.9 — AP.9 resuelve restricciones contradictorias del propio prompt;
|
||||
AP.10 propaga inconsistencias entre el prompt y otra fuente.
|
||||
|
||||
## AP.11 — Contrato desactualizado por código
|
||||
**Comportamiento:** una etapa modifica el comportamiento real del sistema (seed, esquema,
|
||||
RLS, RPCs) pero no actualiza el documento que sirve como contrato o fuente única
|
||||
(`SEED_SPEC.md`, `ARCHITECTURE.md`, ADRs). El documento queda mintiendo por silencio.
|
||||
**Daño:** el próximo agente o autor de prompts que lo lea va a tomar decisiones basadas
|
||||
en información falsa. *Caso real:* en Etapa 1D del proyecto Datos País se modificó
|
||||
`seed.sql` para que A1/A2/A3 fueran widgets geo (Departamento/Ciudad/Barrio en lugar de
|
||||
texto libre País/Ciudad/Sucursal), pero no se actualizó `SEED_SPEC.md`. Dos sprints
|
||||
después, Etapa 2E se escribió tomando SEED_SPEC como verdad y proponiendo agregar una
|
||||
pregunta A1b que duplicaba la A1 ya existente. El conflicto se detectó antes de ejecutar
|
||||
porque el agente aplicó el Patrón C.8; sin C.8, se habría creado una pregunta duplicada
|
||||
en producción.
|
||||
**Detectar:** cuando una etapa cambia comportamiento del sistema, preguntar explícitamente
|
||||
"¿qué documento de contrato describe esto, y lo actualicé?". Si la respuesta es "no sé"
|
||||
o "después lo veo", AP.11 latente. También: cuando una etapa posterior asume contenido
|
||||
del contrato que ya no coincide con la realidad.
|
||||
**Evitar:** todo prompt que modifique un sistema documentado en un contrato debe incluir,
|
||||
como parte del DoD, "actualizar el documento de contrato correspondiente con los cambios".
|
||||
Es Principio 4 aplicado al mantenimiento, no solo a la creación.
|
||||
**Distinción:** ≠ AP.4 — AP.4 es decisión no registrada; AP.11 es decisión registrada en
|
||||
código pero NO actualizada en el contrato escrito.
|
||||
513
method/PATTERNS.md
Normal file
513
method/PATTERNS.md
Normal file
@@ -0,0 +1,513 @@
|
||||
# Patrones operativos
|
||||
|
||||
> Aplicaciones concretas de los 5 principios. Estructurados por categoría operativa.
|
||||
>
|
||||
> Estado de cada patrón: **propuesto** (validado en 1 proyecto) o **estable** (validado en 2+ proyectos).
|
||||
>
|
||||
> A la fecha, todos los patrones aquí son propuestos. Caso de validación: InQ ROI Sprint 1.5.
|
||||
|
||||
---
|
||||
|
||||
## Categoría A — Disciplina antes de codear
|
||||
|
||||
### A.1 Brief versionado antes que código
|
||||
**Estado:** propuesto · **Deriva de:** Principio 1
|
||||
|
||||
Todo sprint o cambio mayor arranca con un brief en `sprints/sprint-N/BRIEF.md` antes de pedirle algo a un agente. El brief define alcance, decisiones tomadas, criterios de éxito y lo que NO entra.
|
||||
|
||||
**Indicador de aplicación correcta:** podés iniciar una sesión nueva con Claude Code pasándole solo el path del brief y CLAUDE.md, sin contexto adicional, y empieza a trabajar correctamente.
|
||||
|
||||
**Indicador de violación:** el agente te hace preguntas que ya están respondidas en otro lugar (o peor, no las hace y asume).
|
||||
|
||||
---
|
||||
|
||||
### A.2 Mockup como contrato visual
|
||||
**Estado:** propuesto · **Deriva de:** Principio 1
|
||||
|
||||
Si el cambio es visual, antes de implementarlo se construye un mockup interactivo (HTML, Figma, Visualizer) que el humano aprueba. El mockup es el contrato: el agente lo replica, no lo interpreta libremente.
|
||||
|
||||
**Indicador de aplicación correcta:** cuando ves el output del agente, podés compararlo lado a lado con el mockup y la diferencia es marginal.
|
||||
|
||||
**Indicador de violación:** llegás a Etapa N de un rediseño visual y descubrís que el output "no tiene nada que ver" con lo que esperabas.
|
||||
|
||||
---
|
||||
|
||||
### A.3 Roadmap explícito antes de arrancar
|
||||
**Estado:** propuesto · **Deriva de:** Principios 1 y 4
|
||||
|
||||
Todo sprint con más de 3 etapas tiene un roadmap explícito desde el inicio. Cada etapa tiene **expectativa calibrada** de qué cambia y qué NO cambia respecto a la anterior.
|
||||
|
||||
**Indicador de aplicación correcta:** después de cada checkpoint, el resultado coincide con lo que esperabas ver, no te sorprende ni para bien ni para mal.
|
||||
|
||||
**Indicador de violación:** te sorprende negativamente un output intermedio porque pensabas que ya tenía X feature que en realidad está agendado 3 etapas después.
|
||||
|
||||
---
|
||||
|
||||
## Categoría B — Operación con agentes
|
||||
|
||||
### B.1 Prompt como artefacto del repo
|
||||
**Estado:** propuesto · **Deriva de:** Principios 4 y 5
|
||||
|
||||
Cada etapa de trabajo agéntico tiene su prompt en `sprints/sprint-N/ETAPA_X_PROMPT.md`, versionado. El chat con el agente es **puente** entre el prompt y el reporte, no contenedor de la decisión.
|
||||
|
||||
**Por qué:** los prompts en archivo son auditables, editables antes de ejecutar, replicables, y no se pierden al cerrar la sesión.
|
||||
|
||||
**Estructura mínima de un prompt de etapa:**
|
||||
1. Objetivo
|
||||
2. Pre-requisitos / contexto previo
|
||||
3. Decisiones ya tomadas (no consultar de nuevo)
|
||||
4. Tareas concretas
|
||||
5. Entregables (DoD checklist)
|
||||
6. Qué reportar al final
|
||||
7. Qué NO hacer en esta etapa
|
||||
|
||||
---
|
||||
|
||||
### B.2 Mensaje inicial corto, contexto en archivos
|
||||
**Estado:** propuesto · **Deriva de:** Principio 5
|
||||
|
||||
El mensaje al agente al arrancar una sesión es de 3-5 párrafos, NO el prompt entero pegado. Estructura:
|
||||
1. "Procedé con etapa X"
|
||||
2. Archivos a leer en orden
|
||||
3. Confirmaciones que esperás antes de que empiece
|
||||
4. Restricción de alcance ("no avances a Y sin OK")
|
||||
|
||||
**Por qué:** el contexto en archivos sobrevive a la sesión, queda versionado, y el agente lo consulta cuando lo necesita en vez de tenerlo permanentemente en ventana.
|
||||
|
||||
---
|
||||
|
||||
### B.3 Decisiones tomadas como sección explícita
|
||||
**Estado:** propuesto · **Deriva de:** Principio 4
|
||||
|
||||
En cada prompt de etapa, incluir sección "DECISIONES YA TOMADAS — no consultar de nuevo" con las definiciones cerradas. Esto impide que el agente vuelva a preguntar lo ya resuelto.
|
||||
|
||||
**Ejemplo:**
|
||||
```
|
||||
DECISIONES YA TOMADAS (no consultar de nuevo)
|
||||
- Payback negativo → "Sin payback (costo aumenta)"
|
||||
- Caracteres compactos: "<" y "~"
|
||||
- Pluralización: siempre plural ("1.0 meses")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### B.4 Reglas no negociables en CLAUDE.md
|
||||
**Estado:** propuesto · **Deriva de:** Principio 4
|
||||
|
||||
Las reglas que aplican a TODO el proyecto van en `CLAUDE.md`, no solo en briefs específicos. El agente carga CLAUDE.md en cada sesión nueva por defecto.
|
||||
|
||||
**Ejemplo de qué va ahí:**
|
||||
- Reglas de marca (cómo se escribe el nombre, qué colores)
|
||||
- Restricciones de comportamiento (qué no decir, qué no asumir)
|
||||
- Verificaciones obligatorias pre-entrega
|
||||
|
||||
---
|
||||
|
||||
## Categoría C — Validación de output
|
||||
|
||||
### C.1 Validación visual mandatoria del artefacto final
|
||||
**Estado:** propuesto · **Deriva de:** Principio 2
|
||||
|
||||
Si el output de la etapa es un artefacto visible (PDF, screenshot, página renderizada, documento), el humano lo abre con sus ojos antes del OK. Tests verdes no es suficiente.
|
||||
|
||||
**Tooling sugerido:**
|
||||
- PDFs/imágenes: Preview en Mac, visor PDF en Linux
|
||||
- Páginas web: dev server en navegador
|
||||
- Documentos: editor que renderice (no solo source)
|
||||
|
||||
**Cuándo evolucionar este patrón:** ver methodology/AUTOMATION_STRATEGY.md.
|
||||
La validación visual humana es Fase 1. Puede complementarse con agentes
|
||||
auditores (Fase 3) pero NO se reemplaza completamente hasta que el
|
||||
equipo desarrolle criterio adversarial maduro.
|
||||
|
||||
*Cuando el output incluye textos que referencian otras fuentes del repo (códigos, IDs, rutas), aplicar también C.8.*
|
||||
|
||||
---
|
||||
|
||||
### C.2 Validación programática antes de declarar completado (agente)
|
||||
**Estado:** propuesto · **Deriva de:** Principios 2 y 3
|
||||
|
||||
Antes de declarar una etapa completada, el agente debe ejecutar validaciones programáticas, no solo confiar en tests unitarios. Para outputs visuales:
|
||||
- Análisis de píxeles (¿están los colores esperados?)
|
||||
- Text extraction (¿están las palabras clave?)
|
||||
- Tamaño de archivo (¿es razonable?)
|
||||
- Cantidad de páginas (¿coincide con el diseño?)
|
||||
|
||||
**Por qué:** un test unitario puede pasar mientras el output final está roto. La validación programática del artefacto cierra ese gap.
|
||||
|
||||
---
|
||||
|
||||
### C.3 Auditoría adversarial al reporte
|
||||
**Estado:** propuesto · **Deriva de:** Principio 3
|
||||
|
||||
Cuando llega un reporte de agente "todo OK", aplicar 3 preguntas:
|
||||
1. ¿Los números reportados cuadran si recalculo o pido dump?
|
||||
2. ¿El artefacto es el que pedí?
|
||||
3. ¿Lo que no menciona podría ser relevante?
|
||||
|
||||
**Casos típicos de detección:**
|
||||
- PDF de 36 KB con 4 páginas (es muy poco → algo no se renderizó)
|
||||
- KPI con valor "0.0 meses" (numéricamente correcto pero confuso)
|
||||
- Reporte que dice "PDF generado" sin abrir el PDF generado
|
||||
|
||||
---
|
||||
|
||||
### C.4 Reporte con datos concretos, no descripciones (agente)
|
||||
**Estado:** propuesto · **Deriva de:** Principio 3
|
||||
|
||||
Los reportes del agente deben incluir métricas concretas, no descripciones genéricas.
|
||||
|
||||
**Mal reporte:** "PDF generado correctamente, contiene todo lo esperado."
|
||||
|
||||
**Buen reporte:**
|
||||
```
|
||||
PDF generado: 78 KB, 4 páginas
|
||||
- Página 1: KPI Hero + 4 cards secundarios + top 3 ahorros
|
||||
- Página 2: tabla con 11 filas + footer
|
||||
- Página 3: 3 secciones (composición, trayectoria, transparencia)
|
||||
- Página 4: nota metodológica
|
||||
Análisis de píxeles del heatmap: verde 36.329px, ámbar 3.889px, rojo 3.713px
|
||||
Text extraction confirma palabras clave: "InQ ROI", "Powered by InQuality", "Payback"
|
||||
```
|
||||
|
||||
### C.5 Validaciones programáticas explícitas en el prompt
|
||||
**Estado:** propuesto · **Deriva de:** Principios 2 y 5
|
||||
|
||||
Cada prompt de etapa incluye una sección **"Validaciones automatizadas mandatorias"** con criterios objetivos verificables por código que el agente debe ejecutar antes de declarar terminada la etapa o usar `present_files`.
|
||||
|
||||
**Por qué:** documenta explícitamente qué se va a auto-validar, hace transferible el conocimiento operativo al equipo, captura el aprendizaje sobre qué problemas son recurrentes y vale la pena chequear automáticamente.
|
||||
|
||||
**Cómo se aplica:**
|
||||
|
||||
```markdown
|
||||
## Validaciones automatizadas mandatorias
|
||||
|
||||
Antes de present_files, ejecutá y confirmá:
|
||||
|
||||
PDFs:
|
||||
- pdf-parse confirma palabras clave esperadas: [lista]
|
||||
- pdf-parse rechaza términos prohibidos: [lista]
|
||||
- Cada página entre [N-M] KB
|
||||
- Cantidad de páginas == [N exacto]
|
||||
|
||||
Tests:
|
||||
- Tests previos verdes (>= [N])
|
||||
- Tests nuevos verdes
|
||||
|
||||
Build:
|
||||
- npm run build limpio
|
||||
- Sin warnings TS ni lint nuevos
|
||||
```
|
||||
|
||||
**Indicador de aplicación correcta:** los problemas que ya cazaste manualmente en sprints anteriores aparecen como validación automatizada en sprints siguientes. Aprendizaje acumulativo.
|
||||
|
||||
**Indicador de violación:** el agente declara etapa terminada y vos cazás el mismo tipo de problema que ya cazaste 3 etapas atrás. Significa que el aprendizaje no se está capturando como validación.
|
||||
|
||||
**Relación con otros patrones:**
|
||||
- Extiende **C.2** (validación programática) haciéndola explícita en el prompt
|
||||
- No reemplaza **C.1** (validación visual humana) — son complementarias
|
||||
- Es Fase 2 del roadmap de `AUTOMATION_STRATEGY.md`
|
||||
|
||||
### C.6 Iniciativa agéntica con consulta clasificada por impacto
|
||||
**Estado:** propuesto · **Deriva de:** Principios 3, 4 y 5
|
||||
|
||||
Cuando el agente detecta una oportunidad de mejora **fuera del scope del prompt**, debe clasificar el impacto del cambio antes de proceder y actuar según la política definida en `PRINCIPLES.md` sección "Política de iniciativa agéntica".
|
||||
|
||||
**Niveles de impacto:**
|
||||
|
||||
1. **Trivial** (cambio interno, no visible al usuario, reversible sin costo)
|
||||
→ ejecutar y reportar al final como "decisión menor tomada"
|
||||
|
||||
2. **Visible reversible** (cambio visible al usuario pero sin afectar marca, datos, ni arquitectura)
|
||||
→ ejecutar, **mencionar explícitamente en el reporte final** como "decisión proactiva, validar/revertir"
|
||||
|
||||
3. **De marca, arquitectura o datos** (cambio que afecta identidad, estructura, o información del usuario)
|
||||
→ **NO ejecutar**. Consultar antes de proceder.
|
||||
|
||||
**Por qué:** los agentes producen mejor cuando tienen libertad para optimizar lo local. Pero esa libertad sin límites lleva a cambios silenciosos que el humano descubre por casualidad. Una clasificación explícita preserva la velocidad agéntica sin perder gobernanza.
|
||||
|
||||
**Caso real:** InQ ROI Sprint 1.5 Etapa 3. Claude Code agregó "Hecho desde Paraguay 🇵🇾" al footer del web app sin estar en el brief. El cambio era razonable, alineado con identidad de marca, pero **el humano lo descubrió revisando screenshots**, no porque se reportara. Decisión retroactiva: mantener, pero documentar el patrón para que en el futuro este tipo de cambios (impacto nivel 3, afecta marca) sean consulta previa, no decisión unilateral.
|
||||
|
||||
**Indicador de aplicación correcta:**
|
||||
- El reporte final del agente tiene una sección explícita "Decisiones tomadas por iniciativa" listando cambios fuera del scope
|
||||
- Los cambios nivel 3 no se ejecutan sin consulta previa documentada
|
||||
- El humano puede aprobar o revertir cambios nivel 2 sin tener que reconstruir contexto
|
||||
|
||||
**Indicador de violación:**
|
||||
- Descubrir cambios en el output que no están ni en el prompt ni en el reporte
|
||||
- Cambios de marca, arquitectura o datos ejecutados sin consulta previa
|
||||
- "Pequeñas mejoras de UX" agregadas en silencio que cambian el comportamiento del producto
|
||||
|
||||
**Relación con otros patrones:**
|
||||
- Complementa **E.2** (Hallazgos no esperados) — pero E.2 es para descubrimientos pasivos (cosas que el agente notó), C.6 es para decisiones activas (cosas que el agente hizo).
|
||||
- Refuerza **C.3** (Auditoría adversarial) — provee al humano una sección concreta donde buscar cambios silenciosos.
|
||||
|
||||
## Refinamiento — criterios explícitos de clasificación (aprendizajes de Sprint 1.5)
|
||||
|
||||
Durante Sprint 1.5 InQ ROI se detectaron dos casos de mala clasificación de impacto. A partir de esos casos, se refinaron los criterios para distinguir entre Niveles 1, 2 y 3:
|
||||
|
||||
### Reglas de clasificación (en orden de evaluación)
|
||||
|
||||
Evaluar en este orden estricto. El primer criterio que aplique determina el nivel — no seguir evaluando los siguientes.
|
||||
|
||||
**1. ¿El cambio resuelve un ítem listado en `TECH_DEBT.md`, `BACKLOG.md`, o archivos de planificación?**
|
||||
→ Nivel 3 (consulta previa). Sin importar el tamaño técnico.
|
||||
|
||||
**Justificación:** ítems en archivos de planificación son decisiones del director sobre cuándo trabajarlos. El agente no puede promover ítems al sprint en curso por iniciativa.
|
||||
|
||||
**2. ¿El cambio choca con una restricción explícita del prompt de la etapa?**
|
||||
→ Nivel 3 (consulta previa). Sin excepciones.
|
||||
|
||||
Ejemplo de restricciones explícitas:
|
||||
- "NO entra: X" en sección de alcance
|
||||
- "NO tocar Y" en reglas operativas
|
||||
- "X está agendado para Etapa Z" en decisiones tomadas
|
||||
|
||||
**3. ¿El cambio modifica paginación, layout, cantidad de archivos generados, o estructura de salida?**
|
||||
→ Nivel 2 mínimo. Reportar.
|
||||
|
||||
**4. ¿El cambio es visible al usuario final (texto en interfaz, color, posición de elemento, contenido de PDF)?**
|
||||
→ Nivel 2 mínimo. Reportar.
|
||||
|
||||
**5. ¿El cambio afecta marca, identidad, dependencias, o configuración del producto?**
|
||||
→ Nivel 3 (consulta previa).
|
||||
|
||||
**6. Si nada de lo anterior aplica → Nivel 1**
|
||||
|
||||
Ejemplos válidos de Nivel 1:
|
||||
- Renombrar variable interna por claridad
|
||||
- Reordenar imports
|
||||
- Mejorar comentario de código
|
||||
- Extraer función helper usada en múltiples lugares
|
||||
- Eliminar código muerto evidente (sin riesgo de uso futuro)
|
||||
- Limpiar artefactos accidentales del refactor (variables `void` no usadas)
|
||||
|
||||
### Argumentos que NO son justificación válida para Nivel 1
|
||||
|
||||
El agente puede invocar argumentos que parecen razonables pero violan los criterios de arriba:
|
||||
|
||||
- **"Es mejora de legibilidad"** → si afecta contenido visible, es Nivel 2 mínimo
|
||||
- **"Es consistencia con etapa anterior"** → verificar si la etapa anterior fue autorizada o si también es adelanto silencioso (ver AP.8)
|
||||
- **"Es trivial de revertir"** → la reversibilidad NO es criterio de clasificación. Cambios reversibles también pueden ser Nivel 3.
|
||||
- **"Resuelve un bug menor"** → si el bug está documentado en TECH_DEBT.md, es Nivel 3
|
||||
- **"Es lo que el usuario esperaría"** → eso es asumir. La política existe para no asumir.
|
||||
|
||||
### Casos reales documentados (Sprint 1.5 InQ ROI)
|
||||
|
||||
**Caso 1 (Etapa 5):** compactación de páginas reportada como Nivel 1.
|
||||
- Realidad: Nivel 2 mínimo (modificó paginación visible)
|
||||
- Resultado: nota metodológica truncada, descubierto en validación visual
|
||||
- Lección: paginación es estructura de salida, criterio 3 aplica
|
||||
|
||||
**Caso 2 (Etapa 7-8):** reescritura de fórmulas Σ → "SUM" reportada inicialmente sin clasificación, después como Nivel 1.
|
||||
- Realidad: Nivel 3 (resolvía ítem de TECH_DEBT.md sobre Unicode roto)
|
||||
- Resultado: aceptado retroactivamente porque resultó ser solución correcta para el dominio, pero el patrón quedó como anti-patrón AP.8
|
||||
- Lección: ítem en TECH_DEBT.md activa criterio 1 directo, sin importar el tamaño
|
||||
|
||||
### Implicancia para CLAUDE.md del proyecto
|
||||
|
||||
En el `CLAUDE.md` de cada proyecto, agregar a la sección "Política de iniciativa proactiva":
|
||||
|
||||
```
|
||||
Antes de aplicar un cambio fuera del scope explícito del prompt actual:
|
||||
|
||||
1. Verificá si el cambio está listado en TECH_DEBT.md, BACKLOG.md, o
|
||||
archivos de planificación equivalentes. Si está, es Nivel 3 — consultá.
|
||||
|
||||
2. Verificá si el cambio choca con una restricción explícita del prompt
|
||||
("NO entra", "NO tocar", "está agendado para Etapa X"). Si choca,
|
||||
es Nivel 3 — consultá.
|
||||
|
||||
3. Verificá si el cambio modifica paginación, layout, estructura de
|
||||
salida, contenido visible. Si lo hace, es Nivel 2 mínimo — reportá.
|
||||
|
||||
4. Solo si nada de lo anterior aplica, ejecutá como Nivel 1.
|
||||
|
||||
Argumentos NO válidos para evitar la consulta:
|
||||
- "Es mejora de legibilidad"
|
||||
- "Es consistencia con etapa anterior" (verificar si esa etapa fue
|
||||
autorizada o también fue adelanto silencioso)
|
||||
- "Es trivial de revertir"
|
||||
- "Resuelve un bug menor"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Categoría D — Estructura del repo
|
||||
|
||||
### D.1 Carpeta `sprints/` para activos de planificación
|
||||
**Estado:** propuesto · **Deriva de:** Principio 4
|
||||
|
||||
Cada sprint tiene su carpeta `sprints/sprint-N/` con todos sus artefactos:
|
||||
- `BRIEF.md`
|
||||
- `ETAPA_X_PROMPT.md` (uno por etapa)
|
||||
- `ETAPA_X_REPORT.md` (opcional, reporte del agente capturado)
|
||||
- `etapa-X-audit.md` (cuando aplica)
|
||||
- `screenshots/` con evidencia visual
|
||||
|
||||
**Por qué:** en 6 meses, abrir esa carpeta te da el historial completo de cómo se construyó el sprint. Sin esto, el conocimiento queda en commits + memoria, que es frágil.
|
||||
|
||||
---
|
||||
|
||||
### D.2 Carpeta `methodology/` para el método en sí
|
||||
**Estado:** propuesto · **Deriva de:** Principio 4
|
||||
|
||||
El método de trabajo vive en `methodology/`, separado del producto. Esto permite que el método evolucione independiente de cualquier producto específico.
|
||||
|
||||
---
|
||||
|
||||
### D.3 ADRs para decisiones arquitectónicas
|
||||
**Estado:** propuesto · **Deriva de:** Principio 4
|
||||
|
||||
Las decisiones arquitectónicas importantes se registran como ADR (Architectural Decision Record) en `ARCHITECTURE.md` o `docs/adr/`. Estructura mínima:
|
||||
- Contexto (qué problema teníamos)
|
||||
- Decisión (qué elegimos)
|
||||
- Alternativas consideradas
|
||||
- Consecuencias (qué implica)
|
||||
|
||||
---
|
||||
|
||||
### D.4 Permisos del agente versionados en `.claude/`
|
||||
**Estado:** propuesto · **Deriva de:** Principios 4 y 5
|
||||
|
||||
Las decisiones sobre qué comandos puede ejecutar el agente sin consultar al humano se versionan en archivos de configuración del repo. Esto evita dos extremos: la fricción de aprobar cada comando manualmente, y el bypass total que abre la puerta a acciones no intencionadas.
|
||||
|
||||
**Estructura recomendada:**
|
||||
|
||||
```
|
||||
repo/
|
||||
└── .claude/
|
||||
├── settings.json ← commitable: deny patterns críticos
|
||||
└── settings.local.json ← gitignored: allow personales del dev
|
||||
```
|
||||
|
||||
**Reglas:**
|
||||
|
||||
1. **`settings.json` es del proyecto** — contiene patrones `deny` (comandos peligrosos prohibidos para todos: `rm -rf /`, `git push --force`, `npm publish`, etc.). Se comitea al repo.
|
||||
|
||||
2. **`settings.local.json` es del desarrollador** — contiene patrones `allow` personales (cada uno decide qué comandos pre-aprueba). Va en `.gitignore`. Cuando un dev nuevo arranca, **debe construir su propio** archivo siguiendo el template, no copiar el de otro.
|
||||
|
||||
3. **El equipo hereda las protecciones, no las preferencias** — los `deny` son compartidos, los `allow` son individuales.
|
||||
|
||||
**Por qué:**
|
||||
- La fricción de aprobar cada `npm install` destruye el flow de trabajo
|
||||
- Pero el bypass total elimina los puntos de control donde se detectan acciones no intencionadas
|
||||
- La gobernanza está en clasificar comandos por riesgo, no en aprobar todo o aprobar nada
|
||||
|
||||
**Indicador de aplicación correcta:**
|
||||
- En cada sesión de Claude Code, los comandos rutinarios pasan sin pedir aprobación
|
||||
- Los comandos no rutinarios siguen pidiendo aprobación (es bueno — ahí prestás atención)
|
||||
- Si un comando peligroso aparece, está bloqueado por `deny` o pide aprobación explícita
|
||||
|
||||
**Indicador de violación:**
|
||||
- El dev usa `--dangerously-skip-permissions` rutinariamente
|
||||
- El archivo `settings.json` contiene `allow` que deberían ser personales (paga la complejidad de mantenerlo sincronizado entre devs)
|
||||
- No hay archivo de configuración y cada dev aprueba todo manualmente cada vez (fricción crónica, señal de que falta este patrón)
|
||||
|
||||
**Template:** ver `methodology/TEMPLATES/CLAUDE_PERMISSIONS_TEMPLATE.md`.
|
||||
|
||||
**Relación con otros patrones:**
|
||||
- Complementa **B.4** (reglas no negociables en CLAUDE.md) — pero B.4 es sobre comportamiento del agente, D.4 es sobre permisos del sistema operativo
|
||||
- Refuerza **Principio 5** (humano director) — sin permisos configurados, el dev se vuelve operador de aprobación
|
||||
|
||||
**Casos reales que valida:**
|
||||
- InQ ROI: el director identificó fatiga de aprobación después de ~30 etapas. Configurar permisos eliminó la fricción sin perder gobernanza.
|
||||
|
||||
## Categoría E — Comunicación humano ↔ agente
|
||||
|
||||
### E.1 Separar lo validado de lo asumido
|
||||
**Estado:** propuesto · **Deriva de:** Principios 2 y 3
|
||||
|
||||
En los reportes del agente, marcar explícitamente qué fue verificado vs qué se asume funcionando.
|
||||
|
||||
**Ejemplo:**
|
||||
```
|
||||
Verificado:
|
||||
- Tests verdes (455/455)
|
||||
- PDF generado y abierto con pdf-parse
|
||||
- Análisis de píxeles confirma heatmap completo
|
||||
|
||||
Asumido:
|
||||
- El componente se renderiza igual en Firefox (solo testeado en Chromium)
|
||||
- La fuente Roboto está disponible en el sistema cliente (no validado)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### E.2 Hallazgos no esperados como sección explícita
|
||||
**Estado:** propuesto · **Deriva de:** Principios 3 y 5
|
||||
|
||||
Al final de cada etapa, el agente reporta "hallazgos no esperados" — cosas que descubrió durante la implementación que merecen decisión arquitectónica, aunque no formen parte del DoD.
|
||||
|
||||
**Ejemplo:** "El cambio de `--primary` propagó a focus rings de inputs (no estaba en el plan). Decisión necesaria: ¿se mantiene o se vuelve a slate?"
|
||||
|
||||
---
|
||||
|
||||
### E.3 Anti-racionalización explícita
|
||||
**Estado:** propuesto · **Deriva de:** Principio 3
|
||||
|
||||
Cuando un test revela algo raro, un cálculo no cuadra, o un output difiere de lo esperado: **investigar causa raíz, no minimizar**.
|
||||
|
||||
Patrón típico de violación: el agente dice "el cálculo es numéricamente correcto" cuando lo que está reportando es un problema de presentación (ej. "0.0 meses" es matemáticamente correcto pero un bug de UX).
|
||||
|
||||
---
|
||||
|
||||
### C.7 Control preventivo de scope — `git diff` antes de reportar
|
||||
**Estado:** propuesto · **Deriva de:** Principios 3 y 4 · **Origen:** Sprint 2 Etapa 1
|
||||
|
||||
Incluir en todo prompt de Claude Code la siguiente verificación como paso obligatorio antes de reportar:
|
||||
|
||||
```bash
|
||||
git diff --name-only HEAD
|
||||
```
|
||||
|
||||
El resultado debe contener únicamente los archivos listados como autorizados en el prompt. Si aparece un archivo no autorizado:
|
||||
1. Revertirlo: `git checkout HEAD -- <archivo>`
|
||||
2. Listarlo bajo "Conflictos de scope" en el reporte
|
||||
3. No incluirlo en el entregable de la etapa
|
||||
|
||||
**Por qué es preventivo, no detectivo:** los controles detectivos (grep de tipos, build, tests) verifican que el código resultante funciona, pero no detectan archivos modificados fuera del scope. Un agente puede tocar `ActivityPanel.tsx` y los tests seguir verdes si el cambio no rompe nada. El `git diff` captura el cambio antes de que se declare la etapa terminada.
|
||||
|
||||
**Indicador de aplicación correcta:** el reporte del agente incluye explícitamente la salida de `git diff --name-only HEAD` y todos los archivos listados son los autorizados.
|
||||
|
||||
**Indicador de violación:** el reporte no incluye la salida del git diff, o el diff contiene archivos no listados sin explicación.
|
||||
|
||||
**Relación con otros patrones:**
|
||||
- Es respuesta directa al daño documentado en **AP.9** (resolución unilateral de restricciones contradictorias).
|
||||
- Complementa **C.3** (auditoría adversarial) dándole al humano una checklist concreta de qué verificar.
|
||||
|
||||
---
|
||||
|
||||
### 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`,
|
||||
ADRs) **antes** de ejecutar. Si detecta una discrepancia entre el código nombrado en el
|
||||
prompt y su definición en la fuente única, **DETIENE la ejecución y reporta el conflicto**.
|
||||
Ejecutar "literalmente" y delegar la verificación semántica al director es AP.10.
|
||||
- ✅ **Correcto:** el reporte del agente incluye una sección "Verificación C.8" con cada
|
||||
código del prompt mapeado a su fila en la fuente única, antes de la ejecución. Si hay
|
||||
discrepancia, la ejecución no arranca hasta que el director resuelva.
|
||||
- ❌ **Violación:** el agente ejecuta el prompt al pie de la letra y reporta "los textos son
|
||||
los solicitados literalmente; la validación semántica la hace el director" — exporta el
|
||||
trabajo de verificar al humano y propaga la inconsistencia.
|
||||
|
||||
**Cómo se incorpora a un prompt:** agregar un paso "Verificación C.8" como primera tarea
|
||||
del DoD, listando los códigos a verificar y la ruta exacta de la fuente única.
|
||||
**Casos reales en Datos País:** Etapa 2D (no se aplicó — daño AP.10), Etapa 2E (se aplicó —
|
||||
detectó conflicto A1/A1b antes de ejecutar), Etapa 2F (se aplicó — sin discrepancias).
|
||||
|
||||
---
|
||||
|
||||
## Cómo agregar un patrón nuevo
|
||||
|
||||
1. Identificar el patrón durante un proyecto real (no inventarlo en abstracto).
|
||||
2. Documentarlo con: nombre, categoría, principio del que deriva, descripción, indicador de aplicación correcta, indicador de violación.
|
||||
3. Marcarlo como **propuesto** con cita del proyecto donde apareció.
|
||||
4. Después de aplicarlo en un 2do proyecto distinto, promoverlo a **estable**.
|
||||
|
||||
## Cómo retirar un patrón
|
||||
|
||||
Si un patrón no se sostiene en práctica o tiene falsos positivos frecuentes, se retira con un PR que documente:
|
||||
- Por qué no funcionó
|
||||
- Qué alternativa probamos
|
||||
- Si hay un patrón nuevo que lo reemplaza
|
||||
81
package-lock.json
generated
81
package-lock.json
generated
@@ -10,7 +10,7 @@
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.4.0",
|
||||
"@supabase/supabase-js": "^2.49.8",
|
||||
"next": "15.3.3",
|
||||
"next": "15.3.8",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.55.0",
|
||||
@@ -895,15 +895,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "15.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.3.tgz",
|
||||
"integrity": "sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==",
|
||||
"version": "15.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.8.tgz",
|
||||
"integrity": "sha512-SAfHg0g91MQVMPioeFeDjE+8UPF3j3BvHjs8ZKJAUz1BG7eMPvfCKOAgNWJ6s1MLNeP6O2InKQRTNblxPWuq+Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "15.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.3.tgz",
|
||||
"integrity": "sha512-WRJERLuH+O3oYB4yZNVahSVFmtxRNjNF1I1c34tYMoJb0Pve+7/RaLAJJizyYiFhjYNGHRAE1Ri2Fd23zgDqhg==",
|
||||
"version": "15.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.5.tgz",
|
||||
"integrity": "sha512-lM/8tilIsqBq+2nq9kbTW19vfwFve0NR7MxfkuSUbRSgXlMQoJYg+31+++XwKVSXk4uT23G2eF/7BRIKdn8t8w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -917,9 +917,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "15.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.3.tgz",
|
||||
"integrity": "sha512-XHdzH/yBc55lu78k/XwtuFR/ZXUTcflpRXcsu0nKmF45U96jt1tsOZhVrn5YH+paw66zOANpOnFQ9i6/j+UYvw==",
|
||||
"version": "15.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.5.tgz",
|
||||
"integrity": "sha512-WhwegPQJ5IfoUNZUVsI9TRAlKpjGVK0tpJTL6KeiC4cux9774NYE9Wu/iCfIkL/5J8rPAkqZpG7n+EfiAfidXA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -933,9 +933,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "15.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.3.tgz",
|
||||
"integrity": "sha512-VZ3sYL2LXB8znNGcjhocikEkag/8xiLgnvQts41tq6i+wql63SMS1Q6N8RVXHw5pEUjiof+II3HkDd7GFcgkzw==",
|
||||
"version": "15.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.5.tgz",
|
||||
"integrity": "sha512-LVD6uMOZ7XePg3KWYdGuzuvVboxujGjbcuP2jsPAN3MnLdLoZUXKRc6ixxfs03RH7qBdEHCZjyLP/jBdCJVRJQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -952,9 +952,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "15.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.3.tgz",
|
||||
"integrity": "sha512-h6Y1fLU4RWAp1HPNJWDYBQ+e3G7sLckyBXhmH9ajn8l/RSMnhbuPBV/fXmy3muMcVwoJdHL+UtzRzs0nXOf9SA==",
|
||||
"version": "15.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.5.tgz",
|
||||
"integrity": "sha512-k8aVScYZ++BnS2P69ClK7v4nOu702jcF9AIHKu6llhHEtBSmM2zkPGl9yoqbSU/657IIIb0QHpdxEr0iW9z53A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -971,9 +971,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "15.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.3.tgz",
|
||||
"integrity": "sha512-jJ8HRiF3N8Zw6hGlytCj5BiHyG/K+fnTKVDEKvUCyiQ/0r5tgwO7OgaRiOjjRoIx2vwLR+Rz8hQoPrnmFbJdfw==",
|
||||
"version": "15.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.5.tgz",
|
||||
"integrity": "sha512-2xYU0DI9DGN/bAHzVwADid22ba5d/xrbrQlr2U+/Q5WkFUzeL0TDR963BdrtLS/4bMmKZGptLeg6282H/S2i8A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -990,9 +990,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "15.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.3.tgz",
|
||||
"integrity": "sha512-HrUcTr4N+RgiiGn3jjeT6Oo208UT/7BuTr7K0mdKRBtTbT4v9zJqCDKO97DUqqoBK1qyzP1RwvrWTvU6EPh/Cw==",
|
||||
"version": "15.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.5.tgz",
|
||||
"integrity": "sha512-TRYIqAGf1KCbuAB0gjhdn5Ytd8fV+wJSM2Nh2is/xEqR8PZHxfQuaiNhoF50XfY90sNpaRMaGhF6E+qjV1b9Tg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1009,9 +1009,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "15.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.3.tgz",
|
||||
"integrity": "sha512-SxorONgi6K7ZUysMtRF3mIeHC5aA3IQLmKFQzU0OuhuUYwpOBc1ypaLJLP5Bf3M9k53KUUUj4vTPwzGvl/NwlQ==",
|
||||
"version": "15.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.5.tgz",
|
||||
"integrity": "sha512-h04/7iMEUSMY6fDGCvdanKqlO1qYvzNxntZlCzfE8i5P0uqzVQWQquU1TIhlz0VqGQGXLrFDuTJVONpqGqjGKQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1025,9 +1025,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "15.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.3.tgz",
|
||||
"integrity": "sha512-4QZG6F8enl9/S2+yIiOiju0iCTFd93d8VC1q9LZS4p/Xuk81W2QDjCFeoogmrWWkAD59z8ZxepBQap2dKS5ruw==",
|
||||
"version": "15.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.5.tgz",
|
||||
"integrity": "sha512-5fhH6fccXxnX2KhllnGhkYMndhOiLOLEiVGYjP2nizqeGWkN10sA9taATlXwake2E2XMvYZjjz0Uj7T0y+z1yw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2808,13 +2808,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "15.3.3",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.3.3.tgz",
|
||||
"integrity": "sha512-JqNj29hHNmCLtNvd090SyRbXJiivQ+58XjCcrC50Crb5g5u2zi7Y2YivbsEfzk6AtVI80akdOQbaMZwWB1Hthw==",
|
||||
"deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details.",
|
||||
"version": "15.3.8",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.3.8.tgz",
|
||||
"integrity": "sha512-L+4c5Hlr84fuaNADZbB9+ceRX9/CzwxJ+obXIGHupboB/Q1OLbSUapFs4bO8hnS/E6zV/JDX7sG1QpKVR2bguA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "15.3.3",
|
||||
"@next/env": "15.3.8",
|
||||
"@swc/counter": "0.1.3",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"busboy": "1.6.0",
|
||||
@@ -2829,14 +2828,14 @@
|
||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "15.3.3",
|
||||
"@next/swc-darwin-x64": "15.3.3",
|
||||
"@next/swc-linux-arm64-gnu": "15.3.3",
|
||||
"@next/swc-linux-arm64-musl": "15.3.3",
|
||||
"@next/swc-linux-x64-gnu": "15.3.3",
|
||||
"@next/swc-linux-x64-musl": "15.3.3",
|
||||
"@next/swc-win32-arm64-msvc": "15.3.3",
|
||||
"@next/swc-win32-x64-msvc": "15.3.3",
|
||||
"@next/swc-darwin-arm64": "15.3.5",
|
||||
"@next/swc-darwin-x64": "15.3.5",
|
||||
"@next/swc-linux-arm64-gnu": "15.3.5",
|
||||
"@next/swc-linux-arm64-musl": "15.3.5",
|
||||
"@next/swc-linux-x64-gnu": "15.3.5",
|
||||
"@next/swc-linux-x64-musl": "15.3.5",
|
||||
"@next/swc-win32-arm64-msvc": "15.3.5",
|
||||
"@next/swc-win32-x64-msvc": "15.3.5",
|
||||
"sharp": "^0.34.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.4.0",
|
||||
"@supabase/supabase-js": "^2.49.8",
|
||||
"next": "15.3.3",
|
||||
"next": "15.3.8",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.55.0",
|
||||
|
||||
124
sprints/sprint-3-instrumento/ETAPA_3ALL_PROMPT.md
Normal file
124
sprints/sprint-3-instrumento/ETAPA_3ALL_PROMPT.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# ETAPA 3ALL — Flag p_all en el dashboard (vista agregada de todos los surveys)
|
||||
|
||||
## 1. Objetivo
|
||||
Permitir que el dashboard muestre datos de TODOS los surveys mezclados, usando un
|
||||
UUID especial como flag. Cambio mínimo — solo toca `_dash_responses` y el frontend.
|
||||
Las 11 RPCs no se modifican.
|
||||
|
||||
---
|
||||
|
||||
## 2. Contexto previo
|
||||
- `_dash_responses(p_survey_id uuid, ...)` es la función base que usan las 11 RPCs.
|
||||
- Todas las RPCs pasan `p_survey_id` a `_dash_responses`.
|
||||
- El dashboard lee `?survey_id=UUID` de la URL y lo pasa a `/api/dashboard/[widget]`.
|
||||
- `route.ts` requiere `survey_id` en la query string (400 si falta).
|
||||
|
||||
## 3. DECISIONES YA TOMADAS — no consultar de nuevo
|
||||
- UUID especial para "todos": `00000000-0000-0000-0000-000000000000` (UUID cero).
|
||||
No puede existir como survey real. Es el flag `p_all`.
|
||||
- Implementación en `_dash_responses`: una sola línea cambia.
|
||||
- Las 11 RPCs NO se tocan — el cambio es transparente para ellas.
|
||||
- El cambio va en una NUEVA migración, no modifica las existentes.
|
||||
- El k-anonimato sigue aplicando — el pool más grande solo beneficia la protección.
|
||||
|
||||
---
|
||||
|
||||
## 4. Cambios requeridos
|
||||
|
||||
### 4.1 Nueva migración SQL
|
||||
Crear `supabase/migrations/20260611000001_dash_responses_all.sql`:
|
||||
|
||||
```sql
|
||||
-- Migración: agregar flag p_all a _dash_responses
|
||||
-- UUID especial '00000000-0000-0000-0000-000000000000' = todos los surveys
|
||||
CREATE OR REPLACE FUNCTION public._dash_responses(
|
||||
p_survey_id uuid,
|
||||
p_depto text DEFAULT NULL,
|
||||
p_genero text[] DEFAULT NULL,
|
||||
p_etario text[] DEFAULT NULL,
|
||||
p_modalidad text[] DEFAULT NULL
|
||||
)
|
||||
RETURNS TABLE(response_id uuid)
|
||||
LANGUAGE sql STABLE SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
SELECT r.id
|
||||
FROM public.responses r
|
||||
WHERE (p_survey_id = '00000000-0000-0000-0000-000000000000'
|
||||
OR r.survey_id = p_survey_id)
|
||||
AND (p_depto IS NULL OR r.qi_zone = p_depto)
|
||||
AND (p_genero IS NULL OR cardinality(p_genero) = 0 OR EXISTS (
|
||||
SELECT 1 FROM public.answers a
|
||||
JOIN public.questions q ON q.id = a.question_id
|
||||
WHERE a.response_id = r.id AND q.code = 'A6'
|
||||
AND a.value #>> '{}' = ANY(p_genero)
|
||||
))
|
||||
AND (p_etario IS NULL OR cardinality(p_etario) = 0
|
||||
OR r.qi_age_bucket = ANY(p_etario))
|
||||
AND (p_modalidad IS NULL OR cardinality(p_modalidad) = 0 OR EXISTS (
|
||||
SELECT 1 FROM public.answers a
|
||||
JOIN public.questions q ON q.id = a.question_id
|
||||
WHERE a.response_id = r.id AND q.code = 'A7'
|
||||
AND a.value #>> '{}' = ANY(p_modalidad)
|
||||
));
|
||||
$$;
|
||||
```
|
||||
|
||||
### 4.2 route.ts — permitir el UUID especial
|
||||
En `app/api/dashboard/[widget]/route.ts`, donde se valida `survey_id`:
|
||||
El UUID cero debe pasar la validación existente — verificar si hay algún check
|
||||
adicional que lo rechazaría (ej. validación de formato UUID o lookup en DB).
|
||||
Si `route.ts` solo verifica presencia del parámetro (400 si falta), no hay
|
||||
nada que cambiar. Reportar cuál es el caso.
|
||||
|
||||
### 4.3 dashboard/page.tsx — agregar link/selector "Todos"
|
||||
Agregar en el header del dashboard un selector o links de navegación mínimo:
|
||||
|
||||
```
|
||||
[ Todos ] [ InQuality ] [ Fundación Solidaridad ] [ Demo ]
|
||||
```
|
||||
|
||||
Implementación sugerida: links `<a>` simples que navegan a:
|
||||
- Todos: `/dashboard?survey_id=00000000-0000-0000-0000-000000000000`
|
||||
- InQuality: `/dashboard?survey_id=30000000-0000-0000-0000-000000000002`
|
||||
- Fundación Solidaridad: `/dashboard?survey_id=30000000-0000-0000-0000-000000000003`
|
||||
- Demo: `/dashboard?survey_id=30000000-0000-0000-0000-000000000001`
|
||||
|
||||
El survey activo se destaca visualmente (comparar `surveyId` del searchParam
|
||||
con el UUID de cada link). Estilo `.admin-*` no aplica acá — usar clases
|
||||
`.db-*` o tokens CSS existentes del dashboard.
|
||||
|
||||
El label de cada opción puede ser hardcodeado por ahora — no hacer fetch
|
||||
a `surveys` para obtener los nombres (Nivel 3, fuera de scope).
|
||||
|
||||
---
|
||||
|
||||
## 5. Entregables (DoD)
|
||||
|
||||
- [ ] Nueva migración `20260611000001_dash_responses_all.sql` creada.
|
||||
- [ ] `_dash_responses` actualizada con el flag de UUID cero.
|
||||
- [ ] `route.ts` permite el UUID especial (verificar y reportar).
|
||||
- [ ] Dashboard muestra selector de empresa con "Todos" + las 3 empresas.
|
||||
- [ ] Build exitoso.
|
||||
- [ ] `git diff --name-only HEAD` en scope.
|
||||
|
||||
## 6. Qué reportar
|
||||
```
|
||||
Verificado:
|
||||
- Migración creada ✓
|
||||
- route.ts: [necesitó cambio / no necesitó cambio — explicar]
|
||||
- Selector visible en dashboard ✓
|
||||
- Build exitoso ✓
|
||||
- Diff en scope ✓
|
||||
|
||||
URL de validación:
|
||||
- Todos: /dashboard?survey_id=00000000-0000-0000-0000-000000000000
|
||||
- InQuality: /dashboard?survey_id=30000000-0000-0000-0000-000000000002
|
||||
```
|
||||
|
||||
## 7. Qué NO hacer
|
||||
- ❌ No modificar las migraciones existentes — solo agregar una nueva.
|
||||
- ❌ No tocar las 11 RPCs directamente.
|
||||
- ❌ No hacer fetch a `surveys` para obtener nombres de empresa.
|
||||
- ❌ No agregar dependencias npm.
|
||||
- ❌ No tocar el formulario ni `/admin/responses`.
|
||||
33
supabase/migrations/20260611000001_dash_responses_all.sql
Normal file
33
supabase/migrations/20260611000001_dash_responses_all.sql
Normal file
@@ -0,0 +1,33 @@
|
||||
-- Migración: agregar flag p_all a _dash_responses
|
||||
-- UUID especial '00000000-0000-0000-0000-000000000000' = todos los surveys
|
||||
CREATE OR REPLACE FUNCTION public._dash_responses(
|
||||
p_survey_id uuid,
|
||||
p_depto text DEFAULT NULL,
|
||||
p_genero text[] DEFAULT NULL,
|
||||
p_etario text[] DEFAULT NULL,
|
||||
p_modalidad text[] DEFAULT NULL
|
||||
)
|
||||
RETURNS TABLE(response_id uuid)
|
||||
LANGUAGE sql STABLE SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
SELECT r.id
|
||||
FROM public.responses r
|
||||
WHERE (p_survey_id = '00000000-0000-0000-0000-000000000000'
|
||||
OR r.survey_id = p_survey_id)
|
||||
AND (p_depto IS NULL OR r.qi_zone = p_depto)
|
||||
AND (p_genero IS NULL OR cardinality(p_genero) = 0 OR EXISTS (
|
||||
SELECT 1 FROM public.answers a
|
||||
JOIN public.questions q ON q.id = a.question_id
|
||||
WHERE a.response_id = r.id AND q.code = 'A6'
|
||||
AND a.value #>> '{}' = ANY(p_genero)
|
||||
))
|
||||
AND (p_etario IS NULL OR cardinality(p_etario) = 0
|
||||
OR r.qi_age_bucket = ANY(p_etario))
|
||||
AND (p_modalidad IS NULL OR cardinality(p_modalidad) = 0 OR EXISTS (
|
||||
SELECT 1 FROM public.answers a
|
||||
JOIN public.questions q ON q.id = a.question_id
|
||||
WHERE a.response_id = r.id AND q.code = 'A7'
|
||||
AND a.value #>> '{}' = ANY(p_modalidad)
|
||||
));
|
||||
$$;
|
||||
1150
supabase/seed.sql
1150
supabase/seed.sql
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user