Pular para o conteúdo principal

Visão Geral da Arquitetura

Plataforma multi-tenant de apostas esportivas (Cactus Gaming). Um único template (front-web-base) serve 13 marcas via diretórios de override, distribuídas em 28 ambientes de deploy. Não há nenhum fork ativo.

:::warning Antes de continuar: leia Trunks de marca O front-web-base opera com várias branches de longa duração em paralelo (main, main-7k, main-b, …), e features são portadas entre elas, não mergeadas. Estas docs descrevem a main-7k — parte do que está aqui pode não existir na branch em que você está. :::

Repositórios

Lista viva em feca.yaml na raiz do workspace. Hoje:

RepoDescrição
front-cactus-coreSDK TypeScript — pacotes @cactus-agents/* (monorepo)
front-web-baseTemplate React Router v7 (SSR / Cloudflare Workers) — 13 marcas em overrides/
front-cactus-docsEsta documentação (Docusaurus)
front-opsReusable workflows de deploy + config por brand/repo/ambiente
front-service-apiWorker proxy da API (Smart Placement + cache por path)
front-service-api-dev-proxyDev proxy (domain remap + auth por dev)
front-service-api-warmupWorker de warmup de cache (cron)
fecadmAdmin CLI + web dashboard (CF workers, Access, Zero Trust)
front-tests-e2eTestes E2E Playwright

Stack

CamadaTecnologia
FrameworkReact Router v7 (SSR nativo no Cloudflare Workers)
Build/bundlerVite (via React Router v7)
StylingTailwind CSS v3 + CSS custom properties para tokens de tema por cliente
State (client)Zustand — ~28 stores em app/store/ (lista viva) + useAccountsStore do core (auth, usuário, wallet, transações, coins)
State (server)React Router loaders
i18ni18next + react-i18next (traduções) + @cactus-agents/i18n
Íconesunplugin-icons + datasets Iconify (lucide, mdi, simple-icons) + SVGs custom em app/icons/custom/. Ver Icon System
Fonte@fontsource/montserrat
Flagscountry-flag-icons
Linter/formatterBiome (todos os repos)
TestesVitest + React Testing Library
Pre-commitHusky + lint-staged → biome check --write
CommitsConventional Commits (commitlint)
Package managerpnpm (enforced)
Node>=24.0.0 (recomendado: 24.14.1)

SDK — Pacotes @cactus-agents/*

Lista viva em front-cactus-core/packages/. Hoje são 16 pacotes publicados:

PacoteDescrição
accountsAuth, usuário, wallet, recovery, responsible gaming + a store useAccountsStore (/react)
api-clientApiClient base (fetch, headers, auth, extractApiError)
brandBrandConfig (appearance, features, settings) + selectByCountry
country-configConfiguração por país (moeda, locale, validadores)
gamesGamesService (home, categorias, providers, busca)
gamificationSmartico SDK integration
i18nInternacionalização (setup i18next, namespaces)
kycKYC (operadores, iframe, polling)
mocksDEPRECATED — stub vazio; substituído por integrações reais de API
paymentsPaymentsService (deposit, withdraw, providers)
platform-cacheEngine de cache multi-tier do brand worker
sportsSportsService (providers: first, altenar, betby, rogue)
sweepstakesFluxo sweepstakes / dual-currency
typesTipos compartilhados
utilsUtilitários compartilhados (imagens, forms, money)
validationsValidações (modules, contexts, runtime)

:::note Pacotes removidos e dependências externas @cactus-agents/auth, /user e /wallet foram unificados em accounts em 2026-04-17; os diretórios seguem no repo como cascas vazias. E não presuma que todo @cactus-agents/* mora no core: @cactus-agents/sports-rogue é dependência real publicada de outro lugar. :::

Deploy

  • SSR via Cloudflare Workers (React Router v7)
  • Assets estáticos → Cloudflare R2
  • 1 Worker por ambiente (SSR + static, sem api-proxy separado)
  • Deploy centralizado via reusable workflow no front-ops
  • O ci-deploy.yml do base dispara em globs de branch (main, main-*, stage, stage-*, sports, sports-*) — ver Trunks de marca
  • Configuração em config/brands/<repo>/brand.yml (identidade + repo:) e environments/<env>/deploy.yml mapeia ambiente → worker_name + default_branch + origin_domain. Ver Deploy
  • Secrets de deploy (Cloudflare) são org secrets com acesso restrito

Pacotes privados

  • GitHub Packages como registry privado
  • Token de leitura por cliente (acesso apenas a @cactus-agents/*, sem código-fonte)

Modelo de marca: um repo + overrides/

Não existe um repo por marca. As 13 marcas vivem como diretórios de override dentro do próprio front-web-base:

overrides/
├── 7k-bet-br/ ├── pb-bet/
├── betpontobet-bet-br/ ├── ph-state77-com/
├── casateste-com/ ├── pt-state77-com/
├── cl-bet7k-com/ ├── rj-bet/
├── donald-bet-br/ ├── state77-com/
├── fi-7k-bet/ └── x2b-bet/
├── ng-7k-bet/

A marca ativa é resolvida em build time pelo brandOverridesPlugin (vite-plugins/brand-overrides.ts) + brand-resolver.mjs, com chave derivada do ORIGIN_DOMAIN do worker.

Não existe fork do front-web-base — toda marca em produção é um diretório de override dentro do próprio template. As marcas cassino-bet-br e vera-bet-br foram removidas do base em 2026-07-28 e não têm mais override nem ambiente de deploy.

Pontos rápidos de customização por marca:

  • app/config/theme/colors.ts — paleta de cores
  • app/config/layout/composition.ts — composição do layout (slots e variantes)
  • app/config/routes/paths.ts — customização de URL paths

O registro de rotas fica em app/router/routes.ts (não overrideable). A árvore de variantes de layout vive em app/layouts/variants/ (não overrideable).

Única restrição: não alterar pacotes @cactus-agents/* diretamente — eles são dependências npm atualizáveis via pnpm update.

Regra arquitetural: CORE vs BASE

O front-web-base é burro por design. Ele chama métodos do SDK, recebe dados normalizados e renderiza. Toda inteligência de domínio vive nos pacotes @cactus-agents/*.

O que vai onde

Se é...Onde vive
Shape real da API (snake_case)@cactus-agents/<pkg>/src/ como RawXxx
Tipo público normalizado (camelCase)@cactus-agents/<pkg>/src/types.ts
Transform raw → público@cactus-agents/<pkg>/src/transform.ts
Constante de domínio (opções de seleção, etc.)@cactus-agents/<pkg>/src/ exportada
Helper de cálculo (parseLimitPeriod, etc.)@cactus-agents/<pkg>/src/ exportado
Validador de formato (CPF, CLABE, RUT...)@cactus-agents/country-config/src/validators/
Lógica condicional por país@cactus-agents/country-config/src/countries/
Feature flag da API@cactus-agents/brand/src/transform/features.ts
Proxy HTTP server-sidefront-web-base/app/routes/api/<domínio>/<arquivo>.ts (simples, sem lógica)
Renderização de UIfront-web-base/app/components/**/*.tsx

