From 7cccce5866df1756fc3019b5ae2776ec168bd135 Mon Sep 17 00:00:00 2001 From: markosbenitez Date: Tue, 2 Jun 2026 08:24:50 -0300 Subject: [PATCH] Sprint 1 cerrado: seed real (34 preguntas) + REVOKE consent SELECT + validado en Cloud --- .DS_Store | Bin 0 -> 8196 bytes .gitignore | 7 +- DATA_MODEL.md | 15 + package-lock.json | 147 ++++ sprints/sprint-1/ETAPA_1C_PROMPT.md | 82 ++ supabase/.DS_Store | Bin 0 -> 6148 bytes .../20260602111851_revoke_consent_select.sql | 5 + supabase/seed.sql | 828 +++++++++++++----- 8 files changed, 864 insertions(+), 220 deletions(-) create mode 100644 .DS_Store create mode 100644 package-lock.json create mode 100644 sprints/sprint-1/ETAPA_1C_PROMPT.md create mode 100644 supabase/.DS_Store create mode 100644 supabase/migrations/20260602111851_revoke_consent_select.sql diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..be6a6c2d0f9bb8aef8a98a4e3b43fa51291018f8 GIT binary patch literal 8196 zcmeHMYitx%6uxI#;Ep&jeEZl?FGqp$ojkv@}m)zW_ z)!{_wfzSh?2SN{o9tb^fEqDOmY*y$P?>^^-ZRml}1OH18u=_#9%4jU0WlsI80~
}cX{K%DeF0N9aLJB-)7G6*r&F+X z*OyIj1{JB8lBXX!)Y{g*vPE6d+BV#x9%^3_ZBg5nE*~Bi>6FOg)xD{GR@Qcoaa$m` z4~SG{+rw8f%yNryZdgsaV%XOe!^9gV-Y88{k^{-XA!%nyt}mhuJvq;GYJwVOa_Dpy3W`2l|Wy9_ur~Oy04qt&VFl zjRQ$5- zQ8AfuvRO3J<#Tam9hE<#yV%BeXs*|jGwjMI#g%SK2i?6|-smY|XjUXPQ|g~D&NGp9 zy6HK2?u};UPD%%uvJPEGJ@Sm1(-VD)g2!XpMH7L5N6PUapQV&WVz=_ciB!%g@~&R? z`@W;aNGuv~D49*#$a=B`!IvdR$V22fIYCa5_sCiDIr)zKO#UF}VLVKN2AB%dpb6%{ z0$2>~unN||T38P~uoZeC0lUG3yI~)cpbYoH{qO)Bg~#CucoLq1XW#`m0WZQ!a1!2x zQ}7nN4QJqE_yj(MFX0>b7QTlc;5RrA7vOI}6zYWrp;4G8EEb|dtFT(=68eP!VNe(n zb_j-G3nk&OFw89wDhS*aSQHT9LF|mWS`dLbjzHm^F%YP3=-#k#leGDIMBrDGIp(HL zYnn4}VRTvh?VT4RlLvAob}z+*4};6VKMb+2 g;!-9P3uu{BoWb&+{}Ax6{skVs|Ka<8eZ2qw09r_xQUCw| literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore index 9ac8057..3a19ab7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .env .env.* .env.local +!.env.example # Claude Code — settings locales (allow personales del usuario) .claude/settings.local.json @@ -12,4 +13,8 @@ supabase/.temp/ # Node node_modules/ -package-lock.json + +# Supabase — claves y credenciales +*.key +service_role* +.env.production \ No newline at end of file diff --git a/DATA_MODEL.md b/DATA_MODEL.md index b5d2bbd..002fbaa 100644 --- a/DATA_MODEL.md +++ b/DATA_MODEL.md @@ -150,3 +150,18 @@ granted_at timestamptz not null default now() - `agg_segment(survey_id, dimensions[])` → agregación que **suprime** automáticamente celdas con `count < k_threshold` (devuelve "n insuficiente" en lugar del valor). - Un trigger/constraint que rechaza `respondent_id` no nulo cuando `is_anonymous = true`. + +--- + +## Decisiones ratificadas en ejecución (Sprint 1) +- **`options` en preguntas `rating` (F3, G4):** se guarda como objeto + `{"min":1,"max":5,"min_label":"...","max_label":"..."}`, NO como array. El frontend (Sprint 2) + debe manejar dos formas de `options`: array para `single_choice`/`multiple_choice`, objeto para + `rating`. +- **`required = false` en todas las preguntas seeded:** la obligatoriedad real se define en Sprint 2 + (validación del formulario). Nota: B1 estaba marcada obligatoria en el mockup; revisar al construir + el formulario. +- **Policies de `org_admin` presentes pero inertes en v1:** las policies RLS para `org_admin` + (acceso multi-tenant por empresa) existen en migración 003, pero v1 NO tiene login de cliente + ("nosotros presentamos a cada empresa"). El acceso real lo hace el analyst vía `service_role`. El + camino `org_admin` queda reservado para un futuro de auto-servicio, no operado en v1. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f299f93 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,147 @@ +{ + "name": "Motor de encuestas", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "supabase": "^2.102.0" + } + }, + "node_modules/@supabase/cli-darwin-arm64": { + "version": "2.102.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-darwin-arm64/-/cli-darwin-arm64-2.102.0.tgz", + "integrity": "sha512-DzQetP1iOnxAmbarZJLoFr3yMtyDrfOC/T2aNpYEASykFGnvCb87CbWMQhjp9zDdA0pFfDYgzM5yESg7cC/anw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@supabase/cli-darwin-x64": { + "version": "2.102.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-darwin-x64/-/cli-darwin-x64-2.102.0.tgz", + "integrity": "sha512-jJxyULNvgBuWxDrjVySwIB76IUlWOf5lYd/2Zz/Lrop8r1sr7aKJFULz7l1A1vk09ImbvnbkM9IBtH8rNdmBUg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@supabase/cli-linux-arm64": { + "version": "2.102.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-linux-arm64/-/cli-linux-arm64-2.102.0.tgz", + "integrity": "sha512-vOEdz4mWoqSpygXliGysYsDobtTlZLMhC2KpDsoYio9vQuQPRgaK2kW4fIGPKfagR4Odv3PycgjoNfdsg7TYUQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@supabase/cli-linux-arm64-musl": { + "version": "2.102.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.102.0.tgz", + "integrity": "sha512-VCEJiXK4JhDRXPR32UcIijKdsVT0zg81EoIt6ydmNDJ9vfPI+eFRIzJpeEST1XobTMAVzbJEBw/b/zq5BywR5w==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@supabase/cli-linux-x64": { + "version": "2.102.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-linux-x64/-/cli-linux-x64-2.102.0.tgz", + "integrity": "sha512-Jcekp6NOWlLuj8vs/ToR11bbqGWhlijs0rmMRyQvDRocWFgeDWX5IBWZwCXZX2xxr7pICJaSdRZx6V8uW2XVEQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@supabase/cli-linux-x64-musl": { + "version": "2.102.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-linux-x64-musl/-/cli-linux-x64-musl-2.102.0.tgz", + "integrity": "sha512-KFzdivlbk8rnMHebxFa//AlQE3uxCWF6ZNIWkYUjE+1EBVXJr/j2Mr1SxkJJyt9h/8jztBUUPJgzuRzIJW0GyA==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@supabase/cli-windows-arm64": { + "version": "2.102.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-windows-arm64/-/cli-windows-arm64-2.102.0.tgz", + "integrity": "sha512-yY3qbHhMtQFI2TBXbU611UI7b0TaShxGwbunMUwmTUyaCLDTSsxOjxwbpCyfvgY7eDEeYI0WnkbBKHKxknKVDQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@supabase/cli-windows-x64": { + "version": "2.102.0", + "resolved": "https://registry.npmjs.org/@supabase/cli-windows-x64/-/cli-windows-x64-2.102.0.tgz", + "integrity": "sha512-PpIBXxGRi3bk0Zj2JeDCsr2edDt2qd5j31aaixuxHSsPJh0A052kDEDw4jSOWFHBL1UEai6rGF7zWuX/F3gNiw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/supabase": { + "version": "2.102.0", + "resolved": "https://registry.npmjs.org/supabase/-/supabase-2.102.0.tgz", + "integrity": "sha512-PMoWgoQOb1/mZ3Y+e/HIIvWmEfui20cWELXYURrb6s66Swvo1jnkBmRvtCtSA8lan+aHtkgVQAyekocugle2eg==", + "license": "MIT", + "bin": { + "supabase": "dist/supabase.js" + }, + "optionalDependencies": { + "@supabase/cli-darwin-arm64": "2.102.0", + "@supabase/cli-darwin-x64": "2.102.0", + "@supabase/cli-linux-arm64": "2.102.0", + "@supabase/cli-linux-arm64-musl": "2.102.0", + "@supabase/cli-linux-x64": "2.102.0", + "@supabase/cli-linux-x64-musl": "2.102.0", + "@supabase/cli-windows-arm64": "2.102.0", + "@supabase/cli-windows-x64": "2.102.0" + } + } + } +} diff --git a/sprints/sprint-1/ETAPA_1C_PROMPT.md b/sprints/sprint-1/ETAPA_1C_PROMPT.md new file mode 100644 index 0000000..5a3720b --- /dev/null +++ b/sprints/sprint-1/ETAPA_1C_PROMPT.md @@ -0,0 +1,82 @@ +# ETAPA 1C — Cierre de la fundación: REVOKE del GRANT peligroso + documentación + +> Prompt versionado (Patrón B.1). Cierra la auditoría de la Etapa 1B con el único cambio bloqueante +> que encontró y documenta tres decisiones que el agente tomó por iniciativa. Claude Code lo ejecuta +> tras leer `CLAUDE.md`, `DATA_MODEL.md` y este prompt. + +## 1. Objetivo +Quitar el `GRANT SELECT ON consent_records TO authenticated` (puerta apoyada-sin-llave: hoy inerte, +peligrosa si alguien suma una policy SELECT a futuro), **confirmar en ejecución que no rompe el flujo +de envío de la encuesta** (la FK `responses.consent_record_id`), y documentar en `DATA_MODEL.md` tres +decisiones tomadas en la 1B. + +## 2. Contexto previo +La 1B aplicó las migraciones 001-006 a Supabase Cloud y la auditoría aprobó la fundación de datos +salvo este punto. La migración **006 ya está aplicada en Cloud** → **NO se edita** (rompería el +historial de migraciones). El cambio va en una migración nueva, 007. + +## 3. Decisiones ya tomadas (no consultar de nuevo) +- El cambio es un `REVOKE`, en migración **nueva** 007. NO editar 001-006. +- El `GRANT INSERT ON consent_records TO anon, authenticated` **se mantiene** (el envío del formulario + lo necesita). Solo se revoca el SELECT. +- NO se agrega ninguna policy SELECT a `consent_records`. El analyst lee vía `service_role` (que + bypassa RLS y no necesita GRANTs). +- No se toca el resto del esquema, ni las policies, ni `service_role`. + +## 4. Tareas +1. Crear la migración con `supabase migration new revoke_consent_select` y poner como contenido: + ```sql + -- 007: Revoca el SELECT sobre consent_records a authenticated. + -- Auditoría 1B: GRANT inerte hoy (no hay policy SELECT) pero peligroso si a futuro alguien + -- agrega una policy SELECT para org_admin → expondría quién consintió qué, vector de + -- correlación con respuestas en encuesta anónima. El analyst lee vía service_role. + REVOKE SELECT ON public.consent_records FROM authenticated; + ``` +2. Aplicar a Cloud: `supabase db push` (usar `npx supabase ...` si el CLI no es global). +3. Correr las validaciones de la sección 6 contra la base real. +4. Documentar en `DATA_MODEL.md` las tres decisiones (texto exacto en la sección 5 de este prompt). + +## 5. Texto EXACTO a agregar al final de `DATA_MODEL.md` (sección nueva) +```markdown +## Decisiones ratificadas en ejecución (Sprint 1) +- **`options` en preguntas `rating` (F3, G4):** se guarda como objeto + `{"min":1,"max":5,"min_label":"...","max_label":"..."}`, NO como array. El frontend (Sprint 2) + debe manejar dos formas de `options`: array para `single_choice`/`multiple_choice`, objeto para + `rating`. +- **`required = false` en todas las preguntas seeded:** la obligatoriedad real se define en Sprint 2 + (validación del formulario). Nota: B1 estaba marcada obligatoria en el mockup; revisar al construir + el formulario. +- **Policies de `org_admin` presentes pero inertes en v1:** las policies RLS para `org_admin` + (acceso multi-tenant por empresa) existen en migración 003, pero v1 NO tiene login de cliente + ("nosotros presentamos a cada empresa"). El acceso real lo hace el analyst vía `service_role`. El + camino `org_admin` queda reservado para un futuro de auto-servicio, no operado en v1. +``` + +## 6. Validaciones automatizadas mandatorias (contra la base Cloud) +La central es la #1 — confirma que revocar el SELECT NO rompe el envío: +- **FK intacta:** simulando rol `anon` (SET ROLE anon + claims, o vía anon key), `INSERT` en + `consent_records` → obtener su id → `INSERT` en `responses` referenciando ese `consent_record_id` + (con `survey_id` de una encuesta `status='live'` y `respondent_id` NULL) → **debe insertar sin + error**. Esto prueba que la verificación de FK no requiere SELECT sobre `consent_records`. +- **REVOKE efectivo:** como `authenticated`, `SELECT FROM consent_records` → ahora "permission + denied" (ya no "0 filas"). Confirma que el revoke aplicó. +- **Sin regresión:** re-correr las claves de la 1B → RLS sobre `responses`/`answers` = 0 filas para + org_admin; `agg_segment` n=3 → suprimido, n=5 → valor; inmutabilidad de `consent_records` (UPDATE/ + DELETE → error) sigue vigente. +- Si CUALQUIERA falla: **NO declarar lista la etapa.** Reportar y esperar. En especial, si la #1 + fallara (la FK SÍ necesitaba el SELECT), **detenerse y reportar** — sería el caso que invalida el + cambio. + +## 7. Qué reportar +- **Verificado:** salida concreta de cada validación (el INSERT anónimo que efectivamente insertó, el + "permission denied" del SELECT como authenticated, y las 3 de no-regresión). +- **Asumido / Hallazgos / Decisiones por iniciativa** (clasificadas por nivel). +- **`git diff --name-only HEAD`:** debe contener solo la migración 007 nueva + `DATA_MODEL.md`. + +## 8. Qué NO hacer +- NO editar las migraciones 001-006 (006 ya está en Cloud). +- NO agregar policy SELECT a `consent_records`. +- NO tocar `service_role`, ni las policies de org_admin, ni el resto del esquema. +- NO usar `respondent_id` ni exponer texto libre. +- Ante conflicto entre una restricción y "que aplique limpio": **detenerse y reportar**, no resolver + unilateralmente. \ No newline at end of file diff --git a/supabase/.DS_Store b/supabase/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e07a13b1f1f8be8b5ec1d8cfd08672b12407bc65 GIT binary patch literal 6148 zcmeHK%}T>S5T0$Tri##mf_Pf+TCw#HiGPdXzNYatZ+8v*1HgMt*Y znP@irM+Ru`mf;b1tq%kEvVUQbJ%x!1(f<}cC2^LwTJNG#tt~7rIZMupbL-!$!q0jWV6<8;4VO>fOfX zXw=?6=rrZ_-oeqhDMy{%R#Wcp9gfF!XMJnu_`Lg&Jf`Z!P%7~6)3R!D0