OSCapstack CRM
Sistema operacional comercial para originação de crédito imobiliário, em produção.
Desenvolvimento full-stack, de ponta a ponta · 26 dias de construção
- RLS policies
- 146
- telas
- 42
01 Problema
A operação inteira rodava em planilha. Lead chegava por três canais — Instagram, LinkedIn e WhatsApp — e era copiado à mão; ninguém sabia de cabeça quem já tinha sido atendido, e agenda, proposta e documento viviam cada um num lugar diferente. O produto tinha que virar o sistema operacional da corretora, não uma planilha com verniz.
02 Arquitetura
Monorepo TypeScript com Fastify 5 na API e Supabase/PostgreSQL como banco. Três frontends isolados: painel administrativo em React, painel do consultor externo (acesso restrito, dados só do que é dele) e uma landing em Astro para captação. São 56 tabelas com 146 RLS policies, 219 endpoints e 42 telas. Em produção em os.capstack.capital.
03 Decisões difíceis
146 RLS policies em 56 tabelas
A autorização mora no banco, não na aplicação: cada tabela tem suas próprias políticas de row-level security, então um bug na API não vaza dado de outro consultor. O custo é performance — toda policy roda uma função de contexto por linha — resolvido reescrevendo os helpers como subquery escalar, para o planner do PostgreSQL avaliar uma vez por consulta em vez de uma vez por linha (otimização de InitPlan).
Deploy blue-green em VPS
A cada deploy, a nova versão sobe numa porta separada da que está servindo. Um health-check de 15 tentativas decide se ela está saudável antes de qualquer usuário ser roteado para lá. Se falha, o container novo é removido e o antigo continua servindo — o pior caso de um deploy ruim é o deploy não acontecer, nunca um ar fora do ar.
Dead man’s switch do WhatsApp
Um container pode responder 200 no health-check HTTP e ainda assim estar com a instância do WhatsApp desconectada — é uma falha que nenhum probe HTTP convencional enxerga. Um cron de 2 em 2 minutos consulta o estado real da conexão; se caiu, ele para de pingar o healthchecks.io de propósito, para que o serviço externo dispare o alarme pela ausência de sinal.
Roleta ponderada segura sob concorrência
A distribuição de leads usa peso por consultor (clientes_ativos / peso), mas dois leads chegando ao mesmo tempo não podem cair no mesmo consultor por uma leitura desatualizada. A escolha do cliente e do consultor usa SELECT … FOR UPDATE dentro de uma única transação, serializando a leitura-e-escrita sem lock de tabela inteira.
04 Stack
- TypeScript
- Fastify 5
- Supabase
- PostgreSQL
- React
- Astro
- Playwright
- pgTAP
- Docker
05 O que mudou
Os três canais passaram a cair num lugar só, e o lead é distribuído na hora — por roleta ponderada ou direcionado a um consultor. A agenda sincroniza com o Google Calendar, documento de cliente é lido por IA em vez de conferido à mão, a mensageria responde sozinha o que é repetitivo, e o relatório de performance chega com o que a IA achou fora do padrão. Admin e vendedor têm cada um o seu app. E o dono monta o próprio funil e as próprias automações: mudar uma regra do processo deixou de depender de mim.