Compare commits

10 Commits
Author SHA1 Message Date
EricandClaude Sonnet 5 d7ebfd41ea docs: add repository setup documentation
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-28 15:07:19 -03:00
EricandClaude Sonnet 5 be145fad95 docs: confirma pipeline de audio validado com execucao real (#476)
Execucao 476 (2026-08-21 17:31) confirma o fluxo de audio ponta-a-ponta:
download via bmToken, transcricao Whisper correta, e resposta do AI Agent
com o valor certo do exame perguntado.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 14:32:15 -03:00
EricandClaude Sonnet 5 a367637606 feat(n8n): adiciona suporte a audio (transcricao via Whisper)
Baixa o audio (mesma autenticacao Bearer usada pra imagem/PDF), transcreve
com o node OpenAI (resource audio, operation transcribe, modelo whisper-1)
e trata o texto transcrito exatamente como uma mensagem digitada pelo
cliente. Erro no download ou na transcricao cai na mensagem padrao de
fallback. AI Agent agora le entrada do item atual ($json.entrada) em vez
de referenciar sempre o node original, para funcionar nos tres caminhos
(texto direto, imagem/PDF, audio transcrito).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 11:36:52 -03:00
EricandClaude Sonnet 5 ed1d154b95 feat(n8n): aceita PDF do pedido medico, alem de foto e texto
Extrai a URL do documento (msg.document.url) do mesmo jeito que a imagem,
baixa com a mesma autenticacao, e habilita passthroughBinaryPdfs no AI
Agent para o modelo ler o PDF diretamente. Prompt atualizado pra mencionar
PDF como formato aceito.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 11:30:56 -03:00
EricandClaude Sonnet 5 c37ea1189e feat(n8n): trata formularios com checkbox, orca so exames marcados
Adiciona instrucao no prompt: quando o pedido medico vier como formulario
pre-impresso com caixinhas de selecao, orcar apenas os exames com a
caixinha marcada, ignorando os listados mas nao marcados. Se nao houver
duvida clara sobre a marcacao, trata como nao identificado.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 11:07:25 -03:00
EricandClaude Sonnet 5 294a8923f3 feat(n8n): adiciona lista real de 44 exames e valores no prompt
Substitui a tabela de exemplo pela lista real fornecida pelo usuario. Item
'Sorologia para outras doencas' tratado como sob consulta (nao entra no
total automatico, sempre avisa que um atendente confirma o valor).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 11:05:15 -03:00
EricandClaude Sonnet 5 091878d484 fix(n8n): so processa mensagens quando o ticket esta no fluxo de Orcamento
O Webhook do Canal dispara pra toda mensagem do numero, entao o bot estava
respondendo (pedindo exames) pra qualquer coisa -- CPF, agendamento,
resultado de exame etc. Filtra por ticket.stepChatFlow, so seguindo quando
o ticket esta parado no passo Menu Orcamento ou Menu Orcamento 2; qualquer
outra coisa e ignorada sem resposta.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 10:50:13 -03:00
EricandClaude Sonnet 5 2322940264 fix(n8n): usa canal/API dedicados deste projeto para o envio (nao mais compartilhado)
O envio estava saindo pelo canal do bot de agendamento (sub-workflow
enviar_texto, projeto diferente) -- por isso recebia num numero e
respondia por outro. Cria credencial httpHeaderAuth propria ('ZPRO API
Token - Orcamento de Exames') e troca o envio para uma chamada HTTP direta
dentro deste workflow, isolada do projeto de agendamento.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 10:40:16 -03:00
EricandClaude Sonnet 5 deeefe2b40 feat(n8n): reconstroi o fluxo em cima do Webhook do Canal (resolve imagem)
O 'Webhook do Canal' do Z-PRO (fora do ChatFlow, opcao 'Mensagens
recebidas') manda o payload completo da mensagem, incluindo msg.image.url
para fotos -- resolve a limitacao que bloqueava imagem. Novo endpoint
orcamento-exames-canal-zpro substitui o antigo orcamento-exames-zpro
(removido). Download da imagem autenticado com Bearer usando o bmToken do
proprio payload. Volta ao envio assincrono via sub-workflow enviar_texto,
ja que esse webhook e uma notificacao (nao e capturado/reenviado pelo
Z-PRO como no ChatFlow). Prompt volta a aceitar foto normalmente.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 10:16:09 -03:00
EricandClaude Sonnet 5 fd77afad61 feat(n8n): adiciona webhook diagnostico para o 'Webhook do Canal' do Z-PRO
Descoberta: existe um mecanismo de webhook por canal (fora do ChatFlow),
com opcao 'Mensagens recebidas' que dispara a cada mensagem -- possivel
caminho pra receber midia, ja que o ChatFlow nao oferece isso. Endpoint
separado (orcamento-exames-canal-zpro) so pra inspecionar o payload real
antes de integrar ao fluxo principal.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 10:10:15 -03:00
4 changed files with 543 additions and 48 deletions
+22
View File
@@ -0,0 +1,22 @@
# Segredos e configuração local
.env
.env.*
!.env.example
# Logs
*.log
logs/
# Arquivos temporários
*.tmp
*.temp
*.bak
*.swp
# Sistema operacional
.DS_Store
Thumbs.db
# Editores/IDE
.vscode/
.idea/
+53
View File
@@ -0,0 +1,53 @@
# Identificar Texto em Imagem — Orçamento Automático de Exames
Projeto baseado em um workflow [n8n](https://n8n.io/) que recebe, via WhatsApp
(canal Z-PRO), a foto/PDF/áudio de um pedido médico de exames, identifica os
exames solicitados e responde automaticamente ao cliente com um orçamento
formatado (itens + valores + total), usando um agente de IA.
## Arquivos principais
- `n8n/orcamento-exames-imagem.workflow.json` — export do workflow n8n com todo
o fluxo (webhook de entrada, tratamento de imagem/PDF/áudio, agente de IA,
envio da resposta via API do Z-PRO).
- `Z-PRO v4.x.x.x.postman_collection.json` — collection Postman com a API
externa do Z-PRO (criação de ticket, envio de mensagem, listagem de
contatos, entre outros endpoints).
- `docs/superpowers/specs/2026-08-20-orcamento-exames-imagem-design.md` —
spec/design do fluxo.
- `docs/superpowers/plans/2026-08-20-orcamento-exames-imagem.md` — plano de
implementação.
## Dependências externas
- **n8n** — plataforma de automação onde o workflow é executado.
- **Z-PRO** — canal/API de WhatsApp usada para receber mensagens do cliente e
enviar o orçamento de volta.
- **OpenAI** — modelo de IA usado para leitura da imagem/PDF do pedido médico,
transcrição de áudio (Whisper) e montagem do orçamento.
## Como importar o workflow no n8n
1. Na instância n8n de destino, use **Import from File** (ou **Import from
URL**) e selecione `n8n/orcamento-exames-imagem.workflow.json`.
2. Após a importação, o n8n vai pedir para associar cada nó que usa credencial
a uma credencial existente na sua instância (ver seção abaixo).
3. Revise o node de webhook e a URL de envio ao Z-PRO — ambos contêm
identificadores específicos da instância de origem (ver "Credenciais e
portabilidade").
## Credenciais e portabilidade
- **As credenciais NÃO são armazenadas neste repositório.** O JSON do
workflow referencia credenciais apenas por `id` e `name` (ex.:
`"openAiApi": { "id": "h16jFm10cTU15k6M", "name": "OpenAI - Orçamento de
Exames" }`), que são identificadores da instância n8n de origem — não são as
chaves/segredos em si.
- Após importar o workflow, é necessário **configurar/reassociar as
credenciais correspondentes** (OpenAI e API do Z-PRO/`httpHeaderAuth`) na
instância n8n de destino.
- O workflow também contém identificadores específicos da instância de
origem que não são segredos, mas reduzem a portabilidade direta: o `id` do
workflow, o `webhookId` do nó de entrada, e a URL de envio ao Z-PRO
(`https://apigchat.gtigrupo.com.br/...`), que inclui um identificador de
integração usado por essa instância.
@@ -281,15 +281,125 @@ 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).
### Descoberta 5: "Webhook do Canal" resolve a imagem (solução final)
O Z-PRO tem, nas configurações do canal, uma opção **"Webhook do Canal"**
independente do ChatFlow: `Habilitar Webhook do Canal` + `URL de destino` +
toggle `Mensagens recebidas` (dispara a cada mensagem recebida pelo canal,
"em paralelo ao webhook geral do tenant"). Payload real capturado:
```json
{
"method": "message",
"msg": {
"from": "5575...",
"id": "wamid....",
"timestamp": "...",
"type": "image",
"image": { "mime_type": "image/jpeg", "id": "...", "url": "https://lookaside.fbsbx.com/whatsapp_business/attachments/?..." }
},
"ticket": {
"protocol": "...", "id": 469774, "status": "pending",
"contact": { "name": "...", "number": "..." },
"whatsapp": { "bmToken": "EAA..." }
}
}
```
Pra mensagem de texto, `msg` vem como `{ "type": "text", "text": { "body": "..." } }`
em vez de `image`. O workflow foi reconstruído em cima desse webhook:
- Novo endpoint: `orcamento-exames-canal-zpro` (path completamente separado
do antigo `orcamento-exames-zpro`, que foi removido).
- Campos extraídos: `sessionId` (`ticket.protocol`), `telefone` (`msg.from`),
`nomeContato` (`ticket.contact.name`), `entrada` (`msg.text.body`),
`imagemUrl` (`msg.image.url`), `tipoMidia` (`msg.type`), `mediaToken`
(`ticket.whatsapp.bmToken`).
- A URL da imagem é do WhatsApp/Meta (`lookaside.fbsbx.com`) e exige
autenticação — o node "Baixar imagem" manda `Authorization: Bearer
{{ mediaToken }}` usando o `bmToken` do próprio payload.
- Como esse webhook é uma notificação assíncrona (não é capturado e
reenviado pelo Z-PRO como no mecanismo do ChatFlow), o envio da resposta
voltou a ser feito via API, reaproveitando o sub-workflow
`enviar_texto` (mesma lógica da primeira versão do desenho).
- **Importante:** esse webhook dispara em paralelo a qualquer coisa que o
ChatFlow também dispare para a mesma mensagem. Por isso as interações
"Webhook Avançado" e "Mensagem" dos passos `Menu Orçamento` / `Menu
Orçamento 2` no ChatFlow precisam ser removidas (ação do usuário, fora do
n8n) — senão o cliente recebe duas respostas pra cada mensagem.
- O prompt do `AI Agent` voltou a aceitar/pedir foto normalmente, já que a
imagem agora chega de verdade.
### Teste ponta-a-ponta validado
Com uma imagem real de teste, o fluxo completo funcionou: download
autenticado da mídia (44.9 kB, jpg) → IA de visão leu a imagem e
identificou 2 exames da tabela + 1 fora da tabela (avisando que precisa de
confirmação) → resposta enviada com sucesso pelo Z-PRO. Texto puro também
validado (cliente manda "oi" → IA pede pra digitar/mandar os exames).
### Descoberta 6: canal/API errados no envio (corrigido)
O envio estava reaproveitando o sub-workflow `enviar_texto`
(`Y4ndG7GuIUrmVRxV`), que pertence a **outro projeto** (bot de agendamento)
e usa um ApiID/canal diferente — por isso as respostas saíam por um número
errado (recebendo num número, respondendo por outro). O usuário criou um
ApiID e token dedicados a este projeto. Correção aplicada:
- Nova credencial `httpHeaderAuth` **"ZPRO API Token - Orçamento de
Exames"** (nome deliberadamente diferente de "ZPRO API Token - Canal QR -
MC AGENDAMENTO", pra não confundir entre projetos), header `Authorization:
Bearer <token>`.
- O envio deixou de chamar o sub-workflow compartilhado — agora é uma
chamada HTTP direta (`POST`) dentro deste próprio workflow, isolada,
usando a URL `https://apigchat.gtigrupo.com.br/v2/api/external/<ApiID
deste projeto>/` e a credencial acima.
- Isso mantém este projeto completamente independente do de agendamento —
nenhum node ou credencial é compartilhado entre os dois.
### Descoberta 7: filtro por fluxo/fila (o bot respondia a qualquer mensagem)
Como o "Webhook do Canal" dispara pra **toda mensagem do número** (não só
pra quem está no menu de Orçamento), o bot estava respondendo pra qualquer
coisa — cliente mandando foto do CPF, pedindo agendamento, etc. — sempre
pedindo exames. Isso não reflete o atendimento real: o cliente manda
mensagem, recebe o menu de filas, escolhe "Orçamento", e só a partir daí a
automação deve agir.
Correção: o payload do webhook traz `ticket.stepChatFlow`, identificando em
qual passo do ChatFlow o ticket está parado. Os passos `Menu Orçamento` e
`Menu Orçamento 2` (os únicos dois pontos do ChatFlow que levam a essa
automação) têm ids fixos: `node-1787312722889` e `node-1787312980617`.
Adicionado o node **"Está no fluxo de Orçamento?"**, logo após extrair os
campos: só segue adiante (pro resto do pipeline) se `stepChatFlow` for um
desses dois; qualquer outra coisa cai num `NoOp` e é ignorada — sem
resposta nenhuma, deixando pra fila/fluxo correspondente cuidar.
**Risco conhecido:** esse filtro depende dos ids de step atuais do
ChatFlow. Se o `Menu Orçamento`/`Menu Orçamento 2` forem recriados (não só
editados) no editor do Z-PRO, os ids mudam e o filtro precisa ser
atualizado no node `Está no fluxo de Orçamento?`.
### Descoberta 8: pipeline de áudio confirmado ponta-a-ponta
Adicionado suporte a mensagens de áudio: `Baixar áudio` (HTTP Request com o
`bmToken`) → `Transcrever áudio` (OpenAI node, resource=audio,
operation=transcribe, Whisper `whisper-1`) → `Preparar texto do áudio` (Set
node que popula `entrada` com a transcrição) → segue pro `AI Agent -
Orçamento` normalmente.
Confirmado via execução real **#476** (2026-08-21 17:31): cliente mandou um
áudio de 5s, o Whisper transcreveu corretamente *"Eu não tenho foto não,
quero saber o valor da Hemograma."*, e o AI Agent respondeu certo *"Olá! O
Hemograma custa R$ 35,00."*. Pipeline de texto, imagem, PDF e áudio todos
validados com execuções reais.
### 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.
- Remover as interações antigas "Webhook Avançado"/"Mensagem" dos passos
`Menu Orçamento`/`Menu Orçamento 2` no ChatFlow (apontavam pro endpoint
antigo, que não existe mais no n8n — não causam mais duplicidade, mas
ficam como chamada morta até serem removidas).
## Fora de escopo (por enquanto)
+351 -41
View File
@@ -6,19 +6,18 @@
{
"parameters": {
"httpMethod": "POST",
"path": "orcamento-exames-zpro",
"responseMode": "responseNode",
"path": "orcamento-exames-canal-zpro",
"options": {}
},
"id": "webhook-orcamento",
"name": "Webhook - Foto Exame",
"id": "webhook-canal-zpro",
"name": "Webhook - Canal Z-PRO",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
-608,
0
320
],
"webhookId": "5ea5f89e-7351-4d45-a881-0055009423a0",
"webhookId": "a00a2a36-f7ec-4e8e-a98f-93d3f93dfe45",
"onError": "continueRegularOutput"
},
{
@@ -29,37 +28,55 @@
"id": "campo-sessionid",
"name": "sessionId",
"type": "string",
"value": "={{ $json.body.protocol }}"
"value": "={{ $json.body.ticket.protocol }}"
},
{
"id": "campo-telefone",
"name": "telefone",
"type": "string",
"value": "={{ $json.body.number }}"
"value": "={{ $json.body.msg.from }}"
},
{
"id": "campo-nomecontato",
"name": "nomeContato",
"type": "string",
"value": "={{ $json.body.contactName }}"
"value": "={{ $json.body.ticket.contact.name }}"
},
{
"id": "campo-entrada",
"name": "entrada",
"type": "string",
"value": "={{ String($json.body.entrada || $json.body.text || '').trim() }}"
"value": "={{ ($json.body.msg.text && $json.body.msg.text.body) || '' }}"
},
{
"id": "campo-imagemurl",
"name": "imagemUrl",
"type": "string",
"value": "={{ $json.body.mediaUrl || $json.body.fileUrl || $json.body.file || '' }}"
"value": "={{ ($json.body.msg.image && $json.body.msg.image.url) || ($json.body.msg.document && $json.body.msg.document.url) || '' }}"
},
{
"id": "campo-audiourl",
"name": "audioUrl",
"type": "string",
"value": "={{ ($json.body.msg.audio && $json.body.msg.audio.url) || '' }}"
},
{
"id": "campo-tipomidia",
"name": "tipoMidia",
"type": "string",
"value": "={{ $json.body.mediaType || $json.body.type || '' }}"
"value": "={{ $json.body.msg.type || '' }}"
},
{
"id": "campo-mediatoken",
"name": "mediaToken",
"type": "string",
"value": "={{ ($json.body.ticket.whatsapp && $json.body.ticket.whatsapp.bmToken) || '' }}"
},
{
"id": "campo-stepchatflow",
"name": "stepChatFlow",
"type": "string",
"value": "={{ $json.body.ticket.stepChatFlow || '' }}"
}
]
},
@@ -70,10 +87,178 @@
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-368,
0
-544,
-32
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"combinator": "or",
"conditions": [
{
"id": "cond-step-menu-orcamento-1",
"leftValue": "={{ $json.stepChatFlow }}",
"rightValue": "node-1787312722889",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"id": "cond-step-menu-orcamento-2",
"leftValue": "={{ $json.stepChatFlow }}",
"rightValue": "node-1787312980617",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
},
"options": {}
},
"id": "if-esta-em-orcamento",
"name": "Está no fluxo de Orçamento?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-368,
-48
]
},
{
"parameters": {},
"id": "noop-fora-orcamento",
"name": "Não é fluxo de Orçamento - ignora",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
-240,
208
]
},
{
"id": "if-e-audio",
"name": "É mensagem de áudio?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-120,
-160
],
"parameters": {
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-tem-audio",
"leftValue": "={{ $json.audioUrl }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
]
},
"options": {}
}
},
{
"id": "baixar-audio",
"name": "Baixar áudio",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
80,
-400
],
"parameters": {
"url": "={{ $json.audioUrl }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $json.mediaToken }}"
}
]
},
"options": {
"response": {
"response": {
"responseFormat": "file"
}
},
"timeout": 20000
}
},
"onError": "continueErrorOutput"
},
{
"id": "transcrever-audio",
"name": "Transcrever áudio",
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 2.3,
"position": [
280,
-400
],
"parameters": {
"resource": "audio",
"operation": "transcribe",
"binaryPropertyName": "data",
"modelId": {
"mode": "id",
"value": "whisper-1"
},
"options": {}
},
"credentials": {
"openAiApi": {
"id": "h16jFm10cTU15k6M",
"name": "OpenAI - Orçamento de Exames"
}
},
"onError": "continueErrorOutput"
},
{
"id": "preparar-texto-audio",
"name": "Preparar texto do áudio",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
480,
-400
],
"parameters": {
"assignments": {
"assignments": [
{
"id": "audio-entrada",
"name": "entrada",
"type": "string",
"value": "={{ $json.text }}"
}
]
},
"options": {}
}
},
{
"parameters": {
"conditions": {
@@ -105,12 +290,21 @@
"typeVersion": 2.3,
"position": [
-128,
0
-48
]
},
{
"parameters": {
"url": "={{ $json.imagemUrl }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $json.mediaToken }}"
}
]
},
"options": {
"response": {
"response": {
@@ -158,10 +352,11 @@
{
"parameters": {
"promptType": "define",
"text": "={{ $('Extrair campos do webhook').item.json.entrada || 'O cliente mandou algo que não chegou como texto (provavelmente uma foto ou áudio, que ainda não conseguimos processar). Peça pra ele digitar os nomes dos exames.' }}",
"text": "={{ $json.entrada || 'O cliente enviou uma imagem. Veja a imagem anexada, que é o pedido médico dele.' }}",
"options": {
"systemMessage": "Você é o assistente de orçamentos de exames de uma clínica, atendendo automaticamente pelo WhatsApp, numa conversa com várias mensagens (não só uma).\n\nIMPORTANTE (limitação técnica atual): você NUNCA recebe fotos/imagens nesta conversa, mesmo que o cliente mande uma — só texto chega até você. Por isso, NUNCA peça pro cliente mandar foto ou imagem do pedido. Se o cliente mandar uma foto (você vai perceber isso porque a mensagem dele chega vazia, sem texto), peça educadamente pra ele DIGITAR os nomes dos exames que estão no pedido médico, sem mencionar foto.\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- Triglicérideos — 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\nCOMO CONVERSAR:\n1. O cliente pode digitar os nomes dos exames de uma vez ou aos poucos, em mensagens separadas — use também o histórico da conversa (mensagens anteriores) pra juntar as informações, não só a mensagem atual.\n2. Se o cliente ainda não digitou, nem agora nem antes nesta conversa, nenhum nome de exame reconhecível, NÃO invente orçamento — cumprimente rapidamente e peça pra ele digitar os nomes dos exames conforme o pedido médico.\n3. Assim que tiver pelo menos um exame identificável (considerando o texto atual E o histórico da conversa), identifique cada exame pedido.\n4. 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.\n5. Para exames identificados que NÃO correspondem a nenhum item da tabela, ou que você não conseguiu entender com confiança, liste-os separadamente avisando que um atendente vai confirmar o valor.\n6. Nunca deixe de orçar por causa de um item não reconhecido — sempre orçe o que for possível reconhecer.\n7. Calcule e mostre o valor total somando apenas os itens com preço confirmado na tabela.\n8. Responda sempre 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.\n9. Sua resposta deve conter SOMENTE o texto a ser enviado ao cliente nesta mensagem — nada de comentários extras, explicações do seu raciocínio, ou texto fora da conversa com o cliente.",
"passthroughBinaryImages": true
"systemMessage": "Você é o assistente de orçamentos de exames de uma clínica, atendendo automaticamente pelo WhatsApp, numa conversa com várias mensagens (não só uma).\n\nTABELA DE EXAMES E VALORES:\n- Hemograma — R$ 35,00\n- Sódio e Potássio — R$ 35,00\n- Ureia e Creatinina — R$ 40,00\n- 25OH / Vitamina D — R$ 80,00\n- Glicemia de Jejum — R$ 20,00\n- Teste Oral de Tolerância à Glicose — R$ 70,00\n- Hemoglobina Glicada — R$ 40,00\n- Colesterol Total e Frações — R$ 50,00\n- Triglicerídeos — R$ 20,00\n- TSH e T4 Livre — R$ 60,00\n- CK — R$ 30,00\n- TGO/TGP — R$ 30,00\n- Gama GT — R$ 20,00\n- Bilirrubina Total e Frações — R$ 30,00\n- Amilase — R$ 30,00\n- Lipase — R$ 35,00\n- Fosfatase Alcalina — R$ 20,00\n- Proteínas Totais e Frações — R$ 25,00\n- Ferro, Ferritina e Transferrina — R$ 100,00\n- Cálcio Total — R$ 20,00\n- Cálcio Ionizado — R$ 30,00\n- Selênio — R$ 100,00\n- Magnésio — R$ 20,00\n- Fósforo — R$ 20,00\n- Ácido Úrico — R$ 20,00\n- VHS — R$ 20,00\n- PCR — R$ 35,00\n- APTT / TTPa — R$ 25,00\n- VDRL — R$ 25,00\n- Dosagem de Vitamina B12 — R$ 60,00\n- Dosagem de Ácido Fólico — R$ 55,00\n- Sorologia para Hepatites A, B e C — R$ 180,00\n- Sorologia para outras doenças (fora hepatites A, B e C) — sob consulta, valor confirmado por um atendente\n- PSA Total e Livre — R$ 60,00\n- Alfa-Fetoproteína + CEA + CA 19-9 — R$ 180,00\n- CA 125 + CA 15-3 — R$ 130,00\n- Eletroforese de Proteínas — R$ 60,00\n- Eletroforese de Hemoglobina — R$ 70,00\n- Tipo Sanguíneo — R$ 30,00\n- Urina I — R$ 25,00\n- Urocultura — R$ 50,00\n- Urina de 24 horas (Creatinina, Albumina e Cálcio) — R$ 90,00\n- Protoparasitológico de Fezes – 1 amostra — R$ 30,00\n- Sangue Oculto nas Fezes — R$ 30,00\n\nCOMO LER O PEDIDO MÉDICO NA IMAGEM:\n- Muitos pedidos médicos vêm como um formulário com caixinhas de seleção (checkbox) ao lado do nome de cada exame, tipo um formulário de \"Solicitação de Exames\" com lista pré-impressa.\n- Nesses formulários, ORCE APENAS os exames cuja caixinha está MARCADA (com X, risco, preenchida, ou qualquer marcação visível). NUNCA orçe um exame que está listado no formulário mas cuja caixinha está vazia/desmarcada — ele não foi solicitado, mesmo que apareça impresso na folha.\n- Se não conseguir ver com clareza se uma caixinha específica está marcada ou não, NÃO inclua esse exame no orçamento — trate como não identificado com confiança (ver regra 5 abaixo).\n- Se o pedido não for desse formato de formulário (por exemplo, uma receita escrita à mão listando os exames diretamente, sem caixinhas), considere todos os exames escritos como solicitados normalmente.\n\nCOMO CONVERSAR:\n1. O cliente pode mandar uma foto do pedido médico, um PDF do pedido médico, digitar os nomes dos exames, ou combinar essas formas em mensagens separadas, em qualquer ordem — use também o histórico da conversa (mensagens anteriores) pra juntar as informações, não só a mensagem atual. Uma imagem ou PDF anexado a esta mensagem (quando houver) é o pedido médico do cliente — leia o conteúdo do PDF (todas as páginas relevantes) do mesmo jeito que leria uma foto.\n2. Se o cliente ainda não mandou, nem agora nem antes nesta conversa, nenhuma foto de pedido nem nomes de exames, NÃO invente orçamento — cumprimente rapidamente e peça pra ele mandar a foto do pedido médico ou digitar os nomes dos exames.\n3. Assim que tiver pelo menos um exame identificável (considerando a imagem atual, o texto atual E o histórico da conversa), identifique cada exame pedido.\n4. 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 com valor fixo, inclua na resposta o nome do exame e o valor.\n5. Para exames que caírem em itens \"sob consulta\" (como sorologias fora de Hepatites A, B e C), ou que NÃO correspondem a nenhum item da tabela, ou que você não conseguiu ler/entender com confiança, liste-os separadamente avisando que um atendente vai confirmar o valor.\n6. Nunca deixe de orçar por causa de um item não reconhecido — sempre orçe o que for possível reconhecer.\n7. Calcule e mostre o valor total somando apenas os itens com preço fixo confirmado na tabela (nunca inclua itens \"sob consulta\" no total).\n8. Responda sempre 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.\n9. Sua resposta deve conter SOMENTE o texto a ser enviado ao cliente nesta mensagem — nada de comentários extras, explicações do seu raciocínio, ou texto fora da conversa com o cliente.",
"passthroughBinaryImages": true,
"passthroughBinaryPdfs": true
}
},
"id": "ai-agent-orcamento",
@@ -179,10 +374,28 @@
"assignments": {
"assignments": [
{
"id": "resp-output",
"name": "output",
"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
}
]
},
@@ -214,22 +427,36 @@
},
{
"parameters": {
"respondWith": "text",
"responseBody": "={{ $json.output }}",
"options": {}
"method": "POST",
"url": "https://apigchat.gtigrupo.com.br/v2/api/external/fc30a773-0089-43b9-8b05-a5d92c48eb9b/",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ body: $json.body, number: $json.number, externalKey: $json.externalKey, isClosed: $json.isClosed }) }}",
"options": {
"timeout": 15000
}
},
"id": "responder-webhook",
"name": "Responder Webhook (texto)",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"id": "enviar-orcamento-zpro-http",
"name": "Enviar orçamento (Z-PRO)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
912,
-128
]
],
"credentials": {
"httpHeaderAuth": {
"id": "6IxSBH9y4fXsIoyA",
"name": "ZPRO API Token - Orçamento de Exames"
}
},
"onError": "continueErrorOutput"
}
],
"connections": {
"Webhook - Foto Exame": {
"Webhook - Canal Z-PRO": {
"main": [
[
{
@@ -240,6 +467,100 @@
]
]
},
"Extrair campos do webhook": {
"main": [
[
{
"node": "Está no fluxo de Orçamento?",
"type": "main",
"index": 0
}
]
]
},
"Está no fluxo de Orçamento?": {
"main": [
[
{
"node": "É mensagem de áudio?",
"type": "main",
"index": 0
}
],
[
{
"node": "Não é fluxo de Orçamento - ignora",
"type": "main",
"index": 0
}
]
]
},
"É mensagem de áudio?": {
"main": [
[
{
"node": "Baixar áudio",
"type": "main",
"index": 0
}
],
[
{
"node": "É mensagem de imagem?",
"type": "main",
"index": 0
}
]
]
},
"Baixar áudio": {
"main": [
[
{
"node": "Transcrever áudio",
"type": "main",
"index": 0
}
],
[
{
"node": "Montar corpo da resposta",
"type": "main",
"index": 0
}
]
]
},
"Transcrever áudio": {
"main": [
[
{
"node": "Preparar texto do áudio",
"type": "main",
"index": 0
}
],
[
{
"node": "Montar corpo da resposta",
"type": "main",
"index": 0
}
]
]
},
"Preparar texto do áudio": {
"main": [
[
{
"node": "AI Agent - Orçamento",
"type": "main",
"index": 0
}
]
]
},
"É mensagem de imagem?": {
"main": [
[
@@ -320,18 +641,7 @@
"main": [
[
{
"node": "Responder Webhook (texto)",
"type": "main",
"index": 0
}
]
]
},
"Extrair campos do webhook": {
"main": [
[
{
"node": "É mensagem de imagem?",
"node": "Enviar orçamento (Z-PRO)",
"type": "main",
"index": 0
}