Skip to content

Eventos e Ingressos (Tickets)

Sistema de eventos e ingressos integrado com codigos promocionais.


Visao Geral

  PromoCode                         Event (Evento)
  ┌──────────────────────┐          ┌──────────────────────┐
  │ _id                  │          │ _id                  │
  │ code (6 chars)       │          │ title                │
  │ title                │          │ description          │
  │ code_type:           │          │ event_type:          │
  │   'promo'            │    ┌────►│   'movie_premiere'   │
  │   'ticket'           │    │     │   'lecture'          │
  │                      │    │     │   'screening'        │
  │ event_id ────────────│────┘     │   'other'            │
  │ target_customer_ids  │          │ event_date           │
  │ discount_first_month │          │ location             │
  │ bonus_tickets        │          │ capacity (max)       │
  │ image_id             │          │ tickets_issued       │
  │ is_active            │          │ ticket_value         │
  └──────────────────────┘          │ is_sale_box_office   │
                                    │ image_id             │
                                    │ is_active            │
                                    └──────────────────────┘
                                             │ 1 : N
                                    ┌──────────────────────┐
                                    │ Ticket (Ingresso)    │
                                    ├──────────────────────┤
                                    │ _id                  │
                                    │ event_id (FK)        │
                                    │ customer_id (FK)     │
                                    │ ticket_order_id (FK) │
                                    │ title                │
                                    │ status:              │
                                    │   'available'        │
                                    │   'consumed'         │
                                    │   'expired'          │
                                    │ promo_code_id (FK)   │
                                    │ promo_code (string)  │
                                    │ consumed_at          │
                                    │ image_id             │
                                    └──────────────────────┘

Promo Code — 2 Tipos (code_type)

code_type Comportamento
promo Desconto no 1o mes da assinatura (discount_first_month em R$). Pode conter filhos via children_code_ids
ticket Gera ingresso(s) vinculado(s) a um Event. event_id: obrigatorio. bonus_tickets: quantidade de ingressos a criar. NAO aplica desconto na subscription

Entidades

Event (events collection)

Campo Tipo Descricao
_id ObjectId ID unico
title str Titulo do evento
description str Descricao
event_type str movie_premiere, lecture, screening, other
event_date datetime Data/hora do evento
location str Local
capacity int \| null Capacidade maxima (null = ilimitado)
tickets_issued int Contagem de tickets emitidos (admin + store)
ticket_value float \| null Preco unitario em reais (null = gratuito). Ex: 50.00 = R$50,00
is_sale_box_office bool true = evento visivel e disponivel para compra no Ticket Store
image_id ObjectId \| null Imagem no GridFS
is_active bool Ativo/inativo (visibilidade geral)
created_at datetime Criado em (UTC)
updated_at datetime Atualizado em (UTC)

Ticket (tickets collection)

Campo Tipo Descricao
_id ObjectId ID unico
event_id ObjectId FK para Event
customer_id ObjectId FK para User
title str Titulo do ingresso
description str \| null Descricao opcional
status str pending, available, consumed, expired. pending so ocorre em compra PIX via Ticket Store aguardando confirmacao (ADR-051); webhook muda para available. Tickets de admin/promo iniciam em available.
promo_code_id ObjectId \| null FK para PromoCode (se criado via promo ou redeem)
promo_code str \| null String do codigo promo (rastreabilidade)
ticket_order_id ObjectId \| null FK para TicketOrder (se criado via compra no Store). null = criado por admin ou promo
consumed_at datetime \| null Timestamp de consumo
image_id ObjectId \| null Imagem no GridFS
created_at datetime Criado em (UTC)
updated_at datetime Atualizado em (UTC)

Campos computados via $lookup (presentes no response da API, nao armazenados):

Campo Origem Descricao
customer_name users.full_name Nome do customer
customer_email users.email Email do customer
event_title events.title Titulo do evento
event_date events.event_date Data do evento
event_location events.location Local do evento

Classificacao de Tickets (Origem)

A origem do ticket e determinada por campos existentes no documento, sem campo origin dedicado:

Origem Discriminador Descricao
Compra (store) ticket_order_id != null Criado via Ticket Store (POST /store/purchase/*)
Promo code promo_code_id != null AND ticket_order_id == null Criado via resgate de codigo promo
Admin promo_code_id == null AND ticket_order_id == null Criado manualmente pelo admin (POST /tickets)

O dashboard de eventos usa esses discriminadores para contabilizar tickets_sold (store) e tickets_promo (promo code).


Ticket Store — Campos de Venda Publica

Os campos ticket_value e is_sale_box_office controlam se um evento aparece para compra no Ticket Store (rota publica para customers).

Campo Tipo Valor Efeito
is_sale_box_office bool true Evento aparece em GET /api/v1/store/events
is_sale_box_office bool false (default) Evento invisivel no Store — apenas admin ve
ticket_value float ex: 50.00 Preco cobrado por ingresso no Store (R$50,00)
ticket_value null null Ingresso gratuito

Regra de negocio: Para que um evento seja exibido no Store, ele precisa satisfazer simultaneamente: - is_sale_box_office = true - is_active = true - event_date > agora (evento nao expirado)

Ver detalhes completos do fluxo de compra em ticket-store.md.


Rotas — Events

Metodo Rota Descricao Auth
GET /api/v1/events Lista eventos JWT
GET /api/v1/events?_id=<id> Detalhe do evento (filtro por _id) Admin
POST /api/v1/events Criar evento Admin
PUT /api/v1/events Atualizar evento (_id no body) Admin
DELETE /api/v1/events Remover evento (?_id=<id> ou body) Admin

Exemplo — Criar Evento

POST /api/v1/events
Authorization: Bearer <admin_token>
Content-Type: application/json

{
  "title": "Lancamento Filme X",
  "description": "Pre-estreia exclusiva",
  "event_type": "movie_premiere",
  "event_date": "2026-04-15T20:00:00Z",
  "location": "Cinema Goiania - Sala 3",
  "capacity": 100,
  "ticket_value": 50.00,
  "is_sale_box_office": true
}

Nota: ticket_value e is_sale_box_office sao necessarios para habilitar o evento no Ticket Store (venda publica ao customer). Ver ticket-store.md.

Exemplo — Buscar Evento por ID

GET /api/v1/events?_id=69c1dadce9c9cdd70fe6b58c
Authorization: Bearer <admin_token>

Exemplo — Atualizar Evento

PUT /api/v1/events
Authorization: Bearer <admin_token>
Content-Type: application/json

[
  {
    "_id": "69c1dadce9c9cdd70fe6b58c",
    "title": "Lancamento Filme X — Sala VIP",
    "capacity": 80,
    "ticket_value": 75.00,
    "is_sale_box_office": true
  }
]

Exemplo — Remover Evento

DELETE /api/v1/events?_id=69c1dadce9c9cdd70fe6b58c
Authorization: Bearer <admin_token>

Exemplo — Response GET /api/v1/events

{
  "docs": [
    {
      "_id": "69c1dadce9c9cdd70fe6b58c",
      "title": "Lancamento Filme Teste",
      "description": "Pre-estreia exclusiva",
      "event_type": "movie_premiere",
      "event_date": "2026-04-15T20:00:00Z",
      "location": "Cinema Goiania - Sala 3",
      "capacity": 100,
      "tickets_issued": 4,
      "ticket_value": 50.00,
      "is_sale_box_office": true,
      "image_id": null,
      "is_active": true,
      "created_at": "2026-03-24T00:29:16.130000Z"
    }
  ],
  "info": null,
  "links": [
    { "link_type": "GET", "rel": "self", "href": "https://api.example.com/api/v1/events" },
    { "link_type": "GET", "rel": "get document", "href": "https://api.example.com/api/v1/events/{id}" },
    { "link_type": "DELETE", "rel": "delete document", "href": "https://api.example.com/api/v1/events/{id}" },
    { "link_type": "POST", "rel": "insert document", "href": "https://api.example.com/api/v1/events" },
    { "link_type": "PUT", "rel": "update document", "href": "https://api.example.com/api/v1/events/{id}" }
  ],
  "msg": "ok",
  "pagination": {
    "current_page": 0,
    "qty_docs_page": 10,
    "qty_of_pages": 1,
    "qty_total_docs": 1
  }
}

Rotas — Tickets

Metodo Rota Descricao Auth
GET /api/v1/me/tickets Meus ingressos Customer
GET /api/v1/tickets Todos os tickets Admin
GET /api/v1/tickets?_id=<id> Detalhe do ticket (filtro por _id) Admin
POST /api/v1/tickets Criar ticket avulso Admin
PUT /api/v1/tickets Atualizar ticket (_id no body) Admin
PUT /api/v1/tickets/{ticket_id}/consume Consumir ingresso Customer/Admin
DELETE /api/v1/tickets Remover ticket (?_id=<id> ou body) Admin

Exemplo — Criar Ticket (Admin)

POST /api/v1/tickets
Authorization: Bearer <admin_token>
Content-Type: application/json

{
  "customer_id": "69c192958dc6f156c84b5a00",
  "event_id": "69c1dadce9c9cdd70fe6b58c",
  "title": "Ingresso Filme X"
}

Exemplo — Atualizar Ticket (Admin)

PUT /api/v1/tickets
Authorization: Bearer <admin_token>
Content-Type: application/json

[
  {
    "_id": "69c1dd8cbd68ae02cf80fad7",
    "title": "Ingresso Filme X — Atualizado"
  }
]

Exemplo — Remover Ticket (Admin)

DELETE /api/v1/tickets?_id=69c1dd8cbd68ae02cf80fad7
Authorization: Bearer <admin_token>

Exemplo — Response GET /api/v1/me/tickets

{
  "docs": [
    {
      "_id": "69c1dd8cbd68ae02cf80fad7",
      "customer_id": "69c192958dc6f156c84b5a00",
      "event_id": "69c1dadce9c9cdd70fe6b58c",
      "title": "Lancamento Filme Teste",
      "description": null,
      "status": "available",
      "promo_code_id": null,
      "promo_code": null,
      "consumed_at": null,
      "image_id": null,
      "created_at": "2026-03-24T00:40:44.374000+00:00"
    }
  ],
  "info": null,
  "links": [
    { "link_type": "GET", "rel": "self", "href": "https://api.example.com/api/v1/me/tickets" },
    { "link_type": "GET", "rel": "get document", "href": "https://api.example.com/api/v1/me/tickets/{id}" },
    { "link_type": "DELETE", "rel": "delete document", "href": "https://api.example.com/api/v1/me/tickets/{id}" },
    { "link_type": "POST", "rel": "insert document", "href": "https://api.example.com/api/v1/me/tickets" },
    { "link_type": "PUT", "rel": "update document", "href": "https://api.example.com/api/v1/me/tickets/{id}" }
  ],
  "msg": "ok",
  "pagination": {
    "current_page": 0,
    "qty_docs_page": 10,
    "qty_of_pages": 1,
    "qty_total_docs": 1
  }
}

Exemplo — Consumir Ticket

PUT /api/v1/tickets/69c1dd8cbd68ae02cf80fad7/consume
Authorization: Bearer <customer_token>

Response:

{
  "docs": [
    {
      "_id": "69c1dd8cbd68ae02cf80fad7",
      "customer_id": "69c192958dc6f156c84b5a00",
      "event_id": "69c1dadce9c9cdd70fe6b58c",
      "title": "Lancamento Filme Teste",
      "description": null,
      "status": "consumed",
      "promo_code_id": null,
      "promo_code": null,
      "consumed_at": "2026-03-24T00:51:34.146000+00:00",
      "image_id": null,
      "created_at": "2026-03-24T00:40:44.374000+00:00"
    }
  ],
  "info": null,
  "links": [
    { "link_type": "PUT", "rel": "self", "href": "https://api.example.com/api/v1/tickets/69c1dd8cbd68ae02cf80fad7/consume" },
    { "link_type": "GET", "rel": "get document", "href": "https://api.example.com/api/v1/tickets/{id}" },
    { "link_type": "DELETE", "rel": "delete document", "href": "https://api.example.com/api/v1/tickets/{id}" },
    { "link_type": "POST", "rel": "insert document", "href": "https://api.example.com/api/v1/tickets" },
    { "link_type": "PUT", "rel": "update document", "href": "https://api.example.com/api/v1/tickets/{id}" }
  ],
  "msg": "ok",
  "pagination": {
    "current_page": 0,
    "qty_docs_page": 1,
    "qty_of_pages": 1,
    "qty_total_docs": 1
  }
}

Criacao de Tickets — 3 caminhos

A) Admin cria ticket avulso

POST /api/v1/tickets
body: { customer_id, event_id, title }
→ Ticket.event_id = referencia ao Event
→ Event.tickets_issued incrementado

B) Via PromoCode (code_type='ticket')

POST /api/v1/admin/promo-codes
body: { code_type: "ticket", event_id: "...",
        title: "Ingresso Filme X",
        target_customer_ids: ["id1", "id2"] }
→ Auto-push: cria Ticket para cada target_customer
→ Ticket.event_id = Event do PromoCode
→ Ticket.promo_code_id = referencia ao PromoCode
→ Event.tickets_issued incrementado

C) Customer resgata codigo manualmente

PUT /api/v1/customers/me/redeem-promo-code?code=ABC123
→ Se code_type='ticket': cria Ticket automaticamente
→ Ticket.event_id = herdado do PromoCode
→ Event.tickets_issued incrementado

Aplicacao do Promo Code — 3 momentos

1. No SIGNUP

POST /api/v1/customers/signup
body: { ..., promo_code: "ABC123" }
→ Valida codigo → vincula promo_code_ids
→ Se ticket: cria Ticket

2. No SUBSCRIBE (na hora do pagamento)

POST /api/v1/asaas/subscribe
body: { ..., promo_code: "ABC123" }
→ Valida codigo → vincula promo_code_ids
→ Aplica discount_first_month se promo
→ Se ticket: cria Ticket

3. REDEEM avulso (qualquer momento)

PUT /api/v1/customers/me/redeem-promo-code?code=ABC123
→ Valida codigo → vincula promo_code_ids
→ Se ticket: cria Ticket

Consumo do Ticket

PUT /api/v1/tickets/{ticket_id}/consume
→ Valida: ticket pertence ao customer (ou admin)
→ Valida: status != 'pending'  (bloqueia ingressos PIX nao pagos)
→ Valida: status != 'consumed' e status != 'expired'
→ Valida: event_date nao expirou
→ status: 'available' → 'consumed'
→ consumed_at: now()

Fluxo Completo (exemplo real)

1. Admin cria Evento "Lancamento Filme X" (15/abril)
   POST /api/v1/events
   { title: "Lancamento Filme X", event_type: "movie_premiere",
     event_date: "2026-04-15T20:00:00Z", location: "Cinema Sala 3",
     capacity: 100, ticket_value: 50.00, is_sale_box_office: true }
2. Admin cria PromoCode tipo 'ticket':
   POST /api/v1/admin/promo-codes
   { code_type: "ticket", event_id: "<event_id>",
     title: "Ingresso Filme X",
     target_customer_ids: ["<daniel_id>", "<maria_id>"] }
3. Sistema auto-cria (cascade):
   → Ticket p/ Daniel (event_id → Filme X, status: available)
   → Ticket p/ Maria  (event_id → Filme X, status: available)
   → Event.tickets_issued = 2
4. Daniel abre o app:
   GET /api/v1/me/tickets → ve "Ingresso Filme X" (status: available)
5. No dia do evento:
   PUT /api/v1/tickets/{ticket_id}/consume → status: consumed
6. Apos 15/abril:
   Tickets nao consumidos → status: expired (via cron/manual)

Campos do Customer relevantes

Campo Tipo Descricao
promo_code_ids list[OID] Promos vinculados (detalhes via $lookup)
redeemed_promo_codes list[str] Codigos ja consumidos
tickets list Ingressos do customer (via $lookup ou /me/tickets)
promo_code_used str Codigo usado no signup (legado)
promo_code_id OID PromoCode do signup (legado)