Ir para o conteúdo

WF1 — Envio de Fotos de Pedidos Faturados

ID: YvGAoK7CrZOBVSj2 · Status: ✅ ATIVO · Trigger: Webhook POST

Objetivo

Ao receber o XML de uma NF-e de venda, busca automaticamente as fotos dos produtos faturados no Backblaze B2, gera um link seguro com token temporário e envia para o cliente via e-mail (RD Station) e WhatsApp (Evolution API).

Endpoint do Webhook

POST https://n8n.turmadabia.com.br/webhook/1ac13120-af86-485f-bd22-e3cc3c46bcf0

O corpo da requisição deve conter o XML da NF-e como anexo binário (attachments).


Fluxo Resumido

  1. Recebe XML da NF-e via webhook
  2. Extrai o XML do anexo (suporta base64 e texto puro) e converte para JSON
  3. Filtra somente notas de venda com CNPJ de destinatário preenchido
  4. Extrai dados do cliente (nome, e-mail, telefone 1, CNPJ formatado) e referências dos produtos (primeiros 10 chars do cProd)
  5. Busca fotos na tabela materiais_telegram por referência — apenas imagens (jpg, jpeg, png, gif, webp), sem duplicatas por nome_arquivo
  6. Gera token alfanumérico de 24 caracteres, monta lista de arquivos com nome do produto formatado e URLs de acesso
  7. Grava token no Cloudflare KV (TTL 45 dias)
  8. Salva registro na tabela pedidos_midias (UPSERT por numero_nf)
  9. Autentica no RD Station e dispara e-mail ao cliente
  10. Aguarda 5 minutos
  11. Verifica se o telefone 1 (NF-e) existe no WhatsApp via Evolution API
  12. Se válido: envia WhatsApp para o telefone 1 → grava whatsapp_enviado_para no Postgres
  13. Se inválido: consulta telefone 2 na API Sisplan (por CNPJ), trata e verifica no WhatsApp
  14. Se telefone 2 válido: envia WhatsApp → grava whatsapp_enviado_para no Postgres
  15. Se telefone 2 também inválido: grava whatsapp_enviado_para = NULL no Postgres

Lógica de Validação de Telefone (Fallback WhatsApp)

  • Telefone 1: extraído diretamente da NF-e (campo fone do destinatário). Formatado como 55 + dígitos.
  • Telefone 2: consultado via API Sisplan (api.turmadabia.com.br/clientes?cnpj=XX.XXX.XXX/XXXX-XX). Campo telefone2 do retorno. Formatado como 55 + dígitos.

A verificação usa o nó Evolution API (chat-api / checkWhatsapp). O campo avaliado é data[0].exists = true.

Etapa Ação Resultado
1 Verificar Telefone 1 no WhatsApp exists = true → envia WA para fone 1 → grava Tel 1 no banco ✅
2 (fallback) Consultar fone 2 na API Sisplan Busca por CNPJ formatado (XX.XXX.XXX/XXXX-XX)
3 (fallback) Verificar Telefone 2 no WhatsApp exists = true → envia WA para fone 2 → grava Tel 2 no banco ✅
Fim Se fone 2 também inválido Grava whatsapp_enviado_para = NULL no banco

Descrição dos Nós

