ADR-028 — Completar y corregir datos del cliente desde la landing¶
- Status: accepted (2026-08-23; feature nueva pedida por el operador — el cliente que vuelve debe poder sumar/corregir sus datos)
- Context: Hoy el caso E (cliente ya registrado que vuelve) es una confirmación de un toque
(
Pantalla2Confirmar.tsx), sin editar nada. Dos huecos reales: (1) si el cliente no cargó email, no puede sumarlo nunca desde la landing; (2) si tipeó mal el celular, no puede corregirlo — y el celular es el canal para contactar al ganador: un celular mal cargado = premio no entregable. Corregir datos es sensible: el teléfono puede ser compartido, así que no cualquiera que llegue con un voucher debe ver/editar los datos de otro. Sub-caso crítico (origen de los datos): el alta puede ser por escaneo del DNI (los datos vienen del documento) o manual (la persona los tipea — la carga manual siempre está disponible, AC-22). Si el alta fue manual,nombre/apellido/sexo/fechason tan falibles como el email: un "Gonzáles" por "González" tipeado a mano no puede quedar trabado para siempre. Hoy el backend no sabe si el alta fue por escaneo o a mano — recibe el mismo payload en ambos casos. - Alternatives:
- No tocar nada (statu quo): el cliente nunca completa/corrige desde la landing; todo pasa por el operador en el gestor. Simple, pero un celular mal cargado queda mal para siempre salvo que el operador lo note (y no hay momento en que el cliente pida corregirlo).
- Mostrar y editar todo siempre: flexible, pero expone datos en un celular potencialmente compartido y permite pisar el contacto de otra persona. Rechazado.
- Editar gated por presentación del DNI, y con la identidad gobernada por el origen de los datos (elegido): el DNI es la identidad/auth del sistema. Quien presentó el DNI (escaneo o carga manual) puede ver/corregir lo suyo; el reconocimiento por dispositivo (ADR-027) no alcanza para editar. Los campos de identidad son de solo lectura solo cuando vinieron del escaneo (documento = verdad); si el alta fue manual, son corregibles.
- Decision:
- Agregar opcional vacío: el confirmar de caso E sigue siendo un toque. Debajo, un link
discreto "Agregar {opcional}" aparece solo si el opcional está vacío (hoy el único
opcional es
email; el celular es obligatorio en el alta, AC-21). Campo inline, opcional, se puede confirmar sin llenarlo. El label dice "Agregar" (no "Modificar"): solo suma lo que falta, sin exponer lo existente. - Corregir dato existente: exige haber presentado el DNI en la sesión.
- Path DNI (caso E del flujo actual — llegó ingresando/escaneando el DNI): aparece "Revisá tus datos" con los campos editables, enmascarados hasta tocar "editar".
- Path reconocimiento (ADR-027 — reconocido sin pedir DNI): solo confirmar de un toque; para corregir, "Revisar mis datos" pide el DNI primero. En un celular compartido, nadie edita los datos de otro sin tener su DNI en la mano.
-
Origen de los datos (nuevo
datos_origenencliente): la landing YA sabe si escaneó (estadodatosEscaneadosenPantalla1bAlta); manda un flag en el alta y el backend persistedatos_origen ∈ { escaneo, manual }. Gobierna la editabilidad de la identidad:Campo datos_origen = escaneodatos_origen = manualnombre/apellido/sexo/fecha_nacimientosolo lectura (documento = verdad; para cambiar, re-escanear) editable con DNI presentado email/celulareditable con DNI presentado editable con DNI presentado -
Re-escaneo corrige y verifica: un cliente
manualque escanea el DNI corrige sus datos de identidad Y sube sudatos_origenaescaneo(queda verificado por documento → identidad pasa a solo lectura). No se obliga (por eso existe la carga manual), pero es el mejor camino cuando hay cámara. - Transacción: un solo submit. "Confirmar y participar" guarda los datos corregidos y
suma la chance en una transacción. Una edición NO permitida (identidad escaneada) se rechaza
antes de consumir la chance (no gasta el voucher). Abandonar antes de confirmar no persiste
cambios. Límite conocido (SCR-001): el consumo de chance vive en su propia transacción
REQUIRES_NEW(garantía de AC-05, "un voucher = una chance", intocable). Por eso, en la rara colisión concurrente sobre el mismo cliente (landing vs gestor, o dos pestañas), la participación se registra (chance sumada) pero la corrección se rechaza con 409 y debe reintentarse — nunca hay doble chance ni corrupción silenciosa. Cerrar ese hueco exigiría tocar el aislamiento deconsumirChance(riesgo a AC-05); se prioriza el invariante sobre el edge. - Auditoría: todo cambio de dato del cliente desde la landing genera un evento interno en
tabla nueva
evento_dato_cliente (cliente_id, campo, valor_anterior, valor_nuevo, cambiado_en, hash_token, ip)— cubre contacto E identidad manual. Guarda los valores reales (no enmascarados): es un registro interno para investigación antifraude, nunca expuesto por la API pública (mismo criterio quevoucher_rechazadoy AC-33). El display al usuario va enmascarado. - Concurrencia: el path de edición escribe sobre
Cliente, que hoy no tiene@Version. Se agrega la columnaversionaclientey@Versiona la entidad (reusa el patrón de ADR-024). Edición de landing vs gestor sobre el mismo cliente → una gana, la otra 409; la landing re-lee y re-muestra. Sin lost update sobre el celular del ganador. - Validación: al corregir se reaplica la validación del alta — celular 10 dígitos normalizado, email con formato, sexo ∈ {M,F,X}, fecha DD/MM/AAAA (AC-21).
- Consequences:
- El ciclo se cierra: el cliente puede agregar lo que falta y corregir lo que tipeó mal (contacto siempre; identidad si el alta fue manual), siempre presentando el DNI.
- El principio "documento = verdad" se aplica donde de verdad corresponde (cuando escaneó), sin trabar al cliente que se registró a mano.
- El teléfono compartido queda protegido: editar exige el DNI, no basta el reconocimiento.
- Trazabilidad completa de cambios de datos (antifraude).
- Schema nuevo:
evento_dato_cliente+cliente.version+cliente.datos_origen(migración V12). - Atomicidad acotada (SCR-001, aceptado): datos+chance atómicos en el caso común; bajo colisión concurrente, la chance cuenta y la corrección se rechaza 409 (reintentable). Daño real mínimo y recuperable (el cliente conserva su chance; el operador o el próximo voucher corrigen); AC-05 (no doble chance) queda intacto. Decisión del operador: enmendar AC-57, no destabilizar el núcleo.
- La landing debe transmitir el flag de escaneo en el alta (dato que hoy se pierde).
Pantalla2Confirmar.tsxgana estados de UI (agregar / revisar / editar) — acotados, sin salir del presupuesto de la landing (AC-24); la edición no agrega peso crítico.
Implementation Plan¶
- Affected paths:
backend/.../db/migration/V11__reconocimiento_y_datos_cliente.sql—ALTER TABLE cliente ADD version(NOT NULL default 0) +ALTER TABLE cliente ADD datos_origen(CHECK IN ('escaneo','manual'), NOT NULL, default por back-fill según lo que se sepa del padrón existente — o 'manual' conservador) + tablaevento_dato_cliente.backend/.../dominio/Cliente.java—@Version Long version+datos_origen(enum STRING).backend/.../dominio/EventoDatoCliente.java(entity) + repositorio.backend/.../participacion/ParticipacionService.java— el alta persistedatos_origendesde el flag del request; métodoconfirmarConDatos(token, dni, cambios, ip)que, en una transacción, valida el DNI presentado, aplica solo los cambios permitidos segúndatos_origen, escribeevento_dato_clientey consume la chance (caso E). Rechaza cambios de identidad si el origen esescaneo. El re-escaneo con datos nuevos actualiza identidad y subedatos_origenaescaneo.backend/.../api/ParticipacionController.java— extenderPOST /api/participacion/confirmary.../altapara transportar el flag de escaneo y los cambios editados opcionales.backend/.../api/dto/*—AltaRequest/ConfirmarRequest: flagdatosDeEscaneo+cambiosopcionales.landing/src/screens/Pantalla1bAlta.tsx— enviardatosDeEscaneosegúndatosEscaneados.landing/src/screens/Pantalla2Confirmar.tsx— "Agregar {email}" (solo si vacío) + "Revisá tus datos" (editable según DNI presentado +datos_origen) + enmascarado hasta editar + "escaneá para corregir" cuando origen manual.landing/src/api/client.ts+types/api.ts— extender los requests con el flag y los cambios.- Patterns:
@Versionoptimista = ADR-024. Registro interno no expuesto =voucher_rechazado. Validación de campos = la del alta (AC-21). - Tests: editar celular por path DNI → guardado +
evento_dato_clienteescrito; editar apellido condatos_origen=manual→ permitido; editar apellido condatos_origen=escaneo→ rechazado; re-escaneo de cliente manual → identidad actualizada +datos_origenpasa aescaneo; editar por path reconocimiento sin DNI → rechazado; edición concurrente landing/gestor → una 409; confirmar guarda datos y chance atómicamente.
Verification¶
- [ ] AC-54 — Caso E con opcional vacío: "Agregar {email}" aparece solo si
emailestá vacío; se puede confirmar sin completarlo; completarlo lo guarda junto con la chance en la misma transacción. - [ ] AC-55 — Corregir datos exige DNI presentado: por el path DNI los campos permitidos son editables (enmascarados hasta editar); por el path reconocimiento (sin DNI), "Revisar mis datos" exige presentar el DNI antes de editar.
- [ ] AC-56 — Con
datos_origen = escaneo,nombre/apellido/sexo/fecha_nacimientoson de solo lectura en la landing (un intento de modificarlos es rechazado por el backend);emailycelulareditables con DNI presentado. - [ ] AC-57 — Confirmar en caso E guarda los datos corregidos y suma la chance en una sola transacción atómica; abandonar antes de confirmar no persiste cambios.
- [ ] AC-58 — Todo cambio de dato del cliente genera un
evento_dato_clienteinterno (cliente, campo, valor anterior→nuevo, timestamp, voucher/ip); nunca aparece en una respuesta de la API pública. - [ ] AC-59 — Concurrencia: edición landing vs gestor sobre el mismo cliente → una gana, la otra
409 (bloqueo optimista
@VersionenCliente), sin lost update; la corrección reaplica la validación de celular (10 dígitos) y email. - [ ] AC-60 — Con
datos_origen = manual,nombre/apellido/sexo/fecha_nacimientoson editables con DNI presentado (el cliente corrige su propio typo); escanear el DNI actualiza esos datos y subedatos_origenaescaneo, tras lo cual quedan de solo lectura (AC-56).