Roleta Online
Sorteio por cotas, com vaga limitada
- Meu papel
- Desenvolvedor full stack
- Cliente
- Catsuc Labs
- Setor
- Produto de consumo · web
- Status
- Em produção
Código proprietário. Sistema privado de cliente — o repositório não é público.
Números
20/40/100
capacidades de roleta
O sorteio dispara no instante em que a última vaga é preenchida.
1
ficha por posição
Garantido por restrição única no banco, não por checagem no código.
0
vaga vendida duas vezes
Alocação transacional — o banco recusa a segunda venda.
Contexto
Bolão de cotas é um formato antigo e conhecido: um grupo divide um prêmio em partes iguais, cada um compra a sua e um sorteio decide. Levar isso para a web parece trivial até você perceber que a parte interessante não é a roleta girando — é o que acontece quando duas pessoas clicam em comprar a última ficha no mesmo milissegundo.
O problema
Vaga é finita e tem dinheiro envolvido. Isso transforma três coisas em requisito duro: nenhuma posição pode ser vendida duas vezes, nenhum pagamento repetido pode gerar ficha extra, e o sorteio precisa ser explicável depois — porque quem perdeu vai querer entender como perdeu.
A abordagem
Coloquei as garantias no banco, não no código de aplicação. Cada posição da roleta é uma linha com restrição única, e a compra acontece dentro de uma transação que ou reserva a posição ou falha — o banco recusa a corrida, em vez de a aplicação torcer para não acontecer. A confirmação de pagamento é idempotente por chave, então reenvio de webhook não vira ficha nova. E o sorteio grava a semente e o resultado, para poder ser reconstruído depois.
Módulos
Roletas
Cada roleta tem capacidade, preço da ficha e estado — aberta, cheia, sorteada. O estado é do servidor: o cliente exibe, não decide.
Fichas
Comprar uma ficha é reservar uma posição numerada dentro de uma transação. Se a roleta encheu no meio do caminho, a compra falha limpa e o valor não é capturado.
Sorteio
Roda no instante em que a última vaga é preenchida, uma única vez por roleta, com semente e resultado persistidos.
Histórico
Quem comprou qual posição, quando, e qual foi o resultado. Sem isso não existe confiança num produto onde alguém perde dinheiro.
As partes difíceis
A última ficha e duas pessoas clicando
É o problema central, e não se resolve com “verifica se ainda tem vaga antes de gravar” — entre a verificação e a gravação cabe outra requisição. A posição é uma linha com restrição única e a reserva acontece na transação: a segunda venda simplesmente não é aceita pelo banco. Deixar a garantia no nível mais baixo possível é o que torna o resto do código simples.
Webhook chega duas vezes
Provedor de pagamento reentrega notificação — é comportamento normal, não exceção. Sem idempotência por chave, a segunda entrega gera uma ficha que ninguém pagou. Tratar reentrega como o caso esperado, e não como erro, é a diferença entre um produto que fecha a conta e um que não fecha.
O resultado tem que ser defensável
Num produto de sorteio, a suspeita é o padrão. O sorteio roda uma vez só por roleta, guarda a semente e o resultado, e não pode ser rodado de novo — nem por mim. Poder reconstruir o sorteio depois vale mais que qualquer animação de roleta girando.
O que é meu
- Modelagem das roletas, posições e fichas, com as garantias de unicidade no banco
- Fluxo de compra transacional e confirmação de pagamento idempotente
- Disparo do sorteio no preenchimento da última vaga, com registro auditável
- Interface de participação, acompanhamento do preenchimento e histórico
Resultado
Um formato de bolão que funcionava no papel passou a funcionar sozinho na web, com vaga limitada, dinheiro real e resultado reconstruível. Foi o projeto que mais me ensinou sobre concorrência — não a teoria, a versão em que errar custa o dinheiro de alguém.
O que eu levei
- 01Garantia de integridade pertence ao banco. Checagem na aplicação é uma sugestão, restrição é uma lei.
- 02Reentrega de webhook é o caso normal. Idempotência não é refinamento, é requisito.
- 03Em produto onde alguém perde, auditabilidade é feature de confiança — não de compliance.