EventQR — Sistema de Gestión de Entradas
Sistema completo de venta y validación de entradas para eventos con códigos QR, similar a Eventbrite.
Capa
Tecnología
Frontend
React 18 + TypeScript + Vite + TailwindCSS
Backend
Node.js + Express + TypeScript
Base de datos
PostgreSQL vía Prisma ORM
Autenticación
JWT (access + refresh token rotation)
QR
qrcode (generación) + html5-qrcode (escaneo)
Estado del servidor
TanStack Query v5
Formularios
React Hook Form + Zod
Deploy
Docker Compose + Nginx
Datos relacionales : User → Ticket ← Event son relaciones fuertes
Transacciones ACID : críticas para evitar double-booking (2 usuarios comprando el último ticket simultáneamente)
Unicidad garantizada : constraint @@unique([userId, eventId]) a nivel de BD
Escalabilidad : índices eficientes en status, eventId, userId
Node.js 20+
Docker Desktop (para PostgreSQL)
npm
1. Clonar y configurar entorno
# Backend
cp backend/.env.example backend/.env
# Edita backend/.env con tus secretos JWT (mínimo 32 caracteres)
# Frontend
cp frontend/.env.example frontend/.env
docker compose up postgres -d
3. Instalar dependencias e inicializar BD
# Backend
cd backend
npm install
npm run db:migrate # crea tablas
npm run db:generate # genera Prisma Client
npm run db:seed # datos de prueba
4. Instalar dependencias frontend
cd ../frontend
npm install
5. Ejecutar en modo desarrollo
# Terminal 1 — Backend (puerto 4000)
cd backend && npm run dev
# Terminal 2 — Frontend (puerto 5173)
cd frontend && npm run dev
Abre: http://localhost:5173
Credenciales de prueba (seed)
┌─────────────────────────────────────────────────────┐
│ Frontend (React) │
│ ┌──────────┐ ┌──────────┐ ┌────────────────────┐ │
│ │ /events │ │/my-tickets│ │ /admin/* │ │
│ │ EventCard│ │ QRTicket │ │ Dashboard │ │
│ │ EventForm│ │ (QR Image)│ │ CRUD de eventos │ │
│ └──────────┘ └──────────┘ │ QRScanner (cámara)│ │
│ └────────────────────┘ │
│ AuthContext → JWT en localStorage + auto-refresh │
└───────────────────────┬─────────────────────────────┘
│ HTTP / Axios
┌───────────────────────▼─────────────────────────────┐
│ Backend (Express) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Middlewares: helmet, cors, rate-limit, JWT auth│ │
│ └──────────┬──────────────────────────────────────┘ │
│ ┌──────────▼──────────────────────────────────────┐ │
│ │ Routes: /api/auth /api/events /api/tickets │ │
│ └──────────┬──────────────────────────────────────┘ │
│ ┌──────────▼──────────────────────────────────────┐ │
│ │ Services: AuthService EventService TicketService│ │
│ │ QrService: jwt.sign(ticketId) → qrcode.toDataURL│ │
│ └──────────┬──────────────────────────────────────┘ │
└─────────────┼───────────────────────────────────────┘
│ Prisma ORM
┌─────────────▼───────────────────────────────────────┐
│ PostgreSQL │
│ User ←──── Ticket ────→ Event │
│ RefreshToken │
└─────────────────────────────────────────────────────┘
1. Usuario se registra en evento
2. Backend crea Ticket en BD
3. QrService.createQrPayload(ticketId):
jwt.sign({ ticketId, eventId, userId }, QR_SECRET) ← sin expiración
4. QrService.generateQrImage(payload):
qrcode.toDataURL(payload) → base64 PNG
5. Usuario ve QR en la app / lo descarga
6. Admin escanea con html5-qrcode (cámara)
7. POST /api/tickets/validate { qrPayload }
8. Backend: jwt.verify(payload, QR_SECRET) → extrae ticketId
9. Busca ticket en BD → verifica status === ACTIVE
10. UPDATE ticket SET status = 'USED', usedAt = NOW()
11. Responde: { valid: true, message: "¡Acceso permitido!" }
Método
Ruta
Descripción
POST
/api/auth/register
Registro de usuario
POST
/api/auth/login
Inicio de sesión
POST
/api/auth/refresh
Renovar access token
POST
/api/auth/logout
Cerrar sesión
GET
/api/auth/me
Perfil del usuario autenticado
Método
Ruta
Auth
Descripción
GET
/api/events
Público
Listar eventos activos
GET
/api/events/:id
Público
Detalle de evento
GET
/api/events/admin/all
Admin
Todos los eventos
POST
/api/events
Admin
Crear evento
PATCH
/api/events/:id
Admin
Actualizar evento
DELETE
/api/events/:id
Admin
Eliminar evento
GET
/api/events/:id/attendees
Admin
Lista de asistentes
Método
Ruta
Auth
Descripción
POST
/api/tickets
Usuario
Obtener entrada
GET
/api/tickets/mine
Usuario
Mis tickets con QR
GET
/api/tickets/mine/:id
Usuario
Un ticket
PATCH
/api/tickets/mine/:id/cancel
Usuario
Cancelar ticket
POST
/api/tickets/validate
Admin
Validar QR escaneado
Despliegue completo con Docker
# Crea archivo .env con los secretos de producción
cp backend/.env.example backend/.env
# edita con secretos reales
docker compose up -d
Servicios:
✅ Contraseñas con bcrypt (12 rounds)
✅ JWT de corta vida (15min) + refresh token rotation
✅ Detección de robo de refresh token (revocación en cascada)
✅ QR firmado criptográficamente (JWT, no falsificable)
✅ Rate limiting en login/register (20 req/15min)
✅ Helmet (headers de seguridad)
✅ CORS restringido al origen del frontend
✅ Validación con Zod en ambos extremos
✅ Timing-safe login (evita timing attacks en email lookup)
✅ Un ticket solo puede ser usado una vez (ACID transaction)
✅ Usuario no puede comprar dos tickets para el mismo evento