Pular para conteúdo

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:

  1. 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.

  2. 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:

  3. [92] SetVariable resolve a UF pra escalar (uf_guard) — a mesma expressão, mas num mapper, onde ela colapsa pra "RS".
  4. [93] GetVariables surface a variável.
  5. 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.email
  • 9.data.empresa.nome — chave do roteamento de tenant Omie
  • 9.data.itens[] — array de produtos finais com codigo_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 field 934241be2703a7ccdaf77bc01a04a7224ec935ad = 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 social 04383a86d143afb3acccb175c8b6f3e7333998cc

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 ajustadoreplace(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 campo estado do [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 pra alimentar o guarda 91 — o [31] continua montando estado no 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/PJ
  • razao_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ão
  • estado: ifempty(33.data[].enderecos[].uf; switch(78.address_admin_area_level_1; ...27 estados...; passthrough)) — DateCode prioritário; fallback Pipedrive passa por switch() 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 == 500 AND faultstring 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: IncluirItemPedido
  • cfop: "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.