g/
← projetos

Hub.IA TecSports

Plataforma multi-tenant white-label

Um sistema que cada escola sente como seu

Construindo a plataforma que atende várias redes esportivas ao mesmo tempo — cada uma com a sua cara, os seus termos e os seus módulos.

Em stagingexit 0mar–jul 2026Arquiteto e desenvolvedor único
Django 5Next.js 14PostgreSQL 16RedisCelery

o problema

Uma rede de escolas esportivas com várias unidades fechava o mês em planilhas que não batiam — e ninguém sabia, no dia 5, quanto a rede inteira tinha a receber.

o projeto

O Hub.IA sustenta várias redes de escolas de esporte ao mesmo tempo — e cada rede opera como se o sistema fosse exclusivamente dela. Os dados de cada cliente ficam isolados no próprio banco de dados, e cada escola ajusta a interface, as cores e até a terminologia do dia a dia: o que numa rede é “Unidade”, na outra pode ser “Polo”; o que é “Aluno” pode virar “Atleta”. Sobre essa base, os módulos ligam e desligam conforme o pacote contratado, sem afetar o resto. Cada unidade enxerga só o que é dela, a matriz enxerga a rede inteira, e a cobrança recorrente roda sozinha.

Como a operação funcionava antes

  • Cada unidade da rede controlava matrícula, presença e mensalidade na própria planilha, com formato próprio.
  • O fechamento do mês era manual: alguém consolidava as planilhas à mão e o número só ficava pronto lá pelo dia 10.
  • Cobrança em atraso dependia de alguém lembrar de olhar. Não havia régua automática.
  • Uma pessoa que trabalhava em duas unidades existia duas vezes no sistema, com cadastros independentes que divergiam.

As decisões difíceis

01

A terminologia é configuração, não código

o caminho óbvio
Escrever “Unidade”, “Aluno” e “Matrícula” fixos na tela — é o vocabulário do sistema.
o que eu fiz
Cada escola tem um dicionário próprio, validado por schema, que sobrepõe os termos padrão e é injetado na interface em tempo real. Uma rede chama “Unidade” de “Polo”, outra chama “Aluno” de “Atleta” — e o sistema inteiro se adapta.
por quê
Cada rede tem o vocabulário dela. Forçar o meu cria um atrito diário com quem usa o sistema — e uma escola que não se reconhece na tela não confia no sistema.
o que custou
Um schema fechado para manter (com teto de termos), e cada tela precisa buscar os rótulos do tenant em vez de usar texto fixo.

02

O isolamento mora no banco, não no código

o caminho óbvio
Filtrar cada consulta por “só os dados deste cliente” e confiar que ninguém vai esquecer.
o que eu fiz
Além do filtro, liguei o Row-Level Security do PostgreSQL: a cada requisição o banco recebe qual é o cliente atual e passa a recusar, sozinho, qualquer dado de outro. É uma trava no banco, não no código.
por quê
Um único filtro esquecido vaza o dado de uma rede para outra — o pior que pode acontecer num sistema multi-cliente. O RLS é a rede de proteção que o código não consegue furar.
o que custou
Um middleware que define e reseta o cliente a cada requisição, e disciplina no manejo das conexões do banco.

03

Ligar e desligar módulos sem quebrar o resto

o caminho óbvio
Cliente quer um pacote menor? Faz um fork do sistema, ou esconde os menus na tela.
o que eu fiz
Um interruptor por cliente que bloqueia o módulo na API — não só na interface — e revoga os acessos ligados a ele. O pacote de entrada acende só o que foi contratado, e as matrículas continuam funcionando.
por quê
Esconder botão na tela não é controle de acesso, é decoração. E o corte precisava ser cirúrgico: desligar o financeiro sem derrubar a gestão.
o que custou
O interruptor convive com outros dois mecanismos parecidos, e a documentação precisa de uma seção só de “não confundir com”.

o pulo do gato

O login que entende gente que trabalha em dois lugares

O primeiro passo prova quem você é. O segundo decide em nome de qual unidade você está agindo — e é esse segundo passo que define o que você pode ver e fazer dali em diante.

POST /api/v1/auth/login/
  → 200  { access, refresh, memberships: [...] }
     A pessoa foi autenticada. Ainda não pode fazer nada.

POST /api/v1/auth/select-membership/
  { membership_id }
  → 200  { access }   # agora com papel + escopo hierárquico
     Coordenadora na Tijuca ≠ professora na Barra.
Fluxo de autenticação em dois estágios (L1 → L2)

O que eu refiz

Das 217.963 linhas trabalhadas, 51.204 foram removidas. Boa parte disso é a modelagem de matrículas, que eu refiz depois que as primeiras falhas apareceram em teste: presença sendo lançada para aluno sem vínculo válido e matrícula sobrevivendo ao fim do vínculo. Dava para ter empurrado com validação em cima. Preferi separar os modelos e reescrever o que dependia deles. Reescrever naquele momento custou dias; reescrever depois custaria a operação do cliente.

Onde chegou

217.963

linhas trabalhadas em 3 meses e meio

431

testes automatizados

15

módulos de negócio

5

gateways de pagamento integrados

A plataforma cobre a hierarquia de matriz, unidade e turma, o controle de acesso por papel e escopo, matrículas e contratos, cobrança recorrente com régua automática, conciliação por gateway e o módulo pedagógico de chamada e avaliação — tudo customizável por escola sobre a mesma base.

git diff --shortstat · autoria própria
+166.75951.2041.039 arquivos
git shortlog -sn --all
   320  Gustavo Pires
     1  (criação do repositório vazio)

343 commits · 66 pull requests mergeados

o que ainda falta

  • Está em staging interno numa VPS, validado por health check e login — ainda não é produção com usuário real.
  • Falta domínio próprio e HTTPS: o tráfego hoje roda em HTTP nas portas publicadas.
  • A migração dos dados históricos das planilhas das unidades ainda não foi feita.

Tem um problema parecido?