Arquitetura -- Ofertas Bepex¶
Mapeamento completo dos 3 workflows, padroes de design e mecanismo de self-trigger.
Visao geral do sistema¶
[Operador] [Launcher] [Dispatcher + Worker] [Canais de saida]
| | | |
| filter_id + | | +-- Amazon SES (email)
| campaign_name --> WF-Oferta-Launcher --+ | |
| tipo_atividade (cria atividades) | | |
| +-> Pipedrive --> WF-Oferta-Dispatcher --> WF-Oferta-Worker -+
| (atividades (crons 8h/14h) (1 lead por vez) |
| como fila) +-- Chatwoot (WhatsApp)
| |
| +-- WF-Resumo-IA (se necessario)
Arquitetura Launcher + Dispatcher + Worker: - WF-Oferta-Launcher (manual) -- cria atividades no Pipedrive como fila de trabalho - WF-Oferta-Dispatcher (2 crons) -- verifica se ha ofertas pendentes, dispara Worker - WF-Oferta-Worker (webhook) -- processa 1 lead por vez, IA + envio, self-trigger com wait 10 min
Workflows n8n¶
WF-Oferta-Launcher (ud1VP04JVajsAfGK)¶
- Trigger: Manual
- Status: Inativo (disparo manual sob demanda)
- Nos: 8
- Finalidade: Cria uma atividade Pipedrive por lead do filtro, servindo como fila de trabalho para o Worker.
Fluxo completo no-por-no:
Manual Trigger
|
v
Config (Set node -- editavel pelo operador)
- filter_id: ID do filtro Pipedrive
- campaign_name: nome da campanha (string livre)
- tipo_atividade: "oferta_email" | "oferta_whatsapp" | "oferta_emailwhatsapp"
|
v
Busca Pessoas do Filtro (HTTP Request)
- GET /api/v1/persons
- Query: filter_id, limit 100, start 0
- Paginado (batchSize 5)
- Credencial: Pipedrive account (CHj11AO6T3756SqM)
|
v
Filtra e Prepara Atividades (Code)
- Remove leads com aceita_comunicacao = 397 ("Nao aceita")
- Remove leads sem email (se tipo_atividade = oferta_email)
- Remove leads sem telefone (se tipo_atividade = oferta_whatsapp)
- Prepara payload de atividade por lead
|
v
Batch de 5 (SplitInBatches)
- batchSize: 5
|
v
Cria Atividade (HTTP Request)
- POST /api/v1/activities
- Body: {
type: tipo_atividade (oferta_email | oferta_whatsapp | oferta_emailwhatsapp),
subject: campaign_name,
person_id: ID do lead,
done: 0
}
- Activity type IDs: 54 (email), 55 (whatsapp), 56 (emailwhatsapp)
|
v
Wait Rate Limit (Wait node)
- 2 segundos entre batches
- Respeita rate limit do Pipedrive (~40-50 req/2s)
|
v
Resumo (Code)
- Contabiliza: atividades criadas, leads pulados, erros
- Output para verificacao do operador
Notas:
- O Launcher NAO faz IA. Apenas cria a fila. A geracao de mensagem acontece no Worker no momento do envio.
- Leads com aceita_comunicacao = 397 sao excluidos (campo Pipedrive: nao aceita comunicacao).
- Paginacao do Pipedrive com batchSize 5 + wait 2s para respeitar rate limit.
WF-Oferta-Dispatcher (LJS7oHdIzQBnjI9O)¶
- Trigger: 2 Crons -- 8h e 14h (America/Sao_Paulo), seg-sex
- Status: ATIVO desde 2026-06-11 (campanha Reengajamento Jun 2026)
- Nos: 6
- Finalidade: Verifica se existem atividades de oferta pendentes e dispara o Worker.
Fluxo completo no-por-no:
Cron 8h (0 8 * * 1-5)
| Cron 14h (0 14 * * 1-5)
| |
+------------+---------------+
v
Guard Horario (Code)
- Calcula hora atual em BRT (America/Sao_Paulo)
- Verifica se esta entre 8-12 ou 14-18
- Verifica se e seg-sex (dia 1-5)
- Se fora do horario: para execucao
|
v
Conta Ofertas Pendentes (HTTP Request)
- GET /api/v1/activities
- Query: type=oferta_email,oferta_whatsapp,oferta_emailwhatsapp, done=0
- Credencial: Pipedrive account (CHj11AO6T3756SqM)
|
v
Tem Ofertas? (IF)
- true (count > 0): dispara Worker
- false: encerra (nada pendente)
|
+--- true ---+
v
Dispara Worker (HTTP Request)
- GET https://n8n-hook.bepex.com.br/webhook/oferta-worker
- Aciona o Worker para comecar a processar a fila
Notas: - O Dispatcher NAO processa leads -- apenas verifica existencia de pendentes e aciona o Worker. - Dois crons (8h e 14h) garantem que atividades criadas ao longo do dia sejam processadas. - Guard de horario protege contra execucoes fora do horario comercial.
WF-Oferta-Worker (fQRxspKUKIj2o8b0)¶
- Trigger: Webhook GET
/webhook/oferta-worker - Status: ATIVO desde 2026-06-11 (campanha Reengajamento Jun 2026)
- Nos: 35
- Finalidade: Processa 1 lead por vez. Busca atividade pendente, gera mensagem por IA, envia email e/ou WhatsApp, marca done, espera 5-15 min (aleatorio), self-trigger.
Fluxo completo no-por-no:
Webhook GET /oferta-worker
|
v
Busca 1 Atividade (HTTP Request)
- GET /api/v1/activities
- Query: type=oferta_email,oferta_whatsapp,oferta_emailwhatsapp, done=0, limit=1
- Credencial: Pipedrive account (CHj11AO6T3756SqM)
|
v
Tem Atividade? (IF)
- true (data.length > 0): continua
- false: encerra (fila vazia)
|
+--- false --- (encerra, fila vazia)
|
+--- true ---+
v
Extrai Atividade (Code)
- Extrai: activity_id, person_id, type (oferta_email/whatsapp/emailwhatsapp), subject (campaign_name)
|
v
Get Person (HTTP Request)
- GET /api/v1/persons/{person_id}
- Credencial: Pipedrive account
|
v
Checa Aceita Comunicacao (Code)
- Le campo aceita_comunicacao (field key 19bc9c4c...)
- Opcoes: 394=So Email, 395=So WhatsApp, 396=Email e WhatsApp, 397=Nao aceita
|
v
Deve Pular? (IF)
- true (397 ou incompatibilidade canal/preferencia): pula lead
- false: continua processamento
|
+--- true (pular) ---+
| v
| Marca Done Skip (HTTP Request)
| - PUT /api/v1/activities/{id}, done=1
| - (pula para Self-Trigger)
|
+--- false (processar) ---+
v
Tem Resumo IA? (IF)
- Verifica campo "Resumo de IA" (field key 366d9e84...)
- true: ja tem resumo, prossegue
- false: precisa gerar
|
+--- false ---+
| v
| Gera Resumo IA (HTTP Request)
| - POST https://n8n-hook.bepex.com.br/webhook/gera-resumo-ia
| - Body: { person_id }
| - Chama WF-Resumo-IA (BL3me3JQiVmSbzn3)
| - Aguarda resposta com resumo gerado
|
+--- true ---+
v
Monta Contexto (Code)
- Agrega: dados do lead, resumo de IA, nutricao_contexto_ia, campaign_name
- Prepara prompt para a IA
|
v
IA Gera Mensagem (chainLlm)
- Modelo: google/gemini-2.5-flash via OpenRouter
- Credencial: Bepex Openrouter (SW7yige5BeCOqaRU)
- Temperatura: 0.4
- Prompt: gera mensagem comercial personalizada baseada no contexto do lead e na campanha
|
v
Processa Mensagem IA (Code)
- Extrai texto da resposta do LLM
- Separa subject (para email) e body
|
v
Envia Email? (IF)
- true se tipo_atividade = oferta_email ou oferta_emailwhatsapp
|
+--- true ---+
| v
| Prepara Email SES (Code)
| - Monta from, to, subject, body
| - Encoding: encodeURIComponent manual
| |
| v
| Envia Email SES (HTTP Request)
| - POST https://email.us-east-2.amazonaws.com
| - Auth: AWS (kqmbhAWS0tyL5fuI), service: ses, region: us-east-2
| - contentType: raw
| - rawContentType: application/x-www-form-urlencoded
| - Body: Action=SendEmail&Source=...&Destination...&Message...
| |
| v
| Restore Email Data (Code)
| - Restaura campos originais do lead (SES response sobrescreve $json)
|
v
Envia WhatsApp? (IF)
- true se tipo_atividade = oferta_whatsapp ou oferta_emailwhatsapp
|
+--- true ---+
| v
| Prepara WhatsApp (Code)
| - Round-robin inbox pool: [64, 61]
| - 64 = Prospeccao 3
| - 61 = WhatsApp pre-vendas frio
| - Monta mensagem personalizada
| |
| v
| Busca Contato Chatwoot (HTTP Request)
| - GET /api/v1/accounts/2/contacts/search?q={telefone}
| - Credencial: Chatwoot Bepex Header Auth (FzLnFD8cohSYSYIN)
| |
| v
| Processa Contato (Code)
| - Verifica se contato foi encontrado
| |
| v
| Criar Contato? (IF)
| - true (nao encontrado): cria novo
| - false (encontrado): usa existente
| |
| +--- true ---+
| | v
| | Cria Contato Chatwoot (HTTP Request)
| | - POST /api/v1/accounts/2/contacts
| | - Body: { name, phone_number }
| | |
| | v
| | Extrai Contact ID (Code)
| |
| +--- false (contato existente) ---+
| |
| +----------------------------------+
| |
| v
| Cria Conversa Chatwoot (HTTP Request)
| - POST /api/v1/accounts/2/conversations
| - Body: { contact_id, inbox_id (round-robin) }
| |
| v
| Extrai Conversa (Code)
| - Extrai conversation_id da resposta
| |
| v
| Envia Msg Chatwoot (HTTP Request)
| - POST /api/v1/accounts/2/conversations/{id}/messages
| - Body: { content, message_type: "outgoing" }
| |
| v
| Salva Inbox Pipedrive (Code -- COMENTADO/TODO)
| - PUT person, salva inbox atribuida no campo WhatsApp Frio Atribuido (field key 8778c52f...)
| - Campo existe no Pipedrive (id 9099), mas Code ainda comentado
|
v
Marca Done (HTTP Request)
- PUT /api/v1/activities/{activity_id}
- Body: { done: 1 }
- Credencial: Pipedrive account
|
v
Guard Horario Final (Code)
- Calcula hora atual em BRT (America/Sao_Paulo)
- Verifica se esta entre 8-12 ou 14-18
- Verifica se e seg-sex (dia 1-5)
|
v
Continua? (IF)
- true (dentro do horario E ha mais atividades): continua
- false (fora do horario OU fila vazia): encerra
|
+--- true ---+
| v
| Wait 5-15 min (Wait node)
| - {{ Math.floor(Math.random() * 11) + 5 }} minutos (aleatorio)
| - Cadencia mais humana e imprevisivel
| |
| v
| Self-Trigger (HTTP Request)
| - GET https://n8n-hook.bepex.com.br/webhook/oferta-worker
| - Dispara nova execucao do proprio Worker
|
+--- false --- (fora do horario ou fila vazia, encerra)
Padroes de design¶
Self-trigger com wait¶
O Worker processa exatamente 1 lead por execucao. Ao final: 1. Marca atividade como done 2. Verifica horario comercial 3. Se dentro do horario: espera 5-15 min (aleatorio), faz GET no proprio webhook 4. Nova execucao comeca, processa proximo lead
Resultado: execucoes curtas e independentes (~30-60s cada), com 5-15 min de intervalo aleatorio. Se o Worker para fora do horario, o Dispatcher o reaciona no proximo cron.
Pipedrive como fila¶
Atividades Pipedrive substituem uma tabela de fila no PostgreSQL:
- done = 0 -- pendente
- done = 1 -- processado
- type -- determina canal de envio
- subject -- nome da campanha (rastreabilidade)
- person_id -- vinculo com o lead
Vantagem: sem banco extra, visibilidade no proprio CRM, operador pode cancelar atividades manualmente.
Round-robin de inboxes WhatsApp¶
Pool de inboxes [64, 61] rotacionado no Code "Prepara WhatsApp":
const INBOX_POOL = [64, 61];
const index = /* logica de distribuicao */;
const inbox_id = INBOX_POOL[index % INBOX_POOL.length];
Distribui carga entre 2 numeros de WhatsApp para evitar bloqueio por volume.
Guarda de horario (dupla)¶
Horario comercial: 8-12 + 14-18 BRT, segunda a sexta.
- Dispatcher -- crons so disparam em dias uteis; Guard Horario no workflow confirma antes de acionar Worker
- Worker -- Guard Horario Final antes de self-trigger; se fora do horario, para
Resultado: se uma execucao ultrapassa as 18h, o Worker para e os leads restantes sao pegos no proximo cron do Dispatcher.
SES email (workaround obrigatorio)¶
Mesmo padrao do Sistema de Representantes. Bug do n8n: AWS auth double-encodes @ em form-urlencoded. Solucao:
- contentType: "raw"
- rawContentType: "application/x-www-form-urlencoded"
- Body montado manualmente com encodeURIComponent
Ver REFERENCIA_TECNICA.md para o snippet completo.
Resumo de IA (pre-requisito)¶
Antes de gerar a mensagem personalizada, o Worker verifica se o lead ja tem "Resumo de IA" no Pipedrive. Se nao tem, chama WF-Resumo-IA (BL3me3JQiVmSbzn3) via webhook POST com {person_id}. O WF-Resumo-IA analisa o historico do lead no Pipedrive e grava o resumo no campo customizado. So depois a IA gera a mensagem da oferta com base nesse contexto.
Chatwoot -- Endpoints usados¶
Base URL: https://chat.bepex.com.br/api/v1/accounts/2
| Operacao | Metodo | Endpoint |
|---|---|---|
| Buscar contato | GET | /contacts/search?q={telefone} |
| Criar contato | POST | /contacts |
| Criar conversa | POST | /conversations |
| Enviar mensagem | POST | /conversations/{id}/messages |
Inboxes WhatsApp (pool):
| Inbox | ID | Descricao |
|---|---|---|
| Prospeccao 3 | 64 | Numero de prospeccao |
| WhatsApp pre-vendas frio | 61 | Numero pre-vendas |