# Orçamento automático de exames a partir de foto (GChat/Z-PRO + n8n + IA) - **Data:** 2026-08-20 - **Status:** Aprovado para implementação - **Instância n8n alvo:** `https://devn8n.gtigrupo.com.br` ## Objetivo Quando um cliente manda, pelo WhatsApp (canal Z-PRO/GChat), uma foto de um pedido médico de exames, o sistema deve: 1. Identificar os exames escritos/impressos na imagem. 2. Cruzar esses exames com uma tabela de preços mantida no prompt da IA. 3. Montar um orçamento formatado para WhatsApp (itens + valores + total). 4. Enviar esse orçamento automaticamente de volta ao cliente, via API do Z-PRO. Este é um teste ponta-a-ponta: o objetivo imediato é validar o fluxo técnico completo (recebimento da imagem → IA de visão → resposta automática), usando uma lista de exames de exemplo. A lista real de exames/valores é trocada depois, diretamente no prompt do agente. ## Contexto / descobertas - A documentação disponível na pasta do projeto (`Z-PRO v4.x.x.x.postman_collection.json`) cobre apenas a **API externa de saída** do Z-PRO (criar ticket, enviar mensagem, listar contatos etc.) — não documenta o formato do webhook de **entrada** que o Z-PRO dispara quando um cliente manda uma mensagem. - A conta n8n já tem vários workflows em produção que integram GChat/Z-PRO com agentes de IA para bots de agendamento (ex.: `Agendamento - GChat - Z-PRO (clínica exemplo)`, id `yG2mi6ECBpPv4Hmn`). Neles, o Z-PRO chama o webhook do n8n com um body como: ```json { "step": "process", "protocol": "...", "entrada": "texto do cliente", "contactName": "...", "number": "55..." } ``` Nenhum desses fluxos existentes trata **imagem** — todos são só texto. Portanto o nome exato do campo que carrega a foto (`mediaUrl`, `file`, `midia`, etc.) é **desconhecido até o primeiro teste real** com uma imagem de verdade. - Existe um sub-workflow reutilizável para envio de texto pelo Z-PRO: `MCP Server - Mensageria ZPRO - enviar_texto` (id `Y4ndG7GuIUrmVRxV`), que recebe `{ number, body, externalKey, isClosed }`, monta o corpo e faz `POST` para `https://apigchat.gtigrupo.com.br/v2/api/external/{apiId}/` usando a credencial HTTP Header Auth **"ZPRO API Token - Canal QR - MC AGENDAMENTO"**. Este fluxo novo vai **reaproveitar esse mesmo sub-workflow** para enviar a resposta, em vez de duplicar a chamada HTTP. - O node `AI Agent` do n8n (`@n8n/n8n-nodes-langchain.agent`) tem a opção `passthroughBinaryImages` (padrão `true`), que repassa automaticamente imagens binárias do item de entrada para o modelo de chat como mensagem de imagem — não é necessário um node de OCR separado. ## Decisões (confirmadas com o usuário) | Decisão | Escolha | |---|---| | Como responder ao cliente | Assíncrono: o workflow processa a imagem e chama a API do Z-PRO (`SendMessageAPIText`, via sub-workflow existente) para enviar a resposta. O webhook de entrada responde rápido (200 OK) e não fica esperando a IA. | | Envio automático vs. revisão humana | Automático — a IA monta e já envia o orçamento, sem aprovação humana no meio. | | Lista de exames/valores | Lista de exemplo (placeholder) embutida no prompt do agente para este teste. Lista real é trocada depois, editando o texto do system prompt do node `AI Agent` no n8n. | | Exame identificado na imagem mas fora da lista de preços | O orçamento traz os itens reconhecidos normalmente (com valor) e avisa, para os não reconhecidos, que um atendente vai confirmar o valor à parte. Nunca bloqueia o envio do orçamento inteiro por causa de um item não reconhecido. | | Canal/token do Z-PRO usado para enviar a resposta | Reaproveita o canal/credencial já cadastrados no n8n para "Canal QR - MC AGENDAMENTO" (mesmo usado pelo bot de agendamento), via o sub-workflow `enviar_texto` existente. | | Workflow | Novo workflow dedicado (não mexe no workflow de agendamento existente). | | Credenciais (IA, Z-PRO) | Configuradas como credenciais nativas do n8n (não hardcoded nos nodes), para poderem ser trocadas depois sem editar o workflow. | ## Arquitetura Novo workflow: **`Orçamento de Exames - GChat - Z-PRO (IA Visão)`** ``` Webhook (Z-PRO) → Responder 200 OK imediatamente (ack rápido, não espera a IA) → Extrair campos do webhook (número, protocolo/ticket, referência da imagem) → IF: é mensagem de imagem? ├─ não → encerra (não faz nada; este fluxo só trata imagem) └─ sim → Baixar imagem (HTTP Request, binário) → AI Agent (visão) — system prompt com tabela de exames/valores + OpenAI Chat Model (gpt-4o, com credencial própria) → Montar corpo da resposta (body/number/externalKey/isClosed) → Execute Workflow: "MCP Server - Mensageria ZPRO - enviar_texto" (erro em qualquer etapa crítica → mensagem padrão de fallback, enviada pelo mesmo sub-workflow: "Recebemos sua solicitação, nossa equipe vai te responder em breve.") ``` ### Componentes 1. **Webhook (trigger)** — novo path dedicado (ex.: `orcamento-exames-zpro`), `responseMode: lastNode`, responde rápido com um ack simples. Este endpoint precisa ser configurado, do lado do Z-PRO, como destino das mensagens do canal-alvo (mesmo mecanismo já usado para plugar os bots de agendamento — o usuário já sabe fazer isso). 2. **Extrair campos do webhook** — node Set/Code que lê `number`, `protocol` (ou equivalente) e a referência da imagem do `body` recebido. Os nomes de campo para a imagem são a melhor suposição inicial e **serão corrigidos após o primeiro teste real** (ver seção "Suposições em aberto"). 3. **IF é imagem?** — segue adiante só se a mensagem recebida for do tipo imagem; qualquer outro tipo (texto, áudio, documento) encerra o fluxo sem ação, já que este workflow trata exclusivamente pedidos de exame por foto. 4. **Baixar imagem** — HTTP Request que baixa o arquivo da imagem como binário (assumindo que o Z-PRO entrega uma URL pública de mídia; se o payload real trouxer a imagem em base64 em vez de URL, este node é trocado por uma conversão base64 → binário). 5. **AI Agent + OpenAI Chat Model (gpt-4o)** — recebe a imagem baixada como binário (repassada automaticamente ao modelo via `passthroughBinaryImages`). System prompt contém: - Persona: assistente de orçamentos de exames da clínica, respondendo por WhatsApp. - Tabela de exames + valores (placeholder nesta fase). - Instruções: ler os exames pedidos na imagem (impressos ou manuscritos), casar com a tabela (tolerando abreviações/sinônimos comuns), montar resposta formatada para WhatsApp com itens + valores + total: para exames não encontrados na tabela, listar separadamente avisando que um atendente vai confirmar o valor. Saída deve ser só o texto final a enviar ao cliente (sem comentários extras, sem markdown incompatível com WhatsApp). - Credencial de API key da OpenAI própria deste node (independente da usada nos bots de agendamento), para poder ser trocada sem afetar outros fluxos. 6. **Montar corpo da resposta** — Set/Code node que monta `{ number, body: , externalKey: <único por execução>, isClosed: false }`. `externalKey` gerado por execução (ex.: `orcamento-{{ $execution.id }}`) para respeitar a exigência do Z-PRO de chave única por ação. 7. **Execute Workflow: enviar_texto** — chama o sub-workflow existente `MCP Server - Mensageria ZPRO - enviar_texto` (id `Y4ndG7GuIUrmVRxV`), reaproveitando a credencial e a lógica de classificação de erro já existentes. ### Tratamento de erro - Falha ao baixar a imagem ou erro na chamada da IA → segue para o mesmo passo de envio (`enviar_texto`), mas com uma mensagem padrão de fallback em vez do orçamento, avisando que a equipe vai responder manualmente. - Falha no próprio envio (API do Z-PRO fora do ar, token inválido etc.) → fica registrada na execução do n8n (o sub-workflow já classifica o tipo de erro); não há novo retry automático nesta primeira versão. ## Suposições em aberto (validar no primeiro teste real) - **Nome do campo da imagem no webhook** — assumimos algo como `mediaUrl` (por analogia com a API de saída do Z-PRO, que usa esse nome para anexar arquivos). Pode ser diferente; corrigido após ver o payload real de uma mensagem de imagem. - **Formato da mídia** — assumimos URL pública, não base64 inline. Se for base64, o node "Baixar imagem" é substituído por uma conversão local. - **Mecanismo de entrega do webhook para imagens** — os exemplos existentes nessa conta só cobrem texto (via um passo "Webhook" dentro de um ChatFlow do Z-PRO). Se imagens não passarem pelo mesmo tipo de passo, pode ser necessário configurar algo diferente no painel do Z-PRO — isso será resolvido junto com o usuário durante o teste. ## Plano de teste 1. Criar o workflow no n8n (inativo) e obter a URL de teste do webhook. 2. Usuário configura essa URL como destino de mensagens do canal-alvo no painel do Z-PRO. 3. Usuário envia uma foto real de pedido de exames para o número de teste. 4. Inspecionar a execução no n8n para confirmar o payload real recebido e ajustar o node "Extrair campos do webhook" (e "Baixar imagem", se necessário). 5. Validar que a resposta com o orçamento (lista de exemplo) chega corretamente no WhatsApp do cliente de teste. 6. Trocar a lista de exemplo pela lista real de exames/valores no prompt do `AI Agent`. ## Revisão 1 (2026-08-21) — fluxo conversacional (texto + imagem) Depois do primeiro teste real, o usuário apontou que um atendimento real não chega só como uma foto isolada: o cliente pode mandar texto antes/depois, ou digitar os nomes dos exames em vez de (ou além de) mandar foto, tudo em mensagens separadas. O desenho original tratava qualquer mensagem sem imagem como "não se aplica" e encerrava sem responder — o que quebra esse caso real. Mudanças no workflow, alinhadas ao mesmo padrão já usado pelo bot de agendamento desta conta (`Agendamento - GChat - Z-PRO (clínica exemplo)`): - O node **"É mensagem de imagem?"** deixou de ser um filtro que encerra o fluxo — agora só decide se baixa a imagem antes do agente ou não. As duas saídas (com ou sem imagem) chegam no `AI Agent`; nenhuma mensagem é mais ignorada. - **"Extrair campos do webhook"** agora também extrai o texto da mensagem (`entrada`), no mesmo nome de campo usado pelos bots de agendamento que já funcionam nessa conta. - Adicionada **memória de conversa por sessão** (node `Memória da Sessão (Postgres)`, reaproveitando a credencial `Postgres Agendamento - (clínica exemplo)`, com `sessionKey` prefixado (`orcamento-`) para não colidir com a memória do bot de agendamento). Isso é necessário porque cada mensagem do WhatsApp chega como uma execução nova e separada do workflow — sem memória persistente, o agente esqueceria o que o cliente já tinha mandado numa mensagem anterior. - O **system prompt do `AI Agent`** foi reescrito para ser conversacional: se o cliente ainda não mandou nem foto nem lista de exames (nesta mensagem ou antes, na mesma conversa), o agente pede isso educadamente em vez de inventar um orçamento vazio; assim que houver pelo menos um exame identificável (por imagem, texto, ou o histórico da conversa), ele monta o orçamento normalmente. - O envio da resposta (via sub-workflow `enviar_texto`) passou a acontecer sempre, em toda mensagem — porque agora toda mensagem gera uma resposta do agente (seja um pedido de mais informação, seja o orçamento final), não só quando uma imagem é reconhecida. ### Descoberta durante o teste: payload do webhook chegou vazio Os dois testes reais feitos pelo usuário mostraram o webhook recebendo `body: {}` (content-length 0, user-agent `axios/1.19.0`) — ou seja, o Z-PRO não mandou `protocol`/`entrada`/`contactName`/`number` como os bots de agendamento recebem. Causa raiz confirmada pelo usuário: no ChatFlow do Z-PRO, o passo "Webhook Avançado" do fluxo de orçamento (`Menu Orçamento` / `Menu Orçamento 2`) estava com o campo **Body** vazio, enquanto o do fluxo de agendamento (`Menu Agendamento` / `Menu Agendamento 2`, que já funciona) tem: ```json { "step": "process", "entrada": "{{entrada}}", "number": "{{phoneNumber}}", "contactName": "{{name}}", "protocol": "{{protocol}}" } ``` Ação do usuário: copiar o mesmo Body (com essas mesmas variáveis) para os passos "Webhook Avançado" de `Menu Orçamento` e `Menu Orçamento 2`. Os nomes de campo já batem com o que o node "Extrair campos do webhook" espera (`body.protocol`, `body.number`, `body.contactName`, `body.entrada`) — não precisou ajustar nada do lado do n8n para esses campos. ### Descoberta 2: o Z-PRO captura a resposta HTTP e reenvia ao cliente Nas telas do ChatFlow, o passo "Webhook Avançado" tem uma seção "Resposta da API (captura e roteamento)": ele salva o corpo da resposta HTTP do nosso webhook numa variável (`n8n1`) e o passo seguinte ("Mensagem") manda `{{n8n1}}` de volta ao cliente pelo WhatsApp. Ou seja, **o Z-PRO não usa a API de envio de mensagens para essa resposta — ele repassa direto o que o webhook devolver na hora**. Como o "Caminho no JSON" fica vazio, o corpo precisa ser **texto puro**, não JSON. Isso invalida a decisão original de "envio assíncrono via API do Z-PRO": o workflow foi ajustado para responder de forma **síncrona**, com `responseMode: responseNode` no Webhook e um node "Respond to Webhook" (`respondWith: text`) devolvendo o texto final direto na resposta HTTP. O node "Enviar orçamento (Z-PRO)" (chamada assíncrona ao sub-workflow `enviar_texto`) foi removido — mantê-lo mandaria a mensagem duas vezes. ### Descoberta 3: memória de conversa A credencial Postgres reaproveitada do bot de agendamento (`Postgres Agendamento - (clínica exemplo)`) é de uso exclusivo daquele fluxo — o usuário pediu para não usá-la aqui sem autorização explícita. A memória de sessão agora usa o node nativo **Simple Memory** (`memoryBufferWindow`, sem credencial), com `sessionKey` prefixado (`orcamento-`) para isolar as conversas de orçamento das de agendamento. ### Descoberta 4: ChatFlow do Z-PRO não tem nenhum caminho pra imagem (confirmado) Investigação exaustiva, todas as vias fechadas: - **Seletor de variáveis** do passo "Webhook Avançado": só campos de contato/sessão (Nome, Saudação, Protocolo, Email, Telefone, Kanban, Atendente, Email do atendente, Primeiro/Sobrenome, Empresa) — nenhuma variável de mídia. - **Consulta à API do Z-PRO** (`showticketchatbot`, buscando o ticket pelo número) — tentativa abandonada: no teste real, retornou um ticket **fechado, de mais de um mês atrás**, sobre um assunto não relacionado. Essa API não busca de forma confiável "a conversa/mensagem atual". - **Passo "n8n"** (interação alternativa ao "Webhook"): mesma lista de variáveis de contato/sessão, sem corpo customizável — não ajuda. - **Passo "Mídia"**: é o oposto do que se imaginava — serve pra o *bot* enviar um arquivo *pro* cliente (seleciona arquivo + legenda), não pra capturar o que o cliente manda. - **"Tipo da variável"** na captura de resposta (seção "Variável (opcional)" dos passos "Mensagem"/"n8n"): só permite capturar texto livre — sem opção de mídia/arquivo. **Conclusão:** o construtor de ChatFlow do Z-PRO, na versão em uso, não tem nenhum mecanismo para repassar uma imagem recebida do cliente para um webhook externo. Não é uma configuração faltando — é uma limitação da ferramenta nesse ponto. Corrigido o prompt do `AI Agent` pra não prometer mais "mande uma foto" ao cliente (antes disso ele pedia foto mesmo sem nunca conseguir processar nenhuma). ### Ainda em aberto Falta o usuário confirmar se existe, fora do construtor de ChatFlow (em Configurações/Integrações do canal ou da empresa), um webhook de propósito geral que dispare para toda mensagem recebida no canal — mecanismo diferente do "Webhook Avançado" dentro de um ChatFlow. Se não existir, verificar com o suporte do Z-PRO se há algum outro caminho suportado pela plataforma. Até lá, o workflow funciona **somente com texto** — o cliente digita os nomes dos exames, e a IA monta o orçamento normalmente. ## Fora de escopo (por enquanto) - Fila/aprovação humana antes do envio (usuário optou por envio automático). - Fonte de preços fora do prompt (planilha/banco) — pode ser considerado no futuro se a lista crescer muito, mas não faz parte deste teste. - Retry automático em caso de falha de envio.