# Nome do Nó Tipo Função
1 [IN] Receber XML Nota Webhook POST Ponto de entrada. Recebe o XML da NF-e como anexo binário
2 Gera xml_puro Code (JS) Extrai o XML do anexo — suporta base64 e texto puro. Lança erro se nenhum XML for encontrado
3 [CONVERT] XML para JSON XML Node Converte xml_puro em objeto JSON (com mergeAttrs: true)
4 [FILTER] Validar E-mail Destino IF Filtra por CNPJ preenchido e natureza de operação válida
5 [JS] Extrair Dados e Itens Code (JS) Extrai número da NF, itens únicos, nome/e-mail/telefone 1/CNPJ formatado
6 [DB] Buscar Imagens por Referência PostgreSQL Busca imagens na tabela materiais_telegram — apenas arquivos de imagem, sem duplicatas por nome_arquivo
7 [JS] Gerar Token e Links Code (JS) Gera token de 24 chars, monta lista de arquivos com nome do produto formatado (Title Case), define expiração
8 [API] Gravar no Cloudflare KV HTTP PUT Grava o JSON completo do pedido no Cloudflare KV com TTL de 45 dias
9 [DB] Salvar no Postgres PostgreSQL UPSERT na tabela pedidos_midias — inclui campo criado_em
10 [API] Autenticar no RD Station HTTP POST Autentica no RD Station via refresh_token e obtém access_token
11 [EMAIL] Disparar E-mail pelo RD HTTP POST Envia evento de conversão ao RD Station (veggi_connect_hub) com NF, URL do portal e data de expiração
12 Espera de 5 Minutos Wait Aguarda antes de verificar WhatsApp
13 Verificar Telefone 1 no whatsapp Evolution API Verifica se o telefone da NF-e existe no WhatsApp (instância Veggi - 984107067)
14 Se Nº 1 Whatsapp Válido IF data[0].exists = true → envia WA / false → fallback
15 [Whatsapp] Envia Whatsapp para o cliente Evolution API Envia link das fotos via WhatsApp para o telefone 1 (delay 2s)
16 [DB] Gravar WhatsApp Enviado - Tel 1 PostgreSQL UPDATE em pedidos_midias gravando whatsapp_enviado_para e whatsapp_enviado_em
17 Consulta Telefone 2 - Sisplan HTTP Request GET api.turmadabia.com.br/clientes?cnpj={cnpj_formatado}
18 Tratamento para Nº2 Code (JS) Extrai telefone2 do retorno da API e formata como 55+dígitos. Mantém todos os dados anteriores via spread
19 Verificar Telefone 2 no whatsapp Evolution API Verifica se o telefone 2 (Sisplan) existe no WhatsApp
20 Se Nº 2 Whatsapp Válido IF data[0].exists = true → envia WA / false → marca sem WhatsApp
21 [Whatsapp] Envia Whatsapp para o cliente nº2 Evolution API Envia link das fotos via WhatsApp para o telefone 2 (delay 2s)
22 [DB] Gravar WhatsApp Enviado - Tel 2 PostgreSQL UPDATE em pedidos_midias gravando whatsapp_enviado_para (Tel 2) e whatsapp_enviado_em
23 [DB] Marcar Sem WhatsApp Enviado PostgreSQL UPDATE em pedidos_midias com whatsapp_enviado_para = NULL e whatsapp_enviado_em = NOW()

Detalhes dos Nós Principais

Gera xml_puro

Extrai o XML do anexo do e-mail. Suporta dois formatos:

  • Texto puro: conteúdo já começa com < — usado diretamente
  • Base64: detectado automaticamente quando o conteúdo não começa com < e passa pelo regex /^[A-Za-z0-9+/=\r\n]+$/ — decodificado via Buffer.from(trimmed, 'base64')

Remove BOM (\uFEFF) e espaços antes de retornar.

Warning

Se nenhum XML for encontrado em body.attachments[].content, o nó lança erro e o fluxo para.

[DB] Buscar Imagens por Referência

SELECT DISTINCT ON (nome_arquivo)
    backblaze_file_id,
    caminho_completo,
    nome_arquivo
FROM materiais_telegram
WHERE referencia = $1
  AND nome_arquivo ~* '\.(jpg|jpeg|png|gif|webp)$'
ORDER BY nome_arquivo, id DESC;
  • Filtra apenas arquivos de imagem (não inclui vídeos nem PDFs)
  • DISTINCT ON (nome_arquivo) evita duplicatas — em caso de conflito, mantém o registro mais recente (id DESC)
  • Parâmetro $1 = $json.referencia (primeiros 10 chars do cProd)

Além de gerar o token, este nó monta a lista de arquivos cruzando os resultados do Postgres com os dados do XML:

  1. Cria um mapa referencia → nome_produto a partir do nó [JS] Extrair Dados e Itens
  2. Para cada arquivo do Postgres, verifica se nome_arquivo contém alguma referência do mapa
  3. Formata o nome do produto em Title Case (com exceções: de, da, do, das, dos, e) e remove o texto após barra (/)
  4. Monta o campo nome_produto no formato: Nome Do Produto (referencia)
// Exemplo de saída:
{
  nome: "verão 2026_linha noite_still_12.34.5678-001.jpg",
  path: "pasta/verão 2026_linha noite_still_12.34.5678-001.jpg",
  nome_produto: "Pijama Turma da Bia (12.34.5678)"
}

[DB] Salvar no Postgres

UPSERT na tabela pedidos_midias. Em caso de conflito por numero_nf, atualiza todos os campos exceto criado_em:

INSERT INTO public.pedidos_midias
  (token, arquivos, numero_nf, expires_at, cliente_nome, cliente_email, zip_url, view_url, criado_em)
