Skip to main content

RFC: Bar Integrado — MVP

CampoValor
AutorEngenharia Nittio
StatusDraft v2
Data2026-03-30
EscopoMVP — 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:

  1. O participante já tem conta, CPF, dados de pagamento
  2. O produtor já gerencia eventos, employers, financeiro e saques
  3. A infraestrutura de pagamento (PagBank PIX/CC), filas (RabbitMQ), notificações, e frontend (Next.js + Expo) já existe
  4. O modelo de receita se multiplica: taxa sobre ingresso + taxa sobre consumação

1.3 Benchmarks do mercado brasileiro

SoluçãoModeloTaxa barObservações
ZigCashless + bar3-5% + setupLíder cashless, hardware próprio
Let'sCashless + bar~3.5%Foco em festas grandes
EventbriteSó ingressoNão tem barNão compete nesse segmento
SymplaSó ingressoNão tem barComeçou a testar cashless 2025
Nittio (hoje)Ingresso + socialN/AOportunidade de expansão
Nittio (MVP)Ingresso + bar3-5% propostaIntegraçã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

FeatureMVPV2V3
Cardápio digital (CRUD)X
Categorias de produto de barX
Controle de estoqueX
Wallet global do usuárioX
QR code próprio da walletX
Recarga via PIXX
Recarga via cartão de créditoX
Recarga no momento do ingressoX
Tela de PDV para operadorX
Venda e débito em walletX
Dashboard de vendas real-timeX
Relatórios (por produto/hora)X
Saque de receita de barX
Cálculo de taxa Nittio no barX
Transaction model unificadoX
Saque de saldo wallet (withdraw)X
NFC/pulseira cashlessX
Comanda por mesaX
Multi-bar (vários pontos)X
Integração fiscal (NFC-e)X
Gorjeta digitalX
Programa de fidelidade barX
Pedido pelo app (self-service)X
Impressora térmicaX
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:

  1. 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"
  2. 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
  3. Experiência de "conta digital" — abre caminho para ser um método de pagamento nativo da plataforma (ingresso, produtos, planos), não apenas bar
  4. Simplifica recarga — não precisa estar no contexto de um evento para carregar
  5. 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
  6. 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:

  1. 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
  2. Consistência — o operador do bar sempre escaneia o mesmo tipo de QR (wallet), independente de como o user entrou no evento
  3. Segurança — o QR da wallet só expõe o nanoId da wallet, não dados do ticket
  4. UX clara — o app mostra "Meu QR para pagamento" separado do "Meu ingresso"
  5. 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) │
└─────────┘ └───────┘ └─────────────┘
  • Order continua 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 orderPaid roda, cria uma Transaction (além de criar tickets/vouchers)
  • Quando bar debita wallet, cria uma Transaction
  • Quando user recarrega wallet, orderPaid cria Transaction
  • Quando refund acontece, cria Transaction
  • Quando withdrawal é aprovado, cria Transaction

Benefícios:

  1. Single source of truth — um query para toda a atividade financeira
  2. Financial reporting — receita da plataforma, volume por produtor, por user, por período — tudo num model
  3. Wallet balance derivávelwallet.balance é cache; a soma de Transaction onde walletId = X e type = CREDIT menos type = DEBIT é o balance real (reconciliação)
  4. Audit trail — toda movimentação tem rastro, referência, operador, metadata
  5. 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 existenteUso no bar
@nittio/mongoConexão, writeConcern, ObjectId utils
@nittio/redisLocks para transações de wallet, cache
@nittio/rabbitmqFila para jobs assíncronos de bar
@nittio/queuesRegistro de nomes de filas
@nittio/jobsRegistro de nomes de jobs
@nittio/aws + @nittio/fileUpload de imagens de produtos do bar
@nittio/email + @nittio/resendNotificações de recarga, recibo
@nittio/slackAlertas internos (estoque baixo, fraude)
@nittio/notificationNotificações in-app
@nittio/uiComponentes de UI (sidebar, inputs, cards, tables)
@nittio/utilsMoney formatting, hashing, API errors
@nittio/addressSchema de endereço para pontos de bar
@nittio/testPadrões de teste
@nittio/typescript-configConfig TS compartilhada
@nittio/baasAbstração de provider de pagamento
@nittio/pagbankPIX + 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:

