Pitch: Leaderboard pago por Pix em 22h, e os 6 bugs que me pegaram no caminho
Comecei ontem (21/08) umas 18h e parei agora, 22/08 às 16h. Umas 22 horas de trabalho, contando as pausas.
O SuperaLance é um leaderboard público de produtos brasileiros onde a posição é o lance. Você coloca seu link, paga por Pix, e fica na frente de quem pagou menos. Não tem algoritmo, não tem curadoria, não tem peso de engajamento. É só o valor.
Sendo honesto sobre o tamanho: hoje tem 2 links no quadro e R$ 11 arrecadados. Não é tração, é um começo. Estou publicando aqui porque a parte que acho que tem valor pra vocês não é o produto, é o que quebrou no caminho.
A mecânica
- Lance mínimo R$ 5, passo de R$ 1, tudo em reais inteiros (sem centavos em lugar nenhum do sistema).
- Se seu link já está no quadro, você paga só a diferença até o novo total.
- Assumir o #1 custa o valor do primeiro + R$ 1. Empatar não assume: o desempate é quem chegou primeiro naquele valor.
- Rebaixada: você paga para empurrar alguém N posições para baixo por H horas. Preço escala com as duas variáveis.
- Tomada: fixa você no #1 por 3 horas, custa 5x o valor do primeiro.
Stack
Next.js 16 (App Router, Turbopack), React 19, TypeScript, Tailwind 4, Drizzle ORM com PostgreSQL, Zod na borda. Pix e cartão via Asaas, e-mail via Resend. Deploy em Docker com output: standalone, rodando em Coolify numa VPS, com Cloudflare na frente. Migrações são arquivos .sql puros aplicados por um script Node de 150 linhas no start do container, sem drizzle-kit em produção.
O que quebrou
1. Float cobrando R$ 1 a mais. O preço da rebaixada é lance do alvo x 0.2 por posição x (horas / 6). Em ponto flutuante, 51 * 0.2 * 5 dá 51.00000000000001, e o Math.ceil cobrava R$ 52. Isso acontecia em mais de dez mil combinações de valor, posição e hora, sempre R$ 1 acima da regra publicada. Virou aritmética inteira: como 0.2 é 2/10, o numerador fecha exato.
2. FOR UPDATE em zero linha não tranca nada. A liquidação do Pix fazia select-for-update e depois insert. Só que travar uma linha que não existe não impede outra transação de inserir a mesma chave. Dois primeiros lances simultâneos no mesmo link estouravam o índice único, a transação inteira voltava atrás, e a cobrança ficava paga e nunca aplicada. Virou upsert atômico (insert ... on conflict do update) com advisory lock por identidade do link, para a criação não atravessar a moderação.
3. Rate limit decorativo. Eu lia o IP da ponta esquerda do x-forwarded-for. Aquele valor é escrito pelo cliente, então qualquer pessoa trocava de bucket a cada requisição. Passei a ler da direita, contando os hops de proxy confiáveis. O IP nunca é guardado em claro: vira HMAC-SHA256 com segredo combinado com o dia, porque o espaço de IPv4 é pequeno o suficiente para um hash sem segredo ser reversível por tabela.
4. O contador de "online agora" mentia. Eu contava sessões distintas na tabela de pageviews nos últimos 5 minutos. O problema é que pageview só é registrado quando a pessoa troca de página. Quem abria o site e ficava lendo saía da janela, e o contador caía para zero com gente na tela. Virou heartbeat com upsert numa tabela de presença, uma linha por sessão.
5. O título da home prometia o que não entregava. O h1 dizia "Pegue o número 1 por R$ X" com o X sendo um input editável. Se você clicasse em "-" e descesse para o valor do primeiro, ele continuava prometendo o #1 por um valor que empata e perde no desempate. O modal acertava e o título mentia, na mesma tela.
6. Analytics de terceiro sobrevivendo em rota sensível. Eu não montava o script de analytics em /pagar/<id> e /painel. Achei que bastava. Não basta: desmontar o componente do script não descarrega um script que já executou, e ele acompanha navegação client-side por conta própria. Fui olhar o dashboard e lá estavam os caminhos /pagar/<uuid> e /painel/entrar. Aquele id na URL é o que abre a cobrança. Resolvi com CSP por rota: nessas páginas a política não lista terceiro nenhum e o Referrer-Policy vira no-referrer.
O que eu queria ouvir de vocês
- A regra do desempate por quem chegou primeiro é justa, ou o certo seria empate não existir e o valor ter que ser sempre maior?
- A rebaixada paga é uma mecânica saudável ou é convite para guerra de gasto entre dois anunciantes?
- Alguém já apanhou de CSP com script injetado na borda (tipo Cloudflare)? A minha saída foi política por rota, mas queria saber se tem caminho melhor.
Podem ser brutalmente exatos. É a primeira coisa que eu publico aqui e prefiro apanhar agora do que descobrir depois.