geo_paraguay_full.json se cargaba dos veces sin cache compartido.
Módulo lib/geo-cache.ts singleton: un fetch para toda la sesión.
Feedback visual mientras carga (disabled + placeholder) y si falla
(mensaje + texto libre). debounce 500ms en saveDraft para no escribir
en localStorage en cada keystroke.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
P1: color de texto teal -> #494F4F en 5 clases del wizard (WCAG AA).
P2: try/catch en handleSubmit - error de red muestra estado error del wizard.
P3: flex-wrap + column-reverse en bottombar <=480px.
P4: onTouchEnd fallback en autocomplete barrio y ciudad.
- CiudadSelectField: autocomplete sobre Object.keys(geoData[depto]),
mismo patrón que BarrioAutocompleteField (position:fixed, getBoundingClientRect,
scroll listener, teclado ARIA). Reutiliza clases CSS .barrio-autocomplete-*.
- Limpia el valor de 2.2 via useEffect cuando cambia departamento (2.1),
usando un ref estable para onChange para evitar dep cíclica.
- QuestionRenderer: branch code==='2.2' dentro del bloque single_choice;
pasa answers['2.1:default'] como prop departamento.
- Sin cambios al seed, form-engine ni RPCs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Issue A: barrio-autocomplete-list cambia a position:fixed + getBoundingClientRect()
para escapar el overflow-y:auto de wizard-answer-area que clippeaba el dropdown.
z-index: 20 → 30 (clearear sticky topbar/bottombar z-index:10 en stacking context raíz).
Listener de scroll cierra el dropdown si el contenedor scrollea.
Issue B: wizard-answer-area max-height 360px → 480px (desktop) / 320px → 400px (mobile)
para mostrar ~8 opciones completas. .wizard-answer-area.has-overflow { padding-bottom: 44px }
asegura que el fade ::after (28px) cubre espacio vacío, no texto de opción.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RPC transaccional SECURITY DEFINER (rpc_submit_response) como único camino
de escritura. Dropea las policies WITH CHECK(true): cierra la falla donde
anon podía insertar cualquier cosa. Validación server-side (7 checks)
reusando el motor. Persistencia atómica con instance_id por instancia.
Verificado contra Postgres real: instancias separadas, atomicidad, RLS.
Riesgo residual de bypass por REST documentado (SEC-004).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
applyAnswer y isRequiredAndUnanswered pasan de wizard-form.tsx al motor
como funciones puras — eran lógica de flujo en el presentador (viola ADR-005).
Limpieza de respuestas huérfanas al remover un vínculo y validación required
por instancia, ahora testeables. 20/20 tests. Verificado end-to-end con navegador.
4E sin construcción pendiente: el mini-wizard es propiedad emergente del motor.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Al responder "No" en las tres, el formulario salta a 6.4 (no muestra secciones 2-5 ni 6.1-6.3)
La pregunta 6.6 aparece antes del cierre
Las preguntas multiple_choice muestran el hint "Podés marcar más de una opción"