EnumNovo valorMotivo
PRODUCER_FEATURES_ENUMBARFeature flag para habilitar bar
ORDER_ITEM_TYPE_ENUMwalletRechargeItem de recarga de wallet na order
ORDER_PAYMENT_METHOD_ENUMWALLETPagamento de ingresso via wallet
WITHDRAWAL_REFERENCE_ENUMBARSaque de receita de bar
NOTIFICATION_TYPE_ENUMWALLET_*, BAR_*Notificações de wallet e bar
EMPLOYER_GROUP_ENUMBAR_OPERATOR, BAR_MANAGERPermissões de operador de bar
EMPLOYER_TUTORIAL_ENUMBAR_SETUPTutorial de configuração de bar

4.2.2 packages/order/ — Integração com Transaction

  • orderPaid.ts — Após cada branch existente (event, product, producerPlan), chamar transactionCreate para registrar a movimentação no ledger. Adicionar novo branch para cart.walletRechargeId que chama orderPaidWalletRecharge (credita wallet + cria Transaction)
  • orderPaidEvent.ts — Sem mudança na lógica de criação de tickets. O orderPaid pai é quem cria a Transaction
  • orderPaidProduct.ts — Idem
  • orderPaidProducerPlan.ts — Idem
  • orderRefund.ts — Adicionar transactionCreate com type REFUND após marcar order como REFUNDED

4.2.3 packages/event/

  • eventModel.ts — Adicionar campo barSettings (embedded schema):
    • isBarEnabled: boolean
    • barFeePercentage: 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 com eventId + type: BAR_SALE

4.2.4 packages/cart/

  • cartModel.ts — Adicionar campo opcional walletRechargeValue: number para carrinho que inclui recarga

4.2.5 packages/user/

  • userModel.ts — Adicionar campo walletId: 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 evento
  • BAR_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 bar
  • apps/bko-api/ — backoffice de bar e transactions pode ser adicionado pós-MVP
  • apps/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:

CampoTipoDescrição
_idObjectIdPK
nanoIdStringID curto para referência humana (nanoid 8)
typeTRANSACTION_TYPE_ENUMTipo da movimentação (ver enum abaixo)
statusTRANSACTION_STATUS_ENUMCOMPLETED, PENDING, FAILED
valueNumberValor em centavos (sempre positivo)
platformFeeNumberTaxa Nittio em centavos (0 quando não aplicável)
gatewayFeeNumber | nullTaxa do gateway (PagBank) quando conhecida
netValueNumberValor líquido (value - platformFee)
userIdObjectId → UserUsuário envolvido (indexado)
producerIdObjectId | null → ProducerProdutor envolvido, null para recargas diretas
eventIdObjectId | null → EventEvento, quando aplicável
walletIdObjectId | null → WalletWallet envolvida, quando aplicável
orderIdObjectId | null → OrderOrder de origem (compra/recarga)
barOrderIdObjectId | null → BarOrderBar order de origem
withdrawalIdObjectId | null → WithdrawalWithdrawal de origem
paymentMethodORDER_PAYMENT_METHOD_ENUM | nullMétodo de pagamento usado
directionStringIN (dinheiro entrou) ou OUT (dinheiro saiu) do ponto de vista do userId
descriptionStringDescrição legível para o extrato
metadataObject | nullDados extras (itens do bar, tier do ticket, etc.)
operatedByObjectId | null → EmployerOperador que executou (para bar operations)
removedAtDate | nullSoft delete
createdAtDateTimestamp
updatedAtDateTimestamp

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 sistemaTransaction typedirectionQuem cria
orderPaidEvent finalizaTICKET_PURCHASEOUTorderPaid (wrapper)
orderPaidProduct finalizaPRODUCT_PURCHASEOUTorderPaid (wrapper)
orderPaidProducerPlan okPLAN_PURCHASEOUTorderPaid (wrapper)
Wallet recarga pagaWALLET_RECHARGEINorderPaidWalletRecharge
Venda no bar (PDV)WALLET_DEBIT_BAROUTbarOrderCreate
User saca saldo da walletWALLET_WITHDRAWOUTwalletWithdraw
Order é refundedREFUNDINorderRefund
Bar order é cancelledREFUNDINbarOrderCancel
Produtor solicita saquePRODUCER_WITHDRAWALN/AwithdrawalCreate

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:

CampoTipoDescrição
_idObjectIdPK
nanoIdStringID curto e único para QR code (nanoid 8, unique, indexed)
userIdObjectId → UserDono da wallet (unique, indexed — 1:1 com User)
statusWALLET_STATUS_ENUMACTIVE, SUSPENDED, CLOSED
balanceNumberSaldo atual em centavos (inteiro, default 0)
totalCreditsNumberTotal histórico creditado (default 0)
totalDebitsNumberTotal histórico debitado (default 0)
removedAtDate | nullSoft delete
createdAtDateTimestamp
updatedAtDateTimestamp

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:

CampoTipoDescrição
_idObjectIdPK
eventIdObjectId → EventEvento dono (indexado)
producerIdObjectId → ProducerDenormalizado (indexado)
nameStringNome do item (ex: "Cerveja Heineken 600ml")
descriptionString | nullDescrição opcional
categoryBAR_ITEM_CATEGORY_ENUMBEER, DRINK, COCKTAIL, WATER, SOFT_DRINK, FOOD, SNACK, OTHER
priceNumberPreço em centavos
imageString | nullURL S3
statusBAR_ITEM_STATUS_ENUMACTIVE, INACTIVE, OUT_OF_STOCK
stockNumberQuantidade em estoque
stockAlertNumberLimite para alerta de estoque baixo
unlimitedStockBooleanSe true, ignora controle de estoque
sortOrderNumberOrdenação no cardápio
createdByObjectId → EmployerQuem criou
removedAtDate | nullSoft delete
createdAtDateTimestamp
updatedAtDateTimestamp

Indexes:

  • eventId + status + removedAt
  • producerId + removedAt
  • category + 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:

CampoTipoDescrição
_idObjectIdPK
nanoIdStringID curto para recibo (nanoid 8)
eventIdObjectId → EventEvento (indexado)
producerIdObjectId → ProducerDenormalizado (indexado)
userIdObjectId → UserConsumidor (indexado)
walletIdObjectId → WalletWallet debitada (indexado)
transactionIdObjectId → TransactionTransaction do ledger (indexado)
barStationIdObjectIdEstação/ponto de bar
itemsBarOrderItemSchema[]Itens do pedido
totalValueNumberValor total em centavos
feeNumberTaxa Nittio sobre a venda
statusBAR_ORDER_STATUS_ENUMCOMPLETED, CANCELLED
operatedByObjectId → EmployerOperador que vendeu (indexado)
cancelledByObjectId | null → EmployerQuem cancelou
cancelledAtDate | nullQuando foi cancelado
cancelReasonString | nullMotivo do cancelamento
removedAtDate | nullSoft delete
createdAtDateTimestamp (hora da venda)
updatedAtDateTimestamp

BarOrderItemSchema (embedded):

CampoTipoDescrição
barItemIdObjectId → BarItemReferência ao item
nameStringDenormalizado
quantityNumberQuantidade
unitPriceNumberPreço unitário em centavos
totalPriceNumberquantity * unitPrice

Indexes:

  • eventId + createdAt
  • eventId + status + removedAt
  • operatedBy + eventId + createdAt
  • userId + eventId
  • walletId
  • transactionId

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:

CampoTipoDescrição
_idObjectIdPK
eventIdObjectId → EventEvento (indexado)
barItemIdObjectId → BarItemItem afetado (indexado)
typeStringSALE, CANCELLATION, ADJUSTMENT, INITIAL
quantityNumberPositivo = entrada, negativo = saída
stockAfterNumberEstoque após movimentação
barOrderIdObjectId | nullSe originado de uma venda
operatedByObjectId → EmployerQuem fez a movimentação
reasonString | nullMotivo (para ajustes manuais)
removedAtDate | nullSoft delete
createdAtDateTimestamp

