Files
INDENTIFICAR-TEXTO-EM-IMAGEM/docs/superpowers/specs/2026-08-20-orcamento-exames-imagem-design.md
T
EricandClaude Sonnet 5 314ccf5cdf fix(n8n): responde de forma sincrona (texto puro) e usa memoria simples
O Z-PRO captura a resposta HTTP do webhook e reenvia direto ao cliente
(variavel n8n1 -> passo Mensagem), entao o envio assincrono via API duplicava
a mensagem. Troca para responseMode: responseNode + Respond to Webhook em
texto puro, remove o node de envio assincrono, e troca a memoria de sessao
de Postgres (reservado para outro fluxo) para o node Simple Memory nativo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 09:13:27 -03:00

271 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: <texto da IA>, 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-<protocolo>`) 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-<protocolo ou telefone>`) 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)
- 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.