Pular para conteúdo

Referência Técnica — Sistema de Prospecção de Representantes

IDs, credenciais, SQL, configs e snippets para operar o sistema.


IDs dos Workflows n8n

Workflow ID n8n Status Trigger Nós
WF-Rep-Import (Google Sheet) cxpqpgjSPIEX18AL Ativo (manual) Manual Trigger
WF-Rep-0 — Ignição (Webhook) I425eBF09Khja5vL Inativo Webhook POST
WF-Rep-1 — Dispatcher cstgB6ONs7R0d01r ATIVO 2 Crons (8h + 14h BRT, seg-sáb) 5
WF-Rep-Worker KrMlipmgxuc2EdlL ATIVO Webhook GET /webhook/rep-worker 31
WF-Rep-Optout IGBDGuIDLMpfCX8O ATIVO Webhook GET /webhook/rep-optout 3
_TEMP_Criar_Tabela jw5weRBzH0TBbbHd Arquivável Manual (one-shot)
_TEMP_Probe (utilitário SQL) toquIjzNhIsNN59O Utilitário Manual

URL base n8n: https://n8n.bepex.com.br


Credenciais no n8n

Nome no n8n ID Tipo Uso
Postgres n8n v2 7F3gxDoPsQGjAbOW PostgreSQL Banco de dados representantes
AWS account kqmbhAWS0tyL5fuI AWS Amazon SES (us-east-2)
Chatwoot Bepex (Header Auth) FzLnFD8cohSYSYIN Header Auth API Chatwoot chat.bepex.com.br account 2

Configuração SENDERS (3 slots)

Array definido em dois Code nodes que devem ser mantidos sincronizados: 1. Code node de round-robin no WF-Rep-0 (ou WF-Rep-Import) 2. Code node "Roteia por Canal" no WF-Rep-Worker

const SENDERS = [
  {
    id: 'A',
    whatsapp_inbox_id: 62,   // Prospecção 1
    from_email: 'andrei.mazer@bepexmkt.com.br'
  },
  {
    id: 'B',
    whatsapp_inbox_id: 63,   // Prospecção 2
    from_email: 'andrei@bepexmkt.com.br'
  },
  {
    id: 'C',
    whatsapp_inbox_id: 64,   // Prospecção 3
    from_email: 'a.mazer@bepexmkt.com.br'
  }
];

Para adicionar slot D: criar caixa de email + inbox Chatwoot, adicionar objeto ao array em ambos os Code nodes (WF-Rep-0/Import e WF-Rep-Worker).


SEQUENCE_MAP (cadência)

const SEQUENCE_MAP = {
  0:  { canal: 'email',    assunto: 'Oportunidade: rede de agentes comerciais Bepex em [Estado]' },
  3:  { canal: 'email',    assunto: 'Re: Rede de agentes Bepex — comissão sobre resultado + parceria com arquitetos' },
  7:  { canal: 'whatsapp', template: 'whatsapp_dia7' },
  14: { canal: 'whatsapp', template: 'whatsapp_dia14' },
  21: { canal: 'whatsapp', template: 'whatsapp_dia21' },   // Catálogo — seg 09/jun (adicionado 2026-06-03)
  22: { canal: 'email',    assunto: 'Catálogo Bepex — veja os projetos antes de qualquer conversa' }  // Catálogo — ter 10/jun
};

Cálculo do próximo dia:

const days = Object.keys(SEQUENCE_MAP).map(Number).sort((a,b) => a-b);
const currentIndex = days.indexOf(sequence_day);
const next_sequence_day = currentIndex < days.length - 1 ? days[currentIndex + 1] : null;
const daysUntilNext = next_sequence_day !== null ? next_sequence_day - sequence_day : null;


Amazon SES — Padrão de envio (CRÍTICO)

Bug do n8n: HTTP Request node com AWS predefined credential double-encodes @ quando usa contentType: "form-urlencoded". Causa erro "Missing final '@domain'" no SES.

Workaround obrigatório:

Node: HTTP Request
- Method: POST
- URL: https://email.us-east-2.amazonaws.com
- Authentication: Predefined Credential → AWS (kqmbhAWS0tyL5fuI)
- Service: ses
- Region: us-east-2
- Send Body: ON
- Content Type: Raw
- rawContentType: application/x-www-form-urlencoded
- Body (expression):

