Geração de leads com IA
Plataforma de Prospecção B2B
Leads B2B gerados com IA, cadastro conferido, sem duplicata no CRM e um modelo que aprende por que o comercial descarta.
- negócios extraídos para treinar o modelo
- 27,4 mil
- mais rápida, a consulta de enriquecimento
- 167×
- commits em 5 meses
- 233
- arquivos de teste no backend
- 92
Números medidos no repositório.
CNAEs sugeridos pela IA
- 2221-8/00
- 2222-6/00
- 1731-1/00
- 1732-0/00
| Empresa | CNPJ | UF | Desde | Gate |
|---|---|---|---|---|
| Embalagens Exemplo Ltda | 00.000.000/0001-00 | PR | 2009 | PERMIT |
| Plásticos Fictícios S.A. | 00.000.000/0001-00 | SC | 1998 | PERMIT |
| Caixas Modelo Indústria | 00.000.000/0001-00 | RS | 2015 | REVIEW |
| Papelão Demonstração Ltda | 00.000.000/0001-00 | PR | 2021 | BLOCK |
| Filmes Teste Industrial | 00.000.000/0001-00 | SC | 2004 | PERMIT |
Contexto e problema
Montar uma lista de prospecção B2B exige três coisas que costumam viver separadas: achar empresas que valem o contato, conferir o cadastro de cada uma e colocar o resultado no CRM sem duplicar o que já existe lá. Cada etapa erra de um jeito. Entra empresa baixada, entra filial no lugar da matriz, o mesmo CNPJ aparece duas vezes em funis diferentes.
A solução foi juntar as três etapas numa plataforma interna. O usuário descreve um mercado ou sobe uma planilha, a plataforma qualifica cada empresa com uma régua única e importa no CRM em lotes, com progresso ao vivo e rollback. Com o tempo vieram mais módulos: um que resgata negócios descartados, um modelo que tenta aprender por que o comercial descarta e uma área de inteligência comercial com funil e mapa.
Os blocos “Detalhes técnicos” abrem com um clique, para quem quiser ver como cada parte funciona por dentro.
Restrições do ambiente
- Uma régua só. Uma empresa reprovada na consulta cadastral não pode passar porque chegou por outro caminho, seja IA, planilha ou resgate.
- O CRM tem vida própria. Ele roda automações nativas que reagem a cada escrita, e a plataforma precisa conviver com elas.
- Custo controlado por pessoa. Cada usuário tem créditos por mês, e nenhuma falha ou repetição pode cobrar duas vezes.
- Login da empresa. SSO corporativo com OIDC e PKCE. A autorização por papel fica na própria plataforma.
- Buscas que podem ser enormes. A base cadastral cobre o país inteiro, e uma busca mal recortada derruba o servidor.
Decisões que definiram o produto
Arquitetura
A empresa chega por geração com IA, por planilha ou como negócio descartado. A consulta cadastral passa por cache, fonte oficial e provedores públicos, sempre com a mesma régua. Depois vêm a lista de bloqueio, as regras de negócio e o scorer de CNAE. O score do modelo aparece, mas não barra. Quando o negócio já existe no CRM, a movimentação acontece em duas fases: primeiro a marcação, depois a troca de etapa. O crédito é cobrado uma vez por negócio.Se a fase B falha, o negócio fica marcado e parado. A retomada reenvia só a fase B, nunca marcação e movimentação juntas, porque o CRM apagava negócios que recebiam as duas na mesma escrita. A chave idempotente por negócio e um índice único no banco garantem que a retomada não cobra crédito de novo.
O backend é um FastAPI em Python 3.12 que também serve o frontend. A fonte da verdade é um PostgreSQL remoto, que concentra o que já foi indicado e importado. O estado local da aplicação fica num SQLite: sessões, jobs, cache de CNPJ e créditos. Trabalho longo (geração, importação, resgate) roda em segundo plano, e o progresso chega ao navegador por SSE.
Detalhes técnicos: stack e escolhas de infraestruturaFastAPI · React sem build · OIDC
O frontend é uma SPA em React 18 sem etapa de build: o JSX é transpilado no navegador e a própria API entrega a página. Escolhi isso para que o deploy fosse um único contêiner, sem pipeline de frontend. O custo aparece no primeiro carregamento, mais lento, e na falta de tipagem e de testes de componente.
A divisão entre os dois bancos segue o dono do dado. O PostgreSQL remoto é compartilhado com outros sistemas e diz o que já existe. O SQLite guarda o que só interessa a esta aplicação, e por isso fica num volume do Docker, fora do contêiner, para sobreviver a um rebuild.
O login é OIDC com PKCE contra o SSO da empresa, com cookie seguro por padrão. Os papéis (usuário e administrador) e os créditos mensais ficam na própria plataforma, porque o SSO diz quem é a pessoa, não o que ela pode gastar. A suíte de testes roda num workflow de CI.
O que a plataforma entrega
Consulta cadastral
Cada CNPJ passa pela mesma régua, venha de onde vier. O resultado fica em cache por 120 dias.
Detalhes técnicos: cache, fonte oficial e três provedorescache · TTL 120 d
A ordem é fixa. O cache em SQLite responde se tiver um registro com menos de 120 dias. Sem ele, a consulta vai para a API cadastral oficial. Se a oficial falhar, e o administrador permitir, entram três provedores públicos em sequência. A régua (ativa, matriz, porte, fundação há 3 anos ou mais, natureza jurídica que não seja de pessoa física) é aplicada depois da resposta, igual em todas as camadas, para que a escolha do provedor não altere o veredito.
O preço do cache é a defasagem: uma empresa que muda de situação dentro da janela só é vista na próxima ida à fonte. Aceitei isso porque a mesma empresa é consultada muitas vezes por usuários diferentes, e cada consulta à fonte paga latência e limite de requisições.
Geração de leads com IA
O usuário descreve o mercado e recebe uma lista de empresas já qualificadas e sem duplicatas contra a base.
Detalhes técnicos: CNAEs sugeridos pelo Gemini, dedup e trava por usuáriojob · trava por usuário
O Gemini pesquisa o mercado descrito e sugere os códigos de atividade econômica (CNAE). A partir deles a plataforma extrai empresas, passa cada uma pela régua cadastral e remove o que já existe na fonte da verdade. CNAEs secundários entram só se o usuário marcar a opção. A geração roda em segundo plano, e as listas ficam salvas: um reload não perde o trabalho.
Cada usuário tem uma trava contra geração concorrente, então duas gerações dele nunca disputam os mesmos créditos e as mesmas empresas. Quando a geração esgota empresas novas, ela recorre ao resgate e move negócios que já existem no CRM, em vez de criar duplicatas. Esse caminho é best effort: se falhar, a geração entrega o que tem e não cai.
Importação para o CRM
Planilha XLSX ou CSV entra, negócios saem no CRM, com progresso ao vivo e a opção de desfazer o job inteiro.
Detalhes técnicos: lotes, throttle, retry exponencial e rollback por jobSSE · rollback por job
A importação é assíncrona e ganha um id de job assim que começa. As linhas são deduplicadas e enviadas em lotes com throttle, para não estourar o limite de requisições do CRM. Falhas transitórias têm retry com backoff exponencial. O progresso vai para o navegador por SSE.
Tudo que um job criou fica associado ao id dele, e o rollback desfaz só aquele job. O que falhou, e o que foi barrado por já existir na fonte da verdade, pode ser exportado num arquivo para correção e reenvio, em vez de se perder no log.
Resgate de negócios descartados
Negócios descartados voltam a ser avaliados com as mesmas regras de um lead novo, e os que passam são devolvidos ao funil.
Detalhes técnicos: gate em camadas e duas fases idempotentesgate L1 a L4 · 2 fases
| Camada | O que faz | Bloqueia? |
|---|---|---|
| L1 | Lista de bloqueio heurística (condomínios, fundos, pessoa física pela natureza jurídica) | Sim |
| L2 | 5 regras de negócio da qualificação | Sim |
| L3 | Scorer de CNAE | Sim |
| L4 | Score do LightGBM | Não, só aconselha |
Quem passa vai para a movimentação em duas fases. A fase A grava a marcação, a plataforma espera um intervalo configurável, e a fase B move o negócio e limpa o responsável. Separei as fases depois de ver o CRM mandar para deduplicação, e apagar, negócios que recebiam marcação e movimentação na mesma escrita.
A chave de idempotência combina execução e negócio. Um negócio preso entre as fases é reconhecido, e a retomada reenvia só a fase B. O crédito é cobrado uma vez por negócio movido, garantido por índice único no livro de créditos. Um limite segue aberto: um dos funis desfaz em silêncio mudanças de categoria feitas pela API, mesmo respondendo sucesso. Daí ficou a regra de tirar um retrato de todos os campos antes de qualquer escrita, para poder reverter com valores lidos e não deduzidos.
Modelo de descarte
Um modelo que aprende com o histórico por que o comercial descarta leads e classifica cada lead novo como PERMIT, REVIEW ou BLOCK antes de ele virar negócio.
Detalhes técnicos: LightGBM calibrado, split temporal e gate de promoçãoLightGBM · calibrado
Foram extraídos 27,4 mil negócios, dos quais 14,8 mil com rótulo conhecido. O split é temporal, 80/10/10 ordenado por data de criação, porque um split aleatório vazaria o futuro para o treino. Variáveis categóricas usam target encoding com suavização. O desbalanceamento é tratado com scale_pos_weight em vez de SMOTE, a saída passa por calibração isotônica, e cada previsão vem com a explicação do SHAP.
No teste: ROC-AUC de 0,797, PR-AUC de 0,095 e precision@10 de 0,239. O PR-AUC parece baixo, mas a classe positiva é rara, e o número tem que ser lido contra essa taxa base. Um modelo novo só é promovido no registro se ganhar em PR-AUC e mantiver o erro de calibração em até 0,05. Para o concept drift, a janela de treino caiu de 365 para 180 dias, com meia-vida de 60 dias nos pesos.
O enriquecimento cruza os negócios com uma tabela de 71 milhões de linhas. A primeira versão filtrava com ANY() sobre um array de texto e caía em varredura completa. Trocar por um JOIN com unnest deixou a consulta 167× mais rápida.
Inteligência comercial
Um funil de prospecção em 7 etapas, um mapa com detalhamento por cidade e importação direto da cidade escolhida.
Detalhes técnicos: busca de similares por escopo e checagem de tamanhoHTTP 422 · ≥N
A primeira versão da busca de empresas similares usava todos os CNAEs da empresa-alvo como semente. Num caso real, um código secundário de transporte bastou para 87,5% dos candidatos serem transportadoras. Hoje a semente é o CNAE principal, expandido pela hierarquia oficial em escopo exato, por grupo ou por setor, e os secundários entram só por opção do usuário.
Buscas amplas batiam no timeout de 120 s por dois motivos: contagens sem filtro de UF e um índice de CNAEs secundários usado mesmo quando não era preciso. Agora o índice só entra quando a busca pede, uma checagem prévia recusa com HTTP 422 o escopo grande demais, e as contagens têm teto curto: acima dele a tela mostra “≥N” em vez de esperar o número exato.
No mapa, a importação por cidade usa a cidade e a UF exatas, sem normalizar grafia, para que o número de créditos bata com o que o mapa mostrou. As abas de análise comercial foram portadas de aplicativos Streamlit de um colega, com conferência numérica contra os originais.
Meu papel
Concebi e construí a plataforma: produto, backend, frontend, modelo de ML, integrações e operação. Foram 233 commits entre maio e outubro de 2026, 92 arquivos de teste no backend e CI rodando a suíte. As abas de análise comercial vieram de aplicativos de um colega; portei essas abas e conferi que os números batiam com os originais.
O que eu faria diferente
- Retrato dos campos antes de escrever, desde o primeiro dia. Um dos funis ainda desfaz em silêncio mudanças feitas pela API. Eu teria adotado a regra antes de descobrir o problema.
- Rotular a população certa para o resgate. O modelo de descarte foi treinado em outra população e por isso só aconselha. Com rótulos dos próprios negócios resgatados, ele poderia decidir.
- Testar o frontend. A SPA sem build é simples de servir, mas tem 3 testes, contra 92 arquivos no backend.