Pular para conteúdo

Cupons e check-in

Máquina de estados atual

stateDiagram-v2
  [*] --> active: createCouponRedemption
  active --> waiting_checkin: activateCoupon
  waiting_checkin --> active: cancelCheckin
  active --> cancelled: cancelCoupon
  active --> used: markCouponAsUsed
  waiting_checkin --> used: markCouponAsUsed / confirmCheckin
  active --> expired: expireCoupons
  waiting_checkin --> expired: expireCoupons

generateCoupon é um endpoint legado e atualmente retorna failed-precondition; o fluxo suportado é createCouponRedemption.

Geração

createCouponRedemption exige usuário autenticado e executa uma Firestore Transaction. A Function valida:

  • usuário e oferta existentes;
  • status/origem da oferta;
  • partner e unidade;
  • campanha ativa quando source == campaign;
  • acesso Premium ou recompensa de referral quando necessário;
  • cooldown de 3 horas por mesma oferta;
  • ausência de outro cupom ativo para a mesma oferta;
  • capacidade restante da oferta.

O cupom nasce active, com TTL de 24 horas e usageSlotReserved=true.

Quota por oferta

couponUsageLimit:

  • ausente ou null: sem limite;
  • inteiro >= 1: capacidade finita.

couponReservedCount conta vagas reservadas por cupons ativos/aguardando check-in; couponUsedCount conta usos confirmados. Os contadores são backend-only.

ocupado = couponReservedCount + couponUsedCount
esgotado = couponUsageLimit != null && ocupado >= couponUsageLimit

A reserva, conversão e liberação são implementadas em functions/src/coupon_usage_limit.js e sempre devem ocorrer dentro da mesma transaction que muda o cupom.

Quando a capacidade acaba, o backend usa resource-exhausted com reason = coupon_usage_limit_reached.

Check-in do garçom

O desktop/web de garçom primeiro autentica no Firebase (anônimo quando necessário) e chama loginWaiter(email,pinText). O backend compara SHA-256 do PIN, filtra garçons ativos e grava waiter_sessions/{firebaseUid} por 24 horas.

confirmCheckin exige:

  • sessão ativa e não expirada;
  • waiterId presente na sessão;
  • unidade autorizada pela sessão;
  • garçom ativo;
  • cupom waiting_checkin e não expirado;
  • store do garçom igual à store do cupom.

Na mesma transaction, converte a reserva em uso e incrementa redemptionCount24h; a oferta vira isTrending=true a partir de 10 resgates no contador corrente.

Expiração

expireCoupons é agendada em alta frequência, mas scheduler_runner aplica intervalo lógico default de 30 minutos. Ela processa até 500 cupons por busca, com concorrência 10, e libera a vaga reservada na mesma transaction da expiração.

resetTrendingProducts roda diariamente às 00:05 pelo cron versionado e zera redemptionCount24h/isTrending.

Geofence legado

Existe validateGeofence com raio de 200 m, mas a Function opera sobre a coleção legada coupons, enquanto o fluxo atual usa coupon_redemptions. Ela não deve ser tratada como etapa obrigatória do resgate atual sem uma migração explícita.

Cuidado ao alterar métricas

getDashboardStats ainda compara um estado waitingCheckin em camelCase em parte da lógica, enquanto o writer atual usa waiting_checkin. Ao alterar ou confiar no contador de espera do dashboard, valide/corrija essa compatibilidade e adicione teste de regressão.