Compare commits
10
Commits
c1aa7b7978
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7ebfd41ea | ||
|
|
be145fad95 | ||
|
|
a367637606 | ||
|
|
ed1d154b95 | ||
|
|
c37ea1189e | ||
|
|
294a8923f3 | ||
|
|
091878d484 | ||
|
|
2322940264 | ||
|
|
deeefe2b40 | ||
|
|
fd77afad61 |
+22
@@ -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/
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user