={{ 'Action=SendEmail&Source=' + encodeURIComponent($json.email_from) + '&Destination.ToAddresses.member.1=' + encodeURIComponent($json.email_to) + '&Message.Subject.Data=' + encodeURIComponent($json.email_subject) + '&Message.Body.Text.Data=' + encodeURIComponent($json.email_body) }}

NUNCA usar: - Node nativo n8n-nodes-base.awsSes (mesmo bug) - contentType: "form-urlencoded" com AWS auth - SMTP node (não configurado, SES API é mais confiável)


Chatwoot — Endpoints usados

Base URL: https://chat.bepex.com.br/api/v1/accounts/2

⚠️ Instância correta é chat.bepex.com.br, account_id 2 (confirmado via resposta real da API e execuções de produção em 2026-05-13). Inboxes de prospecção (62/63/64) existem nesta instância, NÃO em atendimento.agencialacos.com.br.

Operação Método Endpoint
Buscar contato GET /contacts/search?q={telefone}
Criar contato POST /contacts
Criar conversa POST /conversations
Enviar mensagem POST /conversations/{id}/messages
Adicionar labels POST /conversations/{id}/labels — body: {"labels":["ia-atendendo"]}

Inboxes de prospecção:

Inbox ID Slot Número WhatsApp
Prospecção 1 62 A (número conectado via API bridge)
Prospecção 2 63 B (número conectado via API bridge)
Prospecção 3 64 C (número conectado via API bridge)

ATENÇÃO: Account 3 também tem inbox 25 (pessoal Rafael) e inbox 26 (Whats Bepex comercial). Nunca confundir com as inboxes de prospecção.

Auth direta via Chrome (quando o MCP Chatwoot não cobre a conta): o MCP 46639869... só cobre atendimento.agencialacos.com.br (contas 3/4/5/7/8/9) — não alcança chat.bepex.com.br (account 2). Com o browser do Rafael já logado em chat.bepex.com.br, os headers de auth ficam expostos em window.axios.defaults.headers.common (via javascript_tool): access-token, client, uid, token-type, expiry. Usar esses 5 headers em fetch() direto pra qualquer endpoint /api/v1/accounts/2/... sem precisar reautenticar. Validado em 2026-07-03/04 pra auditoria de 538 conversas. Endpoint útil para filtro server-side por label: POST /api/v1/accounts/2/conversations/filter com body {payload:[{attribute_key:"labels", filter_operator:"equal_to", values:["<label>"], query_operator:null}]} (o param de query ?labels[]= não funciona).


PostgreSQL — Queries úteis

Ver leads ativos e próxima ação

SELECT id, nome, email, sender_slot, sequence_day, sequence_status, next_action_date
FROM representantes
WHERE sequence_status = 'active'
ORDER BY next_action_date, sender_slot;

Inserir lead manualmente

INSERT INTO representantes (nome, email, telefone, empresa, cidade, estado, linkedin_url, sender_slot, sequence_day, sequence_status, next_action_date, created_at, updated_at, notas)
VALUES (
  'Nome do Lead',
  'email@empresa.com',
  '11999999999',
  'Empresa X',
  'São Paulo',
  'SP',
  NULL,
  'A',  -- ou B, ou C (round-robin manual)
  0,
  'active',
  CURRENT_DATE,
  NOW(),
  NOW(),
  'Fonte: ExpoRevestir 2026'
)
RETURNING id, nome, email, sender_slot;

Pausar/retomar lead

-- Pausar
UPDATE representantes SET sequence_status = 'paused' WHERE id = X;

-- Retomar
UPDATE representantes
SET sequence_status = 'active', next_action_date = CURRENT_DATE
WHERE id = X;

Opt-out (lead pediu pra não receber mais)

Via WhatsApp: manual — setar direto no banco:

UPDATE representantes
SET sequence_status = 'opted_out', updated_at = NOW()
WHERE id = X;

Via email: automático — o lead clica no link de descadastro no rodapé do email. O WF-Rep-Optout (IGBDGuIDLMpfCX8O) processa e atualiza o banco.

URL de descadastro: https://n8n-hook.bepex.com.br/webhook/rep-optout?email=EMAIL_CODIFICADO

O rodapé já está nos emails dos dias 0, 3 e 22 (Code node "Prepara Email (SES)" do Worker).


  • Dropbox (direct download): https://www.dropbox.com/scl/fi/df3jjfzkg3noa5zy9zkbj/Catalogo-Bepex-_.pdf?rlkey=qw1tbys8ntmeuw13bqo05gpl8&dl=1
  • Usado nas copies dos dias 21 (WhatsApp) e 22 (email)
  • ⚠️ O parâmetro st= do link original foi removido para estabilidade. Se o link deixar de funcionar, gerar novo shared link no Dropbox e atualizar os dois nós (Prepara WhatsApp (Chatwoot) e Prepara Email (SES)) no WF-Rep-Worker.

