refactor(engine): mover limpieza de instancias y validación required al motor [4E]

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>
This commit is contained in:
markosbenitez
2026-06-23 22:57:42 -03:00
parent ce679ec1c3
commit 61676e1423
3 changed files with 141 additions and 16 deletions

View File

@@ -4,12 +4,13 @@ import type { BrandConfig, Survey } from '@/lib/types'
import type { AnswerValue, FormAnswers, FormSchema, Screen } from '@/lib/form-engine.types'
import {
answerKey,
applyAnswer,
expandInstances,
initialState,
isRequiredAndUnanswered,
next,
back,
resolveScreens,
setAnswer,
} from '@/lib/form-engine'
import { loadDraft, saveDraft } from '@/lib/storage'
import { ConsentScreen } from './consent-screen'
@@ -137,20 +138,7 @@ export function WizardForm({ survey, schema }: WizardFormProps) {
const code = screen.question.code
const instanceId = screen.instanceId ?? undefined
setState((prev) => {
const updated = setAnswer(prev, code, value, instanceId)
// Limpia respuestas de pantallas que dejaron de ser visibles tras este cambio.
const before = resolveScreens(schema, prev.answers)
const after = resolveScreens(schema, updated.answers)
const afterIds = new Set(after.map((s) => s.id))
const disappeared = before.filter((s) => !afterIds.has(s.id) && s.question)
if (disappeared.length === 0) return updated
const clearedAnswers = { ...updated.answers }
for (const s of disappeared) {
delete clearedAnswers[answerKey(s.question!.code, s.instanceId ?? undefined)]
}
return { ...updated, answers: clearedAnswers }
})
setState((prev) => applyAnswer(prev, schema, code, value, instanceId))
setErrors((prev) => {
if (!prev[code]) return prev
@@ -160,7 +148,7 @@ export function WizardForm({ survey, schema }: WizardFormProps) {
}
function handleNext() {
if (screen.kind === 'question' && screen.question?.required && isEmpty(currentValue)) {
if (isRequiredAndUnanswered(screen, state.answers)) {
setErrors((e) => ({ ...e, [screen.question!.code]: 'Este campo es obligatorio.' }))
return
}