Ventas y analytics
Cada evento tiene su propia página de Ventas con todos los números que necesitas para entender qué está pasando: cuánto vendiste, por dónde llegaron los compradores, qué tier vende mejor, cuántos QRs ya entraron a la puerta.
Cómo llegar
Desde tres lugares del dashboard:
- En Eventos (la lista), cada card tiene un botón Ver ventas.
- Dentro del editor de un evento, arriba a la derecha junto al status badge.
- URL directa:
/dashboard/events/<id>/sales.
Solo el owner de la org tiene acceso. Cualquier otro request devuelve 403.
Headline numbers
Arriba ves cuatro KPIs:
| Card | Qué representa |
|---|---|
| Ingresos brutos | Suma de totalCents de todas las órdenes paid + fulfilled |
| Service fees cobrados | Lo que agregaste como % sobre el precio. También va a tu Stripe |
| Órdenes | Cuántas órdenes pagadas hay (independiente de la cantidad de boletos) |
| Boletos vendidos | Suma de items.quantity — un mismo comprador con 4 boletos cuenta 4 aquí |
Todos los montos están formateados en la moneda del evento y en el locale activo (es-MX, en-US, etc.).
Estado de los QRs
Una tarjeta muestra el conteo por estado:
- Emitidos — QR generado y entregado al comprador, todavía no validado.
- Validados — escaneado correctamente por un device autorizado.
- Anulados — invalidados manualmente (admin) o por anti-fraude.
- Reembolsados — la orden fue revertida; el QR ya no entra.
Te sirve durante el evento para saber cuánta gente entró (validados) vs cuánta falta llegar (emitidos - validados).
Atribución por canal (UTM)
Cualquier link al evento puede llevar ?utm_source=<canal>&utm_medium=<medio>&utm_campaign=<campaign>. El parser de Boletra guarda esos parámetros en la orden, y la página de Ventas los agrega en una tabla:
| Columna | Valor |
|---|---|
| Fuente | utm_source (instagram, x, mailchimp, …) |
| Medio | utm_medium (story, post, email, paid_ads, …) |
| Ingresos | Suma de totalCents para esa combinación |
| Órdenes | Cuántas órdenes generó |
Si no usas UTMs, todo cae en direct. Algunos ejemplos de links útiles:
?utm_source=instagram&utm_medium=story?utm_source=mailchimp&utm_medium=email&utm_campaign=preventa?utm_source=ig&utm_medium=paid&utm_campaign=lanzamiento
Lista de órdenes
Abajo de todo va la lista paginada de órdenes recientes, ordenada por fecha (más nueva primero). Por cada una ves:
- Comprador — nombre + email + status badge (pendiente / pagada / cumplida / reembolsada / fallida).
- Boletos — desglose
cantidad × nombre del tier. - Canal — UTM source + medium (o
—si vino directo). - Total — lo que pagó el comprador (con fee de servicio si corresponde).
- Fecha — timestamp local.
Búsqueda
El input de arriba filtra por email o nombre del comprador con un debounce de 300ms (no le pega a la API en cada tecla). Útil para encontrar la orden de alguien que reporta un problema.
Filtros
Los pills filtran por status:
- Todas — vista por default.
- Pagadas — la orden cobró pero todavía no se emitieron los QRs (suele durar milisegundos en producción).
- Cumplidas — pagada + QRs emitidos + créditos debitados + email enviado.
- Reembolsadas — revertidas por Stripe; el comprador no puede entrar.
- Falladas — Stripe rechazó el cobro o el flujo se cortó.
Mobile vs desktop
- Mobile: cada orden es una card vertical, con el email y los items apilados.
- Desktop (
sm:y arriba): tabla con columnas, scroll horizontal si no entra.
La paginación es de 25 por página por default. Usa Cargar más / Anterior abajo.
Cómo se llenan estos números
| Evento | Qué pasa |
|---|---|
| Comprador paga con Stripe | Orden queda paid, webhook checkout.session.completed la dispara |
Webhook → fulfillOrder() | Emite QRs, debita 1 crédito por boleto (free primero), incrementa sold del tier |
Orden pasa a fulfilled | Aparece en KPIs y en la lista — el comprador recibe el email |
| Reembolso por Stripe | El webhook marca la orden refunded y los QRs como refunded (dejan de entrar) |
| Staff valida QR en la puerta | El conteo de validated en la tarjeta de QRs sube |
No hace falta refrescar la página: React Query refetchea cada 15s en background. Si quieres un pull manual, recarga el navegador o cambia de filtro.