Buscar por nome para opt-out manual:

SELECT id, nome, email, telefone, sequence_status
FROM representantes
WHERE nome ILIKE '%nome_do_lead%';

Ver leads em recontato (pós-incidente inbox 63):

SELECT id, nome, sender_slot, sequence_day, sequence_status, next_action_date
FROM representantes
WHERE recontato_inbox63 = TRUE
ORDER BY next_action_date;

recontato_inbox63 = TRUE identifica os 52 leads do Grupo B (DataAdv V2) que perderam WhatsApp durante a desconexão da inbox 63 (09/jun–24/jun/2026) e foram resetados para day 7 em 2026-06-30. Quando esses leads chegam ao dia 22 (email catálogo), o "Recontato Email Guard" no Worker os marca completed sem enviar o email. A flag é zerada (FALSE) ao marcar completed.

Estatísticas da base

SELECT
  sequence_status,
  COUNT(*) as total,
  COUNT(CASE WHEN email IS NOT NULL THEN 1 END) as com_email,
  COUNT(CASE WHEN telefone IS NOT NULL THEN 1 END) as com_telefone
FROM representantes
GROUP BY sequence_status;

Caixas de email (Hostinger)

Email Slot Senha Encaminha para
andrei.mazer@bepexmkt.com.br A ${BEPEXMKT_EMAIL_PASSWORD} andrei@bepexmkt.com.br
andrei@bepexmkt.com.br B ${BEPEXMKT_EMAIL_PASSWORD} — (caixa principal)
a.mazer@bepexmkt.com.br C ${BEPEXMKT_EMAIL_PASSWORD} andrei@bepexmkt.com.br

Webmail: https://mail.hostinger.com (ou via hPanel > Emails) Domínio: bepexmkt.com.br (Hostinger, plano Business Email gratuito)


Calendly

  • Link público: https://calendly.com/rafael-bepex/30min
  • Conta: rafael@bepex.com.br
  • Calendar conectado: Google Calendar "Agenda Representantes" (mesma conta)
  • Configurado em: 2026-04-17

Fonte de leads

ExpoRevestir 2026 (lote atual)

  • Arquivo: ExpoRevestir_2026_Representantes_MASTER_DEDUP.csv
  • Total: 460 registros
  • Com email + telefone: 12 (10 inseridos como lote teste, 2 duplicados)
  • Com email apenas: 5
  • Com telefone apenas: 4
  • Só LinkedIn: 162
  • Sem contato: 277

Apollo.io (fonte original do protótipo)

  • Filtros definidos no protótipo para 200 contatos
  • Não usado no lote atual (ExpoRevestir foi priorizado)

Filtros Pipedrive — Reengajamento de reps já cadastrados (2026-07-03)

Frente paralela: usa o Pipedrive (não o banco representantes) para achar representantes antigos e reengajar. Filtros salvos na conta do Pipedrive Bepex (dono = usuário Enrico, visible_to=1; API não altera visibilidade — só UI).

Filtro ID Tipo Contagem (2026-07-03) Link
REPS 01 — Pessoas ligadas a Representação 139534 people 167 pessoas https://bepex.pipedrive.com/persons/list/filter/139534
REPS 02 — Negócios de Representação 139535 deals 237 negócios https://bepex.pipedrive.com/deals/list/filter/139535

Lógica (grupo OR — operator: LIKE '%$%'):

  • 139534 (Pessoas) — organização.Nome ~ representa/agente comercial/agenciamento comercial; organização.CNAE(nome) ~ representa; pessoa.Nome ~ representa; negócio.Título ~ representa/agente comercial/agenciamento comercial.
  • 139535 (Negócios) — negócio.Título ~ representa/agente comercial/agenciamento comercial; organização.Nome ~ representa/agenciamento comercial; organização.CNAE(nome) ~ representa.

Field IDs numéricos do filtro (não confundir com field keys hash):

Objeto Campo field_id key/obs
organization Nome 3993
organization CNAE (nome) Date Solutions 4029 key 7260d417366db01613178e83765748753fcfaf7e
organization CNAE (número) Date Solutions 4028 key e9bd20c...; 46.1x = representantes comerciais
person Nome 9038
deal Título 12451
deal Organização 12457

