RFC: Bar Integrado — MVP
| Campo | Valor |
|---|---|
| Autor | Engenharia Nittio |
| Status | Draft v2 |
| Data | 2026-03-30 |
| Escopo | MVP — Sistema de bar integrado ao ecossistema Nittio |
1. Contexto e Oportunidade de Mercado
1.1 O mercado de bar em eventos no Brasil
O mercado brasileiro de eventos (festas, festivais, shows, baladas) movimenta cerca de R$ 100 bilhões/ano (ABEOC). Dentro desse universo, a operação de bar/alimentos e bebidas (A&B) representa entre 40-60% do faturamento total de um evento, muitas vezes superando a receita de ingressos. O mercado de bebidas alcoólicas no Brasil é o 3o maior do mundo.
Dores atuais do mercado:
- Filas longas — o maior killer de receita de bar; cada minuto de fila perde vendas
- Controle de estoque precário — contagem manual, furto, desperdício
- Fraudes de caixa — operadores de PDV com acesso a dinheiro vivo
- Falta de dados — organizadores não sabem quais produtos vendem mais, em quais horários, qual o ticket médio
- Conciliação financeira manual — requer dias/semanas para fechar evento
- Fichas físicas — filas duplas (fila da ficha + fila do bar), desperdício de fichas não usadas, risco de falsificação
- Fragmentação — sistemas de ingresso, cashless e bar são fornecedores diferentes que não conversam
1.2 Por que o Nittio
O Nittio já resolve ingresso + validação + social + clube de assinatura + PDV de produtos. Adicionar bar integrado cria um ecossistema fechado onde:
- O participante já tem conta, CPF, dados de pagamento
- O produtor já gerencia eventos, employers, financeiro e saques
- A infraestrutura de pagamento (PagBank PIX/CC), filas (RabbitMQ), notificações, e frontend (Next.js + Expo) já existe
- O modelo de receita se multiplica: taxa sobre ingresso + taxa sobre consumação
1.3 Benchmarks do mercado brasileiro
| Solução | Modelo | Taxa bar | Observações |
|---|---|---|---|
| Zig | Cashless + bar | 3-5% + setup | Líder cashless, hardware próprio |
| Let's | Cashless + bar | ~3.5% | Foco em festas grandes |
| Eventbrite | Só ingresso | Não tem bar | Não compete nesse segmento |
| Sympla | Só ingresso | Não tem bar | Começou a testar cashless 2025 |
| Nittio (hoje) | Ingresso + social | N/A | Oportunidade de expansão |
| Nittio (MVP) | Ingresso + bar | 3-5% proposta | Integração nativa, sem hardware extra |
2. Visão do MVP
2.1 Proposta de valor
Um sistema de bar digital integrado onde o participante possui uma carteira digital global (wallet) com seu próprio QR code, carrega saldo via PIX/cartão, consome no bar apresentando o QR code da wallet, e o produtor tem controle total de cardápio, estoque, vendas e financeiro em tempo real — tudo dentro do Nittio.
2.2 Fluxo macro
┌──────────────┐ ┌──────────────┐ ┌───────────────┐
│ Participante│ │ Operador │ │ Produtor │
│ (app-console│ │ de Bar │ │ (hub-console)│
│ + webview) │ │ (hub-console)│ │ │
└──────┬───────┘ └──────┬───────┘ └──────┬────────┘
│ │ │
1. Carrega wallet │ A. Cria cardápio
2. Compra ticket (opcion.)│ B. Define estoque
3. Mostra QR da wallet │ C. Gerencia operadores
│ │ │
└────────┬───────────┘ │
│ │
4. Operador escaneia QR da wallet │
5. Seleciona itens │
6. Confirma venda │
7. Debita wallet │
│ │
└─────────────────────────────────┘
│
D. Dashboard real-time
E. Relatórios
F. Saque
2.3 Escopo MVP vs Futuro
| Feature | MVP | V2 | V3 |
|---|---|---|---|
| Cardápio digital (CRUD) | X | ||
| Categorias de produto de bar | X | ||
| Controle de estoque | X | ||
| Wallet global do usuário | X | ||
| QR code próprio da wallet | X | ||
| Recarga via PIX | X | ||
| Recarga via cartão de crédito | X | ||
| Recarga no momento do ingresso | X | ||
| Tela de PDV para operador | X | ||
| Venda e débito em wallet | X | ||
| Dashboard de vendas real-time | X | ||
| Relatórios (por produto/hora) | X | ||
| Saque de receita de bar | X | ||
| Cálculo de taxa Nittio no bar | X | ||
| Transaction model unificado | X | ||
| Saque de saldo wallet (withdraw) | X | ||
| NFC/pulseira cashless | X | ||
| Comanda por mesa | X | ||
| Multi-bar (vários pontos) | X | ||
| Integração fiscal (NFC-e) | X | ||
| Gorjeta digital | X | ||
| Programa de fidelidade bar | X | ||
| Pedido pelo app (self-service) | X | ||
| Impressora térmica | X | ||
| Estoque inteligente (IA) | X |
3. Decisões Arquiteturais Fundamentais
Antes de detalhar o impacto no codebase, as três decisões arquiteturais que guiam toda a implementação:
3.1 Decisão: Wallet GLOBAL do usuário (não por evento)
A wallet pertence ao usuário, não ao evento. É uma entidade de primeiro nível, 1:1 com User.
Motivos:
- A wallet é do usuário — o saldo é dinheiro dele, não está "preso" a um produtor ou evento. Isso é mais justo e evita a dor de "perdi meu saldo porque o evento acabou"
- Reuso entre eventos — o usuário vai a 3 eventos no mês e não precisa recarregar 3 vezes; o saldo restante do evento A funciona no evento B
- Experiência de "conta digital" — abre caminho para ser um método de pagamento nativo da plataforma (ingresso, produtos, planos), não apenas bar
- Simplifica recarga — não precisa estar no contexto de um evento para carregar
- Reduz fricção de devolução — não existe "devolução pós-evento"; o saldo simplesmente continua na wallet. O user pode sacar quando quiser
- Long-term thinking — a wallet global é a base para se tornar um método de pagamento universal no Nittio
Trade-offs aceitos:
- O produtor não tem "saldo cativo" do seu evento (o user pode gastar em outro lugar)
- Relatórios de "saldo alocado ao evento" não existem nativamente (mas "saldo gasto no evento" sim, via transactions)
- Responsabilidade fiscal de saque é do Nittio (intermediador), não do produtor
3.2 Decisão: Wallet com QR code PRÓPRIO
A wallet tem seu próprio nanoId e QR code, separado do ticket.
Motivos:
- Desacoplamento — wallet e ticket são entidades independentes. Um user pode ter wallet sem ter ticket (comprou ingresso na porta, mas quer usar bar digital). Um user pode ter ticket sem wallet
- Consistência — o operador do bar sempre escaneia o mesmo tipo de QR (wallet), independente de como o user entrou no evento
- Segurança — o QR da wallet só expõe o nanoId da wallet, não dados do ticket
- UX clara — o app mostra "Meu QR para pagamento" separado do "Meu ingresso"
- Futuro — o QR da wallet pode ser usado em contextos fora de eventos (loja do produtor, etc.)
3.3 Decisão: Transaction model UNIFICADO
Criar um model Transaction que registra toda movimentação financeira da plataforma — não apenas wallet, mas também compras de ingressos, produtos, planos, refunds, withdrawals.
Problema atual:
Hoje o Order acumula responsabilidades: é checkout state (PENDING, QR codes, expiry), é record financeiro (value, fee, paidAt), e é referência para tickets/vouchers. Não existe um ledger — para saber "quanto dinheiro passou pela plataforma em março", é preciso agregar Orders + Withdrawals + (futuro) bar orders separadamente.
Proposta:
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
│ Order │────▶│ Transaction │◀────│ BarOrder │
│ (checkout) │ │ (ledger) │ │ (bar sale) │
└──────────────┘ └──────────────────┘ └──────────────┘
▲
┌────────┼────────┐
│ │ │
┌──────┴──┐ ┌───┴───┐ ┌──┴──────────┐
│ Wallet │ │Refund │ │ Withdrawal │
│ (credit)│ │ │ │ (payout) │
└─────────┘ └───────┘ └─────────────┘
Ordercontinua sendo o document de checkout/gateway (PagBank interaction, QR, status tracking)Transactioné o ledger entry — registra cada movimento de dinheiro com tipo, valor, participantes, referências- Quando
orderPaidroda, cria umaTransaction(além de criar tickets/vouchers) - Quando bar debita wallet, cria uma
Transaction - Quando user recarrega wallet,
orderPaidcriaTransaction - Quando refund acontece, cria
Transaction - Quando withdrawal é aprovado, cria
Transaction
Benefícios:
- Single source of truth — um query para toda a atividade financeira
- Financial reporting — receita da plataforma, volume por produtor, por user, por período — tudo num model
- Wallet balance derivável —
wallet.balanceé cache; a soma deTransactionondewalletId = Xetype = CREDITmenostype = DEBITé o balance real (reconciliação) - Audit trail — toda movimentação tem rastro, referência, operador, metadata
- Não é breaking change — o Order model não muda; Transaction é aditivo. Os fluxos existentes (orderPaid, orderRefund) passam a também criar Transactions
Trade-off: +1 write em cada operação financeira. Mitigation: é um insert, não update; MongoDB handles well.
4. Análise de Impacto no Codebase Atual
4.1 O que REUTILIZAMOS (sem modificação)
| Componente existente | Uso no bar |
|---|---|
@nittio/mongo | Conexão, writeConcern, ObjectId utils |
@nittio/redis | Locks para transações de wallet, cache |
@nittio/rabbitmq | Fila para jobs assíncronos de bar |
@nittio/queues | Registro de nomes de filas |
@nittio/jobs | Registro de nomes de jobs |
@nittio/aws + @nittio/file | Upload de imagens de produtos do bar |
@nittio/email + @nittio/resend | Notificações de recarga, recibo |
@nittio/slack | Alertas internos (estoque baixo, fraude) |
@nittio/notification | Notificações in-app |
@nittio/ui | Componentes de UI (sidebar, inputs, cards, tables) |
@nittio/utils | Money formatting, hashing, API errors |
@nittio/address | Schema de endereço para pontos de bar |
@nittio/test | Padrões de teste |
@nittio/typescript-config | Config TS compartilhada |
@nittio/baas | Abstração de provider de pagamento |
@nittio/pagbank | PIX + cartão para recargas de wallet |
4.2 O que ADAPTAMOS (modificação em código existente)
4.2.1 packages/enum/src/
Novos enums necessários (novos arquivos):
barItemCategoryEnum.ts → BAR_ITEM_CATEGORY_ENUM
barItemStatusEnum.ts → BAR_ITEM_STATUS_ENUM
barOrderStatusEnum.ts → BAR_ORDER_STATUS_ENUM
barStationStatusEnum.ts → BAR_STATION_STATUS_ENUM
transactionTypeEnum.ts → TRANSACTION_TYPE_ENUM
transactionStatusEnum.ts → TRANSACTION_STATUS_ENUM
walletStatusEnum.ts → WALLET_STATUS_ENUM
Enums existentes que precisam de novos valores:
| Enum | Novo valor | Motivo |
|---|---|---|
PRODUCER_FEATURES_ENUM | BAR | Feature flag para habilitar bar |
ORDER_ITEM_TYPE_ENUM | walletRecharge | Item de recarga de wallet na order |
ORDER_PAYMENT_METHOD_ENUM | WALLET | Pagamento de ingresso via wallet |
WITHDRAWAL_REFERENCE_ENUM | BAR | Saque de receita de bar |
NOTIFICATION_TYPE_ENUM | WALLET_*, BAR_* | Notificações de wallet e bar |
EMPLOYER_GROUP_ENUM | BAR_OPERATOR, BAR_MANAGER | Permissões de operador de bar |
EMPLOYER_TUTORIAL_ENUM | BAR_SETUP | Tutorial de configuração de bar |
4.2.2 packages/order/ — Integração com Transaction
orderPaid.ts— Após cada branch existente (event, product, producerPlan), chamartransactionCreatepara registrar a movimentação no ledger. Adicionar novo branch paracart.walletRechargeIdque chamaorderPaidWalletRecharge(credita wallet + cria Transaction)orderPaidEvent.ts— Sem mudança na lógica de criação de tickets. OorderPaidpai é quem cria a TransactionorderPaidProduct.ts— IdemorderPaidProducerPlan.ts— IdemorderRefund.ts— AdicionartransactionCreatecom typeREFUNDapós marcar order como REFUNDED
4.2.3 packages/event/
eventModel.ts— Adicionar campobarSettings(embedded schema):isBarEnabled: booleanbarFeePercentage: number(taxa Nittio sobre vendas de bar)barStations: BarStationSchema[](estações/pontos de bar)
eventWithdrawalAvailableValueGet.ts— Adaptar para incluir receita de bar nos cálculos, usando Transactions comeventId+type: BAR_SALE
4.2.4 packages/cart/
cartModel.ts— Adicionar campo opcionalwalletRechargeValue: numberpara carrinho que inclui recarga
4.2.5 packages/user/
userModel.ts— Adicionar campowalletId: ObjectId → Wallet(denormalizado para lookup rápido). A wallet é criada lazily no primeiro acesso
4.2.6 Apps — Rotas e Handlers
apps/hub-api/ — Novas rotas de bar (detalhadas na seção 6)
apps/hub-console/ — Novas páginas:
- Configuração de bar do evento
- CRUD de cardápio
- Tela de PDV (ponto de venda) para operador
- Dashboard de vendas de bar
- Gerenciamento de estoque
apps/app-api/ — Novas rotas:
- CRUD de wallet (create/get/recharge/withdraw)
- Histórico de transações
- QR code da wallet
apps/app-console/ — Novas páginas:
- Seção de wallet no perfil do usuário
- Tela de recarga
- QR code da wallet
- Extrato de transações
- Saque de saldo
apps/worker/ — Novos jobs:
- Processar estoque após venda
- Enviar notificação de recarga
- Processar saque de wallet
- Gerar relatórios de bar
- Alertas de estoque baixo
apps/scheduler/ — Novos crons:
BAR_LOW_STOCK_ALERT— verificar estoque a cada X minutos durante eventoBAR_SALES_SNAPSHOT— snapshot de vendas para analytics
4.3 O que NÃO MEXEMOS no MVP
@nittio/lead,@nittio/apify,@nittio/bigdatacorp,@nittio/openai,@nittio/moskit— sem relação@nittio/producer-plan,@nittio/subscription— planos são ortogonais (mas ganham Transaction no ledger)@nittio/section,@nittio/participant— social/interações mantêm-se separadas@nittio/meta— futuramente pode enviar eventos de compra de barapps/bko-api/— backoffice de bar e transactions pode ser adicionado pós-MVPapps/landing/— sem impacto
5. Novos Pacotes (packages/)
5.1 packages/transaction/
Responsabilidade: Ledger unificado de toda movimentação financeira da plataforma. Source of truth para relatórios, reconciliação, e auditoria.
packages/transaction/
├── src/
│ ├── transactionModel.ts
│ ├── transactionCreate.ts
│ ├── transactionsGet.ts
│ ├── transactionsByUser.ts
│ ├── transactionsByProducer.ts
│ ├── transactionsByEvent.ts
│ ├── fixture/
│ │ ├── transactionCreateFixture.ts
│ │ └── index.ts
│ └── index.ts
├── package.json
└── tsconfig.json
TransactionModel:
| Campo | Tipo | Descrição |
|---|---|---|
_id | ObjectId | PK |
nanoId | String | ID curto para referência humana (nanoid 8) |
type | TRANSACTION_TYPE_ENUM | Tipo da movimentação (ver enum abaixo) |
status | TRANSACTION_STATUS_ENUM | COMPLETED, PENDING, FAILED |
value | Number | Valor em centavos (sempre positivo) |
platformFee | Number | Taxa Nittio em centavos (0 quando não aplicável) |
gatewayFee | Number | null | Taxa do gateway (PagBank) quando conhecida |
netValue | Number | Valor líquido (value - platformFee) |
userId | ObjectId → User | Usuário envolvido (indexado) |
producerId | ObjectId | null → Producer | Produtor envolvido, null para recargas diretas |
eventId | ObjectId | null → Event | Evento, quando aplicável |
walletId | ObjectId | null → Wallet | Wallet envolvida, quando aplicável |
orderId | ObjectId | null → Order | Order de origem (compra/recarga) |
barOrderId | ObjectId | null → BarOrder | Bar order de origem |
withdrawalId | ObjectId | null → Withdrawal | Withdrawal de origem |
paymentMethod | ORDER_PAYMENT_METHOD_ENUM | null | Método de pagamento usado |
direction | String | IN (dinheiro entrou) ou OUT (dinheiro saiu) do ponto de vista do userId |
description | String | Descrição legível para o extrato |
metadata | Object | null | Dados extras (itens do bar, tier do ticket, etc.) |
operatedBy | ObjectId | null → Employer | Operador que executou (para bar operations) |
removedAt | Date | null | Soft delete |
createdAt | Date | Timestamp |
updatedAt | Date | Timestamp |
TRANSACTION_TYPE_ENUM:
TICKET_PURCHASE → Compra de ingresso
PRODUCT_PURCHASE → Compra de produto
PLAN_PURCHASE → Compra de plano de assinatura
WALLET_RECHARGE → Recarga de wallet (via PIX/CC)
WALLET_DEBIT_BAR → Débito de wallet no bar
WALLET_WITHDRAW → Saque de saldo da wallet pelo user
REFUND → Refund de qualquer tipo
PRODUCER_WITHDRAWAL → Saque do produtor (payout)
TRANSACTION_STATUS_ENUM:
COMPLETED → Concluída
PENDING → Aguardando processamento (ex: saque em análise)
FAILED → Falhou
Indexes:
userId+createdAt(extrato do usuário)producerId+eventId+createdAt(relatório do produtor)walletId+createdAt(extrato da wallet)orderId(lookup por order)barOrderId(lookup por bar order)type+createdAt(relatórios por tipo)type+producerId+status+createdAt(analytics de receita)nanoId(unique, lookup rápido)
Quando uma Transaction é criada (mapeamento completo):
| Evento no sistema | Transaction type | direction | Quem cria |
|---|---|---|---|
orderPaidEvent finaliza | TICKET_PURCHASE | OUT | orderPaid (wrapper) |
orderPaidProduct finaliza | PRODUCT_PURCHASE | OUT | orderPaid (wrapper) |
orderPaidProducerPlan ok | PLAN_PURCHASE | OUT | orderPaid (wrapper) |
| Wallet recarga paga | WALLET_RECHARGE | IN | orderPaidWalletRecharge |
| Venda no bar (PDV) | WALLET_DEBIT_BAR | OUT | barOrderCreate |
| User saca saldo da wallet | WALLET_WITHDRAW | OUT | walletWithdraw |
| Order é refunded | REFUND | IN | orderRefund |
| Bar order é cancelled | REFUND | IN | barOrderCancel |
| Produtor solicita saque | PRODUCER_WITHDRAWAL | N/A | withdrawalCreate |
Normalização — Por que isso melhora o longo prazo:
Hoje, para calcular "quanto o produtor X faturou no evento Y", é preciso:
OrderModel.find({ producerId, eventId, status: PAID }) → sum(originalValue)
Com Transaction:
TransactionModel.find({ producerId, eventId, type: { $in: [TICKET_PURCHASE, WALLET_DEBIT_BAR] } }) → sum(netValue)
A segunda query inclui todas as fontes de receita (tickets + bar + futuros) sem precisar saber quais models consultar. Relatórios ficam triviais de estender.
5.2 packages/wallet/
Responsabilidade: Carteira digital global do usuário. Entidade de primeiro nível, 1:1 com User.
packages/wallet/
├── src/
│ ├── walletModel.ts
│ ├── walletCreate.ts
│ ├── walletCreditAdd.ts
│ ├── walletDebit.ts
│ ├── walletBalanceGet.ts
│ ├── walletWithdraw.ts
│ ├── jobs/
│ │ ├── jobWalletWithdrawProcess.ts
│ │ └── jobWalletRechargeNotificationSend.ts
│ ├── fixture/
│ │ ├── walletCreateFixture.ts
│ │ └── index.ts
│ └── index.ts
├── package.json
└── tsconfig.json
WalletModel:
| Campo | Tipo | Descrição |
|---|---|---|
_id | ObjectId | PK |
nanoId | String | ID curto e único para QR code (nanoid 8, unique, indexed) |
userId | ObjectId → User | Dono da wallet (unique, indexed — 1:1 com User) |
status | WALLET_STATUS_ENUM | ACTIVE, SUSPENDED, CLOSED |
balance | Number | Saldo atual em centavos (inteiro, default 0) |
totalCredits | Number | Total histórico creditado (default 0) |
totalDebits | Number | Total histórico debitado (default 0) |
removedAt | Date | null | Soft delete |
createdAt | Date | Timestamp |
updatedAt | Date | Timestamp |
WALLET_STATUS_ENUM:
ACTIVE → Operacional
SUSPENDED → Bloqueada temporariamente (fraude, disputa)
CLOSED → Encerrada pelo user
Decisão: Wallet NÃO tem eventId nem producerId
A wallet é da pessoa, não do evento. Ao recarregar, o saldo vai para a wallet global. Ao consumir no bar do evento X, a Transaction registra eventId = X e producerId = Y, mas a wallet em si é agnóstica.
Decisão: Wallet tem QR code PRÓPRIO
O campo nanoId gera um QR code exclusivo da wallet. No app do participante, existe uma tela "Meu QR de Pagamento" separada da tela "Meu Ingresso". No PDV, o operador escaneia o QR da wallet (não do ticket).
Formato do QR: nittio://wallet/{nanoId} — permite deep link no app e scan no hub-console.
Criação lazy:
A wallet é criada no primeiro acesso do user à funcionalidade de wallet (primeira recarga, primeiro acesso à tela de wallet). Não é criada automaticamente no registro do user para evitar poluir o banco com wallets vazias.
Operações críticas — Concorrência:
walletDebit usa findOneAndUpdate com:
{
_id: walletId,
status: WALLET_STATUS_ENUM.ACTIVE,
$expr: { $gte: ['$balance', debitValue] }
},
{
$inc: { balance: -debitValue, totalDebits: debitValue }
}
O operador $inc do MongoDB é atômico — garante que o balance nunca fique negativo mesmo com requests concorrentes. Complementado por redisGetLock para operações compostas (débito + criação de transaction + criação de bar order).
walletCreditAdd usa o mesmo padrão $inc mas sem a validação de $gte (crédito sempre pode entrar).
5.3 packages/bar-item/
Responsabilidade: Modelo e operações de itens do cardápio de bar.
packages/bar-item/
├── src/
│ ├── barItemModel.ts
│ ├── barItemCreate.ts
│ ├── barItemUpdate.ts
│ ├── barItemRemove.ts
│ ├── barItemsGet.ts
│ ├── fixture/
│ │ ├── barItemCreateFixture.ts
│ │ └── index.ts
│ └── index.ts
├── package.json
└── tsconfig.json
BarItemModel:
| Campo | Tipo | Descrição |
|---|---|---|
_id | ObjectId | PK |
eventId | ObjectId → Event | Evento dono (indexado) |
producerId | ObjectId → Producer | Denormalizado (indexado) |
name | String | Nome do item (ex: "Cerveja Heineken 600ml") |
description | String | null | Descrição opcional |
category | BAR_ITEM_CATEGORY_ENUM | BEER, DRINK, COCKTAIL, WATER, SOFT_DRINK, FOOD, SNACK, OTHER |
price | Number | Preço em centavos |
image | String | null | URL S3 |
status | BAR_ITEM_STATUS_ENUM | ACTIVE, INACTIVE, OUT_OF_STOCK |
stock | Number | Quantidade em estoque |
stockAlert | Number | Limite para alerta de estoque baixo |
unlimitedStock | Boolean | Se true, ignora controle de estoque |
sortOrder | Number | Ordenação no cardápio |
createdBy | ObjectId → Employer | Quem criou |
removedAt | Date | null | Soft delete |
createdAt | Date | Timestamp |
updatedAt | Date | Timestamp |
Indexes:
eventId+status+removedAtproducerId+removedAtcategory+eventId
5.4 packages/bar-order/
Responsabilidade: Modelo e operações de pedidos do bar (venda no PDV).
packages/bar-order/
├── src/
│ ├── barOrderModel.ts
│ ├── barOrderCreate.ts
│ ├── barOrderCancel.ts
│ ├── barOrdersGet.ts
│ ├── barOrderAnalytics.ts
│ ├── item/
│ │ └── barOrderItemSchema.ts
│ ├── jobs/
│ │ ├── jobBarStockUpdate.ts
│ │ └── jobBarSalesSnapshot.ts
│ ├── fixture/
│ │ ├── barOrderCreateFixture.ts
│ │ └── index.ts
│ └── index.ts
├── package.json
└── tsconfig.json
BarOrderModel:
| Campo | Tipo | Descrição |
|---|---|---|
_id | ObjectId | PK |
nanoId | String | ID curto para recibo (nanoid 8) |
eventId | ObjectId → Event | Evento (indexado) |
producerId | ObjectId → Producer | Denormalizado (indexado) |
userId | ObjectId → User | Consumidor (indexado) |
walletId | ObjectId → Wallet | Wallet debitada (indexado) |
transactionId | ObjectId → Transaction | Transaction do ledger (indexado) |
barStationId | ObjectId | Estação/ponto de bar |
items | BarOrderItemSchema[] | Itens do pedido |
totalValue | Number | Valor total em centavos |
fee | Number | Taxa Nittio sobre a venda |
status | BAR_ORDER_STATUS_ENUM | COMPLETED, CANCELLED |
operatedBy | ObjectId → Employer | Operador que vendeu (indexado) |
cancelledBy | ObjectId | null → Employer | Quem cancelou |
cancelledAt | Date | null | Quando foi cancelado |
cancelReason | String | null | Motivo do cancelamento |
removedAt | Date | null | Soft delete |
createdAt | Date | Timestamp (hora da venda) |
updatedAt | Date | Timestamp |
BarOrderItemSchema (embedded):
| Campo | Tipo | Descrição |
|---|---|---|
barItemId | ObjectId → BarItem | Referência ao item |
name | String | Denormalizado |
quantity | Number | Quantidade |
unitPrice | Number | Preço unitário em centavos |
totalPrice | Number | quantity * unitPrice |
Indexes:
eventId+createdAteventId+status+removedAtoperatedBy+eventId+createdAtuserId+eventIdwalletIdtransactionId
5.5 packages/bar-stock-log/
Responsabilidade: Log de movimentações de estoque para auditoria.
packages/bar-stock-log/
├── src/
│ ├── barStockLogModel.ts
│ ├── barStockLogCreate.ts
│ ├── barStockLogsGet.ts
│ ├── fixture/
│ │ └── barStockLogCreateFixture.ts
│ └── index.ts
├── package.json
└── tsconfig.json
BarStockLogModel:
| Campo | Tipo | Descrição |
|---|---|---|
_id | ObjectId | PK |
eventId | ObjectId → Event | Evento (indexado) |
barItemId | ObjectId → BarItem | Item afetado (indexado) |
type | String | SALE, CANCELLATION, ADJUSTMENT, INITIAL |
quantity | Number | Positivo = entrada, negativo = saída |
stockAfter | Number | Estoque após movimentação |
barOrderId | ObjectId | null | Se originado de uma venda |
operatedBy | ObjectId → Employer | Quem fez a movimentação |
reason | String | null | Motivo (para ajustes manuais) |
removedAt | Date | null | Soft delete |
createdAt | Date | Timestamp |
Indexes:
barItemId+createdAteventId+type+createdAt
6. Novos Handlers, Rotas e Zod Schemas
6.1 apps/hub-api/ — Rotas do produtor/operador
Bar Item (Cardápio)
| Método | Path | Handler | Descrição |
|---|---|---|---|
| GET | /event/:eventId/bar/items | handleEventBarItemsGet | Listar cardápio |
| POST | /event/:eventId/bar/item | handleEventBarItemPost | Criar item |
| PUT | /event/:eventId/bar/item/:id | handleEventBarItemPut | Editar item |
| DELETE | /event/:eventId/bar/item/:id | handleEventBarItemDelete | Remover item |
| PUT | /event/:eventId/bar/item/:id/stock | handleEventBarItemStockPut | Ajuste manual de estoque |
Bar Station (Estação de bar)
| Método | Path | Handler | Descrição |
|---|---|---|---|
| GET | /event/:eventId/bar/stations | handleEventBarStationsGet | Listar estações |
| POST | /event/:eventId/bar/station | handleEventBarStationPost | Criar estação |
| PUT | /event/:eventId/bar/station/:id | handleEventBarStationPut | Editar estação |
| DELETE | /event/:eventId/bar/station/:id | handleEventBarStationDelete | Remover estação |
Bar PDV (Ponto de Venda — tela do operador)
| Método | Path | Handler | Descrição |
|---|---|---|---|
| GET | /event/:eventId/bar/pdv/items | handleEventBarPdvItemsGet | Cardápio ativo (para tela do PDV) |
| POST | /event/:eventId/bar/pdv/scan | handleEventBarPdvScanPost | Escanear QR da wallet → retorna user + saldo |
| POST | /event/:eventId/bar/pdv/order | handleEventBarPdvOrderPost | Criar venda (debita wallet + cria bar order + transaction) |
| POST | /event/:eventId/bar/pdv/order/:id/cancel | handleEventBarPdvOrderCancelPost | Cancelar venda (estorna wallet + estoque + transaction) |
Bar Analytics / Relatórios
| Método | Path | Handler | Descrição |
|---|---|---|---|
| GET | /event/:eventId/bar/analytics | handleEventBarAnalyticsGet | Dashboard de vendas |
| GET | /event/:eventId/bar/analytics/products | handleEventBarAnalyticsProductsGet | Vendas por produto |
| GET | /event/:eventId/bar/analytics/operators | handleEventBarAnalyticsOperatorsGet | Vendas por operador |
| GET | /event/:eventId/bar/analytics/hourly | handleEventBarAnalyticsHourlyGet | Vendas por hora |
| GET | /event/:eventId/bar/orders | handleEventBarOrdersGet | Lista de pedidos de bar |
| GET | /event/:eventId/bar/orders/:id | handleEventBarOrderGet | Detalhe de pedido |
| GET | /event/:eventId/bar/stock/logs | handleEventBarStockLogsGet | Histórico de movimentações |
Bar Withdrawal (Saque de receita do bar)
| Método | Path | Handler | Descrição |
|---|---|---|---|
| GET | /event/:eventId/bar/withdrawal/available | handleEventBarWithdrawalAvailableGet | Valor disponível |
| POST | /event/:eventId/bar/withdrawal | handleEventBarWithdrawalPost | Solicitar saque |
Bar Settings
| Método | Path | Handler | Descrição |
|---|---|---|---|
| GET | /event/:eventId/bar/settings | handleEventBarSettingsGet | Obter configurações |
| PUT | /event/:eventId/bar/settings | handleEventBarSettingsPut | Atualizar configurações |
Novos Zod schemas em apps/hub-api/src/event/bar/
barItemZod.ts → barItemZodCreate, barItemZodUpdate, barItemZodRead
barOrderZod.ts → barOrderZodCreate, barOrderZodRead
barStationZod.ts → barStationZodCreate, barStationZodUpdate, barStationZodRead
barAnalyticsZod.ts → barAnalyticsZodRead, barAnalyticsProductsZodRead
barSettingsZod.ts → barSettingsZodUpdate, barSettingsZodRead
barStockLogZod.ts → barStockLogZodRead
6.2 apps/app-api/ — Rotas do participante
Wallet
| Método | Path | Handler | Descrição |
|---|---|---|---|
| GET | /wallet/me | handleWalletMeGet | Dados da wallet + saldo + QR |
| POST | /wallet | handleWalletPost | Criar wallet (se não existe) |
| POST | /wallet/recharge | handleWalletRechargePost | Iniciar recarga (cria cart + order) |
| POST | /wallet/withdraw | handleWalletWithdrawPost | Sacar saldo da wallet via PIX |
| GET | /wallet/transactions | handleWalletTransactionsGet | Extrato de transações da wallet |
| GET | /wallet/qrcode | handleWalletQrcodeGet | QR code da wallet (imagem ou data URL) |
Nota sobre a mudança: As rotas de wallet agora são top-level (/wallet/*), não aninhadas em /event/:eventId/wallet/*, porque a wallet é global do usuário. O eventId só aparece nos dados da Transaction.
Novos Zod schemas em apps/app-api/src/wallet/
walletZod.ts → walletZodRead, walletZodRecharge, walletZodWithdraw
transactionZod.ts → transactionZodRead
6.3 Estrutura de arquivos por rota
Seguindo padrões existentes do codebase:
apps/hub-api/src/event/bar/
├── barRoutes.ts
├── item/
│ ├── handleEventBarItemsGet.ts
│ ├── handleEventBarItemPost.ts
│ ├── handleEventBarItemPut.ts
│ ├── handleEventBarItemDelete.ts
│ ├── handleEventBarItemStockPut.ts
│ └── barItemZod.ts
├── station/
│ ├── handleEventBarStationsGet.ts
│ ├── handleEventBarStationPost.ts
│ ├── handleEventBarStationPut.ts
│ ├── handleEventBarStationDelete.ts
│ └── barStationZod.ts
├── pdv/
│ ├── handleEventBarPdvItemsGet.ts
│ ├── handleEventBarPdvScanPost.ts
│ ├── handleEventBarPdvOrderPost.ts
│ ├── handleEventBarPdvOrderCancelPost.ts
│ └── barPdvZod.ts
├── analytics/
│ ├── handleEventBarAnalyticsGet.ts
│ ├── handleEventBarAnalyticsProductsGet.ts
│ ├── handleEventBarAnalyticsOperatorsGet.ts
│ ├── handleEventBarAnalyticsHourlyGet.ts
│ └── barAnalyticsZod.ts
├── order/
│ ├── handleEventBarOrdersGet.ts
│ ├── handleEventBarOrderGet.ts
│ └── barOrderZod.ts
├── stock/
│ ├── handleEventBarStockLogsGet.ts
│ └── barStockLogZod.ts
├── withdrawal/
│ ├── handleEventBarWithdrawalAvailableGet.ts
│ ├── handleEventBarWithdrawalPost.ts
│ └── barWithdrawalZod.ts
└── settings/
├── handleEventBarSettingsGet.ts
├── handleEventBarSettingsPut.ts
└── barSettingsZod.ts
apps/app-api/src/wallet/
├── walletRoutes.ts
├── handleWalletMeGet.ts
├── handleWalletPost.ts
├── handleWalletRechargePost.ts
├── handleWalletWithdrawPost.ts
├── handleWalletTransactionsGet.ts
├── handleWalletQrcodeGet.ts
├── walletZod.ts
└── transactionZod.ts
7. Jobs e Background Processing
7.1 Novos Jobs (registrar em @nittio/jobs)
| Job Name | Trigger | Ação |
|---|---|---|
BAR_STOCK_UPDATE | Após barOrderCreate | Decrementa estoque dos itens, cria stock logs |
BAR_LOW_STOCK_ALERT | Scheduler (5 min) | Verifica itens abaixo do stockAlert, notifica Slack |
BAR_SALES_SNAPSHOT | Scheduler (15 min) | Agrega vendas para dashboard real-time |
WALLET_RECHARGE_NOTIFICATION | Após orderPaid (wallet) | Envia push/email confirmando recarga |
WALLET_WITHDRAW_PROCESS | Após wallet withdraw | Processa PIX de saque para a conta do user |
BAR_ORDER_CANCEL_STOCK_RETURN | Após barOrder cancel | Retorna estoque dos itens cancelados |
BAR_EVENT_CLOSE_REPORT | Scheduler (pós-evento) | Gera relatório final do bar do evento |
7.2 Novos Crons (registrar em apps/scheduler/)
| Cron | Frequência | Ação |
|---|---|---|
BAR_LOW_STOCK_ALERT | */5 * * * * (5min) | Enqueue job para eventos em andamento |
BAR_SALES_SNAPSHOT | */15 * * * * (15min) | Enqueue snapshot para eventos ativos |
BAR_EVENT_CLOSE | 0 6 * * * (6h) | Fechar bar de eventos que terminaram |
Nota: Não existe mais WALLET_AUTO_REFUND — wallet é global, saldo persiste. O user saca quando quiser.
7.3 Worker — Novos handlers em apps/worker/
apps/worker/src/
├── bar/
│ ├── jobBarStockUpdate.ts
│ ├── jobBarLowStockAlert.ts
│ ├── jobBarSalesSnapshot.ts
│ ├── jobBarOrderCancelStockReturn.ts
│ └── jobBarEventCloseReport.ts
├── wallet/
│ ├── jobWalletRechargeNotification.ts
│ └── jobWalletWithdrawProcess.ts
8. Modelo de Taxas e Monetização
8.1 Estrutura de taxas proposta
┌─────────────────────────────────────────────────────┐
│ VENDA NO BAR: R$ 20,00 │
│ │
│ Consumidor paga: R$ 20,00 (do saldo da wallet) │
│ │
│ Distribuição: │
│ ├── Produtor recebe: R$ 19,00 (95%) │
│ ├── Nittio taxa bar: R$ 1,00 (5%) │
│ │ ├── PagBank fee: R$ 0,40 (~2% do PIX) │
│ │ └── Nittio líq.: R$ 0,60 (~3%) │
│ └──────────────────────────────────────────────────│
│ │
│ Taxa PagBank é absorvida NO MOMENTO DA RECARGA: │
│ Recarga R$ 100 PIX → PagBank cobra ~R$ 1,99 │
│ Recarga R$ 100 CC → PagBank cobra ~R$ 4,99 │
│ │
│ Taxa do bar Nittio → cobrada NA VENDA (sobre valor │
│ bruto) e retida no saque do produtor │
└─────────────────────────────────────────────────────┘
8.2 Configurabilidade de taxa
Assim como event.feePercentage e producer.feePercentage já existem para ingressos:
event.barSettings.barFeePercentage— taxa Nittio sobre vendas de bar naquele evento (padrão: 5%)producer.feeBarPercentage— override no nível do produtor (se negociado comercialmente)
A taxa efetiva: event.barSettings.barFeePercentage || producer.feeBarPercentage || 5
8.3 Momento da cobrança
- Recarga de wallet — o PagBank cobra a taxa de gateway (PIX ~2%, CC ~4.99%). O valor integral vai para o saldo do user
- Venda no bar — a taxa Nittio é calculada sobre o valor da venda.
Transaction.platformFeeregistra o valor. O produtor vê nos relatórios o valor bruto e a taxa - Saque do produtor — o valor sacável = sum(Transaction.netValue where producerId + eventId) - saques já realizados
- Saque do user — o saldo integral da wallet pode ser sacado pelo user a qualquer momento (sem taxa no MVP, avaliar no futuro)
8.4 Fluxo financeiro detalhado
┌──────────────┐
│ Consumidor │
└──────┬───────┘
│
1. PIX R$100
│
┌──────▼───────┐
│ PagBank │ ← cobra R$1,99 (fee gateway)
└──────┬───────┘
│
2. R$100 no saldo wallet
Transaction: WALLET_RECHARGE, value=10000, IN
│
┌──────▼───────┐
│ Wallet │ balance = R$100,00
└──────┬───────┘
│
3. Compra cerveja R$20 no Evento X
Transaction: WALLET_DEBIT_BAR, value=2000,
platformFee=100, netValue=1900,
eventId=X, producerId=Y, OUT
│
Wallet balance = R$80,00
│
┌───────────┴────────────┐
│ │
┌───────▼──────┐ ┌───────▼──────┐
│ Produtor Y │ │ Nittio │
│ sacável: │ │ receita: │
│ R$ 19,00 │ │ R$ 1,00 │
└──────────────┘ └──────────────┘
8.5 Impacto fiscal
No MVP, o Nittio NÃO emite nota fiscal pelo bar. O produtor é o responsável fiscal pela operação de A&B do seu evento. O Nittio atua como intermediador de pagamento.
Considerações:
- ISS — incide sobre a taxa de serviço do Nittio (intermediação), não sobre a venda de A&B
- ICMS — incide sobre a venda de mercadorias (responsabilidade do produtor/CNPJ do bar)
- NFC-e — obrigatória para venda de alimentos e bebidas no varejo; no MVP, é responsabilidade do produtor emitir. Em V2, podemos integrar com APIs de NFC-e (Focus NFe, Omie, etc.)
- Enquadramento do Nittio — marketplace/intermediador de pagamento, similar a iFood/Rappi
- Wallet como conta de pagamento — atenção à regulação do Banco Central sobre contas de pagamento pré-pagas. No MVP, a wallet é "crédito para consumo na plataforma", não uma conta de pagamento regulada. Consultar jurídico para garantir enquadramento adequado
Saque da wallet pelo user:
- O saque é uma devolução do saldo não consumido. Não é uma transferência entre contas
- O Nittio atua fazendo um PIX de volta ao CPF cadastrado do user
- Futuramente, se o volume justificar, pode-se obter licença de Instituição de Pagamento junto ao BACEN
Recomendação: Incluir nos termos de uso que o produtor é responsável pela emissão de documentos fiscais referentes à venda de A&B. O Nittio fornece relatórios detalhados via Transaction para facilitar a apuração.
9. Fluxos Detalhados
9.1 Fluxo de Recarga de Wallet
Participante app-api PagBank
│ │ │
│ POST /wallet/recharge │ │
│ { value: 5000, │ │
│ paymentMethod: PIX } │ │
│─────────────────────────▶│ │
│ │ │
│ 1. Busca/cria Wallet (lazy create) │
│ 2. Cria Cart (walletRecharge) │
│ 3. Cria Order (PENDING_PAYMENT) │
│ 4. Chama baas.pixOrderCreate │
│ │─────────────────────────▶│
│ │◀─────────────────────────│
│ │ { qrCode, emvCode } │
│ │ │
│ { order, qrCode } │ │
│◀─────────────────────────│ │
│ │ │
│ [Paga PIX no banco] │ │
│ │ │
│ │ POST /pagbank/webhook │
│ │◀─────────────────────────│
│ │ │
│ 5. orderPaid → branch walletRecharge │
│ 6. walletCreditAdd (+5000) │
│ 7. transactionCreate(WALLET_RECHARGE, │
│ value=5000, direction=IN, │
│ walletId, orderId) │
│ 8. Enqueue WALLET_RECHARGE_NOTIFICATION │
│ │ │
│ [Push notification] │ │
│◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │
9.2 Fluxo de Venda no Bar (PDV)
Operador (hub) hub-api MongoDB
│ │ │
│ POST /event/:id/bar │ │
│ /pdv/scan │ │
│ { walletNanoId: "X8K2" } │
│────────────────────────▶│ │
│ │ │
│ Busca Wallet por nanoId │
│ │────────────────────────▶│
│ │ Busca User via walletId│
│ │────────────────────────▶│
│ │◀────────────────────────│
│ │ │
│ { user: { name }, │ │
│ wallet: { │ │
│ balance: 8000, │ │
│ nanoId: "X8K2" │ │
│ }} │ │
│◀────────────────────────│ │
│ │ │
│ [Seleciona itens] │ │
│ │ │
│ POST /event/:id/bar │ │
│ /pdv/order │ │
│ { walletId, items: [ │ │
│ { barItemId, qty: 2 }│ │
│ ]} │ │
│────────────────────────▶│ │
│ │ │
│ 1. Valida wallet.balance >= total │
│ 2. Redis lock (walletId) │
│ 3. walletDebit ($inc -total, atômico) │
│ 4. transactionCreate(WALLET_DEBIT_BAR, │
│ value, platformFee, netValue, │
│ walletId, eventId, producerId, │
│ direction=OUT) │
│ 5. barOrderCreate(COMPLETED, │
│ transactionId, items, fee) │
│ 6. Enqueue BAR_STOCK_UPDATE │
│ 7. Unlock │
│ │ │
│ { barOrder, wallet: │ │
│ { balance: 4000 } } │ │
│◀────────────────────────│ │
9.3 Fluxo de Saque da Wallet (User withdraw)
Participante app-api Worker
│ │ │
│ POST /wallet/withdraw│ │
│ { value: 3000 } │ │
│──────────────────────▶│ │
│ │ │
│ 1. Valida wallet.balance >= value │
│ 2. Redis lock (walletId) │
│ 3. walletDebit ($inc -3000) │
│ 4. transactionCreate(WALLET_WITHDRAW,│
│ value=3000, direction=OUT, │
│ status=PENDING) │
│ 5. Enqueue WALLET_WITHDRAW_PROCESS │
│ 6. Unlock │
│ │ │
│ { success, tx } │ │
│◀──────────────────────│ │
│ │ │
│ │ Worker processa │
│ │─────────────────────────▶│
│ │ │
│ │ 7. PIX para CPF do user │
│ │ via PagBank │
│ │ 8. Update Transaction │
│ │ status=COMPLETED │
│ │ │
│ [Recebe PIX] │ │
│◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│ │
9.4 Fluxo de Recarga no Momento do Ingresso
Participante app-api
│ │
│ POST /event/:id/cart │
│ { tickets: [...], │
│ walletRechargeValue: │
│ 5000 } │
│───────────────────────▶│
│ │
│ 1. Cria Cart com tickets +
│ walletRechargeValue = 5000
│ │
│ POST /cart/:id/order │
│ { paymentMethod: PIX }│
│───────────────────────▶│
│ │
│ 2. orderCartMapper calcula:
│ tickets total + walletRechargeValue
│ items: [...tickets, {
│ type: 'walletRecharge',
│ value: 5000
│ }]
│ 3. Cria Order com valor combinado
│ 4. Gera PIX
│ │
│ [Paga PIX total] │
│ │
│ [Webhook / Polling] │
│ 5. orderPaid → orderPaidEvent (tickets)
│ → transactionCreate(TICKET_PURCHASE)
│ 6. orderPaidWalletRecharge (credita wallet)
│ → transactionCreate(WALLET_RECHARGE)
│ │
9.5 Fluxo de Compra de Ingresso com Wallet como Pagamento (futuro imediato)
Com a wallet global e o enum ORDER_PAYMENT_METHOD_ENUM.WALLET, abre-se a possibilidade de pagar ingressos com saldo de wallet:
Participante app-api
│ │
│ POST /cart/:id/order │
│ { paymentMethod: │
│ WALLET } │
│───────────────────────▶│
│ │
│ 1. Valida wallet.balance >= order.value
│ 2. walletDebit
│ 3. transactionCreate(TICKET_PURCHASE,
│ direction=OUT, walletId, paymentMethod=WALLET)
│ 4. orderUpdate(status=PAID, paidAt=now,
│ paymentMethod=WALLET)
│ 5. orderPaidEvent (cria tickets)
│ │
│ { order, tickets } │
│◀───────────────────────│
Isso é uma extensão natural que a wallet global permite. A decisão de implementar no MVP ou V2 fica a critério do time, mas a arquitetura já suporta.
10. Telas e Interface (Hub Console + App Console)
10.1 Hub Console (Produtor/Operador)
10.1.1 Configuração do Bar (dentro de Event Settings)
- Toggle "Habilitar Bar" → mostra/esconde seção
- Campo taxa do bar (%) com default do produtor
- CRUD de estações de bar (nome, localização, status)
10.1.2 Cardápio (/event/:id/bar/items)
- Lista em grid/tabela com imagem, nome, categoria, preço, estoque, status
- Botão "Novo Item" → modal/drawer com form
- Edição inline de preço e estoque
- Filtros por categoria, status
- Drag-and-drop para reordenar
- Badge de "Estoque baixo" em vermelho
10.1.3 PDV — Tela do Operador (/event/:id/bar/pdv)
Esta é a tela mais crítica do MVP. Deve ser otimizada para velocidade.
Layout proposto:
┌────────────────────────────────────────────────────┐
│ [Escanear QR Wallet] │ Cliente: João Silva │
│ │ Saldo: R$ 80,00 │
├────────────────────────────────────────────────────┤
│ │
│ [Cervejas] [Drinks] [Águas] [Destilados] │
│ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ Hein │ │Brahma│ │ Skol │ │Corona│ │
│ │R$20 │ │R$15 │ │R$12 │ │R$25 │ │
│ │ [+] │ │ [+] │ │ [+] │ │ [+] │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │
├────────────────────────────────────────────────────┤
│ Carrinho: │
│ 2x Heineken 600ml ............ R$ 40,00 │
│ 1x Caipirinha ................ R$ 25,00 │
│ ────────── │
│ Total: R$ 65,00 │
│ │
│ [CANCELAR] [CONFIRMAR VENDA] │
└────────────────────────────────── ──────────────────┘
Requisitos da tela de PDV:
- Scan QR da WALLET (não do ticket) via câmera ou input manual de nanoId
- Grid de produtos por categoria com botões grandes (touch-friendly)
- Carrinho lateral com +/- por item
- Validação de saldo em tempo real (saldo atualizado após cada venda)
- Feedback sonoro e visual ao confirmar
- Funcionar em tablet (landscape) e celular (portrait)
- Sem navegação — tela full-screen dedicada ao operador
10.1.4 Dashboard de Vendas (/event/:id/bar/analytics)
- Receita total (bruta e líquida) — via
Transactionaggregation - Número de vendas
- Ticket médio
- Gráfico de vendas por hora (Recharts — já usado no hub-console)
- Ranking de produtos mais vendidos
- Ranking de operadores por volume
- Status de estoque (itens críticos)
10.1.5 Pedidos de Bar (/event/:id/bar/orders)
- Tabela paginada: nanoId, hora, operador, itens, valor, status
- Filtros por operador, status, período
- Ação de cancelamento (com motivo obrigatório)
- Export CSV
10.1.6 Estoque (/event/:id/bar/stock)
- Visão consolidada: item, estoque atual, vendido, ajustado
- Histórico de movimentações (stock logs)
- Ajuste manual com motivo obrigatório
- Alertas visuais para estoque baixo
10.2 App Console (Participante)
10.2.1 Wallet no Perfil (/wallet)
A wallet é uma seção de primeiro nível no app, acessível do menu principal (não apenas dentro de um evento).
- Saldo em destaque
- QR code da wallet (botão "Mostrar QR" que abre full-screen)
- Botão "Recarregar"
- Botão "Sacar"
- Link para extrato
10.2.2 QR Code da Wallet (/wallet/qrcode)
- Tela full-screen com QR grande e brilho máximo (para facilitar scan no bar)
- Nome e nanoId abaixo do QR
- Saldo atual visível
- Botão "Recarregar" acessível da mesma tela
10.2.3 Tela de Recarga (/wallet/recharge)
- Input de valor (valores pré-definidos: R$50, R$100, R$200 + custom)
- Seleção PIX ou Cartão
- Fluxo padrão de checkout (já existe para ingressos)
- Confirmação com novo saldo
10.2.4 Extrato (/wallet/transactions)
- Lista cronológica: tipo (recarga/compra/saque), valor, data, detalhes
- Ícones diferentes por tipo de transaction
- Filtro por tipo e período
- Saldo atual no topo
- Cada item mostra contexto: "Bar - Evento X", "Recarga PIX", "Ingresso - Evento Y"
10.2.5 Saque (/wallet/withdraw)
- Input de valor (ou "Sacar tudo")
- Mostra CPF/chave PIX do user (confirmação)
- Prazo estimado (ex: "até 1 dia útil")
- Tela de confirmação
11. Modelo de Permissões
11.1 Novos grupos de employer
Adições ao EMPLOYER_GROUP_ENUM:
| Grupo | Permissões |
|---|---|
BAR_MANAGER | CRUD cardápio, estações, ajuste de estoque, cancelar vendas, ver analytics, configurar bar |
BAR_OPERATOR | Usar PDV (escanear, vender), ver estoque (read-only) |
11.2 Validação de acesso
- Handlers de bar verificam
employer.groupsparaBAR_MANAGERouBAR_OPERATOR - PDV só requer
BAR_OPERATOR(ouBAR_MANAGER) - Analytics e configuração requerem
BAR_MANAGER - Admin (
bko-api) tem acesso total (pós-MVP)
12. Normalização e Impacto de Longo Prazo
12.1 Transaction como spine financeiro
A introdução do Transaction model não é apenas para o bar — é uma normalização retroativa do financeiro da plataforma. A tabela abaixo mostra como as queries atuais migram:
| Query atual | Como é feito hoje | Como ficaria com Transaction |
|---|---|---|
| Faturamento do evento X | Order.find({eventId, PAID}).sum(originalValue) | Transaction.find({eventId, type: [TICKET_*, BAR_*]}).sum(netValue) |
| Faturamento do produtor Y no mês | Order.find({producerId, PAID, paidAt ∈ range}) | Transaction.find({producerId, createdAt ∈ range}).sum(netValue) |
| Receita plataforma (Nittio) | Não existe query simples hoje | Transaction.find({status: COMPLETED}).sum(platformFee) |
| Extrato financeiro do user | Não existe | Transaction.find({userId}).sort(createdAt) |
| Valor sacável do produtor no evento | Order.find + Withdrawal.find (complexo) | Transaction.find({producerId, eventId, type: [TICKET_*, BAR_*]}).sum(netValue) - withdrawals |
| Conciliação gateway vs interno | Não existe | Transaction.find({orderId}).gatewayFee vs PagBank report |
12.2 Migração dos dados existentes
A introdução é não-breaking. Os flows existentes continuam funcionando via Order. A Transaction é aditiva: cada novo orderPaid passa a também criar Transaction. Orders históricas podem ser migradas para Transaction via script (backfill), mas não é obrigatório no MVP.
Script de backfill sugerido (pós-MVP):
script/backfillTransactions.script.ts
→ Para cada Order com status PAID, cria Transaction correspondente
→ Para cada Withdrawal com status WITHDRAWN, cria Transaction
12.3 Wallet global — Implicações de longo prazo
| Aspecto | Impacto |
|---|---|
| Wallet como payment method | Permite pagar ingressos, produtos, planos via wallet (não só bar) |
| Fidelização | User com saldo "preso" no Nittio tem mais propensão a comprar mais |
| Cross-selling | "Você tem R$30 na wallet, ingresso custa R$25" — conversão alta |
| Regulação | Se volume crescer, avaliar licença de Instituição de Pagamento (BACEN) |
| Float | Dinheiro na wallet antes de ser gasto gera float para o Nittio |
| Rendimento | V3: oferecer rendimento sobre saldo parado (cashback / CDI) |
12.4 O que NÃO normalizar agora
- Order model — NÃO mudar. Order continua sendo checkout/gateway state. Transaction é o ledger
- Ticket/Voucher — NÃO referencia Transaction diretamente. A relação é
Ticket → Order → Transaction - Withdrawal — NÃO substituir por Transaction. Withdrawal tem seu próprio lifecycle (REQUESTED → WITHDRAWN/REJECTED). A Transaction registra o momento do WITHDRAWN
13. Perguntas em Aberto (Decision Points)
13.1 Wallet como payment method para ingressos no MVP?
Recomendação: V2. A arquitetura suporta, mas implementar no MVP adiciona escopo a handleCartOrderPost (novo branch WALLET). Focar no bar primeiro.
13.2 Saque de wallet tem taxa?
Recomendação: Sem taxa no MVP. Avaliar taxa (ex: R$1 fixo) se volume de saques for muito alto ou se users estiverem "arbitrando" (recarga CC com cashback → saque PIX).
13.3 Saque de wallet tem valor mínimo?
Recomendação: Mínimo R$ 5,00 para evitar micro-transações que custam proporcionalmente mais para processar.
13.4 Recarga tem valor mínimo/máximo?
Recomendação: Mínimo R$ 10,00, máximo R$ 1.000,00.
13.5 O operador vê o saldo do cliente?
Recomendação: Sim. Necessário para evitar tentar vender mais do que o cliente tem. Mostra só nome + saldo, nada mais.
13.6 Cancelamento de venda com devolução de estoque?
Recomendação: Sim, até 15 minutos. Após isso, só via BAR_MANAGER. Estoque retornado automaticamente. Wallet creditada de volta. Transaction de REFUND criada.
13.7 E se o user não tem wallet no momento do scan?
Recomendação: O scan retorna "Wallet não encontrada" e sugere ao consumidor criar/recarregar via app. O operador NÃO pode criar wallet.
13.8 Offline mode?
Não no MVP. Exige complexidade significativa (sync, conflitos). V3 com PWA + IndexedDB.
13.9 Backfill de Transactions para Orders históricas?
Recomendação: Não no MVP. Pode ser feito como script pós-MVP. No MVP, só novas operações criam Transactions.
14. Estimativa de Esforço
14.1 Breakdown por módulo
| Módulo | Estimativa | Detalhamento |
|---|---|---|
| packages/transaction/ | 4-5 dias | Model, create, queries by user/producer/event, fixtures, tests |
| packages/wallet/ | 5-7 dias | Model, CRUD, debit/credit, withdraw, QR, fixtures, tests |
| packages/bar-item/ | 3-4 dias | Model, CRUD, fixtures, tests |
| packages/bar-order/ | 4-5 dias | Model, create, cancel, analytics, fixtures, tests |
| packages/bar-stock-log/ | 2-3 dias | Model, create, list, fixtures, tests |
| Enums e adaptações existentes | 3-4 dias | Novos enums, adaptar orderPaid (+ transaction hooks), orderRefund, cart, event, user models |
| hub-api: Bar routes + handlers | 8-10 dias | ~25 handlers + Zod schemas + mappers |
| hub-api: PDV routes + handlers | 4-5 dias | Scan (by wallet nanoId), order, cancel + performance |
| app-api: Wallet routes + handlers | 5-6 dias | Create, recharge, withdraw, transactions, QR code |
| Worker: novos jobs | 4-5 dias | 7 jobs + testes |
| Scheduler: novos crons | 1-2 dias | 3 crons |
| hub-console: Configuração + Cardápio | 5-6 dias | UI settings, CRUD cardápio, estações |
| hub-console: PDV | 8-10 dias | Tela completa, scan QR wallet, carrinho, UX |
| hub-console: Analytics + Relatórios | 5-6 dias | Dashboard, gráficos, tabelas, export (via Transaction) |
| hub-console: Estoque + Pedidos | 3-4 dias | Listagens, filtros, ajustes |
| app-console: Wallet + Recarga + QR | 6-8 dias | UI wallet, QR full-screen, recarga, extrato, saque |
| app-webview: adaptações | 2-3 dias | QR display otimizado, deeplinks |
| Testes de integração end-to-end | 5-7 dias | Fluxos completos, edge cases, concorrência |
| QA e bug fixes | 5-7 dias | Testes manuais, ajustes |
14.2 Total estimado
| Cenário | Duração total | Equipe |
|---|---|---|
| 1 dev | 14-17 semanas | Full-stack sênior |
| 2 devs | 8-10 semanas | 1 backend + 1 frontend |
| 3 devs | 6-8 semanas | 1 backend + 1 frontend + 1 full |
Nota: ~2 semanas a mais que a v1 do RFC devido ao Transaction model + wallet global + wallet QR + saque.
14.3 Fases sugeridas
Fase 1 — Foundation (semanas 1-4):
packages/transaction/(primeiro, pois é o spine)packages/wallet/(depende de transaction)packages/bar-item/,packages/bar-stock-log/,packages/bar-order/(dependem de transaction e wallet)- Enums e adaptações em código existente (orderPaid hooks, orderRefund hook)
- Testes unitários dos packages
Fase 2 — APIs (semanas 4-6):
- hub-api: todos os handlers de bar
- app-api: handlers de wallet (create, recharge, withdraw, transactions, QR)
- Worker jobs + scheduler crons
- Testes de integração
Fase 3 — Frontend Hub (semanas 6-9):
- hub-console: configuração, cardápio, estações
- hub-console: PDV (tela mais complexa — scan wallet QR)
- hub-console: analytics (usando Transaction aggregation), pedidos, estoque
Fase 4 — Frontend App + Polish (semanas 9-12):
- app-console: wallet section, QR code full-screen, recarga, extrato, saque
- app-webview: adaptações
- QA end-to-end
- Bug fixes e otimizações de performance
Fase 5 — Beta (semanas 12-14):
- Deploy em staging
- Teste com 1-2 produtores parceiros
- Iteração baseada em feedback
- Documentação
15. Riscos e Mitigações
| Risco | Impacto | Probabilidade | Mitigação |
|---|---|---|---|
| Latência no PDV — operação lenta mata o bar | Alto | Média | Redis cache, indexes otimizados, $inc atômico, debounce |
| Concorrência em wallet — race conditions em débito | Alto | Alta | $inc atômico MongoDB + redisGetLock, testes de stress |
| PagBank offline — gateway fora = sem recarga | Alto | Baixa | Cache de QR codes gerados, retry com backoff |
| Conectividade no evento — internet instável | Alto | Alta | PWA caching, operações otimistas (V2), alertas |
| Fraude de operador — vendas fantasma ou cancelamentos indevidos | Médio | Média | Audit log via Transaction, limites de cancelamento, relatório por operador |
| Escopo creep — features de V2 entrando no MVP | Médio | Alta | Scope freeze após aprovação do RFC, backlog separado |
| Fiscal — produtor não emite NFC-e | Médio | Alta | Termos de uso claros, relatórios via Transaction facilitando apuração |
| Regulação BACEN — wallet como conta de pagamento | Médio | Baixa | Enquadramento como "crédito para consumo", consultar jurídico |
| UX do PDV em celular — tela pequena | Médio | Média | Design responsivo, teste em devices reais, foco em tablet |
| Wallet fraud — recargas com cartão roubado | Alto | Média | Limites de recarga, 3DS obrigatório para CC, KYC via taxID |
16. Métricas de Sucesso do MVP
| Métrica | Target MVP |
|---|---|
| Tempo médio de venda no PDV | < 8 segundos |
| Uptime do sistema durante evento | > 99.5% |
| Taxa de adoção (eventos com bar/total) | > 10% em 3 meses |
| Volume de recargas por evento | > R$ 5.000 médio |
| NPS do produtor (feature bar) | > 7 |
| NPS do consumidor (wallet) | > 7 |
| Receita incremental Nittio (3 meses) | > R$ 50.000 |
| Wallets criadas vs. users ativos | > 20% em 3 meses |
| Taxa de saque de saldo | < 30% do carregado |
17. Checklist de Arquivos Tocados (Resumo)
Novos Packages (5)
packages/transaction/(7 arquivos + fixtures)packages/wallet/(8 arquivos + fixtures)packages/bar-item/(6 arquivos + fixtures)packages/bar-order/(7 arquivos + fixtures)packages/bar-stock-log/(4 arquivos + fixtures)
Novos Enums (7 arquivos)
packages/enum/src/barItemCategoryEnum.tspackages/enum/src/barItemStatusEnum.tspackages/enum/src/barOrderStatusEnum.tspackages/enum/src/barStationStatusEnum.tspackages/enum/src/transactionTypeEnum.tspackages/enum/src/transactionStatusEnum.tspackages/enum/src/walletStatusEnum.ts
Enums Modificados (7 arquivos)
packages/enum/src/producerFeaturesEnum.ts(+BAR)packages/enum/src/orderTypeEnum.ts(+walletRecharge)packages/enum/src/orderPaymentMethodEnum.ts(+WALLET)packages/enum/src/withdrawalReferenceEnum.ts(+BAR)packages/enum/src/notificationTypeEnum.ts(+WALLET**, BAR**)packages/enum/src/employerGroupEnum.ts(+BAR_OPERATOR, BAR_MANAGER)packages/enum/src/employerTutorialEnum.ts(+BAR_SETUP)
Packages Modificados (5)
packages/order/src/orderPaid.ts(novo branch wallet + transactionCreate hook em todos os branches)packages/order/src/orderRefund.ts(transactionCreate hook)packages/event/src/eventModel.ts(barSettings embedded schema)packages/event/src/withdrawal/eventWithdrawalAvailableValueGet.ts(incluir bar revenue via Transaction)packages/cart/src/cartModel.ts(walletRechargeValue field)
Novos Handlers hub-api (~30 arquivos)
apps/hub-api/src/event/bar/**(item, station, pdv, analytics, order, stock, withdrawal, settings)
Novos Handlers app-api (~7 arquivos)
apps/app-api/src/wallet/**(wallet, recharge, withdraw, transactions, QR)
Novos Jobs worker (7 arquivos)
apps/worker/src/bar/**apps/worker/src/wallet/**
Novos Crons scheduler (3 registros)
apps/scheduler/src/scheduler.ts
Frontend hub-console (~15-20 páginas/componentes)
- Configuração bar, cardápio CRUD, estações, PDV, analytics, pedidos, estoque, saque
Frontend app-console (~10-12 páginas/componentes)
- Wallet section, QR code full-screen, recarga, extrato, saque, transaction details
Total estimado: ~130-150 novos arquivos
18. Glossário
| Termo | Definição |
|---|---|
| Wallet | Carteira digital global do usuário. Saldo em centavos. QR code próprio |
| Transaction | Registro no ledger financeiro. Toda movimentação de dinheiro gera uma Transaction |
| Bar Item | Produto vendido no bar (cerveja, drink, água, etc.) |
| Bar Order | Pedido/venda realizada no bar (conjunto de bar items) |
| Bar Station | Ponto físico de bar no evento (ex: "Bar Principal", "Bar VIP") |
| PDV | Ponto de Venda — tela do operador para registrar vendas |
| Recarga | Ato de adicionar saldo à wallet via PIX ou cartão |
| Saque | Retirada de saldo da wallet pelo user (recebe PIX) |
| Stock Log | Registro de movimentação de estoque (entrada/saída) |
| Operador | Employer com grupo BAR_OPERATOR que opera o PDV |
| Fee bar | Taxa percentual do Nittio sobre cada venda no bar |
| nanoId | Identificador curto e único usado em QR codes (wallet, ticket, bar order) |
19. Referências
- Codebase atual: Análise completa de 47 packages, 11 apps, 22 Mongoose models
- PagBank API: PIX + Credit Card já integrados via
@nittio/baas - Benchmarks: Zig, Let's, Tag&Go (cashless brasileiro)
- Legislação: Lei do SAC, CDC, LC 116/2003 (ISS), Convênio ICMS
- Regulação: Lei 12.865/2013 (arranjos de pagamento), Resolução BCB 80/2021
20. Próximos Passos
- Review deste RFC com stakeholders (produto, comercial, jurídico)
- Consulta jurídica sobre enquadramento da wallet (conta de pagamento vs crédito para consumo)
- Validar modelo de taxa com time comercial (3-5% é competitivo?)
- Protótipo do PDV — mockup interativo da tela mais crítica
- Definir produtores beta — 2-3 parceiros para teste piloto
- Decidir scope do MVP — wallet como payment method para ingressos entra ou não?
- Kickoff de desenvolvimento — Fase 1 (foundation: transaction + wallet)