g/
← trabalhos

Plataforma multi-tenant

Uma rede de escolas com o financeiro sob controle

Construindo o ERP multi-unidade de uma rede esportiva, do banco de dados ao front.

Hub.IA TecSportsEm 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.

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 três decisões difíceis

Cada uma tinha um caminho óbvio. Em todas eu segui o outro — e em todas isso custou alguma coisa.

01

A mesma pessoa é duas pessoas diferentes

o caminho óbvio
Um usuário tem um papel. Faz login, carrega a permissão, pronto.
o que eu fiz
Login em dois estágios: POST /auth/login/ autentica a pessoa, POST /auth/select-membership/ escolhe em qual vínculo ela está operando agora. O token carrega papel e escopo hierárquico.
por quê
Na vida real da rede, a mesma pessoa é coordenadora numa unidade e professora em outra. Um papel único ou dava permissão demais numa unidade, ou de menos na outra. Não é detalhe técnico — é o modelo mental de quem trabalha em rede.
o que custou
Todo cliente da API precisa entender dois estágios, o front carrega um seletor de contexto e o refresh token precisa preservar a escolha.

02

Autorização e matrícula são coisas separadas

o caminho óbvio
“Aluno da turma X” é uma linha só. O vínculo é a permissão.
o que eu fiz
Membership (permissão) e Enrollment (aluno↔turma) viraram modelos distintos, com Matrícula → Contrato → Cobrança pendurados no enrollment.
por quê
Juntar os dois cria bugs que só aparecem meses depois: presença lançada para aluno sem vínculo válido, matrícula órfã de vínculo encerrado, contagem de vagas escrita em três lugares diferentes.
o que custou
Mais tabelas, mais joins, e a disciplina de manter a contagem de vagas escrita por um único serviço.

03

Vender o produto pela metade sem quebrar o produto

o caminho óbvio
Cliente quer pacote sem o módulo financeiro? Esconde os menus no front.
o que eu fiz
Uma feature flag por tenant que bloqueia a API — não só a interface — e revoga automaticamente as permissões financeiras ao ser desligada.
por quê
Esconder botão no front-end não é controle de acesso, é decoração. E o pacote de entrada precisava manter matrículas funcionando: o corte tinha que ser cirúrgico, não bruto.
o que custou
A flag convive com dois outros mecanismos parecidos, e a documentação precisa de uma seção inteira só de “não confundir com”.

o pulo do gato

O login que entende gente que trabalha em dois lugares

O primeiro estágio prova quem você é. O segundo decide em nome de qual unidade você está agindo — e é esse segundo token que carrega o escopo usado em toda checagem de permissão daí 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)

em português claro

A mesma professora dá aula em duas unidades e vê coisas diferentes em cada uma — o sistema entende isso sozinho.

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

O sistema cobre hierarquia de matriz, unidade e turma, controle de acesso por papel e escopo, matrículas e contratos, cobrança recorrente com régua automática, conciliação por webhook assinado e o módulo pedagógico de chamada e avaliação.

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?