Estrutura de conditions (API): {glue:"and", conditions:[ {glue:"and", conditions:[]}, {glue:"or", conditions:[<cada condição>]} ]} — grupo AND vazio + todas no grupo OR. Cada condição: {object, field_id:"<num>", operator:"LIKE '%$%'", value:"<texto>", extra_value:null, json_value_flag:false}. Criar/editar filtro exige api_token (POST/PUT /api/v1/filters); o token pessoal fica em /settings/api (usar só em memória, nunca logar). Sessão de browser NÃO cria filtro (writes 401), só lê.

Entregável do levantamento: Representantes_Pipedrive_Levantamento.html (no Drive — ver ARQUIVOS.md).

Prova de cobertura: 53 orgs com CNAE 46.1x → 100% já capturadas pelos filtros. Distribuidoras (~344) e revendas (~13) ficam FORA (categoria distinta; filtros próprios "Distr Ferro e Aço", "DISTRIBUIDORES").


Camada de IA para WhatsApp (2026-07-07)

Varia o texto de cada WhatsApp (dias 7/14/21) com IA leve + cap de volume por número. Email intocado. Worker KrMlipmgxuc2EdlL = 40 nós, deixado INATIVO.

LLM

Item Valor
Endpoint https://openrouter.ai/api/v1/chat/completions (HTTP Request, predefinedCredentialTypeopenRouterApi)
Credencial OpenRouter Diversos Bepexb11busTUZQ9ihsS9
Modelo google/gemini-2.5-flash (o slug gemini-2.0-flash-001 dá 404 nessa conta)
Params temperature 0.95, top_p 0.95, max_tokens 500, neverError:true
Custo ~US$ 0,0003–0,0006 por mensagem

Nós novos no WF-Rep-Worker

Conta WA Hoje (pg) → Atingiu Cap? (IF, enviados_hoje >= 20) → Adia Lead +1 (pg) / Responde Cap · Busca Anti-Repeticao (pg) → Monta Prompt WA (code) → IA Reescreve WA (http) → Valida e Fallback WA (code) → (Chatwoot) → Grava Historico WA (pg). Prepara WhatsApp (Chatwoot) agora lê $('Roteia por Canal e Monta Payload').first().json (não $input).

Cap diário

  • 20 por inbox/dia. Editar no nó IF "Atingiu Cap?" (rightValue). Ao bater, Adia Lead +1 faz next_action_date = CURRENT_DATE + 1 (não avança sequence_day, não envia).

Tabela rep_msg_historico