Padrão Raw → Transform → Público

API (snake_case) → RawXxx { campo_api } → transformXxx() → Xxx { campoNormalizado }

Exemplo: Login History

  • A API retorna date: "2026-03-17 18:14:42" — o SDK converte para createdAt: "2026-03-17T18:14:42"
  • A API retorna city + state separados — o SDK compõe location: "São Paulo, SP"
  • O template só usa item.createdAt e item.location — zero normalização local

Documentação detalhada: CORE vs BASE — Arquitetura

Regra arquitetural: app/config

A pasta app/config no template deve conter somente configuração estática de brand (dados que variam por fork/marca):

  • ✅ Objetos de config exportados (ex: sportsConfig, gamificationConfig, depositConfig)
  • ❌ Tipos e interfaces de config — ficam em app/types/ (importados pelos configs)
  • ❌ Hooks (devem ficar em app/hooks/)
  • ❌ Parsers, helpers, lógica de negócio
  • ❌ Registros estruturais (registro de rotas → app/router/, registry de variantes → app/layouts/)
  • ❌ Dados globais (catálogo de países, DDI, currency-country) — vêm do SDK @cactus-agents/*

app/config/ é organizada em subpastas semânticas por domínio (22 hoje — consulte app/config/ pra lista viva). Imports usam o path completo ~/config/<dir>/X, sem sufixo .config: ~/config/theme/colors, ~/config/widgets/sidebar-buttons, ~/config/layout/composition.

Overrides de brand são resolvidos pelo brandOverridesPlugin do Vite: qualquer import ~/xxx é checado primeiro em overrides/<brand-key>/app/xxx e, se o arquivo existir lá, substitui o base. Não há whitelist — qualquer arquivo sob ~/ é potencialmente overridável. Os alvos mais comuns estão em forking/override-files.

:::danger Override substitui o arquivo INTEIRO A substituição é file-replacement, nunca deep-merge. Ao adicionar um campo novo a um config que tem overrides (features.ts, theme/colors.ts, …), é obrigatório propagar o campo pra todos os overrides existentes — senão aquela marca recebe undefined. :::

Tipos de config ficam em app/types/ (ex: navigation.ts, layout.ts, licenses.ts, deposit.ts, topbar.ts). Configs e overrides importam tipos diretamente de ~/types/<file>.

Decisões técnicas

DecisãoMotivo
Cookie HttpOnly para JWTToken não fica acessível no browser JS; leitura e validação acontecem no server.
AuthService singleton client-sideUm único ApiClient pro browser, separado do SSR.
Zustand sem persist para authO documento SSR é auth-agnóstico: a store hidrata client-side a partir do cookie is_authenticated + /api/auth/profile. É isso que torna o HTML público compartilhável entre usuários (e portanto cacheável). Ver Fluxo de autenticação.
Uma store Zustand por domínioEvita store monolítica e facilita code-splitting. Auth/usuário/wallet ficam numa store do core (useAccountsStore), não no base.
Modais controlados por ZustanduseAuthModalStore (app/store/authModal.ts). Qualquer componente abre/fecha.
EnvProvider separado do BrandProviderclientEnv precisa existir antes de qualquer chamada client-side. Não carrega API_BASE_URL — o host do BFF nunca chega ao browser; o client só fala com rotas same-origin /api/*.
Marca = overrides/<brand-key>/ no mesmo repoFile-replacement resolvido em build time. Sem repo por marca.
Biome em vez de ESLintMais rápido, config única pra lint + format, sem plugins.
i18next para i18nBiblioteca consolidada com suporte a namespaces, interpolação e pluralização.