Indexes:

  • barItemId + createdAt
  • eventId + type + createdAt

6. Novos Handlers, Rotas e Zod Schemas

6.1 apps/hub-api/ — Rotas do produtor/operador

Bar Item (Cardápio)

MétodoPathHandlerDescrição
GET/event/:eventId/bar/itemshandleEventBarItemsGetListar cardápio
POST/event/:eventId/bar/itemhandleEventBarItemPostCriar item
PUT/event/:eventId/bar/item/:idhandleEventBarItemPutEditar item
DELETE/event/:eventId/bar/item/:idhandleEventBarItemDeleteRemover item
PUT/event/:eventId/bar/item/:id/stockhandleEventBarItemStockPutAjuste manual de estoque

Bar Station (Estação de bar)

MétodoPathHandlerDescrição
GET/event/:eventId/bar/stationshandleEventBarStationsGetListar estações
POST/event/:eventId/bar/stationhandleEventBarStationPostCriar estação
PUT/event/:eventId/bar/station/:idhandleEventBarStationPutEditar estação
DELETE/event/:eventId/bar/station/:idhandleEventBarStationDeleteRemover estação

Bar PDV (Ponto de Venda — tela do operador)

MétodoPathHandlerDescrição
GET/event/:eventId/bar/pdv/itemshandleEventBarPdvItemsGetCardápio ativo (para tela do PDV)
POST/event/:eventId/bar/pdv/scanhandleEventBarPdvScanPostEscanear QR da wallet → retorna user + saldo
POST/event/:eventId/bar/pdv/orderhandleEventBarPdvOrderPostCriar venda (debita wallet + cria bar order + transaction)
POST/event/:eventId/bar/pdv/order/:id/cancelhandleEventBarPdvOrderCancelPostCancelar venda (estorna wallet + estoque + transaction)

Bar Analytics / Relatórios

MétodoPathHandlerDescrição
GET/event/:eventId/bar/analyticshandleEventBarAnalyticsGetDashboard de vendas
GET/event/:eventId/bar/analytics/productshandleEventBarAnalyticsProductsGetVendas por produto
GET/event/:eventId/bar/analytics/operatorshandleEventBarAnalyticsOperatorsGetVendas por operador
GET/event/:eventId/bar/analytics/hourlyhandleEventBarAnalyticsHourlyGetVendas por hora
GET/event/:eventId/bar/ordershandleEventBarOrdersGetLista de pedidos de bar
GET/event/:eventId/bar/orders/:idhandleEventBarOrderGetDetalhe de pedido
GET/event/:eventId/bar/stock/logshandleEventBarStockLogsGetHistórico de movimentações

Bar Withdrawal (Saque de receita do bar)

MétodoPathHandlerDescrição
GET/event/:eventId/bar/withdrawal/availablehandleEventBarWithdrawalAvailableGetValor disponível
POST/event/:eventId/bar/withdrawalhandleEventBarWithdrawalPostSolicitar saque

Bar Settings

MétodoPathHandlerDescrição
GET/event/:eventId/bar/settingshandleEventBarSettingsGetObter configurações
PUT/event/:eventId/bar/settingshandleEventBarSettingsPutAtualizar 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étodoPathHandlerDescrição
GET/wallet/mehandleWalletMeGetDados da wallet + saldo + QR
POST/wallethandleWalletPostCriar wallet (se não existe)
POST/wallet/rechargehandleWalletRechargePostIniciar recarga (cria cart + order)
POST/wallet/withdrawhandleWalletWithdrawPostSacar saldo da wallet via PIX
GET/wallet/transactionshandleWalletTransactionsGetExtrato de transações da wallet
GET/wallet/qrcodehandleWalletQrcodeGetQR 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 NameTriggerAção
BAR_STOCK_UPDATEApós barOrderCreateDecrementa estoque dos itens, cria stock logs
BAR_LOW_STOCK_ALERTScheduler (5 min)Verifica itens abaixo do stockAlert, notifica Slack
BAR_SALES_SNAPSHOTScheduler (15 min)Agrega vendas para dashboard real-time
WALLET_RECHARGE_NOTIFICATIONApós orderPaid (wallet)Envia push/email confirmando recarga
WALLET_WITHDRAW_PROCESSApós wallet withdrawProcessa PIX de saque para a conta do user
BAR_ORDER_CANCEL_STOCK_RETURNApós barOrder cancelRetorna estoque dos itens cancelados
BAR_EVENT_CLOSE_REPORTScheduler (pós-evento)Gera relatório final do bar do evento

