Aplicación web para gestionar invitaciones, listas de invitados y control de acceso a eventos mediante códigos QR.
- Descripción del producto
- Stack tecnológico
- Instalación local
- Configuración Firebase
- Flujo de desarrollo
- Testing
- Despliegue
- Checklist de lanzamiento
- Problemas conocidos
PaseLink resuelve el problema de organizar el ingreso a un evento (fiesta, conferencia, evento privado) sin depender de listas en papel o planillas sueltas: el organizador crea el evento, carga o deja que los propios invitados se autorregistren, y cada invitado recibe un pase con código QR único. En la puerta, el organizador escanea ese QR desde el celular y la app confirma el ingreso en tiempo real, evitando duplicados y respetando el cupo del evento.
Funcionalidades principales:
- Creación de eventos con portada, color de marca, mensaje de bienvenida y campos personalizados.
- Tres modos de ingreso: lista cerrada (el organizador carga invitados), ingreso libre (los invitados se autorregistran) e híbrido (ambos).
- Generación de pases con QR único por invitado, exportables en PDF o por CSV.
- Escaneo de QR desde cámara para check-in/check-out en tiempo real, con control de cupo.
- Lista de espera automática cuando el evento se llena, con promoción manual por el organizador.
- Muro del evento en tiempo real (comentarios, preguntas, reacciones) con moderación del organizador.
- Recordatorios por email a invitados sin confirmar.
- Reportes y analíticas básicas por evento (asistencia, horarios pico, recaudación si el evento cobra entrada).
- Co-organizadores: el dueño del evento puede dar acceso de escaneo a otras personas.
- Panel de administración global (solo para el email configurado como admin).
Frontend
- React 19 + TypeScript, compilado con Vite.
- React Router 7 (rutas con
lazy()+Suspensepor página). - Tailwind CSS 4.
html5-qrcode(escaneo de cámara),qrcode/qrcode.react(generación de QR),jspdf(exportación de pases en PDF),react-easy-crop(recorte de fotos),canvas-confetti(feedback visual de check-in).
Firebase
- Authentication — email/password, Google y Facebook.
- Firestore — única base de datos de la app; también actúa como "backend": no hay Cloud Functions, toda la autorización vive en
firestore.rules. - Hosting — sirve el build estático de Vite.
- App Check (opcional) — anti-bot con reCAPTCHA v3 para formularios públicos.
Servicios externos
- EmailJS — correo de bienvenida y recordatorios, 100% desde el cliente (sin backend propio).
- Cloudinary — almacenamiento y optimización de fotos de portada/perfil.
Testing y calidad
- Vitest + jsdom para tests unitarios.
- ESLint (
typescript-eslint) +tsc --noEmitcomo gates de CI.
- Node.js 20.x (la misma versión que usa CI — ver
.github/workflows/). - Una cuenta de Firebase con un proyecto creado (o acceso al proyecto existente
app-pases-9e6e7). - Opcional para correr todas las funcionalidades en local: cuentas de EmailJS y Cloudinary (la app funciona sin ellas, simplemente esas dos integraciones quedan inactivas).
npm install # instalar dependencias
npm run dev # levantar servidor de desarrollo (Vite)
npm run build # type-check + build de producción a dist/
npm run preview # servir el build de dist/ localmente
npm run lint # ESLint
npm run test # Vitest (modo run, no watch)Copiar .env.example a .env y completar:
| Variable | Obligatoria | Descripción |
|---|---|---|
VITE_FIREBASE_API_KEY |
Sí | Config del proyecto Firebase (Project Settings → General → Tus apps). |
VITE_FIREBASE_AUTH_DOMAIN |
Sí | Idem. |
VITE_FIREBASE_PROJECT_ID |
Sí | Idem. |
VITE_FIREBASE_STORAGE_BUCKET |
Sí | Idem. |
VITE_FIREBASE_MESSAGING_SENDER_ID |
Sí | Idem. |
VITE_FIREBASE_APP_ID |
Sí | Idem. |
VITE_EMAILJS_SERVICE_ID |
No | emailjs.com — sin esto, no se envían correos de bienvenida/recordatorio (falla en silencio, no rompe el resto de la app). |
VITE_EMAILJS_TEMPLATE_ID_WELCOME |
No | Idem. |
VITE_EMAILJS_TEMPLATE_ID_REMINDER |
No | Idem. |
VITE_EMAILJS_PUBLIC_KEY |
No | Idem. |
VITE_CLOUDINARY_CLOUD_NAME |
No | cloudinary.com — sin esto, no se pueden subir fotos de portada/perfil. |
VITE_CLOUDINARY_UPLOAD_PRESET |
No | Debe ser un upload preset sin firmar (unsigned), restringido a imágenes en la configuración de Cloudinary. |
VITE_RECAPTCHA_SITE_KEY |
No | reCAPTCHA v3 desde google.com/recaptcha/admin. Sin esto, los formularios públicos (muro, lista de espera, auto-registro) quedan sin protección anti-bot. Ver checklist de lanzamiento. |
Importante: estas variables se inyectan en el bundle de Vite en tiempo de build (import.meta.env.VITE_*). No son secretas en el sentido de "ocultas del cliente" — quedan visibles en el JS servido al navegador. La seguridad real de Firestore depende de firestore.rules, no de ocultar estas variables.
Proyecto actual: app-pases-9e6e7 (ver .firebaserc).
Providers habilitados en el código: Email/Password, Google, Facebook. Deben estar activados en Firebase Console → Authentication → Sign-in method para que el login/registro funcione.
- Base de datos en modo nativo (no Datastore).
- Reglas de seguridad en
firestore.rules— son la única capa de autorización del proyecto (no hay backend propio ni Cloud Functions). - Índices compuestos en
firestore.indexes.json(hoy solo uno:eventsporownerId+createdAt, usado por "Mis eventos"). Si se agrega una query nueva conwhere+orderByen campos distintos, Firestore va a pedir un índice nuevo — agregarlo aquí, no solo aceptarlo desde la consola, para que quede versionado.
Configurado en firebase.json: sirve dist/ con rewrite de SPA (todas las rutas devuelven index.html, el ruteo lo resuelve React Router en el cliente).
Opcional pero recomendado antes de tener usuarios reales. Protege contra scripts automatizados en los formularios públicos (auto-registro, lista de espera, muro). Pasos:
- Crear una clave reCAPTCHA v3 en google.com/recaptcha/admin para el dominio de producción.
- Completar
VITE_RECAPTCHA_SITE_KEY(local y como secret de GitHub Actions). - Registrar la app web en Firebase Console → App Check, y activar el proveedor reCAPTCHA v3.
- Activar "Enforce" en App Check → Firestore. Sin este paso, App Check está configurado pero no bloquea nada — es el paso que realmente activa la protección.
Resumen de lo que protegen hoy:
events,wall(lectura) — públicos por diseño (el evento y su muro son compartibles sin login).guests— documento legible públicamente solo para que el pase compartible (/pass/:eventId/:qrToken) funcione sin login;email/phoneviven aparte enguestContacts, que nunca es público.wall,waitlist, auto-registro de invitados —createpúblico pero con validación de tamaño/tipo de campos (verisValidWallMessage,isValidWaitlistEntry,isValidPublicGuestRegistrationen el propio archivo).- El email de administrador está hardcodeado en
isAdmin()(firestore.rules) y ensrc/config/admin.ts— si se cambia, hay que actualizar ambos lugares manualmente (no hay una sola fuente de verdad todavía).
No hay un modelo de branching formal todavía (proyecto de un solo desarrollador). Recomendado al crecer el equipo:
main— siempre desployable; cada push acá dispara el deploy de producción.- Rama por feature/fix, mergeada a
mainvía Pull Request.
- Cualquier PR dispara
firebase-hosting-pull-request.yml: corre lint +tsc --noEmit+ tests (jobvalidate); si pasa y la PR es del mismo repo (no de un fork), además publica un preview channel de Firebase Hosting. - Push a
maindisparafirebase-hosting-merge.yml: mismo gatevalidatey, solo si pasa,build_and_deployconstruye y despliega a producción (channelId: live). - Si
validatefalla (lint, tipos o tests rotos), el deploy no se ejecuta — ver Tarea 1 de la preparación para producción. - El gate de CI no corre
firebase deploy --only firestore:rulesni tests de reglas — los cambios afirestore.rulesse despliegan manualmente (ver Despliegue).
npm run lint # antes de cualquier commit
npx tsc --noEmit # chequeo de tipos sin generar archivos
npm run test # tests unitarios
npm audit # estado de vulnerabilidades de dependencias
firebase deploy --only firestore:rules # desplegar SOLO las reglas (ver más abajo)npm run test # corre toda la suite de Vitest una vez (no watch)
npm run lint # ESLint sobre todo el proyecto
npx tsc --noEmit # type-check sin emitir archivosCobertura actual: tests unitarios sobre funciones puras de src/utils/ (formato de URLs de Cloudinary, extracción de coordenadas de Google Maps, parseo de QR, validaciones de longitud/contenido, y la lógica de progreso/cancelación de la exportación de PDF con jspdf/qrcode mockeados). No hay tests sobre la capa src/firebase/* (la lógica de negocio real: check-in, control de cupo, autenticación) ni sobre firestore.rules — ver Problemas conocidos.
Antes de cualquier deploy manual (no solo el automático de CI), correr en este orden:
npm run lint && npx tsc --noEmit && npm run test && npm run buildSi los cuatro pasan, es seguro continuar con el deploy.
Requiere Firebase CLI instalado (npm install -g firebase-tools) y sesión iniciada (firebase login) con acceso al proyecto app-pases-9e6e7.
Simplemente mergear/pushear a main. El workflow firebase-hosting-merge.yml valida y despliega el hosting solo. Esto NO despliega firestore.rules — son pasos independientes a propósito, porque un cambio de reglas mal hecho puede bloquear escrituras legítimas en producción sin previo aviso, y conviene desplegarlo de forma consciente, no como efecto secundario de cualquier push.
firebase login # si no hay sesión activa
firebase use app-pases-9e6e7 # confirmar proyecto correcto
firebase deploy --only firestore:rules # despliega SOLO las reglasHacerlo después de validar localmente (npm run build exitoso, reglas revisadas) y, si es posible, probar primero contra el emulador de Firestore (firebase emulators:start) antes de aplicar a producción — hoy no hay tests automatizados de reglas, así que la revisión manual es la única red de seguridad.
npm run build
firebase deploy --only hostingUsar solo si se necesita un deploy de emergencia fuera del flujo de CI — el camino normal es pushear a main.
firebase deployDespliega hosting + reglas + índices juntos. Usar con cuidado: junta dos cosas (código y reglas de seguridad) que normalmente conviene desplegar y revisar por separado.
Antes de poner la aplicación en manos de usuarios reales:
- Desplegar
firestore.rules(firebase deploy --only firestore:rules) — el archivo en el repo puede estar adelantado a lo que realmente está protegiendo producción. Confirmar confirebase deploy --only firestore:rules --dry-runsi la versión de Firebase CLI lo soporta, o revisar el historial de reglas en Firebase Console → Firestore → Reglas. - Activar "Enforce" de App Check en Firebase Console → App Check → Firestore (con
VITE_RECAPTCHA_SITE_KEYya configurada y desplegada). - Verificar GitHub Actions: confirmar que los secrets necesarios existen en Settings → Secrets and variables → Actions (ver lista completa en Variables de entorno +
FIREBASE_SERVICE_ACCOUNT_APP_PASES_9E6E7), y que el último run defirebase-hosting-merge.ymlterminó en verde. - Ejecutar tests (
npm run test) y confirmar 0 fallos. - Ejecutar build (
npm run build) y confirmar que termina sin errores de tipos. - Verificar variables de entorno tanto en
.envlocal como en los secrets de GitHub — en particular, que EmailJS/Cloudinary/reCAPTCHA estén completos si se quiere lanzar con esas funcionalidades activas (antes de esta tarea, los workflows de deploy no las pasaban al build; ya está corregido, pero los secrets deben existir en GitHub para que tomen efecto). - Confirmar que el email configurado en
src/config/admin.tsy enisAdmin()defirestore.rulessiguen siendo el mismo (no hay validación automática de esto). - Probar el flujo completo end-to-end al menos una vez en producción real (no solo en local): crear evento → registrar invitado → escanear QR → ver reflejado el check-in.
Las reglas de Firestore no pueden iterar un mapa para validar la longitud de cada valor individual dentro de customData (los campos personalizados que el organizador define para su evento). Por eso, en el auto-registro público de invitados, la regla isValidPublicGuestRegistration() solo limita la cantidad de campos (≤30), no el largo de cada valor — ese límite (300 caracteres) solo lo garantizan la UI y src/firebase/capacity.ts. Si alguien evita por completo la app y escribe directo a Firestore, podría meter un valor de texto muy largo en un campo personalizado. Cerrarlo del todo requeriría rediseñar customData como una lista de pares validables individualmente — no se hizo porque el riesgo real es bajo y el cambio no es trivial.
- Datos de invitados de eventos creados antes de la separación de
guestContacts(si llegaran a existir) conservaríanemail/phoneen el documento público deguestshasta editarse manualmente — no aplica hoy porque los datos actuales son de prueba, pero quedará pendiente si se promueven datos reales de antes de ese cambio. - El email del administrador vive en dos archivos sin sincronía automática (
firestore.rulesysrc/config/admin.ts) — bajo riesgo mientras el proyecto tenga un solo admin, pero es un punto de error humano si se rota. wall/waitlist/auto-registro tienen límites de tamaño, pero no rate-limiting real más allá de lo que aporte App Check (que es opcional hasta que se active "Enforce").- El alta de invitados hecha por el organizador (
addGuest/addGuestsBulk/GuestAddForm) no tiene los mismos límites de longitud que se aplicaron al auto-registro público — es una superficie autenticada, de menor urgencia, pero el mismo patrón de validación debería extenderse ahí.
- Sin tests automatizados sobre
src/firebase/capacity.tsnisrc/firebase/guests.ts(check-in/check-out, control de cupo) — es la lógica más crítica de la app y hoy depende solo de revisión manual. - Sin tests de
firestore.rules(requeriría@firebase/rules-unit-testing+ emulador). - Duplicación de código entre
EventCreate.tsx/EditEventForm.tsx,Dashboard.tsx/MyEvents.tsx, yEventWall.tsx/WallSection.tsx. GuestList/EventAnalyticssin memoización — solo importa con eventos de varios miles de invitados.- Listeners sin
limit()ensubscribeToAllEvents/subscribeToAllUsers/subscribeToUserEvents/subscribeToWaitlist— bajo riesgo mientras el volumen de eventos/usuarios sea moderado. - Sin Context API para
useAuth/useUserProfile(cada componente que los usa abre su propia suscripción a Firestore).