Arquitetura — Pipedrive → Omie (cenário 2912018)¶
Atualizado: 2026-07-07 · por: sessão manual · fix guard UF multi-endereço · rev: 2 46 módulos. Multi-tenant (DALE/ONIX/ATARES). Suporta PJ (CNPJ) e PF (CPF). Roteamento por nome da empresa (
9.data.empresa.nome).
Visão de alto nível¶
WEBHOOK [77]
↓
COLETA INFO + MARCA CONCLUÍDO no FlowTech [9]
↓
ATUALIZA DEAL no Pipedrive (custom field 934241be...=261) [73]
↓
GET ORGANIZATION no Pipedrive [78]
↓
ROUTER PRINCIPAL [45] (4 rotas — escolha do tenant Omie)
├─ Rota 0 (DALE) → SetVar credenciais DALE [49]
├─ Rota 1 (ONIX) → SetVar credenciais ONIX [50]
├─ Rota 2 (ATARES) → SetVar credenciais ATARES [86]
└─ Rota 3 (default) → Router sub-rota [53]
↓
Sub-rota: GetVars [51] → SetVar cnpj ajustado [54] → ListarVendedores Omie [43]
↓
Sub-router [52] (route 0 única)
↓
DateCode [33] (PJ ou PF — condicional tipoPessoa)
↓
SetVariable [92] uf_guard = ifempty(33.data[].enderecos[].uf; switch(27 estados)) ← resolve UF pra ESCALAR (mapper)
↓
GetVariables [93] uf_guard ← surface a variável pra ser lida no filtro do router
↓
Router guarda [91] "Guard UF → alerta ou adiciona cliente"
├─ Rota 0: [90] ⚠️ Alerta UF Vazia (filtro: {{length(93.uf_guard)}} = 0)
└─ Rota 1: IncluirCliente Omie [31] (filtro: {{length(93.uf_guard)}} > 0)
↓
Router pós-cliente [38]
├─ Rota 0 "Cnpj adicionado" (200) → SetVar [36] código cliente omie = data.codigo_cliente_omie
└─ Rota 1 "cnpj existente" (500 + "já cadastrado") → SetVar [39] código cliente omie = parsing faultstring
↓
Sub-router [56] (route 0)
↓
GetVars [55] (código cliente omie)
↓
AdicionarPedido Omie [57] (cabeçalho, sem itens)
↓
Iterar Produtos Finais (Feeder) [11]
↓
IncluirProduto Omie [12]
↓
Router [25] (5 rotas — sucesso e variantes de "já cadastrado")
├─ "tudo certo" (200) → SetVar [28] produto final = 11.codigo_produto
│ ↓
│ Iterar Produtos Secundários [19]
│ ↓
│ IncluirProduto Omie (secundário) [20]
│ ↓
│ Router [63] (4 rotas)
│ ├─ "Tudo certo" → IncluirEstrutura BOM [23]
│ ├─ "Erro" → SetVar [66] → ConsultarProduto [64] → IncluirEstrutura [67]
│ ├─ "Erro 2" → SetVar [81] → ConsultarProduto [82] → IncluirEstrutura [83]
│ └─ default → EMAIL erro [65] pra rafael@bepex.com.br
├─ "Produto já existente" → SetVar [27] parse "BPX..." do erro
├─ "produto já cadastrado" → SetVar [76] usa codigo_produto direto
├─ "PRODUTO JÁ CADASTRADO FORA DA INT." → SetVar [79] usa codigo_produto direto
└─ "outro erro" → EMAIL [26] pra rafael@bepex.com.br
↓
Sub-router [59] (route 0)
↓
GetVars [60] (produto final)
↓
ConsultarProduto Omie [69] (resolve código real)
↓
IncluirItemPedido Omie [61]
Padrões de design importantes¶
1. Multi-tenant via SetVariables roundtrip¶
O router [45] tem 4 rotas que rodam em PARALELO (todas avaliam o bundle). As rotas 0/1/2 têm filtros pelo empresa.nome (DALE/ONIX/ATARES) e cada uma seta variáveis app_key, app_secret, conta corrente específicas do tenant. A rota 3 não tem filtro (sempre roda) e contém o processamento real, que lê as variáveis via GetVariables [51].
Consequência: se a empresa.nome não casar com nenhum dos 3 padrões, as variáveis ficam undefined e as chamadas Omie quebram silenciosamente. Não existe "default Bepex matriz" — vendedor precisa colocar a empresa certa no FlowTech.
2. Roteamento PF vs PJ¶
Pra suportar PF, foi adicionado:
- [73] UpdateDeal filter: Tem cnpj? → Tem cnpj OR cpf? (aceita qualquer dos dois)
- [54] cnpj ajustado: ifempty(cliente.cnpj; cliente.cpf) consolidado, depois remove pontos/traços/barras
- [33] DateCode: tipoPessoa condicional → {{if(9.data.cliente.cnpj; "PJ"; "PF")}} (sem filter — roda pra ambos)
- [31] IncluirCliente: cnpj_cpf usa ifempty(cnpj; cpf), razao_social tem fallback pra cliente.nome, novo nome_fantasia (obrigatório pra NF-e), novo contribuinte: "S"/"N" condicional, fallback estado pra 78.address_admin_area_level_1
Omie infere pessoa_fisica automaticamente pela quantidade de dígitos do cnpj_cpf (11 = CPF, 14 = CNPJ) — não precisa enviar esse campo.
3. Gestão de "produto já existente"¶
Omie retorna o mesmo intent de "já cadastrado" com strings DIFERENTES dependendo de como o produto foi criado anteriormente (importação vs API vs manual):
- "descrição informada" (mais antigo)
- "Produto já cadastrado"
- "produto com ID" (com statusCode 200!)
- Variantes com encoding alternativo (çã)
O router [25] tem 5 rotas pra cobrir todas as variantes + um fallback de email pra erros não-mapeados.
4. Cliente "já cadastrado" no IncluirCliente¶
Quando o cliente já existe na Omie, [31] IncluirCliente retorna 500 com faultstring no formato ERROR: Cliente já cadastrado para o CPF/CNPJ [01.126.123/0001-36] com o ld [10755170773] e código de integração [] !. A rota 1 do [38] faz parsing manual: first(split(get(split(31.data.faultstring; "["); 3); "]")) → "10755170773".
6. Guarda de UF: sub-router + resolução da UF em escalar (uf_guard)¶
Módulo 90 (⚠️ Alerta UF Vazia) é um guarda que alerta quando a UF do cliente está ausente. A rota do adiciona-cliente ([31]) e a rota do alerta ([90]) ficam num sub-router ([91]) alimentado por [33].
Evolução em duas etapas:
-
Fix 2026-07-03 — desacoplar m31 de m90. Antes, m90 ficava sequencialmente antes de m31; como o filtro de m90 só passa bundle com UF vazia, m31 era bloqueado em toda execução normal. Solução: Router 91 bifurcando o bundle de m33 em duas rotas paralelas (m90 alerta / m31 adiciona-cliente). Mas essa versão colocou nos filtros das duas rotas a expressão de UF crua (
{{ifempty(33.data[].enderecos[].uf; switch(...))}}) — o que abriu o bug seguinte. -
Fix 2026-07-07 — corrigir o filtro do guarda. A expressão crua
33.data[].enderecos[].ufé avaliada como array num operando de filtro de router (quando o DateCode retorna >1 endereço), fazendo= ""e≠ ""darem os dois falso → nem alerta nem adiciona-cliente rodam → pedido não sobe. Solução: [92]SetVariable resolve a UF pra escalar (uf_guard) — a mesma expressão, mas num mapper, onde ela colapsa pra "RS".[93]GetVariables surface a variável.- filtros do router 91 passam a usar
{{length(93.uf_guard)}}—= 0→ alerta[90];> 0→ adiciona-cliente[31].
Princípios (regras práticas):
- Módulo de alerta/guarda com filtro nunca deve ficar sequencialmente antes de módulos que precisam do bundle original — isolar numa rota paralela via sub-router.
- Nunca colocar ...array[].campo... cru num filtro de router (vira array); resolver pra escalar antes, num SetVariable (mapper), e filtrar sobre a variável.
- Filtro de router não lê variável por referência crua ({{N.var}} → null); envelopar numa função ({{length(N.var)}}) e ler a variável via um GetVariables antes. Padrão: SetVariable → GetVariables → filtro length().
5. Endereço estruturado vem do Pipedrive Org¶
Pra PF, a Bepex usa o padrão "Org-falsa-pra-PF": criar uma Organização no Pipedrive com nome=nome da pessoa e endereço completo preenchido via autocomplete Google. O Pipedrive geocoda → subcampos address_route, address_street_number, address_postal_code, address_admin_area_level_2, address_sublocality, address_admin_area_level_1 ficam populados igual PJ. O [31] lê esses subcampos.
Detalhamento por módulo¶
Trigger¶
[77] gateway:CustomWebHook¶
hook=1652440- URL pública:
https://hook.us1.make.com/${WEBHOOK_MAKE_PIPEDRIVE_OMIE} - Payload mínimo:
{ "deal id": ..., "id do orcamento": ... }
Setup (sempre roda)¶
[9] HTTP — Coleta informações e marca concluído¶
POST https://bepex.flowtech.net.br/integracao/orcamento/{{77.id do orcamento}}/marcar-concluido- Header:
Authorization: Token ${FLOWTECH_API_TOKEN} - Retorno consumido em todo o flow via
9.data.*: 9.data.cliente.cnpj/9.data.cliente.cpf/9.data.cliente.nome/9.data.cliente.email9.data.empresa.nome— chave do roteamento de tenant Omie9.data.itens[]— array de produtos finais comcodigo_produto,descricao_produto,ncm,preco_unitario_custo,peso_bruto,peso_liquido,modelo,tipo_produto,origem_mercadoria,unidade,qtd,itens[](produtos secundários)9.data.orcamento.codigo_pagamento_omie
[73] Pipedrive UpdateDeal¶
- Filter:
Tem cnpj OR cpf?→[[cnpj exist]] OR [[cpf exist]] - Atualiza deal
id={{77.deal id}}com custom field934241be2703a7ccdaf77bc01a04a7224ec935ad = 261(interpretado como "status: enviado pra Omie") - Connection ID 504644
[78] Pipedrive GetOrganization¶
id={{73.org_id.value}}- Retorna
address_*subcampos + custom field razão social04383a86d143afb3acccb175c8b6f3e7333998cc
Roteamento de tenant¶
[45] Router principal — 4 rotas¶
- Rota 0:
empresa.nome contains "DALE"(case-insensitive) →[49]SetVar credenciais DALE (app_key,app_secret,conta corrente) - Rota 1: contains
"ONIX"→[50]ONIX - Rota 2: contains
"atares"→[86]ATARES - Rota 3: sem filtro (sempre roda) →
[53]sub-router → toda a lógica
Processamento¶
[51] GetVariables — app_key, app_secret, conta corrente¶
[54] SetVariable cnpj ajustado — replace(replace(replace(ifempty(9.data.cliente.cnpj; 9.data.cliente.cpf); "."; ""); "-"; ""); "/"; "")¶
[43] HTTP Omie ListarVendedores — busca vendedor por email do owner Pipedrive (73.user_id.email)¶
[33] HTTP DateCode — enriquecimento CNPJ/CPF¶
POST https://api.datecode.com.br/v2/dados/consulta- Body:
{ "document": "{{54.cnpj ajustado}}", "tipoPessoa": "{{if(9.data.cliente.cnpj; "PJ"; "PF")}}" } - Header:
Authorization: Basic ${FLOWTECH_BASIC_AUTH} - Sem filter (roda pra PJ e PF — DateCode aceita ambos)
[92] SetVariable uf_guard (scope: roundtrip) — resolve UF em escalar pro guarda¶
value: mesma expressão do campoestadodo[31]—{{ifempty(33.data[].enderecos[].uf; switch(78.address_admin_area_level_1; ...27 estados...; passthrough))}}- Em contexto de mapper a expressão colapsa pra escalar (ex. "RS"), ao contrário do filtro do router (que a trataria como array). Existe só pra alimentar o guarda 91 — o
[31]continua montandoestadono próprio body.
[93] GetVariables uf_guard — surface a variável pro filtro do router¶
- Necessário porque um filtro de router não lê a saída de um SetVariable direto — precisa do GetVariables. O
[57]faz o mesmo padrão com[55].
[31] HTTP Omie IncluirCliente¶
POST https://app.omie.com.br/api/v1/geral/clientes/- Body (campos críticos):
cnpj_cpf:ifempty(cliente.cnpj; cliente.cpf)— Omie infere PF/PJrazao_social:substring(ifempty(ifempty(78.<cf>; 33.data[].empresa.razaoSocial); 9.data.cliente.nome); 0; 60)nome_fantasia: igual razao_social (obrigatório pra NF-e)contribuinte:if(cnpj exists; "S"; "N")— PJ é contribuinte ICMS, PF nãoestado:ifempty(33.data[].enderecos[].uf; switch(78.address_admin_area_level_1; ...27 estados...; passthrough))— DateCode prioritário; fallback Pipedrive passa porswitch()que converte nome completo ("Espírito Santo") em sigla UF ("ES"). Fix 2026-06-19 — Omie só aceita sigla.bloquear_faturamento:"N"recomendacoes:[{ codigo_vendedor: 43.data.cadastro[].codigo }]
[38] Router pós-IncluirCliente — 2 rotas¶
- Route 0
Cnpj adicionado(31.statusCode == 200) →[36]SetVar =31.data.codigo_cliente_omie - Route 1
cnpj existente(statusCode == 500ANDfaultstring contains "já cadastrado") →[39]SetVar = parsing do faultstring
Criação do pedido¶
[57] HTTP Omie AdicionarPedido¶
POST https://app.omie.com.br/api/v1/produtos/pedidovenda/- Body:
{ "codigo_pedido_integracao": "{{timestamp}}", "codigo_cliente": {{55.código cliente omie}}, "codigo_cenario_impostos": 4607146685, "codigo_parcela": "{{9.data.orcamento.codigo_pagamento_omie}}", "codigo_categoria": "1.01.01", "codigo_conta_corrente": {{51.conta corrente}} }
Iteração de produtos finais¶
[11] BasicFeeder — array: {{9.data.itens}}¶
[12] HTTP Omie IncluirProduto (final)¶
POST https://app.omie.com.br/api/v1/geral/produtos/- Mapping completo com
recomendacoes_fiscais: [{ origem_mercadoria, market_place: "S" }]
[25] Router — 5 rotas pra "produto novo" + variantes de "produto já existe"¶
Cada rota não-erro escreve variável produto final (scope: roundtrip). Rota 0 (sucesso 200) cria estrutura completa com produtos secundários. Outras rotas resolvem o código e pulam pra adicionar item.
Subfluxo produtos secundários (só na rota 0 do [25])¶
[19] BasicFeeder produtos secundários — array: {{11.itens}}¶
[20] HTTP Omie IncluirProduto (secundário)¶
[63] Router — 4 rotas (Tudo certo / Erro / Erro 2 / default email)¶
[23/67/83] HTTP Omie IncluirEstrutura — BOM (parts list)¶
Finalização¶
[69] HTTP Omie ConsultarProduto — resolve codigo_produto numérico Omie a partir do código BPX¶
[61] HTTP Omie IncluirItemPedido¶
POST https://app.omie.com.br/api/v1/produtos/pedidovenda/call: IncluirItemPedidocfop: "5.102"(intra-estadual hardcoded — limitação atual)
Anatomia do "erro silencioso" (bug arquitetural do Make)¶
Lição crítica: filter em módulo do Make aborta toda a rota depois dele, não só pula o módulo filtrado. Quando coloquei filter PJ apenas no [33] em 29/04, todos os módulos sequenciais nessa rota ([31] IncluirCliente, [38] Router, [36/39] SetVar do código cliente omie) também deixaram de rodar pra PF. Resultado: [55] GetVariables lia variável vazia, [57] AdicionarPedido enviava codigo_cliente: null → Omie respondia 500 "Invalid JSON object" → Make marcava success porque não tinha error handler configurado → todos os pedidos PF entre 30/04 e 04/05 ficaram zumbis.
Solução correta: condicionar o body do módulo (tipoPessoa: if(cnpj; "PJ"; "PF")) em vez de filtrar. DateCode aceita PF, e mesmo se não aceitasse, os ifempty do [31] cobririam.
Detalhes completos em historico.md.