7.2 Novos Crons (registrar em apps/scheduler/)

CronFrequênciaAçã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_CLOSE0 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

  1. Recarga de wallet — o PagBank cobra a taxa de gateway (PIX ~2%, CC ~4.99%). O valor integral vai para o saldo do user
  2. Venda no bar — a taxa Nittio é calculada sobre o valor da venda. Transaction.platformFee registra o valor. O produtor vê nos relatórios o valor bruto e a taxa
  3. Saque do produtor — o valor sacável = sum(Transaction.netValue where producerId + eventId) - saques já realizados
  4. 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 Transaction aggregation
  • 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:

GrupoPermissões
BAR_MANAGERCRUD cardápio, estações, ajuste de estoque, cancelar vendas, ver analytics, configurar bar
BAR_OPERATORUsar PDV (escanear, vender), ver estoque (read-only)

11.2 Validação de acesso

  • Handlers de bar verificam employer.groups para BAR_MANAGER ou BAR_OPERATOR
  • PDV só requer BAR_OPERATOR (ou BAR_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 atualComo é feito hojeComo ficaria com Transaction
Faturamento do evento XOrder.find({eventId, PAID}).sum(originalValue)Transaction.find({eventId, type: [TICKET_*, BAR_*]}).sum(netValue)
Faturamento do produtor Y no mêsOrder.find({producerId, PAID, paidAt ∈ range})Transaction.find({producerId, createdAt ∈ range}).sum(netValue)
Receita plataforma (Nittio)Não existe query simples hojeTransaction.find({status: COMPLETED}).sum(platformFee)
Extrato financeiro do userNão existeTransaction.find({userId}).sort(createdAt)
Valor sacável do produtor no eventoOrder.find + Withdrawal.find (complexo)Transaction.find({producerId, eventId, type: [TICKET_*, BAR_*]}).sum(netValue) - withdrawals
Conciliação gateway vs internoNão existeTransaction.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

AspectoImpacto
Wallet como payment methodPermite pagar ingressos, produtos, planos via wallet (não só bar)
FidelizaçãoUser 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çãoSe volume crescer, avaliar licença de Instituição de Pagamento (BACEN)
FloatDinheiro na wallet antes de ser gasto gera float para o Nittio
RendimentoV3: 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óduloEstimativaDetalhamento
packages/transaction/4-5 diasModel, create, queries by user/producer/event, fixtures, tests
packages/wallet/5-7 diasModel, CRUD, debit/credit, withdraw, QR, fixtures, tests
packages/bar-item/3-4 diasModel, CRUD, fixtures, tests
packages/bar-order/4-5 diasModel, create, cancel, analytics, fixtures, tests
packages/bar-stock-log/2-3 diasModel, create, list, fixtures, tests
Enums e adaptações existentes3-4 diasNovos enums, adaptar orderPaid (+ transaction hooks), orderRefund, cart, event, user models
hub-api: Bar routes + handlers8-10 dias~25 handlers + Zod schemas + mappers
hub-api: PDV routes + handlers4-5 diasScan (by wallet nanoId), order, cancel + performance
app-api: Wallet routes + handlers5-6 diasCreate, recharge, withdraw, transactions, QR code
Worker: novos jobs4-5 dias7 jobs + testes
Scheduler: novos crons1-2 dias3 crons
hub-console: Configuração + Cardápio5-6 diasUI settings, CRUD cardápio, estações
hub-console: PDV8-10 diasTela completa, scan QR wallet, carrinho, UX
hub-console: Analytics + Relatórios5-6 diasDashboard, gráficos, tabelas, export (via Transaction)
hub-console: Estoque + Pedidos3-4 diasListagens, filtros, ajustes
app-console: Wallet + Recarga + QR6-8 diasUI wallet, QR full-screen, recarga, extrato, saque
app-webview: adaptações2-3 diasQR display otimizado, deeplinks
Testes de integração end-to-end5-7 diasFluxos completos, edge cases, concorrência
QA e bug fixes5-7 diasTestes manuais, ajustes

14.2 Total estimado

CenárioDuração totalEquipe
1 dev14-17 semanasFull-stack sênior
2 devs8-10 semanas1 backend + 1 frontend
3 devs6-8 semanas1 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

RiscoImpactoProbabilidadeMitigação
Latência no PDV — operação lenta mata o barAltoMédiaRedis cache, indexes otimizados, $inc atômico, debounce
Concorrência em wallet — race conditions em débitoAltoAlta$inc atômico MongoDB + redisGetLock, testes de stress
PagBank offline — gateway fora = sem recargaAltoBaixaCache de QR codes gerados, retry com backoff
Conectividade no evento — internet instávelAltoAltaPWA caching, operações otimistas (V2), alertas
Fraude de operador — vendas fantasma ou cancelamentos indevidosMédioMédiaAudit log via Transaction, limites de cancelamento, relatório por operador
Escopo creep — features de V2 entrando no MVPMédioAltaScope freeze após aprovação do RFC, backlog separado
Fiscal — produtor não emite NFC-eMédioAltaTermos de uso claros, relatórios via Transaction facilitando apuração
Regulação BACEN — wallet como conta de pagamentoMédioBaixaEnquadramento como "crédito para consumo", consultar jurídico
UX do PDV em celular — tela pequenaMédioMédiaDesign responsivo, teste em devices reais, foco em tablet
Wallet fraud — recargas com cartão roubadoAltoMédiaLimites de recarga, 3DS obrigatório para CC, KYC via taxID

16. Métricas de Sucesso do MVP

MétricaTarget 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.ts
  • packages/enum/src/barItemStatusEnum.ts
  • packages/enum/src/barOrderStatusEnum.ts
  • packages/enum/src/barStationStatusEnum.ts
  • packages/enum/src/transactionTypeEnum.ts
  • packages/enum/src/transactionStatusEnum.ts
  • packages/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

TermoDefinição
WalletCarteira digital global do usuário. Saldo em centavos. QR code próprio
TransactionRegistro no ledger financeiro. Toda movimentação de dinheiro gera uma Transaction
Bar ItemProduto vendido no bar (cerveja, drink, água, etc.)
Bar OrderPedido/venda realizada no bar (conjunto de bar items)
Bar StationPonto físico de bar no evento (ex: "Bar Principal", "Bar VIP")
PDVPonto de Venda — tela do operador para registrar vendas
RecargaAto de adicionar saldo à wallet via PIX ou cartão
SaqueRetirada de saldo da wallet pelo user (recebe PIX)
Stock LogRegistro de movimentação de estoque (entrada/saída)
OperadorEmployer com grupo BAR_OPERATOR que opera o PDV
Fee barTaxa percentual do Nittio sobre cada venda no bar
nanoIdIdentificador 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

  1. Review deste RFC com stakeholders (produto, comercial, jurídico)
  2. Consulta jurídica sobre enquadramento da wallet (conta de pagamento vs crédito para consumo)
  3. Validar modelo de taxa com time comercial (3-5% é competitivo?)
  4. Protótipo do PDV — mockup interativo da tela mais crítica
  5. Definir produtores beta — 2-3 parceiros para teste piloto
  6. Decidir scope do MVP — wallet como payment method para ingressos entra ou não?
  7. Kickoff de desenvolvimento — Fase 1 (foundation: transaction + wallet)