VALUES (...)
ON CONFLICT (numero_nf)
DO UPDATE SET
  token = EXCLUDED.token,
  arquivos = EXCLUDED.arquivos,
  expires_at = EXCLUDED.expires_at,
  cliente_nome = EXCLUDED.cliente_nome,
  cliente_email = EXCLUDED.cliente_email,
  zip_url = EXCLUDED.zip_url,
  view_url = EXCLUDED.view_url
RETURNING *;

Info

O campo expires_at é convertido para o fuso America/Sao_Paulo na inserção.

[API] Autenticar no RD Station

POST https://api.rd.services/auth/token

Body enviado:

{
  "client_id": "0cdfad70-d4db-40f4-beb7-d077d1b8acd6",
  "client_secret": "{ valor atual }",
  "refresh_token": "{ valor atual }",
  "grant_type": "refresh_token"
}

Warning

O client_secret e o refresh_token estão hardcoded no body deste nó. Se o e-mail parar de funcionar, o refresh_token pode ter expirado — atualizar diretamente no nó [API] Autenticar no RD Station.

[EMAIL] Disparar E-mail pelo RD

POST https://api.rd.services/platform/events?event_type=conversion

Campos enviados no payload:

Campo Valor
conversion_identifier veggi_connect_hub
email E-mail do cliente (do Postgres)
name Nome do cliente (do Postgres)
cf_cf_nf_hub Número da NF
cf_cf_view_url URL do portal do cliente
cf_cf_expires_at Data de expiração formatada em pt-BR

Nós de rastreamento de WhatsApp

Após cada envio (ou falha), o workflow grava o resultado em pedidos_midias:

Situação Campo whatsapp_enviado_para Campo whatsapp_enviado_em
Tel 1 enviado Número do Tel 1 NOW()
Tel 2 enviado Número do Tel 2 NOW()
Nenhum válido NULL NOW()

Filtro de Natureza de Operação (natOp)

O nó [FILTER] Validar E-mail Destino só processa notas com as seguintes naturezas:

  • VENDA DE MERCADORIA
  • VENDA DE MERCADORIA DO ESTABELECIMENTO
  • VENDA DE MERCADORIA ADQUIRIDA OU RECEBIDAS DE TERCEIROS
  • VENDA DE PRODUÇÃO DO ESTABELECIMENTO ORIGINADA DE ENCOMENDA
  • VENDA ADQUIRIDA DE TERCEIROS ORIGINADA DE ENCOMENDA PARA ENTREGA FUTURA
  • VENDA DE PRODUTO DO ESTABELECIMENTO DESTINADA A NÃO CONTRIBUINTE
  • VENDA DE MERCADORIA ADQUIRIDA OU RECEBIDA DE TERCEIROS, DESTINADA A NÃO CONTRIBUINTE
  • VENDA DE PRODUÇÃO DO ESTABELECIMENTO DESTINADA A ZONA FRANCA
  • VENDA DE MERCADORIA ADQUIRIDA DE TERCEIRO DESTINADA A ZONA FRANCA DE MANAUS

Notas sem CNPJ de destinatário ou com outras naturezas são descartadas silenciosamente.


Lógica de Token e URLs

  • Token: string alfanumérica de 24 caracteres (minúsculas + números)
  • Expira em 45 dias (TTL no KV: 3.888.000 segundos; campo expires_at no PostgreSQL)
  • view_url: https://hub.grupoveggi.com.br/?token={token}
  • zip_url: https://drive.grupoveggi.com.br/{token}

Banco de Dados — Campos de pedidos_midias gravados por este workflow

Campo Origem Observação
token [JS] Gerar Token e Links 24 chars alfanuméricos
arquivos [JS] Gerar Token e Links JSON com nome, path, nome_produto
numero_nf [JS] Extrair Dados e Itens Chave única — UPSERT
expires_at [JS] Gerar Token e Links NOW + 45 dias, fuso SP
cliente_nome [JS] Extrair Dados e Itens Formatado em Title Case
cliente_email [JS] Extrair Dados e Itens Do campo email da NF-e
zip_url [JS] Gerar Token e Links drive.grupoveggi.com.br/{token}
view_url [JS] Gerar Token e Links hub.grupoveggi.com.br/?token={token}
criado_em Banco (NOW()) Fuso SP — não é atualizado no UPSERT
whatsapp_enviado_para Nós de gravação WhatsApp Número enviado ou NULL
whatsapp_enviado_em Nós de gravação WhatsApp NOW() após tentativa