CREATE TABLE rep_msg_historico (
  id SERIAL PRIMARY KEY,
  rep_id INTEGER,
  canal VARCHAR(20) NOT NULL,               -- 'whatsapp'
  effective_sequence_day INTEGER,           -- 7/14/21
  inbox_id INTEGER,                          -- 62/63 (cap por número)
  message TEXT NOT NULL,
  opening TEXT,                             -- 1ª linha, para a avoid-list
  used_ai BOOLEAN DEFAULT TRUE,
  created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_rmh_dedup ON rep_msg_historico(canal, effective_sequence_day, created_at DESC);
CREATE INDEX idx_rmh_cap   ON rep_msg_historico(canal, inbox_id, created_at);
- Anti-repetição: SELECT opening, message ... WHERE canal='whatsapp' AND effective_sequence_day = X ORDER BY created_at DESC LIMIT 10 → vira "não repita" no prompt. - Cap: SELECT COUNT(*) ... WHERE canal='whatsapp' AND inbox_id=X AND created_at::date = CURRENT_DATE.

Diretrizes de copy embutidas no prompt (Monta Prompt WA)

Citar Bepex + fachadas metálicas sob medida (validação exige "bepex"+"fachada"); SEM território; SEM nome próprio (rejeita "andrei"/"meu nome"); citar Hunter Douglas/Sulmetais (qualidade equivalente, preço competitivo); enquadrar como produto de ticket alto pro portfólio.

Artefatos

O que ID
Backup pré-IA (server-side) 8OYF0qGGSBYxWCey
Workflow de teste de amostras (sem envio) HwNoelEpdkNVBglJ

Reativação (quando o Rafael der o go)

  1. Confirmar por que o Worker estava inativo. 2. GET → PATCH (se precisar) → GET versionId fresh → deactivateactivate. 3. Validar 1 lead real E2E abrindo o output nó-a-nó (trust content, not status).

Prospecção via LinkedIn Sales Navigator (2026-07-07)

Frente de descoberta de novos reps (só levantamento de nomes; contato enriquecido depois no DataAdv). Achado/decisão/status: ver historico.md 2026-07-07. Spec operacional turnkey: o spec turnkey de Sales Navigator (ver ARQUIVOS.md).

Queries booleanas (campo de busca; filtro Localidade = estado)

Cargos (grupo base, reutilizado em tudo): "representante comercial" OR "representante de vendas" OR "representante técnico" OR "agente comercial" OR "representação comercial"

Recorte relevante (~7,5k Brasil): <cargos> AND (fachada OR fachadas OR "fachada ventilada" OR "pele de vidro" OR brise OR ACM OR "metal expandido" OR "metal perfurado" OR "chapa perfurada" OR "revestimento metálico" OR esquadria OR esquadrias OR revestimento OR revestimentos OR porcelanato OR cerâmica OR "revestimento cerâmico")

SP bloco A (~1k): <cargos> AND (fachada OR fachadas OR "fachada ventilada" OR "pele de vidro" OR brise OR ACM OR "metal expandido" OR "metal perfurado" OR "chapa perfurada" OR "revestimento metálico" OR esquadria OR esquadrias)

SP bloco B (~2k): <cargos> AND (revestimento OR revestimentos OR porcelanato OR cerâmica OR "revestimento cerâmico")

Filtro externo/autônomo (~2k, corta o relevante): <relevante> AND ("representações" OR autônomo OR represento OR representamos OR multimarcas OR "escritório de representação" OR "representante externo")

Volume medido (Brasil, contador do Sales Nav, 2026-07-07)

Recorte Volume
Cargo só ~470 mil
Envelope amplo (+vidro/alumínio/vidraçaria/serralheria) ~10 mil
Relevante ~7,5 mil
Externo/autônomo ~2 mil
Núcleo (fachada/metal/ACM/pele de vidro) ~709

Fatiamento (teto de 2.500 por busca)

Regra: 1 UF = 1 fatia; se a fatia ler ≥ 2.500, split A/B por bloco de setor. Só SP estoura (medido). | Fatia | Contagem | geoID (filtro REGION) | |---|---|---| | Brasil (referência) | 470 mil | 106057199 | | São Paulo (estado) | 3.000 → split A+B | 105871508 | | ↳ SP-A / SP-B | ~1k / ~2k | (mesmo geoID SP) | | Minas Gerais (2º maior) | 673 | 100358611 | | Demais 25 estados | < 673 | obter no filtro Localidade → "UF, Brasil" |

Total: 28 buscas. Split de SP bate exato (1k+2k=3k, sobreposição ~0; dedup por URL). Sub-split por bloco de setor (não cidade) porque é exaustivo. Ordem por volume: SP-A → SP-B → MG → RS → PR → SC → RJ → ES → BA → GO → DF → … → Norte/Nordeste menores.

Classificação por regex (sem IA no dado)

  • externo: /representa(ç|c)(õ|o)es|representa(ç|c)ão comercial|aut[oô]nomo|represento|representamos|multimarcas|escrit[óo]rio de representa|representante externo/i em (empresa+bio)
  • interno_provavel: empresa casa fabricante /eliane|portobello|portinari|deca|roca|incepa|biancogres|damme|gaudi|elizabeth|ceusa|itagres|viapol|sika|weber|quartzolit|carmelo fior/i
  • resto: indefinido (revisar)

Quirks técnicos (Chrome MCP) — validados

  • Lista virtualizada: só renderiza com scroll físico (computer), na aba em foco. scrollTop via JS NÃO renderiza (testado: trava). Aba sem foco = 0 cards → não paraleliza em abas.
  • window some ao navegar → master em localStorage. Resultado de tool trunca ~1024 chars → exportar via download de Blob pra uma pasta de Downloads local, não pelo canal de resultado.
  • Boolean (AND/OR/aspas/parênteses) funciona no campo. AND NOT (a OR b) retorna 0 (quebra) — limpar ruído apertando termos.
  • Filtro de região persiste ao trocar a query. "São Paulo e Região" (metro) ≠ "São Paulo, Brasil" (estado) — sempre o estado. API interna de busca = GraphQL ofuscado (/7O4iJ3weJ...), não usar.
  • Extração NÃO gasta crédito de desbloqueio (bio visível na lista); os ~25 créditos/mês só revelam contato.