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_valueeis_sale_box_officesao necessarios para habilitar o evento no Ticket Store (venda publica ao customer). Ver ticket-store.md.
Exemplo — Buscar Evento por ID¶
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¶
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)¶
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¶
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) |