/health
Pública
Healthcheck básico
Confirma que la API responde correctamente.
Response
{
"status": "ok"
}
Backend corriendo correctamente. Esta pantalla resume las rutas activas por grupo, explica para qué sirve cada endpoint y muestra JSON de ejemplo para probar más rápido.
Grupo
Rutas simples para saber si el backend y la base de datos están vivos.
/health
Pública
Confirma que la API responde correctamente.
Response
{
"status": "ok"
}
/health/db
Pública
Valida conexión con Postgres. Si falla, responde 503.
Response
{
"status": "ok",
"database": {
"connected": true
}
}
Grupo
Registro, login, tokens y recuperación de cuenta.
/auth/register
Pública
Crea una cuenta nueva. La password debe tener 8 a 32 caracteres, mayúscula, número y carácter especial. accepted_legal_document_ids debe incluir el id de cada documento legal obligatorio (aviso de privacidad y términos y condiciones) devuelto por GET /legal/documents/active; derechos ARCO no se incluye porque es solo informativo. La cuenta y las aceptaciones se crean en una sola transacción.
Request
{
"email": "ana@evenxa.com",
"password": "Password1!",
"nombre": "Ana",
"apellido_paterno": "López",
"apellido_materno": "García",
"telefono": "5512345678",
"curp": "LOGA990101MDFPRN01",
"fecha_nacimiento": "1999-01-01",
"accepted_legal_document_ids": [
"00000000-0000-4000-8000-000000000000",
"00000000-0000-4000-8000-000000000000"
]
}
Response
{
"data": {
"user": {
"id": "00000000-0000-4000-8000-000000000000",
"email": "ana@evenxa.com",
"nombre": "Ana"
},
"access_token": "jwt_access_token",
"refresh_token": "jwt_refresh_token"
}
}
/auth/login
Pública
Valida credenciales y regresa tokens para consumir rutas protegidas. El usuario incluye legal_acceptance_complete, privacy_accepted, terms_accepted y los timestamps de aceptación calculados desde la base de datos.
Request
{
"email": "ana@evenxa.com",
"password": "Password1!"
}
Response
{
"data": {
"user": {
"id": "00000000-0000-4000-8000-000000000000",
"email": "ana@evenxa.com",
"nombre": "Ana",
"legal_acceptance_complete": true,
"privacy_accepted": true,
"privacy_accepted_at": "2026-07-30T12:00:00.000Z",
"terms_accepted": true,
"terms_accepted_at": "2026-07-30T12:00:00.000Z"
},
"access_token": "jwt_access_token",
"refresh_token": "jwt_refresh_token"
}
}
/auth/refresh
Pública
Recibe un refresh token válido y entrega un access token nuevo.
Request
{
"refresh_token": "jwt_refresh_token"
}
Response
{
"data": {
"access_token": "new_jwt_access_token"
}
}
/auth/logout
Bearer token
Invalida la sesión del usuario autenticado.
Response
{
"message": "Sesión cerrada correctamente"
}
/auth/verificar-email
Pública
Confirma el correo usando el código de 6 dígitos enviado al usuario.
Request
{
"codigo": "123456"
}
Response
{
"message": "Email verificado correctamente"
}
/auth/recuperar-password
Pública
Envía un código para iniciar el flujo de recuperación de password.
Request
{
"email": "ana@evenxa.com"
}
Response
{
"message": "Código enviado correctamente"
}
/auth/reset-password
Pública
Actualiza la password después de validar el código de recuperación.
Request
{
"email": "ana@evenxa.com",
"codigo": "123456",
"password": "NuevaPass1!"
}
Response
{
"message": "Password actualizada correctamente"
}
/auth/google
Pública
Redirige al flujo OAuth de Google.
Response
{
"redirect": "https://accounts.google.com/..."
}
Grupo
Perfil y acciones de la cuenta autenticada.
/usuarios/perfil
Bearer token
Regresa los datos del usuario autenticado, incluyendo dirección de contacto y el estado canónico de aceptación legal cuando existan.
Response
{
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"email": "ana@evenxa.com",
"nombre": "Ana",
"legal_acceptance_complete": true,
"privacy_accepted": true,
"privacy_accepted_at": "2026-07-30T12:00:00.000Z",
"terms_accepted": true,
"terms_accepted_at": "2026-07-30T12:00:00.000Z",
"telefono": "5512345678",
"direccion": {
"estado_id": "22",
"estado": "Querétaro",
"municipio_id": "22014",
"municipio": "Querétaro",
"calle": "Av. Constituyentes",
"numero": "S/N",
"codigo_postal": "76000",
"colonia": "Centro"
}
}
}
/usuarios/actualizar-perfil
Bearer token
Permite cambiar datos personales y dirección de contacto. El municipio debe pertenecer al estado enviado. El código postal debe ser de 5 dígitos.
Request
{
"nombre": "Ana María",
"telefono": "5598765432",
"direccion": {
"estado_id": "22",
"municipio_id": "22014",
"calle": "Av. Constituyentes",
"numero": "S/N",
"codigo_postal": "76000",
"colonia": "Centro"
}
}
Response
{
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"nombre": "Ana Maria",
"telefono": "5598765432",
"direccion": {
"estado_id": "22",
"estado": "Querétaro",
"municipio_id": "22014",
"municipio": "Querétaro",
"calle": "Av. Constituyentes",
"numero": "S/N",
"codigo_postal": "76000",
"colonia": "Centro"
}
}
}
/usuarios/eliminar-cuenta
Bearer token
Elimina o desactiva la cuenta del usuario autenticado.
Response
{
"message": "Cuenta eliminada correctamente"
}
Grupo
Aviso de privacidad, términos y condiciones y derechos ARCO. Los documentos obligatorios se aceptan durante POST /auth/register; POST /legal/acceptances cubre cuentas existentes que aún tengan documentos pendientes.
/legal/documents/active
Pública
Regresa la versión activa de cada documento legal (aviso de privacidad, términos y condiciones, derechos ARCO) con una URL firmada de solo lectura. Usado por el formulario de registro para mostrar los documentos y sus ids antes de crear la cuenta.
Response
{
"documents": [
{
"id": "00000000-0000-4000-8000-000000000000",
"type": "privacy_notice",
"name": "Aviso de privacidad",
"version": "2026-07-22",
"requires_acceptance": true,
"url": "https://storage.googleapis.com/kustika-documentos/...",
"published_at": "2026-07-22T16:04:10.000Z"
}
]
}
/legal/acceptance-status
Bearer token
Fuente de verdad para saber si al usuario autenticado le falta aceptar algún documento legal obligatorio activo. No bloquea login ni navegación; el frontend decide qué hacer con el resultado.
Response
{
"is_complete": false,
"missing_documents": [
{
"id": "00000000-0000-4000-8000-000000000000",
"type": "terms_and_conditions",
"name": "Términos y condiciones",
"version": "2026-07-22",
"requires_acceptance": true,
"url": "https://storage.googleapis.com/kustika-documentos/..."
}
]
}
/legal/acceptances
Bearer token
Para cuentas ya existentes: registra la aceptación de uno o más documentos legales obligatorios y activos por su id. Acepta envíos parciales (no exige incluir todos los pendientes en una sola llamada), es idempotente, y usa transacción. El user_id siempre se toma del token, nunca del body.
Request
{
"accepted_legal_document_ids": [
"00000000-0000-4000-8000-000000000000"
]
}
Response
{
"message": "Documentos legales aceptados correctamente",
"is_complete": true,
"accepted_document_ids": [
"00000000-0000-4000-8000-000000000000"
],
"missing_documents": []
}
Grupo
Consulta pública de eventos y gestión para organizadores.
/eventos?page=1&limit=20&busqueda=rock
Pública
Regresa eventos visibles para usuarios. Acepta filtros por categoria_id, búsqueda, page y limit.
Response
{
"data": {
"eventos": [
{
"id": "00000000-0000-4000-8000-000000000000",
"titulo": "Noche Indie",
"imagen_portada": "http://127.0.0.1:3000/uploads/local/eventos/portada.webp",
"imagen_hero": "http://127.0.0.1:3000/uploads/local/eventos/hero.webp",
"imagen_background": "http://127.0.0.1:3000/uploads/local/eventos/hero.webp",
"status": "publicado",
"estado_venue": "Querétaro",
"estado_venue_id": "22",
"ciudad_venue": "Querétaro",
"municipio_venue_id": "22014"
}
],
"total": 1,
"page": 1,
"limit": 20,
"pages": 1
}
}
/eventos/:id
Pública
Obtiene información completa del evento, incluyendo funciones y tipos de boleto.
Response
{
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"titulo": "Noche Indie",
"imagen_portada": "http://127.0.0.1:3000/uploads/local/eventos/portada.webp",
"imagen_hero": "http://127.0.0.1:3000/uploads/local/eventos/hero.webp",
"imagen_background": "http://127.0.0.1:3000/uploads/local/eventos/hero.webp",
"categoria": "Conciertos",
"funciones": [
{
"id": "00000000-0000-4000-8000-000000000000",
"fecha_inicio": "2026-07-20T21:00:00.000Z",
"tipos_boleto": []
}
]
}
}
/eventos
Bearer token
Crea un evento en borrador. Requiere rol event_manager o admin.
Request
{
"titulo": "Noche Indie",
"descripcion": "Concierto en vivo",
"descripcion_corta": "Una noche de bandas emergentes",
"categoria_id": "00000000-0000-4000-8000-000000000000",
"recinto_id": "00000000-0000-4000-8000-000000000000",
"nombre_venue": "Foro Centro",
"estado_venue_id": "22",
"municipio_venue_id": "22014",
"direccion_venue": "Av. Constituyentes 123",
"imagen_portada": "http://127.0.0.1:3000/uploads/local/eventos/portada.webp",
"imagen_hero": "http://127.0.0.1:3000/uploads/local/eventos/hero.webp",
"imagen_background": "http://127.0.0.1:3000/uploads/local/eventos/hero.webp",
"tags": [
"indie",
"música"
],
"edad_minima": 18
}
Response
{
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"titulo": "Noche Indie",
"imagen_hero": "http://127.0.0.1:3000/uploads/local/eventos/hero.webp",
"imagen_background": "http://127.0.0.1:3000/uploads/local/eventos/hero.webp",
"status": "borrador"
}
}
/eventos/:id
Bearer token
Actualización parcial. Permite cambiar categoría, recinto, textos e imágenes. Envía null para limpiar campos opcionales. recinto_id y venue_id son alias y, si se envían juntos, deben coincidir. No modifica funciones ni tipos de boleto.
Request
{
"titulo": "Noche Indie actualizada",
"descripcion_corta": null,
"categoria_id": "00000000-0000-4000-8000-000000000000",
"recinto_id": "00000000-0000-4000-8000-000000000000",
"imagen_hero": "http://127.0.0.1:3000/uploads/local/eventos/hero.webp",
"imagen_background": "http://127.0.0.1:3000/uploads/local/eventos/background.webp"
}
Response
{
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"titulo": "Noche Indie actualizada",
"descripcion_corta": null,
"categoria_id": "00000000-0000-4000-8000-000000000000"
}
}
/eventos/:id/imagen-portada/transform
Bearer token
Descarga la imagen fuente, aplica encuadre 16:9 en 1920x1080, guarda una nueva imagen optimizada y actualiza la portada del evento.
Request
{
"sourceUrl": "http://127.0.0.1:3000/uploads/local/eventos/original.jpg",
"fitMode": "cover",
"zoom": 1.2,
"scaleX": 1,
"scaleY": 1,
"offsetX": 0,
"offsetY": -40,
"outputWidth": 1920,
"outputHeight": 1080
}
Response
{
"imagen_portada": "http://127.0.0.1:3000/uploads/local/eventos/portadas/portada.webp"
}
/eventos/:id/funciones
Bearer token
Agrega una fecha u horario a un evento administrado por el usuario.
Request
{
"nombre": "Función principal",
"fecha_inicio": "2026-07-20T21:00:00.000Z",
"fecha_fin": "2026-07-20T23:30:00.000Z",
"fecha_apertura_puertas": "2026-07-20T20:00:00.000Z"
}
Response
{
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"nombre": "Función principal",
"fecha_inicio": "2026-07-20T21:00:00.000Z"
}
}
/eventos/funciones/:funcionId/tipos-boleto
Bearer token
Crea precio, inventario y modalidad para una función. En TABLE, label_format NUMERIC conserva Mesa 1..N; ALPHANUMERIC exige units_per_row y genera A1..A20, B1... Los UUID y unit_number son identidades estables; label es sólo presentación. Las reservas continúan recibiendo commercial_unit_ids. Cambiar la distribución se bloquea si existen reservas, órdenes o ventas.
Request
{
"nombre": "Diamante",
"precio": 6000,
"cantidad_total": 200,
"sale_mode": "TABLE",
"people_per_unit": 4,
"has_chairs": true,
"label_format": "ALPHANUMERIC",
"units_per_row": 20,
"max_por_orden": null,
"min_por_orden": 1,
"zona": "Diamante",
"color": "#2563eb",
"sale_phases": [
{
"name": "Preventa",
"price": 350,
"starts_at": "2026-05-01T06:00:00.000Z",
"ends_at": "2026-06-01T06:00:00.000Z"
},
{
"name": "Fase 2",
"price": 450,
"starts_at": "2026-06-01T06:00:00.000Z",
"ends_at": "2026-07-20T20:00:00.000Z"
}
]
}
Response
{
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"nombre": "General",
"precio": 450,
"cargo_servicio": 67.5,
"cargo_servicio_porcentaje": 15,
"cantidad_disponible": 200,
"sale_status": "ACTIVE",
"effective_price": 350,
"effective_service_fee": 52.5,
"active_sale_phase": {
"id": "00000000-0000-4000-8000-000000000000",
"name": "Preventa",
"price": 350
},
"next_sale_phase": {
"id": "00000000-0000-4000-8000-000000000000",
"name": "Fase 2",
"price": 450
},
"sale_phases": [
{
"id": "00000000-0000-4000-8000-000000000000",
"name": "Preventa",
"phase_order": 1,
"price": 350
}
]
}
}
/eventos/tipos-boleto/:tipoBoletoId/sale-phases
Pública
Devuelve todas las fases, la activa, la siguiente y sale_status. ACTIVE permite comprar; UPCOMING y ENDED no tienen precio efectivo vendible.
Response
{
"data": {
"tipo_boleto_id": "00000000-0000-4000-8000-000000000000",
"has_sale_phases": true,
"sale_status": "ACTIVE",
"active_sale_phase": {
"id": "00000000-0000-4000-8000-000000000000",
"name": "Preventa",
"price": 350
},
"next_sale_phase": {
"id": "00000000-0000-4000-8000-000000000000",
"name": "Fase 2",
"price": 450
},
"sale_phases": []
}
}
/eventos/tipos-boleto/:tipoBoletoId/sale-phases
Bearer token
Reemplaza atómicamente las fases de un tipo administrado por el usuario. Enviar sale_phases: [] elimina las fases y restaura el modo de precio fijo.
Request
{
"sale_phases": [
{
"name": "Fase 1",
"price": 350,
"starts_at": "2026-05-01T06:00:00.000Z",
"ends_at": "2026-06-01T06:00:00.000Z"
}
]
}
Response
{
"data": {
"tipo_boleto_id": "00000000-0000-4000-8000-000000000000",
"has_sale_phases": true,
"sale_status": "UPCOMING",
"active_sale_phase": null,
"next_sale_phase": {
"id": "00000000-0000-4000-8000-000000000000",
"name": "Fase 1",
"price": 350
},
"sale_phases": []
}
}
/eventos/:id/publicar
Bearer token
Publica un evento en borrador. Debe tener al menos una función activa.
Response
{
"message": "Evento publicado correctamente"
}
/eventos/:id/cancelar
Bearer token
Cancela un evento publicado. Si está en borrador, se elimina.
Request
{
"motivo": "Cambio de fecha"
}
Response
{
"message": "Evento cancelado correctamente"
}
Grupo
Recintos reutilizables, layouts SVG y disponibilidad por evento sin duplicar geometria.
/recintos?estado_id=11&municipio_id=11020
Pública
Regresa recintos precargados para poblar el selector del admin. Si no hay resultados, el frontend puede usar captura manual.
Response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000000",
"nombre": "Palenque de la Feria de Leon",
"estado_id": "11",
"municipio_id": "11020",
"direccion": "C. Olimpo, Zona Recreativa y Cultural, 37500 Leon de los Aldama, Gto.",
"capacidad_total": 6985,
"venue_layout_id": "palenque-de-leon",
"zonas": [
{
"categoria": "vip",
"capacidad": 1210,
"precio_desde": 1800,
"precio_hasta": 1800
}
]
}
]
}
/venues
Pública
Regresa recintos fisicos reutilizables. El venue no contiene precios, ventas ni disponibilidad.
Response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000000",
"nombre": "Palenque de la Feria de León",
"ciudad": "León de los Aldama",
"tipo": "palenque",
"layout_id": "00000000-0000-4000-8000-000000000000"
}
]
}
/venues/:id/layout
Pública
Regresa geometria SVG por seccion y referencias a seat layouts numerados.
Response
{
"data": {
"venue": {
"id": "00000000-0000-4000-8000-000000000000",
"nombre": "Palenque de la Feria de León"
},
"layout": {
"id": "00000000-0000-4000-8000-000000000000",
"viewBox": {
"x": 0,
"y": 0,
"width": 1000,
"height": 1000
}
},
"sections": [
{
"id": "00000000-0000-4000-8000-000000000000",
"section_id": "VIP-B7",
"name": "Vip B7",
"svgPath": "M0 0H100V100H0Z",
"seatLayoutId": "00000000-0000-4000-8000-000000000000"
}
]
}
}
/eventos/:id/map
Pública
Combina venue, layout reusable, precios y disponibilidad del evento. La geometria nunca se guarda en el evento.
Response
{
"data": {
"event": {
"id": "00000000-0000-4000-8000-000000000000",
"venue_id": "palenque-de-leon",
"recinto_id": "00000000-0000-4000-8000-000000000000"
},
"venue": {
"id": "00000000-0000-4000-8000-000000000000",
"nombre": "Palenque de la Feria de León"
},
"layout": {
"id": "00000000-0000-4000-8000-000000000000"
},
"sections": [
{
"id": "00000000-0000-4000-8000-000000000000",
"availability": {
"AVAILABLE": 120,
"SOLD": 10
},
"prices": {
"min_price": 1800,
"max_price": 1800,
"moneda": "MXN"
}
}
]
}
}
/eventos/:id/section/:sectionId
Pública
Regresa seat layout, estados de asientos y precios para abrir una seccion numerada o general.
Response
{
"data": {
"section": {
"id": "00000000-0000-4000-8000-000000000000",
"name": "Vip B7"
},
"generatedSeats": [
{
"seat_id": "00000000-0000-4000-8000-000000000000",
"seat_code": "VIP-B7:M:701",
"row": "M",
"column": 701,
"x": 0,
"y": 0
}
],
"availability": [
{
"seat_id": "00000000-0000-4000-8000-000000000000",
"seat_code": "VIP-B7:M:701",
"status": "AVAILABLE"
}
],
"prices": []
}
}
/eventos/:id/section/:sectionId/:subsectionId
Pública
Regresa solamente los asientos de una subseccion oficial del recinto, con geometria backend para pintar posiciones, huecos y bloques separados sin recalcular en frontend. Disponible inicialmente para Diamante A1-A8 del Palenque de Leon.
Response
{
"data": {
"section": {
"section_id": "diamante",
"name": "Diamante"
},
"subsection": {
"id": "A6",
"name": "Diamante A6",
"seatCount": 28,
"available": 27,
"layout": {
"grid": {
"rows": 10,
"columns": 15,
"cellSize": 34,
"gap": 8
},
"stage": {
"label": "Escenario",
"gridRow": 9,
"startColumn": 1,
"endColumn": 15
}
}
},
"seats": [
{
"seat_id": "00000000-0000-4000-8000-000000000000",
"seat_code": "diamante:F:601",
"row": "F",
"seat_number": 601,
"estado": "AVAILABLE",
"disponibilidad": true,
"precio": 2200,
"grid_row": 2,
"grid_column": 2,
"x": 42,
"y": 42
}
],
"prices": [
{
"tipo_boleto_id": "00000000-0000-4000-8000-000000000000",
"precio": 2200,
"moneda": "MXN"
}
]
}
}
/eventos/:id/reserve
Bearer token
Crea holds temporales independientes del layout. Conserva el contrato anterior; para una experiencia sin mapa acepta event_section_price_id y quantity representa personas o mesas según event-forms.
Request
{
"funcion_id": "uuid opcional",
"section_id": "uuid para mapa",
"event_section_price_id": "uuid para evento sin mapa",
"commercial_unit_ids": [
"uuid de Mesa 7"
],
"quantity": "personas; para TABLE se deriva de commercial_unit_ids",
"seats": [
{
"seat_id": "00000000-0000-4000-8000-000000000000"
}
],
"hold_minutes": 15
}
Response
{
"data": {
"hold_token": "uuid",
"reserved_until": "2026-07-03T18:15:00.000Z",
"sale_mode": "TABLE opcional",
"people_per_unit": 6,
"total_capacity": 12,
"commercial_units": [
{
"id": "00000000-0000-4000-8000-000000000000",
"label": "Mesa 7",
"unit_number": 7,
"capacity": 6
}
],
"pricing": {
"currency": "MXN",
"price_per_unit": 6000,
"subtotal": 12000,
"service_fee": 1800,
"total": 13800
}
}
}
/eventos/:id/release
Bearer token
Libera asientos reservados por el usuario autenticado usando hold_token o lista de asientos.
Request
{
"hold_token": "uuid"
}
Response
{
"data": {
"released": [
"VIP-B7:M:701"
]
}
}
/eventos/:id/replace-hold
Bearer token
Operacion atomica, multiseccion y exclusiva para asientos numerados. El conjunto enviado se convierte en el estado final completo del hold. Requiere el token cuando ya existe un hold activo; un hold ligado a una orden es inmutable. seats: [] con token libera el hold numerado. Errores de contrato usan 400 y conflictos de token, inventario, expiracion u orden usan 409.
Request
{
"hold_token": "uuid opcional",
"seats": [
{
"seat_id": "00000000-0000-4000-8000-000000000000",
"section_code": "oro"
}
],
"hold_minutes": 15
}
Response
{
"data": {
"hold_token": "uuid",
"reserved_until": "2026-07-03T18:15:00.000Z",
"seats": [
{
"seat_id": "00000000-0000-4000-8000-000000000000",
"seat_code": "oro:Q:1101",
"section_code": "oro",
"row": "Q",
"number": "1101",
"status": "RESERVED",
"reserved_until": "2026-07-03T18:15:00.000Z"
}
]
}
}
/reservas
Bearer token
Flujo estandar de reservas: con evento_id y hold_token crea (o reutiliza) la orden pendiente del hold vigente. ticket_holder_names es opcional y, cuando llega, sólo su primer nombre identifica al comprador de toda la orden; no se exige un nombre por mesa o boleto. Si se omite, el backend usa el nombre completo del usuario autenticado. Clientes anteriores pueden seguir enviando varios nombres, pero los boletos nuevos usarán el primero en todos los accesos. El pago se inicia despues con POST /pagos/tarjeta o POST /pagos/oxxo, igual que cualquier otra orden.
Request
{
"evento_id": "00000000-0000-4000-8000-000000000000",
"hold_token": "uuid",
"ticket_holder_names": [
"Leonardo López"
],
"form_submission": {
"form_id": "00000000-0000-4000-8000-000000000000",
"answers": [
{
"field_id": "00000000-0000-4000-8000-000000000000",
"value": [
{
"value": "vodka",
"quantity": 3
},
{
"value": "whisky",
"quantity": 2
}
]
}
]
},
"general_form_submission": {
"form_id": "00000000-0000-4000-8000-000000000000",
"answers": [
{
"field_id": "00000000-0000-4000-8000-000000000000",
"value": {
"quantity": 2
}
}
]
}
}
Response
{
"data": {
"orden_id": "00000000-0000-4000-8000-000000000000",
"numero_orden": "KST-ABC123",
"subtotal": 1400,
"cargo_servicio": 210,
"descuento": 0,
"addons_subtotal": 200,
"total": 1810,
"expires_at": "2026-07-03T18:15:00.000Z",
"es_cortesia": false
}
}
/event-forms/function/:funcionId/section/:sectionId
Pública
Conserva fields para preguntas y extras. commercial_config es null en secciones normales y contiene PERSON/TABLE, precio canónico, unidades disponibles y capacidad por unidad en secciones configuradas. TABLE siempre publica max_units_per_order como null: su límite es la disponibilidad real de commercial_units. En cortesías PER_UNIT, quantityPerScope se multiplica por las mesas del hold y maxSelections se interpreta por unidad de alcance, no como límite global de la orden.
Response
{
"success": true,
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"commercial_config": {
"sale_mode": "TABLE",
"people_per_unit": 6,
"has_chairs": true,
"inventory_units": 20,
"available_units": 20,
"price_per_unit": 8500,
"min_units_per_order": 1,
"max_units_per_order": null,
"label_format": "ALPHANUMERIC",
"units_per_row": 20
},
"fields": [
{
"field_type": "checkbox",
"required": true,
"config": {
"createsAddon": true,
"includedBenefit": true,
"benefitMode": "ELECTIVE",
"benefitScope": "PER_UNIT",
"quantityPerScope": 1,
"maxSelections": 1,
"options": [
{
"label": "José Cuervo",
"value": "jose_cuervo",
"price": 0
},
{
"label": "Whisky",
"value": "whisky",
"price": 0
}
]
}
}
]
}
}
/event-forms/function/:funcionId/commercial-section/:eventSectionPriceId
Pública
Resuelve la experiencia por event_section_prices.id sin consultar seat_map_sections. Devuelve la misma configuración, campos dinámicos y addons del flujo existente.
Response
{
"success": true,
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"source": "TICKET_COMMERCIAL_SECTION",
"event_section_price_id": "00000000-0000-4000-8000-000000000000",
"commercial_section": {
"id": "00000000-0000-4000-8000-000000000000",
"source": "TICKET_COMMERCIAL_SECTION",
"label": "Mesa VIP",
"ticket_type_id": "00000000-0000-4000-8000-000000000000"
},
"commercial_config": {
"sale_mode": "TABLE",
"people_per_unit": 6,
"has_chairs": false,
"label_format": "ALPHANUMERIC",
"units_per_row": 20
},
"fields": []
}
}
/event-forms/function/:funcionId/general-extras
Pública
Devuelve campos createsAddon disponibles para cualquier comprador de la función, sin depender de su sección.
Response
{
"success": true,
"data": {
"source": "FUNCTION_GENERAL",
"fields": []
}
}
/event-forms/function/:funcionId
Bearer token
Admin recibe SECTION y FUNCTION activos e inactivos. Event manager conserva acceso a sus SECTION, pero los FUNCTION se excluyen de su respuesta.
Response
{
"success": true,
"data": [
{
"id": "00000000-0000-4000-8000-000000000000",
"funcion_id": "00000000-0000-4000-8000-000000000000",
"scope_type": "FUNCTION",
"section_id": null,
"event_section_price_id": null,
"source": "FUNCTION_GENERAL",
"activo": true,
"fields": []
}
],
"total": 1
}
/event-forms/:id
Bearer token
Devuelve todos los campos, incluidos los desactivados. Los formularios FUNCTION son exclusivamente administrativos; event_manager recibe 403.
Response
{
"success": true,
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"scope_type": "FUNCTION",
"source": "FUNCTION_GENERAL",
"activo": true,
"version": 1,
"fields": []
}
}
/event-forms
Bearer token
Sólo admin puede crear FUNCTION. extras acepta el contrato simplificado; backend deriva key, tipo, orden y configuración técnica. fields sigue disponible para compatibilidad. No admite commercial_config ni cortesÃas.
Request
{
"funcion_id": "00000000-0000-4000-8000-000000000000",
"scope_type": "FUNCTION",
"nombre": "Extras globales",
"extras": [
{
"name": "Cerveza",
"price": 100,
"description": "Opcional",
"maxQuantity": 20
}
]
}
Response
{
"success": true,
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"scope_type": "FUNCTION",
"source": "FUNCTION_GENERAL",
"activo": true,
"version": 1
},
"message": "Formulario creado correctamente"
}
/event-forms/:id
Bearer token
Sólo admin puede actualizar o reactivar FUNCTION. extras permite editar por id sin desactivar los extras omitidos. fields conserva la semántica completa anterior.
Request
{
"activo": true,
"extras": [
{
"id": "00000000-0000-4000-8000-000000000000",
"name": "Cerveza",
"price": 120,
"maxQuantity": 20
}
]
}
Response
{
"success": true,
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"scope_type": "FUNCTION",
"activo": true,
"version": 2
},
"message": "Formulario actualizado correctamente"
}
/event-forms/:id
Bearer token
Sólo admin puede desactivar FUNCTION. Es lógica e idempotente; no elimina respuestas, addons ni snapshots históricos.
Response
{
"success": true,
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"activo": false
},
"message": "Formulario desactivado correctamente"
}
/eventos/funciones/:funcionId/commercial-sections/:eventSectionPriceId/units
Pública
Lista id, label, unit_number, capacity y status. Estados posibles: AVAILABLE, RESERVED, SOLD, BLOCKED y DISABLED. Un RESERVED vencido se expone como AVAILABLE; los demas estados se conservan.
Response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000000",
"label": "Mesa 7",
"unit_number": 7,
"capacity": 6,
"status": "AVAILABLE"
}
]
}
Grupo
Catálogo público de estados y municipios para normalizar venues de eventos.
/ubicaciones/estados
Pública
Regresa entidades federativas con clave oficial para poblar selects.
Response
{
"data": [
{
"id": "22",
"nombre": "Querétaro",
"clave": "22"
}
]
}
/ubicaciones/estados/:estadoId/municipios
Pública
Regresa municipios del estado indicado con clave oficial.
Response
{
"data": [
{
"id": "22014",
"estado_id": "22",
"nombre": "Querétaro",
"clave": "014"
}
]
}
Grupo
Catálogo público para clasificar eventos.
/categorias
Pública
Regresa categorías activas para filtros y formularios.
Response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000000",
"nombre": "Conciertos",
"slug": "conciertos"
}
]
}
Grupo
Solicitudes para convertirse en organizador y revision por administradores.
/organizadores/solicitar
Bearer token
Recibe multipart/form-data. rfc_file es obligatorio, solo admite PDF de hasta 5 MB. Acepta company_name, description, company_phone, contact_email y website, además de sus nombres históricos en español. El campo de texto rfc queda deprecado y es opcional. No establezcas Content-Type manualmente: el cliente debe agregar el boundary.
Request
{
"formData": {
"company_name": "Kustika Producciones",
"rfc_file": "constancia-rfc.pdf (application/pdf, máximo 5 MB)",
"description": "Productora de eventos",
"company_phone": "5512345678",
"contact_email": "contacto@kustika.com",
"website": "(opcional) https://kustika.com"
}
}
Response
{
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"status": "pendiente",
"nombre_empresa": "Kustika Producciones",
"rfc_file": {
"url": "/organizadores/solicitudes/:id/rfc",
"name": "constancia-rfc.pdf",
"mime_type": "application/pdf",
"size": 123456,
"uploaded_at": "2026-06-18T00:00:00.000Z"
}
}
}
/organizadores/mi-solicitud
Bearer token
Muestra el estado de la solicitud del usuario autenticado.
Response
{
"data": "Solicitud más reciente o null si el usuario aún no tiene solicitudes"
}
/organizadores/solicitudes?status=pendiente
Admin
Permite al admin revisar solicitudes, opcionalmente filtradas por status.
Response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000000",
"status": "pendiente",
"nombre_empresa": "Evenxa Producciones",
"rfc_file": {
"url": "/organizadores/solicitudes/:id/rfc",
"name": "constancia-rfc.pdf",
"mime_type": "application/pdf",
"size": 123456
}
}
]
}
/organizadores/solicitudes/:id/rfc
Bearer token
Devuelve el PDF con acceso autenticado. Un usuario solo puede consultar su propio documento; un admin puede revisar cualquier solicitud.
Response
"application/pdf"
/organizadores/solicitudes/:id/aprobar
Admin
Convierte la solicitud en organizador aprobado.
Response
{
"message": "Solicitud aprobada correctamente"
}
/organizadores/solicitudes/:id/rechazar
Admin
Marca una solicitud como rechazada y guarda el motivo.
Request
{
"motivo": "Falta documentación fiscal"
}
Response
{
"message": "Solicitud rechazada correctamente"
}
Grupo
Carga y lectura de archivos usados por la plataforma.
/uploads/imagen
Bearer token
Recibe multipart/form-data con el archivo en el campo file. Máximo 10MB.
Request
{
"formData": {
"file": "imagen.jpg"
}
}
Response
{
"data": {
"url": "http://127.0.0.1:3000/uploads/local/eventos/imagen.jpg"
}
}
/uploads/local/*
Pública
Sirve un archivo guardado localmente por la API.
Response
"Contenido binario del archivo"
Grupo
Landing y administración simple de sorteos.
/sorteos
Pública
Regresa sorteos visibles para la landing.
Response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000000",
"title": "Viaje a festival",
"ticketPrice": 99,
"featured": true
}
]
}
/sorteos/:id
Pública
Obtiene la información completa de un sorteo.
Response
{
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"title": "Viaje a festival",
"status": "hot",
"entries": "1200"
}
}
/sorteos/:id/reservas
Bearer token
Crea una orden pendiente de sorteo para iniciar pago con Stripe.
Request
{
"cantidad": 5
}
Response
{
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"orden_id": "00000000-0000-4000-8000-000000000000",
"reserva_id": "00000000-0000-4000-8000-000000000000",
"expira_en": "2026-06-17T23:59:00.000Z"
}
}
Grupo
Boletos del usuario con identidad UUID y datos visuales estructurados del asiento.
/boletos/mis-boletos
Bearer token
seat_id es el UUID canónico. seat_code es únicamente la etiqueta visual; fila, número, sección y subsección se entregan sin interpretar identificadores.
Response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000000",
"seat_id": "00000000-0000-4000-8000-000000000000",
"seat_code": "oro:Q:1101",
"row_label": "Q",
"seat_number": "1101",
"section_name": "Oro",
"subsection_name": "Oro C11",
"commercial_details": {
"commercial_units": [
{
"label": "Mesa 7",
"capacity": 6
}
],
"complimentary_items": [
{
"label": "Cortesia",
"quantity": 1
}
],
"extras": [
{
"label": "Cerveza",
"quantity": 2,
"unit_price": 100,
"subtotal": 200
}
],
"pricing_breakdown": {
"base_subtotal": 6000,
"service_fee": 900,
"discount": 0,
"extras_subtotal": 200,
"total": 7100
}
}
}
]
}
/boletos/:id
Bearer token
Los boletos de admisión general devuelven seat_id, seat_code, row_label y seat_number como null.
Response
{
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"seat_id": "00000000-0000-4000-8000-000000000000",
"seat_code": "oro:Q:1101",
"row_label": "Q",
"seat_number": "1101",
"section_name": "Oro",
"subsection_name": "Oro C11",
"commercial_details": {
"commercial_units": [
{
"label": "Mesa 7",
"capacity": 6
}
],
"complimentary_items": [
{
"label": "Cortesia",
"quantity": 1
}
],
"extras": [
{
"label": "Cerveza",
"quantity": 2,
"unit_price": 100,
"subtotal": 200
}
],
"pricing_breakdown": {
"base_subtotal": 6000,
"service_fee": 900,
"discount": 0,
"extras_subtotal": 200,
"total": 7100
}
}
}
}
Grupo
Invitaciones, membresías y administración de staff por organizador.
/event-manager/staff/invitaciones
Bearer token
Envía invitaciones de siete días. Sólo event_manager.
Request
{
"emails": [
"staff@correo.com"
]
}
Response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000000",
"email": "staff@correo.com",
"estado": "pendiente",
"expires_at": "2026-06-29T23:59:59.000Z"
}
]
}
/event-manager/staff/invitaciones
Bearer token
Lista únicamente invitaciones del organizador autenticado.
Response
{
"data": []
}
/event-manager/staff/invitaciones/:id/reenviar
Bearer token
Invalida el token anterior y renueva el vencimiento.
Response
{
"data": {
"estado": "pendiente"
}
}
/event-manager/staff/invitaciones/:id
Bearer token
Cancela una invitación pendiente sin borrar historial.
Response
{
"data": {
"estado": "cancelada"
}
}
/event-manager/staff
Bearer token
Lista membresías del organizador autenticado.
Response
{
"data": []
}
/event-manager/staff/:membershipId
Bearer token
Desactiva la membresía sin borrar historial.
Response
{
"data": {
"message": "Integrante de staff desactivado"
}
}
/staff/invitaciones/:token
Pública
Devuelve datos mínimos para presentar el registro o login.
Response
{
"data": {
"valida": true,
"organizador": "Kustika",
"email": "st***@correo.com"
}
}
/staff/invitaciones/:token/aceptar
Bearer token
Asigna rol y membresía si el correo verificado coincide.
Response
{
"data": {
"message": "Invitación aceptada",
"role": "staff"
}
}
/admin/event-managers/staff
Admin
Agrupa event managers, organizadores, staff e invitaciones pendientes.
Response
{
"data": []
}
Grupo
Registro anónimo de navegación y métricas agregadas para administración.
/analytics/events
Pública
Acepta page_view anónimo, deduplica por event_id y no almacena IP ni datos personales.
Request
{
"event_id": "00000000-0000-4000-8000-000000000000",
"visitor_id": "00000000-0000-4000-8000-000000000000",
"session_id": "00000000-0000-4000-8000-000000000000",
"event": "page_view",
"path": "/eventos/concierto",
"referrer": "https://www.google.com/"
}
Response
{
"data": {
"accepted": true
}
}
/admin/analytics?period=30d&granularity=day
Admin
Devuelve pageviews, visitantes anónimos, sesiones, páginas principales y serie temporal.
Response
{
"data": {
"timezone": "America/Mexico_City",
"summary": {
"pageviews": 0,
"unique_visitors": 0,
"sessions": 0,
"views_per_session": 0
},
"top_pages": [
{
"path": "/",
"pageviews": 0,
"unique_visitors": 0
}
],
"timeline": [
{
"period": "2026-08-04",
"pageviews": 0,
"visitors": 0,
"sessions": 0
}
]
}
}
Grupo
Acciones protegidas para usuarios administradores.
/admin/usuarios
Admin
Lista usuarios registrados para administración.
Response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000000",
"email": "ana@evenxa.com",
"roles": [
"user"
]
}
]
}
/admin/usuarios/metricas?period=30d&granularity=day
Admin
Devuelve resumen, crecimiento, distribución por rol, serie temporal y registros recientes en America/Mexico_City.
Response
{
"data": {
"timezone": "America/Mexico_City",
"summary": {
"total": 0,
"registered_today": 0,
"active": 0,
"verified": 0
},
"growth": {
"current": 0,
"previous": 0,
"absolute": 0,
"percentage": null
},
"by_role": [
{
"role": "customer",
"count": 0
}
],
"timeline": [
{
"period": "2026-08-04",
"count": 0
}
],
"recent_users": [
{
"id": "00000000-0000-4000-8000-000000000000",
"name": "Ana López",
"role": "customer",
"created_at": "2026-08-04T17:00:00.000Z"
}
]
}
}
/admin/usuarios/:id/rol
Admin
Actualiza el rol de un usuario.
Request
{
"role": "event_manager"
}
Response
{
"message": "Rol actualizado correctamente"
}
/admin/sorteos
Admin
Lista todos los sorteos para administración.
Response
{
"data": [
{
"id": "00000000-0000-4000-8000-000000000000",
"title": "Viaje a festival",
"featured": true
}
]
}
/admin/sorteos
Admin
Crea un sorteo para mostrar en la landing.
Request
{
"title": "Viaje a festival",
"subtitle": "Todo incluido",
"description": "Participa por una experiencia completa.",
"ticketPrice": 99,
"entries": "1200",
"endsIn": "5 días",
"status": "hot",
"image": "https://example.com/sorteo.jpg",
"featured": true
}
Response
{
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"title": "Viaje a festival",
"featured": true
}
}
/admin/sorteos/:id
Admin
Actualiza uno o mas campos de un sorteo.
Request
{
"featured": false,
"status": "limited"
}
Response
{
"data": {
"id": "00000000-0000-4000-8000-000000000000",
"status": "limited",
"featured": false
}
}
/admin/sorteos/:id
Admin
Elimina un sorteo existente.
Response
{
"message": "Sorteo eliminado correctamente"
}