← Barber Cashflow
EM PRODUÇÃO 2026 – Atual · uso diário por funcionários

Gestão de Açaí

Desenvolvido integralmente por Paulo, para um estabelecimento real da família.

NÚMEROS
~1h → ~10min fechamento de caixa diário
~296 testes rodando contra PostgreSQL real a cada push
80% gate de cobertura — build falha abaixo disso
+115 pedidos · +R$ 4,6 mil registrados na primeira semana do módulo de pedidos
STACK
Django PostgreSQL (Neon) Render GitHub Actions Ruff pytest rclone Google Drive API
O PROBLEMA

Sistema financeiro em uso diário por funcionários de um estabelecimento real da família — uma usuária de baixa familiaridade técnica. Uma regressão em produção não é um bug abstrato: atrapalha o dia da loja, o fechamento de caixa e o controle de estoque.

O sistema já tinha ~296 testes e backup diário configurado. Mas nada impedia que código quebrado fosse ao ar: o Render fazia deploy direto a cada git push, sem rodar teste nenhum. A proteção existia só no papel.

O fechamento de caixa levava cerca de uma hora porque era feito manualmente, conciliando registros de formas de pagamento diferentes sem visibilidade consolidada. Com o módulo de caixa, o mesmo processo passou a levar ~10 minutos.

ARQUITETURA
Pipeline CI/CD e backup diário automatizado

Django com CBVs e arquitetura em camadas (View → Service → Selector → Model); PostgreSQL no Neon (serverless, sem servidor para gerenciar); hospedagem no Render; GitHub Actions orquestrando lint, testes e deploy; backup diário via workflow agendado no GitHub Actions — pg_dump do endpoint direto do Neon → gzip → rclone → Google Drive → rotação dos 15 mais recentes. Nenhum artefato persiste no runner.

DECISÕES
DECISÃO

Pipeline de CI/CD com trava de deploy

Problema
Código quebrado podia ir direto ao ar. Push na main disparava deploy imediato no Render, sem rodar lint nem testes. Com uma usuária de baixa familiaridade técnica dependendo do sistema, qualquer regressão visível virava um incidente.
Alternativas
Confiança manual: rodar os testes localmente antes de fazer push. Funciona até o dia em que não funciona. Deploy automático com toggle: desligar o auto-deploy e fazer deploy manual — troca uma automação por outra, mais frágil.
Escolha
Pipeline no GitHub Actions: push/PR → Ruff (lint) → pytest contra Postgres real → gate de cobertura 80% → só com CI verde o job de deploy chama o Deploy Hook do Render. Branch protection na main torna os checks obrigatórios e fecha o push direto. autoDeploy: false no render.yaml desliga o deploy automático por push. Regra central: deploy só acontece depois de CI verde.
Resultado
Nenhum deploy acontece com CI vermelho. A main está sempre em estado deployável. O pipeline virou registro no repositório — qualquer pessoa que olhar o código entende exatamente o que precisa passar para ir a produção.
DECISÃO

PostgreSQL real na CI, não SQLite

Problema
Testes que passam em SQLite podem falhar em PostgreSQL por diferenças de tipo, comportamento de constraints, migrations específicas de Postgres ou SQL que o SQLite não suporta. Um bug desse tipo só apareceria em produção.
Escolha
Service container de PostgreSQL efêmero no runner do GitHub Actions. Sobe ao lado do job de testes, recebe um health check antes dos testes rodarem, e é destruído ao final. Espelha exatamente o banco de produção (Neon/Postgres).
Resultado
~296 testes rodando contra PostgreSQL real a cada push. Nenhum bug de banco só-em-produção após a mudança. O CI valida o mesmo ambiente que o usuário usa.
DECISÃO

Backup diário via GitHub Actions, não cron no servidor

Problema
O roadmap original previa cron + rclone num servidor sempre ligado. No Render free isso não existe: a instância hiberna quando não recebe tráfego, o filesystem é efêmero e não há suporte a cron. Uma queda de dados financeiros sem backup é inaceitável.
Alternativas
Migrar para plano pago no Render (custo recorrente sem benefício técnico). Backup manual periódico (confiança humana, não confiança de sistema). VPS separado só para o cron (infraestrutura para resolver falta de infraestrutura).
Escolha
Workflow agendado no GitHub Actions (cron: '0 3 * * *'): pg_dump com endpoint direto do Neon (sem pooler/PgBouncer) → gzip → validação de tamanho mínimo → rclone para o Google Drive → validação do upload → rotação mantendo os 15 mais recentes. Nada persiste no runner efêmero. Validado em produção em 17/07/2026.
Resultado
Backup diário automatizado, sem servidor extra para manter. Rotação automática de 15 arquivos. O próprio GitHub Actions registra cada execução — auditável, sem dependência de infraestrutura adicional.
DECISÃO

