Saltar a contenido

Los casos de participación (A–E + tope)

Cuando un cliente escanea el QR de su ticket, el sistema tiene que decidir qué mostrarle y qué registrar. Esa decisión no es un if suelto: es un árbol de preguntas encadenadas, en un orden que importa. Cada rama del árbol es uno de los cinco casos A–E, más una rama especial de tope diario. Esta página explica el árbol completo —por qué las preguntas van en ese orden y qué se consume en cada hoja— para que puedas leer una participación cualquiera y saber exactamente dónde cayó y por qué.

El detalle de las pantallas que ve el cliente vive en La experiencia del cliente; acá nos quedamos en la lógica de decisión.

El árbol de decisión

El backend resuelve la participación con una secuencia de preguntas. El orden está pensado para no dar pistas a un atacante y para no quemar el voucher de un cliente legítimo antes de tiempo.

Diagrama

Las preguntas, en orden y con su porqué

1. ¿La firma es válida? → Caso A

Lo primero es verificar el HMAC del token contra la clave que indica su keyId. Si no valida —firma adulterada, keyId inexistente o revocado—, el cliente recibe un error genérico, sin ninguna pista de qué falló. El motivo real ("firma inválida") queda solo en el registro interno (VoucherRechazado), que alimenta las señales antifraude.

Nunca darle un oráculo a quien intenta forzar

El error genérico no es pereza de UX: es seguridad. Si el sistema dijera "firma inválida" vs. "token ya usado" vs. "promo vencida", le estaría enseñando a un atacante a distinguir tokens bien formados de basura. Hacia afuera, todos los rechazos se parecen; la verdad vive puertas adentro. Es un invariante de la CONSTITUTION. La comparación de la firma, además, se hace en tiempo constante para no filtrar información por timing (ver Antifraude y seguridad).

2. ¿El token ya se usó? → Caso B

Si la firma es válida, se pregunta por el hash canónico del token contra las chances ya registradas. Si aparece, el voucher ya participó: se muestra la fecha y hora de la participación original, sin datos personales. Un cliente que reescanea su propio ticket ve "ya participaste el día tal"; un retry de un formulario que se cortó a mitad de camino resuelve también acá, y por eso nunca produce doble chance (ADR-011).

3. ¿La promo está vigente? → Caso C

Se valida el promoId y, sobre todo, que la fecha de emisión del ticket caiga dentro de la vigencia de la campaña. Si no —un ticket emitido antes del inicio o después del cierre—, no hay chance, pero sí hay una oportunidad comercial: se ofrece el alta al padrón para próximas promociones (pantalla 4a para un cliente nuevo, 4b para uno ya registrado). Caracol se lleva un cliente en el padrón aunque ese voucher no participe.

Por qué la fecha de emisión y no la de registración

Si la vigencia se midiera por cuándo el cliente registra el voucher, alguien podría guardar tickets y registrarlos en la semana "conveniente". Midiendo por la fecha de emisión firmada en el token, ese hueco se cierra: la fecha no es manipulable porque viaja firmada. Vale para la vigencia, para el día del tope y para el bucketing a sorteo. El porqué está en ADR-002, y es un invariante.

4. ¿El DNI está en el padrón? → Caso E o Caso D

Recién cuando el voucher es válido y vigente, la landing pide el DNI (tipeado con reingreso de confirmación, o escaneado del código de barras del DNI físico con la cámara). Con el DNI en mano:

  • Está en el padrón → Caso E. El cliente ya existe. Se lo saluda ("Hola {nombre}"), confirma en un toque, se registra +1 chance y se muestra el total acumulado. Es el camino más rápido.
  • No está → Caso D. Cliente nuevo. Se piden los datos de alta (nombre, apellido, celular obligatorio de 10 dígitos normalizado, fecha de nacimiento, sexo, email opcional) y, al confirmar, se hace el alta + la chance. El botón de confirmar implica la aceptación de bases.

La rama transversal: tope diario

Antes de consumir el voucher en los casos D y E, hay un último control: ¿este DNI ya tiene 5 chances con fecha de emisión de ese día? Si es así, el voucher se rechaza sin consumirse y se informa al cliente. El tope es un valor de campaña (5 en esta), pensado como señal antifraude.

El rechazo por tope es definitivo para ese día, y no consume el voucher

Como el día se resuelve por emisión, no hay "reintentá mañana": mañana ese mismo voucher sigue perteneciendo al mismo día de emisión, que ya está colmado. Pero —y esto es lo justo— el voucher no se consume: no se le quema una chance al cliente por un límite temporal. El intento queda en el registro interno con motivo tope_diario para las señales antifraude. Ver ADR-006.

Dos reglas transversales que sostienen todo

Por debajo de los casos, hay dos invariantes que hacen que el flujo funcione con 3G malo y clientes que tocan dos veces:

  • El consumo es atómico a nivel base de datos. La chance se inserta con el hash del token como constraint UNIQUE. Aunque lleguen envíos simultáneos del mismo voucher, se registra exactamente una chance; el segundo choca contra el índice y resuelve a caso B. En los casos D, el alta de cliente + la chance + la aceptación de bases son una sola transacción: se insertan juntas o no se inserta nada. Un formulario cortado no deja un cliente a medias (ADR-011, ADR-024).
  • Todo lo temporal usa la fecha de emisión. No es solo la vigencia (caso C): el día del tope diario y —más adelante— a qué sorteo pertenece la chance también se resuelven por la fecha embebida y firmada en el token. Esta es la pieza que cierra el hueco de guardar vouchers.

Un detalle de API que vale conocer

La landing resuelve el caso en dos pasos, y por una buena razón. El endpoint POST /api/participacion/resolver:

  • Sin DNI, con un token vigente, no puede saber todavía si el cliente es nuevo o conocido, así que devuelve un estado intermedio PENDIENTE_DNI: es lo que dispara la pantalla 1a que pide el DNI.
  • Con DNI, ya resuelve a D, E o tope.

Ninguna llamada a resolver consume el voucher: es una consulta. El consumo ocurre recién en confirmar (caso E) o alta (caso D), ambos idempotentes. Este desdoblamiento fue una enmienda de diseño (SCR-001): la firma original solo con {token} no alcanzaba para producir D/E/tope, que dependen del DNI. El detalle de los contratos está en la Referencia de API.

Dónde sigue