O banco do CRM
O CRM do grupo vive em um PostgreSQL no Supabase e substitui as planilhas. Você acessa direto pelo Claude Code ou Claude Cowork, conversando em português — o Claude traduz seu pedido em SQL e executa.
1Banco de dados
Oportunidades, contatos, empresas, leads, tarefas e propostas. As entidades principais e como se conectam:
- PKidinteger
- nametext
- FKindustry_id→ industries
- domain_nametext
- cnpjtext
- emailtext
- produtos_servicostext
- FKcnae_section_code→ setor
- FKcnae_division_code→ segmento
- PKidinteger
- first_name / last_name
- linkedin_url · site · cargotext
- FKlead_status_id→ lookup
- e-mails / telefones→ contact_channels
- PKidinteger
- FKcontact_id→ contacts
- typeemail/phone…
- valuetext
- is_primary / is_activebool
- PKidinteger
- name / value · numero_proposta
- template · public_url · drive_folder_id
- FKcompany_id→ companies
- FKowner_id→ users
- FKbrand_id→ brands
- FKstage_id→ deal_stages
- PKidinteger
- name / email
- FKstatus_id→ lead_statuses
- PKidinteger
- title / due_date
- FKowner_id→ users
- PKcodetext
- nametext
- PKcodetext
- FKsection_code→ setor
- nametext
- deal_stages
- industries
- lead_statuses
- marketing_statuses
O que você pode fazer
Buscar, inserir, alterar e deletar registros (deals — que são as oportunidades e propostas —, contatos, empresas, leads, tarefas, atividades). O que você não pode: mexer na estrutura do banco (criar/apagar tabelas ou colunas) — é proposital e garantido pelo próprio banco.
Formas de contato
Um contato pode ter vários e-mails e telefones — eles ficam na tabela contact_channels (uma linha por canal), não em colunas do contacts. Cada canal tem type (email, phone, mobile, whatsapp…), is_primary (o principal daquele tipo) e is_active. Para "o e-mail da Maria", peça o canal type='email' e is_primary; para desativar um número antigo sem perdê-lo, marque is_active=false.
Pipelines e estágios
Cada marca tem seu pipeline (Base/labs, Base2, Crowdtest) com estágios próprios — refletindo exatamente os estágios do HubSpot. Um deal não aponta direto para o pipeline: ele tem um stage_id, e o estágio é que pertence a um pipeline (deal → stage_id → deal_stages → pipeline). O brand_id do deal indica a marca. Para "deals da Base/labs por estágio", junte deals com deal_stages e filtre pelo pipeline.
Identificadores
Toda entidade tem um id próprio do nosso banco (a chave que você usa nas relações). O hubspot_id, quando existe, é só referência ao registro de origem no HubSpot — não use como chave nas suas consultas.
2Usar
Com a variável CRM_PG_URL configurada (veja Instalação), abra o Claude e fale naturalmente. Exemplos:
Liste os 10 deals mais recentes da Base/labs com valor acima de 50 mil.
Crie um contato: Maria Silva, maria@acme.com, empresa Acme.
Mude o estágio do deal 26-BL-040 para "Negociação".
Quantas tarefas em aberto o Tales tem?O Claude monta o SQL e executa via psql com a sua credencial — a auditoria registra quem rodou.
3Classificação de empresas
Cada empresa pode ser classificada em dois níveis, seguindo o padrão CNAE (a taxonomia oficial do IBGE, que o mercado brasileiro usa):
- Setor = Seção CNAE — 21 grandes áreas (ex.:
J — Informação e comunicação,F — Construção). Colunacnae_section_code, lookup emcnae_sections. - Segmento = Divisão CNAE — ~87 subdivisões (ex.:
62 — Serviços de TI,41 — Construção de edifícios). Colunacnae_division_code, lookup emcnae_divisions. Cada Segmento pertence a um Setor.
Exemplos de pedido ao Claude:
Quantas empresas temos por setor?
Liste as empresas do segmento "Serviços de TI" (divisão 62).
Mostre os deals abertos de empresas do setor de Construção.Nem toda empresa está classificada
A classificação foi importada do HubSpot (campo "Segmento") e complementada automaticamente a partir do domínio do site. Empresas sem o dado ficam com cnae_section_code/cnae_division_code em branco — o enriquecimento é contínuo. A coluna antiga industry_id permanece como referência, mas a classificação oficial passou a ser o CNAE.
4Tabelas de configuração são só leitura
As tabelas que definem a configuração do CRM — brands, pipelines, deal_stages, industries, lead_statuses, marketing_statuses, e os lookups de classificação cnae_sections (Setor) e cnae_divisions (Segmento) — você consegue consultar, mas não alterar. Mudar um estágio de pipeline afeta todo mundo, então isso fica restrito aos administradores. Se precisar de um novo estágio ou marca, fale com o Hugo.
5Auditoria
Toda inserção, alteração e remoção fica registrada em uma tabela de auditoria, identificando o usuário, o que mudou e quando. Isso é automático — você não precisa fazer nada. Serve para reconstruir o histórico se algo for alterado por engano.
Errou em uma alteração?
Como tudo é auditado, dá para recuperar o valor anterior. Fale com o Hugo com o máximo de detalhe (qual registro, quando) que ele localiza no log de auditoria.
6Para administradores
restritoO acesso via psql permite apenas dados — de propósito, ninguém altera a estrutura do banco por engano. Quem precisa mexer na estrutura (criar/alterar tabelas e colunas, rodar migrations) usa o MCP oficial do Supabase, autenticado com um Personal Access Token (PAT) pessoal.
Só para hbarros e robert
O PAT dá acesso administrativo total ao projeto Supabase — não há como restringi-lo a "só estrutura". O time comum não configura este MCP; usa apenas o psql via CRM_PG_URL.
No dia a dia o admin usa o psql (CRUD, auditado pela sua role); o MCP do Supabase fica reservado para alterações de estrutura.
# gere seu PAT em supabase.com/dashboard/account/tokens (trate como senha de admin)
claude mcp add supabase \
--env SUPABASE_ACCESS_TOKEN="SEU_PAT" \
-- npx -y @supabase/mcp-server-supabase@latest --project-ref=qeocffkayfgstlyoeltrBoa prática
Mesmo sendo admin, prefira o psql via CRM_PG_URL para inserir/alterar dados — assim a auditoria registra você, não o admin genérico. Use o MCP do Supabase só quando o pedido for sobre estrutura.
7Row Level Security está desligado
restritoTodas as tabelas do schema public estão hoje com RLS desligado (decisão de junho de 2026). O motivo: enquanto o banco for acessado por conexões administrativas de confiança — não um app exposto à internet — o RLS sem políticas só atrapalha. Com RLS ligado e nenhuma política definida, o Postgres aplica deny-all e nenhuma linha é visível.
Gatilho para reativar
No dia em que o banco for exposto a um app cliente / frontend (que usa a anon key pública do Supabase), o RLS precisa ser reativado e acompanhado de políticas. Sem isso, qualquer pessoa com a URL e a chave pública lê e escreve as tabelas inteiras. O RLS é a única camada de segurança entre o cliente e os dados.
ALTER TABLE public.companies ENABLE ROW LEVEL SECURITY;
-- depois: CREATE POLICY ... ON public.companies FOR SELECT USING (...);8Email marketing
O CRM integra com o SendGrid para disparar campanhas de email e medir resultados. A lógica é: o CRM é dono da lista; o SendGrid dispara; os resultados voltam para o CRM. Uma campanha nasce de um segmento de contatos do banco, vira uma lista no SendGrid, e o marketing finaliza o email e envia pelo painel.
Três tabelas guardam isso: campaigns (uma por campanha/marca), campaign_recipients (quem estava na lista no disparo) e campaign_events (resultados trazidos do SendGrid). Você consulta resultados em linguagem natural, como o resto do CRM.
Qual a taxa de abertura da última campanha da Base/labs?
Quantos cliques e descadastros teve a campanha #12?
Liste os contatos que descadastraram no último mês.Como uma campanha é montada
- Sincronizar — o script
email-marketing/bin/sync-campaign.mjsresolve o segmento de contatos, cria a lista no SendGrid, grava o snapshot de destinatários no CRM e cria o email como rascunho. Por padrão o segmento são todos os contatos com email ativo marcados como aptos a marketing. - Disparar — o marketing entra no painel do SendGrid, finaliza o conteúdo do email e clica enviar. O disparo é manual, de propósito.
- Buscar resultados — um job rodado pelo nosso lado consulta a API do SendGrid de tempos em tempos: pega os números agregados da campanha e a lista de quem descadastrou/bounceou, e grava no CRM. Quem descadastra é marcado para não receber campanhas futuras.
Descadastro é respeitado automaticamente
Quem descadastra ou tem bounce permanente entra na lista de supressão do SendGrid. O job de busca traz essa lista e atualiza o marketing_status do contato no CRM para Descadastrado (ou Inválido no caso de bounce). Na próxima campanha ele já sai do segmento — você não precisa fazer nada.
Configuração no SendGrid restrito · faz uma vez
Antes da primeira campanha, um administrador precisa configurar o SendGrid. É manual (fora do código) e se faz uma vez por marca:
- Autenticação de domínio por marca — em Settings → Sender Authentication, autenticar o domínio de cada marca (Base2, Base/labs, MeloQA, Crowdtest) com os registros SPF/DKIM. Isso separa a reputação de envio de cada marca e melhora a entregabilidade. Cada marca tem seu próprio sender (remetente).
- Grupo de descadastro (Unsubscribe Group) por marca — garante o rodapé de "descadastrar" no email e a página de preferências, exigência de LGPD. Toda campanha usa o grupo da sua marca.
- Chave de API com escopo Marketing + Stats — confirmar que a
SENDGRID_API_KEYem uso tem permissão de Marketing (Contacts, Lists, Single Sends) e de leitura de Stats/Suppressions, para o job conseguir buscar os resultados.
Como buscamos os resultados modelo atual
Os resultados são trazidos por busca ativa (pull): um job nosso, rodado localmente, consulta a API do SendGrid de tempos em tempos e grava no CRM. Não há nada do SendGrid "ligando para a gente" — somos nós que perguntamos. Isso evita precisar de um servidor público no ar 24/7 e mantém tudo do nosso lado. O job busca duas coisas:
- Números agregados da campanha — via Single Send Stats API: enviados, entregues, aberturas (e aberturas únicas), cliques (e únicos), bounces, spam, descadastros. Grava o resumo na campanha.
- Listas de supressão — via Suppressions / Bounces API: quem descadastrou, bounceou ou marcou spam, com data. Cruza com os contatos e atualiza o
marketing_statusno CRM.
Limitação do modelo pull: sem "quem abriu/clicou"
A API de marketing do SendGrid entrega os totais da campanha (ex.: "120 aberturas, 30 cliques") e as listas de descadastro/bounce, mas não diz qual contato específico abriu ou clicou. Esse detalhe por-pessoa só existe pelo caminho de webhook (push), descrito abaixo. Para acompanhar desempenho de campanha e respeitar descadastros, o pull é suficiente; para "o contato X abriu", não.
O job é o script email-marketing/bin/pull-campaign-stats.mjs. Os números agregados ficam na tabela campaign_stats — uma linha por campanha e por dia, então dá para ver a evolução da campanha ao longo do tempo. As atualizações de descadastro/bounce vão direto no marketing_status do contato.
cd swl-crm/email-marketing # pasta da integração (npm install na 1ª vez)
# busca stats de todas as campanhas ativas + sincroniza descadastros
node bin/pull-campaign-stats.mjs all
# só uma campanha
node bin/pull-campaign-stats.mjs stats --campaign=12Como o job roda do nosso lado, ele pode rodar numa máquina local — não precisa de endereço público. Para gravar no CRM usa psql com a sua credencial (variável CRM_PG_URL), então a auditoria registra quem rodou — sem chave de admin. Precisa também da SENDGRID_API_KEY (com leitura de Stats/Suppressions). O agendamento fica a cargo de um cron do nosso lado, no intervalo que fizer sentido (ex.: de hora em hora enquanto a campanha está ativa).
Webhook em tempo real ligado · captura por contato
Para captura por contato (saber exatamente quem abriu/clicou) e em tempo real, usamos o Event Webhook: o SendGrid faz uma chamada HTTP para um endereço nosso a cada evento. Como isso exige um endereço público no ar 24/7, roda como Cloudflare Worker (conta SWL, ex-Redes@base2) — não numa máquina local atrás de firewall, onde o SendGrid não alcançaria. Ligado em junho de 2026.
- Worker:
email-marketing/sendgrid-events, publicado emhttps://sendgrid-events.baselabs.com.br(custom domain na zona baselabs, SSL automático). Valida a assinatura ECDSA do "Signed Event Webhook", grava cada evento emcampaign_events(dedup porsg_event_id) e atualizamarketing_statusem descadastro/bounce. - Como foi ligado:
wrangler deploy+ segredosSENDGRID_WEBHOOK_PUBLIC_KEY/SUPABASE_URL/SUPABASE_SERVICE_ROLE_KEY(viawrangler secret put); no painel, Settings → Event Webhook apontando para a URL acima com "Signed Event Webhook" ativado. - Atribuição de campanha: Single Send não aceita
custom_args, então osync-campaign.mjscarimba uma categoriacampaign:<id>em cada envio; o webhook devolve essa categoria em todo evento e o worker reconstrói ocampaign_id. O contato é resolvido pelo email.
O webhook complementa o pull: o pull (acima) traz os agregados históricos; o webhook traz o evento por-contato em tempo real. Eventos de clique só aparecem com o click tracking ligado — hoje ele está desligado para preservar o link de descadastro (ver aviso abaixo).
Disparo é sempre pelo painel importante
Não dá para disparar campanha por API — só pelo painel
Testado em junho de 2026: disparar um Single Send via API (PUT /v3/marketing/singlesends/{id}/schedule com send_at:"now") é aceito mas não envia nesta conta — a campanha fica registrada com 0 envios e ninguém recebe, sem erro visível. O disparo só funciona clicando "Send" no painel do SendGrid (Marketing → Single Sends). Por isso o fluxo é: o sync-campaign.mjs prepara o rascunho, e o envio é sempre manual no painel. Não automatize o disparo.
Link de descadastro e click tracking
O link de "Descadastrar" passava pelo domínio de rastreamento de cliques do SendGrid (url8026.baselabs.com.br), que não tem certificado SSL válido sem abrir um ticket no suporte — daí o erro de "conexão não é particular" ao clicar. Solução provisória: click tracking desligado, então os links abrem direto (e o descadastro funciona), ao custo de não capturar o evento de clique. Solução definitiva: abrir o ticket de SSL do Link Branding e religar o click tracking.
9Playbook comercial
Guias de vendas das duas marcas do grupo — ICP, soluções, metodologia comercial, objeções, KPIs e valores. Consulte antes de qualificar um lead ou montar uma proposta.