Fase 2 de 10 — Banco de dados

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.

Empresa: Armazém Pura Saúde Banco: PostgreSQL 16 Frente: Atacado / B2B Data: 01 set 2026
Como ler este documento: cada entidade é apresentada com seus campos-chave, a razão de existir e o link direto para a seção da Fase 1 que ela implementa. O schema completo e executável está em schema.sql, entregue junto com este documento.
01

Visão geral & princípios

O que guiou o desenho

Quatro 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).

02

Diagrama de entidades

10 tabelas

leads é 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.

criado_por responsavel_id celula_id celula_id lead_id lead_id lead_id_a / lead_id_b versão (lógica) lead_id entidade_id (genérico) usuarios id · nome · email · papel admin / gestor / colaborador celulas_territoriais status · termos_count fontes_cruzadas[] · saturação mapeado_em · consolidado_em reciclar_em (+90d) ranking_potencial termos_busca_executados termo · fonte · executado_em leads_novos_count leads tipo_estabelecimento · tier numero_unidades · numero_estados excluido_grande_rede status_validacao status_qualificacao funil_estagio status_contato lead_score · classe motivo_exclusao duplicata_de (self FK) proxima_acao_em celula_id · criado_por interacoes tipo · resumo · usuario_id status_contato_resultante eventos_auditoria insert-only · quem/quando/o quê valor_anterior · valor_novo lead_campo_fonte campo · valor · tipo confirmado / estimado · fonte_url pesos_lead_score versao · bloco · criterio peso · vigente_desde/ate lead_score_historico bloco_a/b/c/d · redutores score_total (calculado) versao_pesos · motivo_recalculo duplicatas_candidatas nivel_confianca (2 ou 3) status · decidido_por — linha sólida = chave estrangeira real · - - - = referência lógica (sem FK no banco)
03

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).

04

Células territoriais

Implementa a seção 3

Cada 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.

CampoPapel
statusnão_iniciado / em_progresso / mapeado / consolidado
mapeado_emquando o critério composto foi atingido — aguarda validação amostral
consolidado_em / reciclar_emvalidado vira consolidado; reciclar_em = consolidado_em + 90 dias
ranking_potencialpreenchido pela Fase 6/9 com dado real (score ponderado), não por achismo — ver seção 3 da Fase 1
Reciclagem automática Um job agendado (fora do escopo desta fase, mas já preparado pelo índice idx_celulas_reciclagem) roda diariamente e volta toda célula com reciclar_em vencido para em_progresso — sem isso, "consolidado" ficaria sendo confiado indefinidamente.
05

Leads — núcleo do sistema

Implementa as seções 1, 5, 8 e 9

A 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)

  • lead_encontrado
  • lead_qualificado
  • em_contato
  • interessado
  • tabela_enviada
  • negociacao
  • pedido_andamento
  • cliente_convertido
  • status_contato (paralelo, nulável)

    • aguardando_primeiro_contato
    • sem_resposta
    • retorno_solicitado
    • sem_interesse_momento
    • contato_futuro_agendado
    • sem_interesse_definitivo
    • perdido

    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").

    06

    Proveniência por campo

    Implementa a seção 5

    lead_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>');
    07

    Lead Score

    Implementa a seção 2

    Os 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.

    Por que isso importa para a recalibração trimestral Quando a Fase 10 recalibrar os pesos com dado real de conversão, uma nova 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.

    08

    Deduplicação

    Implementa a seção 6

    Só 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.

    09

    Interações

    Implementa as seções 8 e 9

    Todo 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.

    10

    Auditoria

    Implementa a seção 12

    eventos_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.

    11

    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ígualeads.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 trimestralpesos_lead_score (versionado) + lead_score_historico
    Critério composto de cobertura + reciclagem 90 diascelulas_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 paraleloleads.funil_estagio + leads.status_contato (colunas separadas)
    Metas com par volume × qualidadeconsulta 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
    12

    Riscos & limitações técnicas

    Para a Fase 3 em diante
    • Enums exigem migração para crescer — adicionar um novo tipo_estabelecimento ou funil_estagio no 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_fonte pode 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ó leadscelulas_territoriais e 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.
    13

    Próximos passos

    Fase 3

    Com 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.