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;
waiterIdpresente na sessão;- unidade autorizada pela sessão;
- garçom ativo;
- cupom
waiting_checkine 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.