Live case study · built with Claude Code

The operations panel
behind Seu Milhão

Seu Milhão is a Brazilian prize-draw brand. It sells through partner bars, individual affiliates and sports teams, and runs its own traffic, CRM, support and finance. This page maps the single system that runs all of that. I designed it and built it with Claude Code as my pair programmer, from August to November 2026.

Explore the 14 modules Source code (sanitized) ↗ Module → code map
14back-office modules
5apps in one front end
118screens / routes
538API endpoints
216numbered DB migrations
134test scripts in CI chain
One build, five audiences

Five apps, each with its own login

One React build serves everyone. Each audience gets its own mount point, session and permissions. Isolation between them is enforced by tests, not by convention.

/admin

Back office

The internal team. Role-based permission map, 14 modules.
/painel

Bar portal

Partner bars: commission, regional leagues, goals, payouts with invoice, marketing art.
/afiliado

Affiliate portal

Individuals: referrals, commission, e-signed contract, payout requests.
/gerente

Manager portal

Regional managers: their bars, their commission.
/time

Team portal

Partner sports teams: fan sign-up, points, rewards store, contract.
The back office

14 modules

The grid mirrors the real panel home. Click a module to see what it does, its screens, its API surface and where its code lives.

How money moves

From ad click to payout

Every sale is attributed to whoever originated it. The system turns that attribution into commission, then into a payout, then into a finance entry.

TráfegoAcquireAds, tracked links, ad-cost sync, conversions sent back to ad platforms
WhatsApp · Telegram · QREngageGroups, bot funnels, QR codes at the bar
VendasSellEach sale tied to a draw edition
Bares · Afiliados · TimesAttributeCredit goes to the bar, affiliate or team
Commission engineEarnCommission, leagues, goals, points
Saques → FinanceiroPayPix + invoice approval, batch payment, ledger
CRMBring buyers backSegments, campaigns, drag-and-drop journeys, frequency caps, opt-outs
AtendimentoSupport everyoneShared inbox for bars, affiliates and customers, plus complaint-site management
Claude inside the product

Where the panel calls an LLM, and the guardrails

The AI works in narrow lanes, each with a written rule about what it may touch.

Support triage

claude-haiku-4-5 · every minute, via scheduler

Reads an open support conversation and suggests type, root cause, urgency and a one-line summary.

  • Never replies to the customer. It only fills fields.
  • CPF, CNPJ, email, phone and card numbers are masked before the request leaves the server.
  • Once an agent classifies a ticket by hand, the AI stops touching it.
  • Waits for the customer to stop typing, and calls for each conversation are capped.

Telegram sales bot

claude-haiku-5-5 · per channel, off by default

Talks to people in private chat and guides them to purchase.

  • Two separate switches: answer during the funnel, or after it ends.
  • "Focused" mode keeps it on the draw and the sale.
  • Can ask for a phone number, which shows up as a share-contact button.
  • If the model fails, a fixed fallback reply goes out, so nobody is left without an answer.
  • No personal data in the prompt. The webhook never waits on the model.

Voice messages

whisper-1 · Telegram audio

Incoming voice notes are transcribed so the bot and human agents can read them. Outgoing audio is converted to Telegram voice format with ffmpeg.

Claude Code as pair programmer

How it was built

I directed and decided; Claude Code wrote code against rules kept in the repo. The rules are what keep a large codebase coherent when most of it is written with AI.

Rules file for every change

AGENTS.md holds the engineering rules Claude Code must follow, for example: no hand-written colors, dates in one timezone helper, no PII in AI calls.

Decision log

ESTADO.md records why each decision was taken, with the date. New sessions read it first, so nobody reopens a settled decision.

Tests that guard the architecture

A single npm test chain runs 134 scripts. They cover the business rules, and also the architecture: one portal can't reach another's data, imports resolve, hooks run in order, and numbers on screen match the database.

Immutable migrations

216 numbered SQL files and no edits to old ones. Two Postgres schemas share one database: the existing sales schema is read-only from here.

One install per client

Built to be resold: each client company gets its own database, processes, domains and credentials, created and published with one command each.

Same name on both sides

Each panel lives in web/src/paineis/<audience>/<panel>/ (screens) and src/paineis/<audience>/<panel>/ (server), each with its own read-me. The map below shows which panels have moved.

Architecture

Stack

          browser — React 19 SPA (Vite, Tailwind 4), one build, routed per audience
                                   │
                        Fastify 5 API  (Node 20, ESM)
                                   │
        ┌──────────────────────────┼───────────────────────────┐
        │                          │                           │
  scheduler / workers        integrations                 PgBouncer
  commission · daily stats   WhatsApp (QR + official API)      │
  CRM journeys · conversions Telegram bots · Meta Ads      PostgreSQL 16
  support triage · ad cost   Anthropic · OpenAI Whisper    schemas: bares | crm
  watchdog                   web push · PDF contracts
Node 20Fastify 5React 19ViteTailwind 4React FlowRechartsPostgreSQL 16PgBouncerpdf-lib / pdfkitweb-pushPlaywrightsystemdClaude HaikuWhisper
Module → code

Where each module lives

Each module's code location now, its API surface and whether it has been moved into the folder layout. Full map: docs/de-para.md.

ModuleRouteScreens (front)ServerAPILayout