Voltar ao portfólio
Piloto passando pela ponte de madeira da pista, com público e o balão de um patrocinador ao fundo Piloto chegando à crista de um salto O mesmo piloto já no ar, sobre o morro de terra

Portal MX

A plataforma que tirou a gestão de um campeonato de motocross da planilha e do grupo de WhatsApp — inscrição, pagamento, documentação, resultado e ranking no mesmo lugar.

PapelDesenvolvedor solo
ClienteCampeonato Paulista de Motocross
Período2025 – 2026
StatusNo ar

Imagens: galeria do Campeonato Paulista de Motocross

Um campeonato inteiro rodando em planilha

Organizar uma etapa de motocross envolve muito mais do que largar as motos. Cada piloto precisa se inscrever numa categoria, provar que tem licença e seguro em dia, pagar a taxa, receber um número, correr, e ter o resultado somado à pontuação da temporada. Multiplique isso por dezenas de pilotos, mais de uma dúzia de categorias e várias etapas.

Antes do Portal MX, esse fluxo vivia espalhado entre planilhas, mensagens de WhatsApp e comprovantes soltos. Os pontos de ruptura eram sempre os mesmos:

Piloto de vermelho contornando uma curva de lama
Imagens: galeria do Campeonato Paulista de Motocross

O que foi construído

O Portal MX é uma aplicação única com duas faces. A pública, onde o piloto se inscreve e o público acompanha o campeonato. E a administrativa, onde a organização conduz a operação da temporada.

Portal público

18 rotas abertas

  • /
  • /eventos
  • /eventos/:slug
  • /eventos/:slug/inscrever
  • /calendario
  • /rankings
  • /resultados
  • /noticias
  • /galeria
  • /pilotos
  • /pilotos/:id
  • /empresa
  • /sobre
  • /blog
  • /parceiros
  • /contato
  • /termos
  • /privacidade

Painel administrativo

14 módulos, acesso por papel

  • Dashboard
  • Eventos
  • Inscrições
  • Pilotos
  • Pagamentos
  • Categorias
  • Rankings
  • Calendário
  • Notícias
  • Galeria
  • Usuários
  • Logs
  • Analytics
  • Configurações

Como as peças se encaixam

É uma SPA em React servida como estático, falando direto com o Supabase. Não existe backend próprio no meio do caminho: a autorização mora no banco, em Row Level Security, e o mesmo conjunto de regras vale para qualquer cliente que se conecte.

Vercel build estático + CDN Navegador SPA React 19 + Vite React Router · React Query Recharts · Zod · Tailwind Supabase Supabase Auth sessão + JWT Postgres 20 tabelas · 72 policies RLS 22 migrations versionadas Storage documentos · galeria toda leitura e escrita passa por RLS login consultas + JWT upload
Piloto saindo de uma curva, com as tendas das equipes e o estacionamento ao fundo
Imagens: galeria do Campeonato Paulista de Motocross

As escolhas que sustentam o sistema

Row Level Security como fonte da verdade

Autorização

Esconder um botão no front-end não é segurança. Como o navegador fala direto com o banco, toda regra de quem-vê-o-quê está escrita em policies de RLS no Postgres — 72 delas. Um piloto lê a própria inscrição porque a policy permite, não porque a tela não mostrou o resto.

Quatro níveis de acesso, verificados no banco

Papéis

superadmin, admin, organizer e pilot. O papel vem do perfil do usuário e é lido dentro das policies, então a mesma consulta devolve conjuntos diferentes conforme quem pergunta. As rotas do admin ainda são protegidas no cliente, mas isso é conveniência de navegação — não é a barreira.

Banco versionado em migrations

Schema

São 22 migrations numeradas, do schema base às permissões e ao fluxo de inscrição. O banco é reconstruível do zero em ordem, e cada mudança tem um arquivo com data e intenção — o que evita o clássico "funciona na minha máquina" quando o schema é editado direto no painel.

React Query no lugar de estado global

Dados no cliente

Quase todo o estado da aplicação é, na verdade, estado do servidor. Delegar cache, revalidação e loading ao React Query eliminou a necessidade de um store global e manteve as telas de listagem consistentes sem sincronização manual.

Validação declarada uma vez

Formulários

Inscrição de evento tem muitos campos condicionais. Com Zod descrevendo o formato e React Hook Form ligando ao formulário, a regra de validação fica em um lugar só e o erro aparece no campo certo, sem cadeia de ifs espalhada pelo componente.

O tamanho do projeto

arquivos TypeScript

122

linhas de TS/TSX

20,4 mil

tabelas no Postgres

20

policies de RLS

72

migrations versionadas

22

módulos no admin

14

rotas públicas

18

Números apurados diretamente no código-fonte do projeto. Não incluí métricas de uso (pilotos cadastrados, inscrições processadas) porque essas eu não consigo verificar aqui.

O que eu levo daqui