Pular para conteúdo

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.

  1. Dispatcher -- crons so disparam em dias uteis; Guard Horario no workflow confirma antes de acionar Worker
  2. 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