Cada decisão da Fase 1, virando tabela
As 7 divergências da seção 14 foram aprovadas integralmente. Esta fase traduz cada uma delas em estrutura de dados real — testada, não apenas desenhada: o schema abaixo foi executado num Postgres 16 limpo e validado com inserções e updates reais antes de virar este documento.
schema.sql, entregue junto com este documento.
Visão geral & princípios
O que guiou o desenhoQuatro princípios organizam todas as tabelas abaixo, todos derivados diretamente da Fase 1:
1 · Estado ≠ registro imutável
Status de validação, qualificação, funil e contato mudam com frequência e cada mudança tem valor histórico — por isso o schema separa estado corrente (nas colunas de leads) de histórico de mudanças (em eventos_auditoria e lead_score_historico), em vez de sobrescrever sem rastro.
2 · Fonte é dado, não metadado
"Confirmado × Estimado" e a fonte pública de cada campo (seção 5 da Fase 1) não cabem em duas colunas fixas por campo — viram uma tabela lateral (lead_campo_fonte) que cobre qualquer campo presente ou futuro, sem exigir migração de schema a cada novo dado rastreável.
3 · Régua versionada, não substituída
Os pesos do Lead Score serão recalibrados trimestralmente (seção 2). Pesos vivem em tabela própria com versão, e cada score calculado grava qual versão usou — isso preserva comparabilidade histórica em vez de corromper score antigo com peso novo.
4 · Log é apêndice
eventos_auditoria só recebe INSERT. Mudanças de estágio de funil, status de contato, classe e score são logadas automaticamente por trigger de banco — não dependem de o colaborador lembrar de registrar (seção 12).
Diagrama de entidades
10 tabelasleads é o centro do modelo — praticamente toda outra tabela existe para descrever, pontuar, rastrear ou logar um lead. As linhas tracejadas indicam referência lógica (não é uma chave estrangeira do banco); todas as demais são FKs reais, com detalhe completo na tabela de relacionamentos abaixo.
Usuários
Quem faz o quêSuporta a rotina diária (seção 8 da Fase 1) e a auditoria (seção 12) — todo evento relevante precisa de um "quem". Três papéis: colaborador (pesquisa, contata, atualiza leads), gestor (valida amostralmente células territoriais, decide duplicatas nível 3), admin (única exceção com permissão de alterar log de auditoria — e essa exceção também fica registrada).
Células territoriais
Implementa a seção 3Cada célula (bairro na Capital, município a partir da Grande SP) carrega o critério composto de cobertura aprovado: termos_distintos_count, fontes_cruzadas[] e rodadas_sem_novo_lead juntos determinam quando o status avança para mapeado. A tabela termos_busca_executados é o registro de apoio — cada termo de busca realmente executado numa célula vira uma linha ali, o que sustenta o critério sem depender de estimativa manual.
| Campo | Papel |
|---|---|
status | não_iniciado / em_progresso / mapeado / consolidado |
mapeado_em | quando o critério composto foi atingido — aguarda validação amostral |
consolidado_em / reciclar_em | validado vira consolidado; reciclar_em = consolidado_em + 90 dias |
ranking_potencial | preenchido pela Fase 6/9 com dado real (score ponderado), não por achismo — ver seção 3 da Fase 1 |
idx_celulas_reciclagem) roda diariamente e volta toda célula com reciclar_em vencido para em_progresso — sem isso, "consolidado" ficaria sendo confiado indefinidamente.
Leads — núcleo do sistema
Implementa as seções 1, 5, 8 e 9A tabela mais importante do schema. Três decisões merecem destaque:
Funil e status de contato são colunas separadas
Exatamente como aprovado na seção 9: funil_estagio é o enum linear de 8 estágios (colunas do Kanban); status_contato é um enum paralelo e opcional, que só passa a fazer sentido a partir de "em_contato" — um card pode oscilar entre "sem resposta" e "retorno solicitado" sem nunca mudar de coluna.
funil_estagio (linear)
status_contato (paralelo, nulável)
aguardando_primeiro_contatosem_respostaretorno_solicitadosem_interesse_momentocontato_futuro_agendadosem_interesse_definitivoperdido
Corte de "grande rede" é regra codificada, não julgamento
Os campos numero_unidades e numero_estados alimentam a coluna booleana excluido_grande_rede: verdadeira quando >20 lojas OU presença em >3 estados, exatamente o corte aprovado na seção 1. Entre 6 e 20 lojas o lead entra normalmente, mas o Lead Score (próxima seção) já reflete o porte maior nos critérios de fit.
Exclusão sempre com motivo
Um check constraint impede fisicamente que um lead vire classe = 'X' sem motivo_exclusao preenchido — testado durante a validação deste schema: a tentativa de gravar uma exclusão sem motivo foi rejeitada pelo banco, como pretendido pela seção 2 ("esses registros não desaparecem — ficam com status Excluído e o motivo").
Proveniência por campo
Implementa a seção 5lead_campo_fonte guarda, para qualquer campo do lead, o valor encontrado, se é confirmado ou estimado, a fonte pública (descrição + URL quando existir) e quem pesquisou. Isso resolve dois requisitos da Fase 1 ao mesmo tempo: "Não localizado" nunca é confundido com "ninguém pesquisou ainda" (a ausência de linha aqui é o segundo caso; um valor explícito é o primeiro), e nenhuma estimativa aparece disfarçada de fato confirmado.
-- exemplo real de uso
insert into lead_campo_fonte (lead_id, campo, valor, tipo, fonte_descricao, fonte_url, pesquisador_id)
values ('<lead_id>', 'numero_unidades', '3', 'estimado', 'Instagram @loja_x — bio menciona "3 unidades"', 'https://instagram.com/loja_x', '<usuario_id>');
Lead Score
Implementa a seção 2Os 4 blocos ponderados (A–D, até 100 pontos) vivem em pesos_lead_score, versionados. Cada cálculo de score gera uma linha em lead_score_historico com os 4 blocos, os redutores e o score_total como coluna GENERATED (calculada pelo próprio banco, nunca pode ficar dessincronizada da soma real) — e referencia qual versao_pesos foi usada.
versao é inserida em pesos_lead_score — os scores antigos continuam legíveis com a régua que realmente usaram, em vez de parecerem ter sido calculados com pesos que só passaram a existir depois.
O campo lead_score em leads é o valor corrente cacheado (para ordenar a fila de contato sem precisar agregar histórico toda hora); o trigger de auditoria grava automaticamente todo recálculo — testado: alterar lead_score de um lead gerou, na validação deste schema, uma linha com ação score_recalculado sem nenhuma ação extra da aplicação.
Os "portões de exclusão" (empresa encerrada, fabricante/distribuidor, grande rede acima do corte, duplicata) não pontuam — chegam direto a classe = 'X' sem passar pelos blocos A–D.
Deduplicação
Implementa a seção 6Só o nível 3 do checklist de dedup (nome muito similar + endereço próximo) precisa de tabela própria — é o único que exige decisão humana obrigatória. Níveis 1 (CNPJ/telefone idênticos) e 2 (mesmo domínio/@Instagram) mesclam automaticamente: o lead duplicado recebe duplicata_de apontando para o registro original, e o evento fica em eventos_auditoria, sem gerar linha em duplicatas_candidatas.
duplicatas_candidatas guarda o par (lead_id_a, lead_id_b), o nível de confiança e a decisão (mesclado / rejeitado) de quem revisou — nunca apaga nenhum dos dois registros, só decide qual vira o principal.
Interações
Implementa as seções 8 e 9Todo contato (WhatsApp, ligação, e-mail, visita) vira uma linha em interacoes, com resumo, o status de contato resultante e a próxima ação com data. Isso é o que alimenta o checklist de fechamento do dia da rotina diária: nenhuma interação registrada hoje deve ficar sem proxima_acao_em definida.
Auditoria
Implementa a seção 12eventos_auditoria é insert-only por desenho: nenhuma coluna de "editado_em" nem trigger de update — a única forma de "corrigir" um evento é inserir um novo evento que explique a correção. Um trigger genérico (fn_log_mudanca_lead) grava automaticamente toda mudança de funil_estagio, status_contato, classe e lead_score, com valor anterior e novo — testado com uma sequência real de insert + updates antes deste documento ser escrito, confirmando que o log é gerado sem qualquer chamada extra da aplicação.
quem | quando | campo | de | para
usuario/sistema | 2026-09-01 14:22:05 | funil_estagio | lead_encontrado | lead_qualificado
usuario/sistema | 2026-09-01 14:22:05 | classe | (nulo) | B
usuario/sistema | 2026-09-01 14:22:05 | lead_score | (nulo) | 62.50
A restrição de que só admin pode alterar ou apagar uma linha de log é reforçada com REVOKE UPDATE, DELETE no papel de conexão da aplicação — depende do usuário de banco de cada ambiente (dev/staging/produção), por isso fica documentada no schema mas aplicada na configuração de cada ambiente, não como constraint fixa.
Mapeamento — Fase 1 → schema
Rastreabilidade completa| Decisão aprovada (seção 14 da Fase 1) | Onde vira estrutura |
|---|---|
| 3 tiers de ICP + regra de academia ambígua | leads.tipo_estabelecimento (enum) + leads.tier |
| Corte numérico de grande rede (>20 lojas ou >3 estados) | leads.numero_unidades, numero_estados, excluido_grande_rede |
| Lead Score em 4 blocos + portões + recalibração trimestral | pesos_lead_score (versionado) + lead_score_historico |
| Critério composto de cobertura + reciclagem 90 dias | celulas_territoriais (status, contadores, reciclar_em) + termos_busca_executados |
| Pesquisa via API oficial + apoio humano (não raspagem irrestrita) | termos_busca_executados.fonte restrito a enum (google_maps, google_search, instagram, facebook, cnpj_receita) — a Fase 5 decide o mecanismo técnico por trás de cada fonte |
| Funil de 8 estágios + status de contato paralelo | leads.funil_estagio + leads.status_contato (colunas separadas) |
| Metas com par volume × qualidade | consulta sobre leads.classe agrupado por período — sem tabela própria; é uma view/relatório da Fase 3/4, não estrutura de dados nova |
Riscos & limitações técnicas
Para a Fase 3 em diante- Enums exigem migração para crescer — adicionar um novo
tipo_estabelecimentooufunil_estagiono futuro é uma migração de schema (ALTER TYPE ... ADD VALUE), não uma configuração de aplicação. Aceitável agora porque esses valores vieram de decisão explícita da Fase 1, mas vale saber que não são "livres". lead_campo_fontepode crescer rápido — cada campo rastreável de cada lead gera uma linha. Para o volume de milhares de empresas citado no briefing, isso é tranquilo para o Postgres, mas indexação por(lead_id, campo)já foi incluída para manter consulta rápida.- Trigger de auditoria cobre hoje só
leads—celulas_territoriaise outras tabelas podem reusar a mesma função com pequena adaptação quando a Fase 3/4 definir quais mudanças nelas também precisam de log automático. - Reciclagem de células depende de job agendado — o schema prepara o índice, mas o agendamento (cron/worker) é decisão de infraestrutura da Fase 4, fora do escopo de banco de dados.
Próximos passos
Fase 3Com o banco definido e testado, a Fase 3 — Telas do app/CRM desenha a interface sobre estas tabelas: Kanban do funil, ficha do lead com histórico de proveniência e interações, painel de células territoriais e fila de contato priorizada por classe. Nenhuma decisão de schema fica pendente de aprovação — o desenho de tela pode começar diretamente.