From 21957262cfb4ae78f1d494fa2de6d15e1a49ef3a Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 20 Aug 2026 22:00:58 -0300 Subject: [PATCH] =?UTF-8?q?docs:=20adiciona=20plano=20de=20implementa?= =?UTF-8?q?=C3=A7=C3=A3o=20do=20or=C3=A7amento=20de=20exames=20via=20foto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 --- .../2026-08-20-orcamento-exames-imagem.md | 462 ++++++++++++++++++ 1 file changed, 462 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-20-orcamento-exames-imagem.md diff --git a/docs/superpowers/plans/2026-08-20-orcamento-exames-imagem.md b/docs/superpowers/plans/2026-08-20-orcamento-exames-imagem.md new file mode 100644 index 0000000..ff83894 --- /dev/null +++ b/docs/superpowers/plans/2026-08-20-orcamento-exames-imagem.md @@ -0,0 +1,462 @@ +# Orçamento Automático de Exames por Foto (GChat/Z-PRO + n8n) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. +> +> **Nota sobre esta plan:** o "código" aqui não é um arquivo-fonte — é um workflow do n8n, construído via chamadas às ferramentas MCP `n8n-mcp` (já conectadas nesta sessão, instância `https://devn8n.gtigrupo.com.br`). O equivalente a "rodar o teste" é chamar `mcp__n8n-mcp__n8n_validate_workflow` e ler os erros; o equivalente a "commit" é salvar um snapshot do JSON do workflow no repositório e commitar. A Task 6 exige uma ação real do usuário (mandar uma foto de teste no WhatsApp) e não pode ser concluída sem ele — por isso a execução recomendada desta plan é **inline, nesta mesma sessão**, não subagent-driven. + +**Goal:** Criar, no n8n, um workflow que recebe (via webhook do Z-PRO/GChat) uma foto de pedido de exames, usa um AI Agent com visão para identificar os exames e cruzá-los com uma tabela de preços no prompt, e envia automaticamente um orçamento formatado de volta ao cliente pela API do Z-PRO. + +**Architecture:** Webhook (ack imediato) → extrai campos do payload → filtra só mensagens de imagem → baixa a imagem → AI Agent (gpt-4o com visão, tabela de preços no system prompt) → monta o corpo da resposta → envia via sub-workflow já existente `MCP Server - Mensageria ZPRO - enviar_texto` (id `Y4ndG7GuIUrmVRxV`). Erros em qualquer etapa caem numa mensagem de fallback, que também é enviada pelo mesmo sub-workflow. + +**Tech Stack:** n8n (self-hosted, `devn8n.gtigrupo.com.br`), n8n-mcp (ferramentas `mcp__n8n-mcp__*`), node `@n8n/n8n-nodes-langchain.agent` + `lmChatOpenAi` (OpenAI gpt-4o), API externa do Z-PRO v4. + +## Global Constraints + +- Envio do orçamento é automático, sem aprovação humana no meio (decisão da spec). +- `externalKey` deve ser único por envio (exigência da API do Z-PRO — não pode repetir). +- `isClosed` do ticket fica `false` por padrão (o ticket permanece aberto após o envio). +- A resposta final da IA deve ser **somente** o texto a mandar ao cliente — sem markdown incompatível com WhatsApp (nada de `**`, `#`, etc.), sem comentários extras. +- Nunca deixar de orçar por causa de um único exame não reconhecido — orça o que reconhecer e avisa do resto (decisão da spec). +- Reaproveitar o sub-workflow existente `MCP Server - Mensageria ZPRO - enviar_texto` (id `Y4ndG7GuIUrmVRxV`) para o envio — não duplicar a chamada HTTP ao Z-PRO. +- A credencial de IA usada neste workflow deve ser própria (não a credencial `GPT` compartilhada com os bots de agendamento), para poder ser trocada sem afetar outros fluxos. +- Instância n8n alvo: `https://devn8n.gtigrupo.com.br`, acessível nesta sessão via as ferramentas `mcp__n8n-mcp__*`. +- Nome do workflow: `Orçamento de Exames - GChat - Z-PRO (IA Visão)`. +- Nomes de campo do payload de entrada de imagem são **desconhecidos até o teste real** (ver Task 6) — o workflow é construído com suposições defensivas (múltiplos nomes de campo candidatos) e corrigido depois de ver o payload real. + +--- + +### Task 1: Esqueleto do workflow (Webhook com ack imediato) + +**Files:** +- Cria (no n8n, via MCP): workflow `Orçamento de Exames - GChat - Z-PRO (IA Visão)`. +- Create: `n8n/orcamento-exames-imagem.workflow.json` (snapshot local do workflow, para ficar versionado no repo). + +**Interfaces:** +- Produces: node `Webhook - Foto Exame` (id `webhook-orcamento`), que entrega o payload recebido do Z-PRO em `$json.body`. Path do webhook: `orcamento-exames-zpro`. Tarefas seguintes conectam a partir deste node. + +- [ ] **Step 1: Criar o workflow com o node de Webhook** + +Chame `mcp__n8n-mcp__n8n_create_workflow` com: + +```json +{ + "name": "Orçamento de Exames - GChat - Z-PRO (IA Visão)", + "nodes": [ + { + "id": "webhook-orcamento", + "name": "Webhook - Foto Exame", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [-600, 0], + "parameters": { + "httpMethod": "POST", + "path": "orcamento-exames-zpro", + "responseMode": "onReceived", + "options": {} + } + } + ], + "connections": {}, + "settings": { + "executionOrder": "v1", + "saveDataErrorExecution": "all", + "saveDataSuccessExecution": "all", + "saveManualExecutions": true, + "saveExecutionProgress": true, + "binaryMode": "separate" + } +} +``` + +Guarde o `id` do workflow retornado — todas as tasks seguintes usam esse `id`. + +- [ ] **Step 2: Validar** + +Chame `mcp__n8n-mcp__n8n_validate_workflow` com `{ "id": "" }`. +Esperado: `0 errors` (um workflow só com um webhook, sem mais nada conectado, pode gerar *warnings* do tipo "nenhum node depois do trigger" — isso é esperado nesta etapa e será resolvido nas próximas tasks; o que importa aqui é não ter `errors`). + +- [ ] **Step 3: Salvar snapshot local e commitar** + +Chame `mcp__n8n-mcp__n8n_get_workflow` com o `id`, pegue o campo `data`, e escreva o JSON formatado (indentado) em `n8n/orcamento-exames-imagem.workflow.json` (crie a pasta `n8n/` se não existir). + +```bash +git add n8n/orcamento-exames-imagem.workflow.json +git commit -m "feat(n8n): cria esqueleto do workflow de orçamento de exames (webhook) + +Co-Authored-By: Claude Sonnet 5 " +``` + +--- + +### Task 2: Extrair campos do webhook e filtrar só mensagens de imagem + +**Files:** +- Modifica (no n8n): workflow criado na Task 1 (via `n8n_update_partial_workflow`). +- Modify: `n8n/orcamento-exames-imagem.workflow.json` (snapshot). + +**Interfaces:** +- Consumes: node `Webhook - Foto Exame` (Task 1), payload em `$json.body`. +- Produces: + - node `Extrair campos do webhook` (id `extrair-campos`), que expõe `telefone`, `nomeContato`, `sessionId`, `imagemUrl`, `tipoMidia` no item de saída — tasks seguintes leem esses campos via `$('Extrair campos do webhook').item.json.`. + - node `É mensagem de imagem?` (id `if-e-imagem`), branch verdadeiro (index 0) ainda sem destino (conectado na Task 3), branch falso (index 1) → node `Não é imagem - encerra` (id `noop-nao-imagem`). + +- [ ] **Step 1: Adicionar os três nodes e as conexões** + +Chame `mcp__n8n-mcp__n8n_update_partial_workflow` com `id` do workflow e: + +```json +{ + "operations": [ + { + "type": "addNode", + "node": { + "id": "extrair-campos", + "name": "Extrair campos do webhook", + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [-360, 0], + "parameters": { + "assignments": { + "assignments": [ + { "id": "campo-sessionid", "name": "sessionId", "type": "string", "value": "={{ $json.body.protocol }}" }, + { "id": "campo-telefone", "name": "telefone", "type": "string", "value": "={{ $json.body.number }}" }, + { "id": "campo-nomecontato", "name": "nomeContato", "type": "string", "value": "={{ $json.body.contactName }}" }, + { "id": "campo-imagemurl", "name": "imagemUrl", "type": "string", "value": "={{ $json.body.mediaUrl || $json.body.fileUrl || $json.body.file || '' }}" }, + { "id": "campo-tipomidia", "name": "tipoMidia", "type": "string", "value": "={{ $json.body.mediaType || $json.body.type || '' }}" } + ] + }, + "options": {} + } + } + }, + { + "type": "addNode", + "node": { + "id": "if-e-imagem", + "name": "É mensagem de imagem?", + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [-120, 0], + "parameters": { + "conditions": { + "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, + "combinator": "and", + "conditions": [ + { + "id": "cond-tem-imagem", + "leftValue": "={{ $json.imagemUrl }}", + "rightValue": "", + "operator": { "type": "string", "operation": "notEmpty" } + } + ] + }, + "options": {} + } + } + }, + { + "type": "addNode", + "node": { + "id": "noop-nao-imagem", + "name": "Não é imagem - encerra", + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [120, 160], + "parameters": {} + } + }, + { "type": "addConnection", "source": "Webhook - Foto Exame", "target": "Extrair campos do webhook" }, + { "type": "addConnection", "source": "Extrair campos do webhook", "target": "É mensagem de imagem?" }, + { "type": "addConnection", "source": "É mensagem de imagem?", "target": "Não é imagem - encerra", "sourceOutput": 1 } + ] +} +``` + +- [ ] **Step 2: Validar** + +Chame `mcp__n8n-mcp__n8n_validate_workflow` com o `id`. Se a condição do IF acusar erro de operador (`notEmpty` não reconhecido nessa versão do n8n), ajuste com `n8n_update_partial_workflow` usando `patchNodeField` no node `if-e-imagem`, campo `parameters.conditions.conditions[0].operator`, testando `{ "type": "string", "operation": "exists" }` como alternativa. Warnings sobre o branch verdadeiro do IF sem conexão são esperados até a Task 3 — não é erro. + +- [ ] **Step 3: Salvar snapshot local e commitar** + +Repita o Step 3 da Task 1 (buscar workflow atualizado, sobrescrever `n8n/orcamento-exames-imagem.workflow.json`, `git add` + `git commit -m "feat(n8n): extrai campos do webhook e filtra mensagens de imagem"`). + +--- + +### Task 3: Baixar a imagem + +**Files:** +- Modifica (no n8n): workflow (via `n8n_update_partial_workflow`). +- Modify: `n8n/orcamento-exames-imagem.workflow.json`. + +**Interfaces:** +- Consumes: node `É mensagem de imagem?` (Task 2), branch verdadeiro; campo `$json.imagemUrl`. +- Produces: node `Baixar imagem` (id `baixar-imagem`), que entrega o arquivo como dado binário (propriedade binária padrão `data`) para a Task 4. Em erro, usa `onError: continueErrorOutput` — a saída de erro (index 1) fica pendente de conexão até a Task 5. + +- [ ] **Step 1: Adicionar o node e conectar** + +```json +{ + "operations": [ + { + "type": "addNode", + "node": { + "id": "baixar-imagem", + "name": "Baixar imagem", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [120, -120], + "onError": "continueErrorOutput", + "parameters": { + "method": "GET", + "url": "={{ $json.imagemUrl }}", + "options": { + "response": { "response": { "responseFormat": "file" } }, + "timeout": 20000 + } + } + } + }, + { "type": "addConnection", "source": "É mensagem de imagem?", "target": "Baixar imagem", "sourceOutput": 0 } + ] +} +``` + +- [ ] **Step 2: Validar** + +`mcp__n8n-mcp__n8n_validate_workflow` com o `id`. Esperado: `0 errors`. + +- [ ] **Step 3: Salvar snapshot local e commitar** + +Mesmo procedimento das tasks anteriores. Mensagem de commit: `"feat(n8n): baixa a imagem recebida do cliente"`. + +--- + +### Task 4: Credencial de IA + AI Agent com visão + +**Files:** +- Cria (no n8n): credencial `OpenAI - Orçamento de Exames` (via `n8n_manage_credentials`). +- Modifica (no n8n): workflow (via `n8n_update_partial_workflow`). +- Modify: `n8n/orcamento-exames-imagem.workflow.json`. + +**Interfaces:** +- Consumes: node `Baixar imagem` (Task 3) — o binário da imagem é repassado automaticamente ao Agent (`passthroughBinaryImages: true`). +- Produces: node `AI Agent - Orçamento` (id `ai-agent-orcamento`), cuja saída de sucesso tem `$json.output` (texto do orçamento) e cuja saída de erro (index 1, `onError: continueErrorOutput`) fica pendente de conexão até a Task 5. + +- [ ] **Step 1: Criar a credencial de OpenAI (placeholder)** + +Chame `mcp__n8n-mcp__n8n_manage_credentials` com: + +```json +{ + "action": "create", + "name": "OpenAI - Orçamento de Exames", + "type": "openAiApi", + "data": { "apiKey": "COLOQUE_SUA_CHAVE_AQUI" } +} +``` + +Guarde o `id` retornado. **Depois**, abra essa credencial na interface do n8n (`Credentials` → `OpenAI - Orçamento de Exames`) e substitua `COLOQUE_SUA_CHAVE_AQUI` pela chave real da OpenAI — assim a chave nunca precisa ser digitada aqui na conversa, e pode ser trocada a qualquer momento sem tocar no workflow. + +- [ ] **Step 2: Adicionar o OpenAI Chat Model e o AI Agent** + +```json +{ + "operations": [ + { + "type": "addNode", + "node": { + "id": "openai-chat-model-orcamento", + "name": "OpenAI Chat Model - Orçamento", + "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", + "typeVersion": 1.3, + "position": [360, -280], + "parameters": { + "model": { "mode": "id", "value": "gpt-4o" }, + "options": {} + }, + "credentials": { + "openAiApi": { "id": "", "name": "OpenAI - Orçamento de Exames" } + } + } + }, + { + "type": "addNode", + "node": { + "id": "ai-agent-orcamento", + "name": "AI Agent - Orçamento", + "type": "@n8n/n8n-nodes-langchain.agent", + "typeVersion": 3.1, + "position": [360, -120], + "onError": "continueErrorOutput", + "parameters": { + "promptType": "define", + "text": "Segue a imagem do pedido de exames do cliente. Monte o orçamento conforme as instruções do system prompt.", + "options": { + "systemMessage": "Você é o assistente de orçamentos de exames de uma clínica, respondendo automaticamente por WhatsApp.\n\nTABELA DE EXAMES E VALORES (lista de exemplo — será substituída pela lista real depois):\n- Hemograma completo — R$ 35,00\n- Glicemia de jejum — R$ 20,00\n- Colesterol total e frações — R$ 45,00\n- Triglicerídeos — R$ 25,00\n- TSH (hormônio tireoidiano) — R$ 40,00\n- Exame de urina (EAS) — R$ 30,00\n- Ultrassonografia abdominal — R$ 150,00\n- Raio-X de tórax — R$ 90,00\n- Eletrocardiograma (ECG) — R$ 80,00\n- Papanicolau — R$ 60,00\n\nINSTRUÇÕES:\n1. Leia a imagem anexada, que é a foto de um pedido médico de exames (pode estar manuscrita ou impressa).\n2. Identifique cada exame solicitado na imagem.\n3. Para cada exame identificado que corresponde (mesmo com sinônimo, abreviação comum ou pequena diferença de grafia) a um item da tabela acima, inclua na resposta o nome do exame e o valor.\n4. Para exames identificados na imagem que NÃO correspondem a nenhum item da tabela, ou que você não conseguiu ler com confiança, liste-os separadamente avisando que um atendente vai confirmar o valor.\n5. Nunca deixe de responder por causa de um item não reconhecido — sempre orce o que for possível reconhecer.\n6. Calcule e mostre o valor total somando apenas os itens com preço confirmado na tabela.\n7. Responda em português, tom cordial e objetivo, formatado para WhatsApp: texto simples, sem markdown (nada de ** ou #), pode usar emojis com moderação e quebras de linha para organizar.\n8. Sua resposta deve conter SOMENTE o texto final a ser enviado ao cliente — nada de comentários extras, explicações do seu raciocínio, ou texto fora do orçamento.", + "passthroughBinaryImages": true + } + } + } + }, + { "type": "addConnection", "source": "Baixar imagem", "target": "AI Agent - Orçamento", "sourceOutput": 0 }, + { "type": "addConnection", "source": "OpenAI Chat Model - Orçamento", "target": "AI Agent - Orçamento", "targetInput": "ai_languageModel" } + ] +} +``` + +- [ ] **Step 3: Validar** + +`mcp__n8n-mcp__n8n_validate_workflow` com o `id`. Se houver erro sobre o formato do `model` ou da credencial, ajuste com `patchNodeField` conforme a mensagem retornada. + +- [ ] **Step 4: Salvar snapshot local e commitar** + +Mesmo procedimento. Mensagem de commit: `"feat(n8n): adiciona AI Agent com visao e tabela de precos no prompt"`. + +**Não inclua a chave real da OpenAI em nenhum arquivo commitado** — o snapshot do workflow não contém segredos (credenciais ficam referenciadas só por `id`/`name`, o n8n nunca devolve o valor da chave nas consultas via API). + +--- + +### Task 5: Montar a resposta e enviar (com fallback de erro) + +**Files:** +- Modifica (no n8n): workflow (via `n8n_update_partial_workflow`). +- Modify: `n8n/orcamento-exames-imagem.workflow.json`. + +**Interfaces:** +- Consumes: + - node `AI Agent - Orçamento` (Task 4), saída de sucesso (`$json.output`) e saída de erro. + - node `Baixar imagem` (Task 3), saída de erro. + - node `Extrair campos do webhook` (Task 2), para reobter `telefone` independente do caminho (sucesso ou erro). +- Produces: envio da resposta via sub-workflow existente `MCP Server - Mensageria ZPRO - enviar_texto` (id `Y4ndG7GuIUrmVRxV`) — fim do fluxo principal. + +- [ ] **Step 1: Adicionar "Montar corpo da resposta" e "Enviar via Z-PRO", e ligar os três caminhos (sucesso + 2 erros)** + +```json +{ + "operations": [ + { + "type": "addNode", + "node": { + "id": "montar-corpo-resposta", + "name": "Montar corpo da resposta", + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [640, -120], + "parameters": { + "assignments": { + "assignments": [ + { "id": "resp-number", "name": "number", "type": "string", "value": "={{ $('Extrair campos do webhook').item.json.telefone }}" }, + { "id": "resp-body", "name": "body", "type": "string", "value": "={{ $json.output ? $json.output : 'Recebemos sua solicitação de exames! Nossa equipe vai confirmar os valores e te retornar em breve.' }}" }, + { "id": "resp-externalkey", "name": "externalKey", "type": "string", "value": "={{ 'orcamento-' + $execution.id }}" }, + { "id": "resp-isclosed", "name": "isClosed", "type": "boolean", "value": false } + ] + }, + "options": {} + } + } + }, + { + "type": "addNode", + "node": { + "id": "enviar-orcamento-zpro", + "name": "Enviar orçamento (Z-PRO)", + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.3, + "position": [900, -120], + "parameters": { + "workflowId": { "__rl": true, "mode": "id", "value": "Y4ndG7GuIUrmVRxV", "cachedResultName": "MCP Server - Mensageria ZPRO - enviar_texto" }, + "workflowInputs": { + "mappingMode": "defineBelow", + "value": { + "number": "={{ $json.number }}", + "body": "={{ $json.body }}", + "externalKey": "={{ $json.externalKey }}", + "isClosed": "={{ $json.isClosed }}" + }, + "matchingColumns": [], + "schema": [ + { "id": "number", "displayName": "number", "required": false, "defaultMatch": false, "display": true, "canBeUsedToMatch": true, "type": "string" }, + { "id": "body", "displayName": "body", "required": false, "defaultMatch": false, "display": true, "canBeUsedToMatch": true, "type": "string" }, + { "id": "externalKey", "displayName": "externalKey", "required": false, "defaultMatch": false, "display": true, "canBeUsedToMatch": true, "type": "string" }, + { "id": "isClosed", "displayName": "isClosed", "required": false, "defaultMatch": false, "display": true, "canBeUsedToMatch": true, "type": "boolean" } + ], + "attemptToConvertTypes": false, + "convertFieldsToString": false + } + } + } + }, + { "type": "addConnection", "source": "AI Agent - Orçamento", "target": "Montar corpo da resposta", "sourceOutput": 0 }, + { "type": "addConnection", "source": "AI Agent - Orçamento", "target": "Montar corpo da resposta", "sourceOutput": 1 }, + { "type": "addConnection", "source": "Baixar imagem", "target": "Montar corpo da resposta", "sourceOutput": 1 }, + { "type": "addConnection", "source": "Montar corpo da resposta", "target": "Enviar orçamento (Z-PRO)" } + ] +} +``` + +- [ ] **Step 2: Validar** + +`mcp__n8n-mcp__n8n_validate_workflow` com o `id`, `options.profile: "strict"`. Se o formato do `workflowId` (resourceLocator) não for reconhecido, ajuste com `patchNodeField` conforme a mensagem de erro retornada — o objetivo é `0 errors` no final desta task (o workflow inteiro, ponta a ponta, deve validar limpo aqui). + +- [ ] **Step 3: Salvar snapshot local e commitar** + +Mesmo procedimento. Mensagem de commit: `"feat(n8n): monta resposta com fallback de erro e envia via sub-workflow Z-PRO"`. + +--- + +### Task 6: Teste real e ajuste dos campos do webhook + +Esta task depende de uma ação do usuário (mandar uma foto real pelo WhatsApp) e não pode ser concluída sem ele — é o próprio objetivo de "testarmos" da spec. + +- [ ] **Step 1: Obter a URL de teste do webhook** + +A URL de teste é `https://devn8n.gtigrupo.com.br/webhook-test/orcamento-exames-zpro` (POST). A URL de produção, usada depois de ativar o workflow, é `https://devn8n.gtigrupo.com.br/webhook/orcamento-exames-zpro`. + +- [ ] **Step 2: Pedir para o usuário configurar o destino no Z-PRO** + +Peça para o usuário apontar esse endpoint como destino de mensagens do canal-alvo, no painel do Z-PRO (mesmo mecanismo que ele já usa para os outros bots dessa conta). + +- [ ] **Step 3: Pedir um envio de teste** + +Peça para o usuário mandar, pelo WhatsApp, uma foto de um pedido de exames (pode ser uma foto de exemplo qualquer com nomes de exames legíveis) para o número configurado. + +- [ ] **Step 4: Inspecionar a execução** + +Chame `mcp__n8n-mcp__n8n_executions` (list, filtrando pelo `workflowId`) para achar a execução mais recente, e leia o `body` recebido pelo node `Webhook - Foto Exame`. + +- [ ] **Step 5: Corrigir os nomes de campo, se necessário** + +Compare o payload real com as suposições do node `Extrair campos do webhook` (Task 2: `body.mediaUrl`/`body.fileUrl`/`body.file` para a imagem, `body.mediaType`/`body.type` pro tipo). Se o campo real tiver outro nome, ajuste com: + +```json +{ + "operations": [ + { + "type": "patchNodeField", + "nodeId": "extrair-campos", + "fieldPath": "parameters.assignments.assignments", + "patches": [ + { "find": "$json.body.mediaUrl || $json.body.fileUrl || $json.body.file", "replace": "$json.body." } + ] + } + ] +} +``` + +- [ ] **Step 6: Repetir o teste até a resposta chegar certa no WhatsApp** + +Repita os Steps 3–5 até o cliente de teste receber, no WhatsApp, o orçamento formatado com a lista de exemplo. + +- [ ] **Step 7: Salvar snapshot final, commitar e ativar** + +Salve o JSON final em `n8n/orcamento-exames-imagem.workflow.json`, `git commit -m "fix(n8n): ajusta campos do webhook conforme payload real do Z-PRO"`. Depois, com o usuário confirmando que está tudo certo, ative o workflow: + +```json +{ "operations": [ { "type": "activateWorkflow" } ] } +``` + +(via `mcp__n8n-mcp__n8n_update_partial_workflow`, mesmo `id`). + +- [ ] **Step 8: Lembrar o usuário de trocar a lista de exemplo** + +Avise o usuário que a tabela de exames/valores no `system message` do node `AI Agent - Orçamento` ainda é a lista de exemplo (Task 4) — ele deve trocá-la pela lista real de exames/valores da clínica antes de usar em produção com clientes de verdade.