Arquitetura em camadas documentada antes do código

Problema
Sem fronteiras claras entre camadas, regras de negócio migram para as views, os testes ficam acoplados à camada HTTP e qualquer mudança de lógica exige alterar em vários lugares.
Escolha
View → Service (escrita) / Selector (leitura) → Model. Views não contêm regra de negócio. Services encapsulam a lógica de escrita com transações. Selectors isolam queries complexas. Regras de nomeação, paginação, índices e validações escritas em PROJECT_RULES.md antes do primeiro modelo.
Resultado
Testes de serviço sem dependência de request/response HTTP. Refatoração de query sem tocar em view. Novos módulos seguem o mesmo padrão sem instrução adicional.
O QUE QUEBROU E COMO RESOLVI
● INCIDENTE

pg_dump 16 incompatível com Neon PostgreSQL 18

Sintoma
O workflow de backup falhava com erro de incompatibilidade de versão: o runner Ubuntu vinha com pg_dump 16, mas o Neon rodava PostgreSQL 18. Dumping de versão inferior para banco superior não é suportado.
Causa
O GitHub Actions usa a versão do pg_dump instalada no runner por padrão, que não acompanha automaticamente a versão do banco de destino.
Correção
Instalação do pg_dump 18 via repositório PGDG e adição do binário ao início do PATH com echo 'caminho' >> $GITHUB_PATH. A versão correta é encontrada primeiro, sem conflito com a versão pré-instalada.
● INCIDENTE

Pooler do Neon incompatível com pg_dump

Sintoma
pg_dump falhava com erro de protocolo ao usar o endpoint padrão do Neon (com PgBouncer). A conexão era estabelecida, mas o dump não completava.
Causa
PgBouncer (pooler de conexões) não suporta o protocolo de comunicação que pg_dump usa para executar dumps. O pg_dump requer conexão direta ao banco.
Correção
Usar o endpoint direto do Neon (sem pooler) na variável de ambiente do workflow. O Neon disponibiliza os dois endpoints — o com pooler para a aplicação e o direto para operações de administração.
● INCIDENTE

Secret multiline rejeitado pelo GitHub

Sintoma
A string de configuração do rclone para o Google Drive tem múltiplas linhas. Ao tentar cadastrar via interface do GitHub, o secret era corrompido ou rejeitado.
Correção
Cadastrar via CLI: echo 'conteúdo multiline' | gh secret set RCLONE_CONFIG. O pipe preserva as quebras de linha que a interface do GitHub não aceita diretamente.
● INCIDENTE

Rotação apagando backup antes de confirmar upload

Sintoma
Na primeira versão do workflow, a rotação dos backups antigos rodava antes da validação de que o novo arquivo tinha sido enviado com sucesso ao Drive. Uma falha de upload silenciosa apagaria o histórico sem novo backup válido.
Correção
Reordenar as etapas: upload → validação do arquivo no Drive (tamanho e existência) → só então rotação dos mais antigos. Falha em qualquer etapa de validação interrompe o workflow antes de apagar qualquer coisa.
RESULTADO

Fechamento de caixa diário reduzido de ~1 hora para ~10 minutos. Na primeira semana do módulo de pedidos: +115 pedidos e +R$ 4,6 mil registrados pelo sistema.

~296 testes automatizados rodando contra PostgreSQL real a cada push, com gate de cobertura de 80%. Backup diário automatizado, validado em produção desde 17/07/2026, com rotação de 15 arquivos no Google Drive. Nenhum deploy com CI vermelho desde a implementação do pipeline.

CAPTURAS
APRENDIZADO

Ter testes sem trava de deploy é ter uma rede de segurança com buracos. O CI/CD não é complexidade extra — é o que transforma testes em garantia real. E backup sem validação do upload não é backup: é um ritual que dá sensação de segurança sem a segurança de fato.