Barber Cashflow
Produto próprio — desenvolvido, implantado e mantido integralmente por Paulo.
A barbearia registrava agendamentos, vendas, comissões e caixa de forma manual e dispersa. Sem visibilidade consolidada, fechar o dia exigia reconciliar anotações de fontes diferentes. O pedido inicial era um sistema de agendamento.
No meio do desenvolvimento veio uma virada: a arquitetura comportava mais de uma barbearia. O sistema virou SaaS. Essa decisão trouxe, de uma vez, isolamento de dados entre tenants, responsabilidade sobre LGPD e a necessidade de subir atualizações sem interromper uma operação que já rodava com cliente real.
O sistema hoje registra vendas por forma de pagamento (PIX, cartão, dinheiro), calcula comissão por barbeiro por venda, controla estoque e fecha o caixa com conciliação automática. Funciona como PWA — instalável no celular do barbeiro, com operação offline parcial.
Django + DRF servindo a API REST com autenticação JWT; PostgreSQL como banco compartilhado entre tenants; isolamento por tenant_id resolvido via TenantMiddleware a cada request; Nginx como reverse proxy; Docker para build reproduzível; Cloudflare para SSL e CDN. A camada RAG corre separada: dados do sistema são chunked, convertidos em embeddings com a API da OpenAI (text-embedding-3-small) e indexados com FAISS. PWA com service worker para uso offline parcial.
Multi-tenancy: shared-db com tenant_id
- Problema
- Múltiplas barbearias precisam de isolamento total de dados. Um barbeiro de um salão não pode ver — nem acessar por erro — os dados de outro.
- Alternativas
- Banco separado por tenant: isolamento máximo, mas custo operacional alto e migrations em N bancos a cada mudança de schema. Schema separado no PostgreSQL: migrations multiplicadas e roteamento de conexão complexo.
- Escolha
- Base compartilhada com tenant_id em cada tabela relevante. TenantMiddleware resolve o tenant atual a partir de três fontes em ordem de prioridade: subdomínio → cabeçalho HTTP → parâmetro de query. O filtro por tenant é obrigatório em toda queryset exposta.
- Resultado
- Um tenant pagante em produção. A arquitetura suporta múltiplas barbearias na mesma instância Django e no mesmo banco PostgreSQL, e adicionar um novo tenant não requer redeploy nem nova instância de banco — o isolamento entre eles é garantido por filtro obrigatório em queryset e provado por teste, não por volume de operação.
Testes de isolamento retornam 404, não 403
- Problema
- A suíte de testes precisa provar que um tenant não acessa dados de outro. A resposta de erro importa tanto quanto a negação de acesso em si.
- Alternativas
- 403 (Forbidden): semanticamente correto — o recurso existe, mas o acesso é negado. 404 (Not Found): não revela se o recurso existe ou não.
- Escolha
- 404. A view trata o recurso de outro tenant como inexistente para o tenant solicitante. Os testes de isolamento tentam ativamente acessar dados de outro tenant e verificam que recebem 404 — não 403.
- Resultado
- Isolamento validado por testes automatizados. Um atacante com credenciais válidas de um tenant não consegue nem confirmar a existência de recursos de outros tenants.
transaction.atomic no cancelamento de venda
- Problema
- Cancelar uma venda toca três domínios simultaneamente: estoque (devolução de produto), comissão do barbeiro (estorno proporcional) e caixa (lançamento de cancelamento). Uma exceção no meio da operação deixaria os três em estados inconsistentes entre si.
- Escolha
- transaction.atomic() envolve toda a operação de cancelamento. Se qualquer etapa falhar — por exceção Python, erro de banco ou violação de constraint — o PostgreSQL reverte todas as alterações automaticamente.
- Resultado
- Garantia de atomicidade delegada ao banco. A lógica de negócio não precisa implementar rollback manual. Em 8 meses de operação: zero registro de inconsistência financeira por cancelamento parcial.
Soft delete para preservar histórico financeiro
- Problema
- Registros financeiros precisam de rastreabilidade completa. Excluir fisicamente uma venda apagaria o histórico contábil e quebraria a conciliação de períodos já fechados.
- Escolha
- Soft delete com campo is_deleted e SoftDeleteManager que filtra automaticamente registros excluídos em todas as queries padrão. Queries de auditoria e relatórios usam .all_objects para acesso irrestrito ao histórico.
- Resultado
- O usuário cancela registros sem perder histórico. Relatórios de períodos anteriores permanecem íntegros mesmo após cancelamentos posteriores.
RAG real para o assistente interno
- Problema
- Os barbeiros e o dono precisam consultar dados do sistema em linguagem natural — saldo do dia, comissão da semana, produto com estoque baixo. Keyword search não entende variações naturais de pergunta nem contexto financeiro.
- Escolha
- Pipeline RAG completo: chunking semântico dos dados do sistema → embeddings com OpenAI text-embedding-3-small → índice FAISS para busca vetorial por similaridade → contexto relevante injetado no prompt de resposta.
- Resultado
- O usuário pergunta em linguagem natural e recebe resposta contextualizada com dados reais do sistema, sem navegar por menus. Não é keyword search com GPT na frente — é retrieval semântico de verdade.
N+1 na listagem de vendas
- Sintoma
- A listagem de 20 vendas demorava visivelmente. O cliente notou a lentidão semanas depois de o sistema entrar em produção — não havia monitoramento de query count configurado no início.
- Investigação
- Django Debug Toolbar mostrou 61 queries para renderizar 20 registros: 1 query para listar as vendas + 20 queries para o barbeiro de cada venda + 20 para o serviço + 20 para o agendamento associado.
- Causa
- As relações FK (barbeiro, serviço, agendamento) não estavam sendo carregadas previamente na queryset da view. O ORM emitia uma query separada para cada objeto no momento em que o template acessava cada FK.
- Correção
- select_related('barbeiro', 'servico') para as FKs diretas — vira JOIN único. prefetch_related('agendamento') para a relação inversa — vira query IN separada. Resultado: 61 queries → 3 queries. Mesma listagem, sem alteração no template.
+R$ 245 mil em volume transacionado pelo sistema entre 25/11/2025 e 29/07/2026 (~8 meses), com ~7.000 vendas registradas e 5 barbeiros operando diariamente. Recorte por forma de pagamento: PIX R$ 143.335 · Cartão R$ 72.210 · Dinheiro R$ 30.150.
Nenhum incidente de isolamento em 8 meses com o tenant em produção. O acesso cross-tenant é coberto por testes que tentam ativamente ler dados de outro tenant e verificam o retorno 404. Suíte de +4.000 linhas de testes automatizados cobrindo isolamento multi-tenant, atomicidade de operações financeiras, soft delete e a interface RAG.
Foi nesse momento que deixei de enxergar o software apenas como código e passei a enxergá-lo como um produto que precisa continuar funcionando enquanto evolui. A virada de sistema único para SaaS — com um cliente real já usando — tornou concreto o que antes era teórico: isolamento de dados, consistência transacional e testes como documentação viva do comportamento esperado.