diff --git a/docs/superpowers/specs/2026-08-20-orcamento-exames-imagem-design.md b/docs/superpowers/specs/2026-08-20-orcamento-exames-imagem-design.md index 0d2c19b..ba898aa 100644 --- a/docs/superpowers/specs/2026-08-20-orcamento-exames-imagem-design.md +++ b/docs/superpowers/specs/2026-08-20-orcamento-exames-imagem-design.md @@ -205,11 +205,62 @@ agendamento desta conta (`Agendamento - GChat - Z-PRO (clínica exemplo)`): 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. Isso sugere que a URL foi configurada num campo de -webhook diferente do usado pelo ChatFlow desses bots (que é o que gera esse -formato JSON). Ainda em aberto — precisa confirmar com o usuário onde -exatamente a URL foi cadastrada no painel do Z-PRO, e ajustar pro mesmo -mecanismo usado pelos bots que já funcionam (ChatFlow → passo "Webhook"). +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. + +### Ainda em aberto + +Não há, até agora, nenhuma variável de mídia/imagem confirmada no seletor de +variáveis do ChatFlow do Z-PRO (o Body de `Menu Agendamento` só tem campos de +texto). Falta o usuário verificar, ao editar o Body do passo "Webhook +Avançado", se existe alguma variável de imagem/anexo no seletor (ícone "×") +— se existir, incluir no Body (ex.: `"mediaUrl": "{{variavel}}"`) e informar +o nome exato pra ajustar o node "Extrair campos do webhook". ## Fora de escopo (por enquanto) diff --git a/n8n/orcamento-exames-imagem.workflow.json b/n8n/orcamento-exames-imagem.workflow.json index d74fcaf..f929d4d 100644 --- a/n8n/orcamento-exames-imagem.workflow.json +++ b/n8n/orcamento-exames-imagem.workflow.json @@ -4,32 +4,24 @@ "active": true, "nodes": [ { + "parameters": { + "httpMethod": "POST", + "path": "orcamento-exames-zpro", + "options": {}, + "responseMode": "responseNode" + }, "id": "webhook-orcamento", "name": "Webhook - Foto Exame", "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ - -600, + -608, 0 ], - "parameters": { - "httpMethod": "POST", - "path": "orcamento-exames-zpro", - "responseMode": "onReceived", - "options": {} - }, "webhookId": "5ea5f89e-7351-4d45-a881-0055009423a0", "onError": "continueRegularOutput" }, { - "id": "extrair-campos", - "name": "Extrair campos do webhook", - "type": "n8n-nodes-base.set", - "typeVersion": 3.4, - "position": [ - -360, - 0 - ], "parameters": { "assignments": { "assignments": [ @@ -72,24 +64,24 @@ ] }, "options": {} - } + }, + "id": "extrair-campos", + "name": "Extrair campos do webhook", + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -368, + 0 + ] }, { - "id": "if-e-imagem", - "name": "É mensagem de imagem?", - "type": "n8n-nodes-base.if", - "typeVersion": 2.3, - "position": [ - -120, - 0 - ], "parameters": { "conditions": { "options": { - "version": 2, - "leftValue": "", "caseSensitive": true, - "typeValidation": "strict" + "leftValue": "", + "typeValidation": "strict", + "version": 2 }, "combinator": "and", "conditions": [ @@ -106,19 +98,18 @@ ] }, "options": {} - } + }, + "id": "if-e-imagem", + "name": "É mensagem de imagem?", + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + -128, + 0 + ] }, { - "id": "baixar-imagem", - "name": "Baixar imagem", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 4.4, - "position": [ - 120, - -120 - ], "parameters": { - "method": "GET", "url": "={{ $json.imagemUrl }}", "options": { "response": { @@ -129,24 +120,33 @@ "timeout": 20000 } }, + "id": "baixar-imagem", + "name": "Baixar imagem", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 64, + -288 + ], "onError": "continueErrorOutput" }, { - "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" }, + "builtInTools": {}, "options": {} }, + "id": "openai-chat-model-orcamento", + "name": "OpenAI Chat Model - Orçamento", + "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", + "typeVersion": 1.3, + "position": [ + 96, + 128 + ], "credentials": { "openAiApi": { "id": "h16jFm10cTU15k6M", @@ -155,14 +155,6 @@ } }, { - "id": "ai-agent-orcamento", - "name": "AI Agent - Orçamento", - "type": "@n8n/n8n-nodes-langchain.agent", - "typeVersion": 3.1, - "position": [ - 360, - -120 - ], "parameters": { "promptType": "define", "text": "={{ $('Extrair campos do webhook').item.json.entrada || 'O cliente enviou uma imagem (sem texto). Veja a imagem anexada, que é o pedido médico dele.' }}", @@ -171,136 +163,67 @@ "passthroughBinaryImages": true } }, + "id": "ai-agent-orcamento", + "name": "AI Agent - Orçamento", + "type": "@n8n/n8n-nodes-langchain.agent", + "typeVersion": 3.1, + "position": [ + 160, + -96 + ], "onError": "continueErrorOutput" }, { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "resp-output", + "name": "output", + "type": "string", + "value": "={{ $json.output ? $json.output : 'Recebemos sua solicitação de exames! Nossa equipe vai confirmar os valores e te retornar em breve.' }}" + } + ] + }, + "options": {} + }, "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": {} - } + -128 + ] }, { - "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 - } - } - }, - { - "id": "memoria-sessao-orcamento", - "name": "Memória da Sessão (Postgres)", - "type": "@n8n/n8n-nodes-langchain.memoryPostgresChat", - "typeVersion": 1.4, - "position": [ - 360, - 40 - ], "parameters": { "sessionIdType": "customKey", "sessionKey": "={{ 'orcamento-' + ($('Extrair campos do webhook').item.json.sessionId || $('Extrair campos do webhook').item.json.telefone) }}", "contextWindowLength": 20 }, - "credentials": { - "postgres": { - "id": "xq3QAN3MZSOCVgdY", - "name": "Postgres Agendamento - (clínica exemplo)" - } + "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow", + "typeVersion": 1.4, + "position": [ + 240, + 112 + ], + "id": "aa58f6fd-5d98-43ed-b645-eeb7e3494564", + "name": "Simple Memory" + }, + { + "id": "responder-webhook", + "name": "Responder Webhook (texto)", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.5, + "position": [ + 900, + -128 + ], + "parameters": { + "respondWith": "text", + "responseBody": "={{ $json.output }}", + "options": {} } } ], @@ -392,18 +315,7 @@ ] ] }, - "Montar corpo da resposta": { - "main": [ - [ - { - "node": "Enviar orçamento (Z-PRO)", - "type": "main", - "index": 0 - } - ] - ] - }, - "Memória da Sessão (Postgres)": { + "Simple Memory": { "ai_memory": [ [ { @@ -413,6 +325,17 @@ } ] ] + }, + "Montar corpo da resposta": { + "main": [ + [ + { + "node": "Responder Webhook (texto)", + "type": "main", + "index": 0 + } + ] + ] } }, "settings": { @@ -420,6 +343,7 @@ "saveDataErrorExecution": "all", "saveDataSuccessExecution": "all", "saveManualExecutions": true, - "saveExecutionProgress": true + "saveExecutionProgress": true, + "binaryMode": "separate" } }