1. Vision General
El check-in con QR es la funcionalidad central del día del evento. Permite al anfitrion (o a un colaborador con rol HOST) verificar la entrada de cada invitado escaneando un código QR único desde la app movil o la PWA.
El sistema está disenado para funcionar incluso sin conexión a internet, un escenario común en salones de eventos, haciendas o locaciones rurales. Los pases se pre-cargan con anticipacion y las validaciones se ejecutan localmente, encolandose para sincronización posterior cuando la conexión se restablezca.
Numeros clave
| Concepto | Detalle |
|---|---|
| Tiempo de validación online | < 200ms |
| Tiempo de validación offline | < 50ms (local) |
| Vigencia del pase | Configurable, por defecto hasta fin del evento |
| Formato del código QR | String alfanumerico de 24 caracteres |
| Formato de imagen | PNG Base64, 300x300px |
2. Generación de Pases QR
La generación de pases QR es automática y se dispara en dos momentos:
- Al agregar invitados: Cuando se importan invitados via Excel o se crean manualmente desde el admin, el sistema genera automáticamente un pase QR para cada uno
- Bajo demanda: El anfitrion puede regenerar pases desde el admin o la app movil
Proceso de generación
- El admin o la app llama a
POST /v1/qr-passes/generatecon{ guestId, eventId, type } QRPassesService.generate()ejecuta:- Verifica que el guest existe y pertenece al evento
- Genera un código único de 24 caracteres alfanumericos con
crypto.randomBytes() - Genera la imagen QR en formato PNG Base64 (300x300px) usando la libreria
qrcode - Crea el registro
QRPassen la base de datos con estadoACTIVE
- Retorna el pase con código e imagen al cliente
Modelo QRPass
| Campo | Tipo | Descripción |
|---|---|---|
id | UUID | Identificador único |
guestId | UUID | Invitado propietario del pase |
eventId | UUID | Evento asociado |
code | String (24) | Código alfanumerico único |
type | Enum | ENTRY, EXIT, VIP |
imageBase64 | Text | Imagen QR en PNG Base64 |
isUsed | Boolean | Si el pase ya fue escaneado |
usedAt | DateTime (nullable) | Momento del escaneo |
scannedBy | UUID (nullable) | ID del HOST que escaneo |
checkInMethod | Enum | QR_SCAN, MANUAL, OFFLINE_SYNC |
expiresAt | DateTime | Momento de expiración |
createdAt | DateTime | Momento de creación |
3. Tipos de Pase
El sistema soporta tres tipos de pase QR, cada uno con un proposito diferente:
| Tipo | Proposito | Validación especial |
|---|---|---|
| ENTRY | Entrada general al evento | Ninguna adicional |
| EXIT | Registro de salida | Solo válido si el guest ya hizo check-in |
| VIP | Acceso a areas restringidas (mesa VIP, backstage) | Verifica atributo VIP del guest |
El tipo más común es ENTRY, que se genera automáticamente para todos los invitados. Los tipos EXIT y VIP se configuran manualmente por el anfitrion.
4. Pre-carga para Modo Offline
Para garantizar el funcionamiento sin conexión, los pases QR se pre-cargan en el dispositivo con anticipacion.
En la app nativa (nvito-client)
useQRPasseshook constaleTime: 10 * 60 * 1000(10 minutos) ygcTime: 7 * 24 * 60 * 60 * 1000(7 dias)- Los pases se almacenan en SecureStore con persistencia entre sesiones
- React Query mantiene los datos en cache incluso sin conexión
- El hook se ejecuta al entrar a la pantalla de scanner, precargando todos los pases del evento
En la PWA (nvito-pwa)
- Los pases se almacenan en IndexedDB usando la tabla
qr-passes useOfflineDatahook sincroniza los pases desde el BFF cuando hay conexión- La PWA puede funcionar como Service Worker para interceptar requests offline
Estructura del cache offline
Cada pase en cache contiene:
code: El código de 24 caracteres (para búsqueda local)guestId: Para actualizar el estado del invitadoguestName: Para mostrar en la UI de confirmacióntype: Tipo de paseisUsed: Estado actualtableNumber: Mesa asignada (si existe)dietaryRestrictions: Restricciones alimentarias (para informar al staff)
5. Escaneo de Códigos QR
El escaneo utiliza tecnologias diferentes según la plataforma:
App nativa: expo-camera
La app usa expo-camera con el modo de escaneo de códigos de barras:
- Escaneo continuo en tiempo real
- Feedback háptico via
expo-hapticsal detectar un código - Overlay visual con guia de encuadre
- Linterna integrada para escaneo en condiciones de poca luz
StatsHeadermuestra conteo en tiempo real: total invitados, checked-in, pendientes
PWA: html5-qrcode
La PWA usa la libreria html5-qrcode que accede a la camara via WebRTC:
- Compatible con todos los navegadores modernos (Chrome, Safari, Firefox)
- Seleccion de camara frontal/trasera
- Misma UX de overlay y feedback que la app nativa
- Fallback a input de archivo si la camara no está disponible
6. Validación del Pase
Una vez escaneado el código, la validación sigue un proceso estricto.
Validación online
- La app envia
POST /v1/qr-passes/validatecon{ code, eventId } QRCheckInService.validate()ejecuta:- Busca el pase por
codeen la tablaqr_passes - Verifica que
eventIdcoincida - Verifica que
isUsed === false - Verifica que
expiresAt > now() - Marca el pase como usado:
isUsed = true,usedAt = now(),scannedBy = hostUserId,checkInMethod = QR_SCAN
- Busca el pase por
- Actualiza el estado del guest:
checkedIn = true,checkedInAt = now() - Emite el evento de dominio
GuestCheckedInEvent - Crea un registro en
AuditLogcon tipoGUEST_CHECK_IN - Retorna la confirmación con datos del invitado (nombre, mesa, restricciones)
Validación offline
- La app busca el código en el cache local (SecureStore o IndexedDB)
- Verifica
isUsed === falseyexpiresAt > now()localmente - Marca el pase como usado en el cache local
- Muestra la confirmación al anfitrion
- Encola la operación en la cola offline para sincronización posterior
Respuestá de validación exitosa
| Campo | Descripción |
|---|---|
valid | true |
guest.name | Nombre completo del invitado |
guest.tableNumber | Numero de mesa asignada |
guest.companionCount | Numero de acompanantes |
guest.dietaryRestrictions | Restricciones alimentarias |
guest.notes | Notas adicionales |
checkInTime | Timestamp del check-in |
7. Flujo Completo: Generar, Escanear, Validar
Flujo Completo de Check-in QR
8. Cola Offline y Sincronización
La cola offline garantiza que ningún check-in se pierda aunque no haya conexión en el momento del escaneo.
Estructura de la cola
Cada operación encolada contiene:
| Campo | Descripción |
|---|---|
id | UUID único de la operación |
type | QR_CHECK_IN |
payload | { code, eventId, scannedAt, scannedBy } |
createdAt | Momento del escaneo offline |
retryCount | Numero de intentos de sincronización |
Proceso de sincronización
Cola Offline y Sincronizacion
Deteccion
Sincronizacion
Resultado
Manejo de conflictos
El escenario de conflicto más común es: dos dispositivos escanean el mismo pase offline. Cuando ambos sincronizan, el segundo recibe un 409 Conflict. En ese caso:
- El sistema mantiene el primer check-in como válido (el que llego primero al servidor)
- El segundo dispositivo recibe una notificación de conflicto con el timestamp del check-in original
- El conflicto se registra en
AuditLogpara revision posterior
Almacenamiento según plataforma
| Plataforma | Tecnologia | Clave de almacenamiento |
|---|---|---|
| App nativa | AsyncStorage | nvito_offline_queue |
| PWA | IndexedDB | Tabla offline-queue |
Hook de sincronización
En la app nativa, useOfflineSync monitorea el estado de conectividad:
- Escucha cambios de red via
ConnectivityContext - Cuando
isConnectedcambia defalseatrue, inicia sincronización - Procesa items en orden FIFO (primero el más antiguo)
- Muestra un badge con el conteo de items pendientes en la UI del scanner
9. Casos de Error
El sistema maneja múltiples escenarios de error con mensajes claros para el anfitrion:
| Escenario | Código HTTP | Mensaje al HOST | Detalle |
|---|---|---|---|
| Pase ya utilizado | 409 | "Pase ya escaneado" | Muestra usedAt y nombre de quien lo escaneo |
| Pase expirado | 410 | "Pase expirado" | Muestra expiresAt y opción de regenerar |
| Pase no encontrado | 404 | "Código QR invalido" | Posible QR falso o de otro sistema |
| Evento no activo | 403 | "Evento no activo" | El evento no está en estado ACTIVE |
| Guest eliminado | 404 | "Invitado no encontrado" | El guest fue removido de la lista |
| Tipo VIP sin permiso | 403 | "Acceso VIP no autorizado" | El guest no tiene atributo VIP |
UI de error
Cada error muestra un indicador visual distinto en la pantalla del scanner:
- Pase valido: Fondo verde, nombre del invitado, número de mesa, efecto haptico suave
- Pase ya usado: Fondo amarillo, nombre del invitado, hora del primer escaneo
- Pase invalido/expirado: Fondo rojo, mensaje de error, sin datos de invitado
10. Archivos Clave
nvito-api (Backend)
| Archivo | Responsabilidad |
|---|---|
qr-passes.service.ts | Orquestador: generación, listado, regeneración de pases |
qr-generation.service.ts | Generación de código único e imagen QR |
qr-check-in.service.ts | Validación de pases y registro de check-in |
qr-passes.controller.ts | Endpoints REST para pases QR |
nvito-client (App Nativa)
| Archivo | Responsabilidad |
|---|---|
app/(app)/(host)/scanner.tsx | Pantalla principal del scanner |
scanner/hooks/use-scanner.ts | Lógica de escaneo y validación |
scanner/stats-header.tsx | Conteo en tiempo real de check-ins |
src/hooks/queries/use-qr-passes.ts | Hook de React Query para pases |
src/storage/offline-queue.ts | Cola FIFO en AsyncStorage |
src/hooks/use-offline-sync.ts | Sincronización automática al reconectar |
nvito-pwa (Progressive Web App)
| Archivo | Responsabilidad |
|---|---|
src/app/(app)/(host)/scanner/page.tsx | Pantalla del scanner PWA |
src/hooks/use-qr-scanner.ts | Wrapper de html5-qrcode |
src/lib/offline/indexed-db.ts | Acceso a IndexedDB |
src/lib/offline/offline-queue.ts | Cola offline en IndexedDB |