This workflow follows the Agent → Googlegemini recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Brokeria-v15",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "waha",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
-128,
4832
],
"id": "e13785cc-46d8-49db-ac71-e33de0b3441d",
"name": "Webhook"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "78415b90-064b-46f3-8a36-8489bd14f43a",
"name": "event",
"value": "={{ $json.body.event }}",
"type": "string"
},
{
"id": "6d70d6a1-58d7-416a-a388-7b2aeabd4b85",
"name": "session",
"value": "={{ $json.body.session }}",
"type": "string"
},
{
"id": "ff0bd58d-efa1-4e49-9c9c-602aed25a290",
"name": "payload_id",
"value": "={{ $json.body.payload.id }}",
"type": "string"
},
{
"id": "0c990094-4206-417a-be86-008c8156acf8",
"name": "from",
"value": "={{ $json.body.payload.from }}",
"type": "string"
},
{
"id": "d33eb4cc-078e-40b7-9c9c-8fbba1e2aa76",
"name": "to",
"value": "={{ $json.body.payload.to }}",
"type": "string"
},
{
"id": "457f7c13-bf6f-4171-80bc-75620cfda604",
"name": "source",
"value": "={{ $json.body.payload.source }}",
"type": "string"
},
{
"id": "8ce79aa1-5408-48c9-8226-9ac6f875df12",
"name": "body",
"value": "={{ $json.body.payload.body }}",
"type": "string"
},
{
"id": "3fc1bd24-4960-40a3-9a52-5e1fa4365813",
"name": "hasMedia",
"value": "={{ $json.body.payload.hasMedia }}",
"type": "boolean"
},
{
"id": "7437da46-cfce-4e5b-909a-5daf06dfd2ad",
"name": "type",
"value": "={{ $json.body.payload._data.type }}",
"type": "string"
},
{
"id": "f8f215f3-ba00-485e-be98-354baa181f28",
"name": "name",
"value": "={{ $json.body.payload._data.notifyName }}",
"type": "string"
},
{
"id": "6508b08d-4b94-4352-8a94-c55ffd11cc91",
"name": "fromMe",
"value": "={{ $json.body.payload.fromMe }}",
"type": "boolean"
},
{
"id": "3ada40ac-45a0-440a-9e4b-44417caadf9c",
"name": "to",
"value": "={{ $json.body.payload.to }}",
"type": "string"
},
{
"id": "0096fa37-d92b-43ca-8c98-269cc5e1899f",
"name": "id",
"value": "={{ $json.body.id }}",
"type": "string"
},
{
"id": "30a509ba-e3aa-4e6a-b3ce-4f7d1fcbf46f",
"name": "session_id",
"value": "={{ $json.body.session + ' ' + $json.body.payload.from + ' chats' }}",
"type": "string"
},
{
"id": "72d18f99-83a5-4bfb-8621-6af03d231409",
"name": "user_text",
"value": "={{ $json.body.payload.body }}",
"type": "string"
},
{
"id": "d1a86623-555b-4571-b71d-b7dc69e0b763",
"name": "media_url",
"value": "={{ $json.body.payload.media.url || $json.body.payload.url || \"\" }}",
"type": "string"
}
]
},
"includeOtherFields": true,
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
48,
4832
],
"id": "198ce6ce-92d4-4de0-9572-a8ffe503279f",
"name": "Dados"
},
{
"parameters": {
"resource": "Chatting",
"operation": "Send Seen",
"session": "default",
"chatId": "={{ $('DefinirContextoAI').first().json.contexto.telefone + '@c.us' }}",
"messageId": "",
"requestOptions": {}
},
"type": "n8n-nodes-waha.WAHA",
"typeVersion": 202411,
"position": [
2512,
5872
],
"id": "14425c66-4428-4564-8869-9ae910b16e66",
"name": "Send Seen",
"alwaysOutputData": true,
"credentials": {
"wahaApi": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"resource": "Chatting",
"operation": "Start Typing",
"session": "={{ $('Padronizar Output').first().json.target_session || 'default' }}",
"chatId": "={{ $('Padronizar Output').first().json.target_chat_id }}",
"requestOptions": {}
},
"type": "n8n-nodes-waha.WAHA",
"typeVersion": 202411,
"position": [
2656,
5872
],
"id": "69f232d6-706d-43d7-bf74-12befe7811c8",
"name": "Start Typing",
"credentials": {
"wahaApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
2800,
5872
],
"id": "993fa305-86a0-42d3-a595-e5847d31e844",
"name": "Wait"
},
{
"parameters": {
"resource": "Chatting",
"operation": "Send Text",
"session": "={{ $('Padronizar Output').first().json.target_session || 'default' }}",
"chatId": "={{ $('Padronizar Output').first().json.target_chat_id }}",
"reply_to": "=",
"text": "={{ $json.text }}",
"requestOptions": {}
},
"type": "n8n-nodes-waha.WAHA",
"typeVersion": 202411,
"position": [
3104,
5872
],
"id": "46bca101-83f0-4595-bb2c-d35a0047c3fa",
"name": "Send a text message",
"credentials": {
"wahaApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"url": "={{ ('http://waha:3000/api/' + $json.session + '/lids/' + encodeURIComponent(($json.from || '').trim())).trim() }}\n\n",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "accept",
"value": "application/json"
},
{
"name": "X-Api-Key",
"value": "GR15VC0m5ueLygSMQPM6In4A8qfnrLT31ECnIEmZvnfyovsGxjUSfaFcMi1YQ3E1VvR9WjdJijFKGllhKAhkQ6yNYzYstzwtlQODhb06lknx3j8JlfxA7UCbzKCe61nbb"
}
]
},
"options": {}
},
"id": "70585f50-5dcd-4584-bd1e-6bbf615f50e3",
"name": "Consultar WAHA Direto",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
208,
4832
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "custom-field",
"name": "PhoneNumber",
"value": "={{ $json.pn ? $json.pn.split('@')[0] : undefined }}",
"type": "string"
},
{
"id": "835b86bc-37d1-4742-a421-3247982275a9",
"name": "LidNumber",
"value": "={{ $json.lid ? $json.lid.split('@')[0] : undefined }}",
"type": "string"
},
{
"id": "92fd4ba6-2d13-4ff9-b215-8895fd828fa2",
"name": "isGroup",
"value": "={{ ($json.pn || '').endsWith('@g.us') }}",
"type": "boolean"
}
]
},
"includeOtherFields": true,
"options": {}
},
"id": "ff32b552-e64c-444b-87a1-830c8323327a",
"name": "Limpar Telefone",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
368,
4832
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "/* BANCO: brokeria_db | SCHEMA: public */\nSELECT \n c.id_cliente, \n c.nome_completo, \n c.cpf, \n c.email, \n c.celular,\n c.telefone,\n (\n SELECT json_agg(a)\n FROM public.apolices_brokeria a\n WHERE a.id_cliente::text = c.id_cliente::text -- \u00f0\u0178\u2019\u00a1 Blindagem: for\u00c3\u00a7a compara\u00c3\u00a7\u00c3\u00a3o entre textos\n ) as apolices_detalhadas\nFROM public.clientes_brokeria c\nWHERE c.celular = '{{ $node[\"Limpar Telefone\"].json.PhoneNumber }}'\n OR c.telefone = '{{ $node[\"Limpar Telefone\"].json.PhoneNumber }}'\nLIMIT 1;",
"options": {
"queryReplacement": "={{ $('Limpar Telefone').item.json.PhoneNumber }}"
}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
1200,
5392
],
"id": "5ebda921-841a-4a67-a6a3-88e275fb4c29",
"name": "Busca_Cliente",
"alwaysOutputData": true,
"executeOnce": true,
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));\nawait wait(Math.floor(Math.random() * 400)); \n\nlet mensagens = [];\nconst dadosEntrada = $input.first().json; \nconst msgId = dadosEntrada.payload_id; \nconst execId = $execution.id; // \u00e2\u0153\u2026 Captura ID exclusivo da execu\u00c3\u00a7\u00c3\u00a3o\n\ntry {\n const redisItem = $(\"Recuperar_Buffer\").first();\n if (redisItem && redisItem.json) {\n const conteudoRedis = redisItem.json.Redis_Check_Buffer || redisItem.json.value;\n if (conteudoRedis && typeof conteudoRedis === 'string') {\n mensagens = JSON.parse(conteudoRedis);\n }\n }\n} catch (e) {\n mensagens = [];\n}\n\nif (!mensagens.some(m => m.id === msgId)) {\n mensagens.push({\n id: msgId,\n exec_id: execId, // \u00e2\u0153\u2026 Salva quem foi o \u00c3\u00baltimo a escrever\n texto: dadosEntrada.user_text || \"MIDIA_INPUT\", \n tipo: dadosEntrada.mediaType || \"chat\"\n });\n}\n\nreturn {\n ...dadosEntrada, \n texto_combinado: mensagens.map(m => m.texto).join(' \\n '),\n lista_raw: JSON.stringify(mensagens),\n quantidade: mensagens.length\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
16,
5408
],
"id": "473036e0-8c18-4343-a118-7994c613a59b",
"name": "Agrupar_Texto"
},
{
"parameters": {
"operation": "set",
"key": "=buffer:{{ $node[\"CheckOrigem\"].json.tecnico.session_id }}",
"value": "={{ $json.lista_raw }}",
"expire": true,
"ttl": 10
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
160,
5408
],
"id": "a1a81ba9-fc73-4e3f-9483-1532cd0ee6fa",
"name": "Atualizar_Buffer",
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"amount": 3
},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
320,
5408
],
"id": "535cad36-ad8e-4591-8a93-dafb7464386f",
"name": "Espera_3_Segundos"
},
{
"parameters": {
"operation": "get",
"propertyName": "Redis_Check_Final",
"key": "=buffer:{{ $node[\"CheckOrigem\"].json.tecnico.session_id }}",
"options": {}
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
480,
5408
],
"id": "8eda9515-bc0b-4bc3-9e87-d0b5020efe9c",
"name": "Check_Pos_Espera",
"alwaysOutputData": true,
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 3
},
"conditions": [
{
"id": "768c0bd8-dd6a-4825-94af-95a9c9d03ad5",
"leftValue": "={{ $node[\"Agrupar_Texto\"].json.payload_id + '_' + $execution.id }}",
"rightValue": "={{ JSON.parse($json.Redis_Check_Final || \"[]\").slice(-1)[0].id + '_' + JSON.parse($json.Redis_Check_Final || \"[]\").slice(-1)[0].exec_id }}",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
656,
5408
],
"id": "cccb5f15-a217-43bc-81dc-c4969ce41dea",
"name": "Filtro_Ultima_Execucao"
},
{
"parameters": {
"operation": "get",
"propertyName": "Redis_Check_Buffer",
"key": "=buffer:{{ $('CheckOrigem').first().json.tecnico.session_id }}",
"options": {}
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
-144,
5408
],
"id": "8162f2eb-9da6-48df-85f2-eac5bdd87af1",
"name": "Recuperar_Buffer",
"alwaysOutputData": true,
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 3
},
"conditions": [
{
"leftValue": "{{ $json.tecnico.isGroup }}",
"rightValue": "false",
"operator": {
"type": "string",
"operation": "equals"
},
"id": "9d8b45b5-c151-4e99-bdaf-6306764ea594"
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 3
},
"conditions": [
{
"id": "27953cd9-b238-4be3-9db8-7f15b5635ba4",
"leftValue": "{{ $json.tecnico.source }}",
"rightValue": "api",
"operator": {
"type": "string",
"operation": "notContains"
}
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 3
},
"conditions": [
{
"id": "ffff129c-9a55-4c12-a18f-e84e087b7a88",
"leftValue": "{{ $json.tecnico.fromMe }}",
"rightValue": "false",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
}
}
]
},
"looseTypeValidation": true,
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.4,
"position": [
1024,
5376
],
"id": "3abe8575-280d-44f8-81d7-7043ce1bbf19",
"name": "SwitchOrigem"
},
{
"parameters": {
"jsCode": "// ==========================================================\n// 1. RECUPERA\u00c3\u2021\u00c3\u0192O DO N\u00c3\u0161MERO REAL (Do n\u00c3\u00b3 Consultar WAHA Direto)\n// ==========================================================\nlet phoneReal = \"\";\nlet nomeReal = \"Cliente\";\n\ntry {\n // Pega o output da consulta que voc\u00c3\u00aa fez na API\n const dadosAPI = $('Consultar WAHA Direto').first().json;\n \n // O WAHA retorna o n\u00c3\u00bamero real em 'pn', 'id.user' ou 'id' dependendo da vers\u00c3\u00a3o\n const rawPhone = dadosAPI.pn || dadosAPI.id || \"\";\n phoneReal = typeof rawPhone === 'string' ? rawPhone.split('@')[0].replace(/\\D/g, '') : \"\";\n \n // PRIORIDADE DE CAPTURA DO NOME (ordem de prefer\u00c3\u00aancia)\n nomeReal = dadosAPI.pushname || dadosAPI.name || dadosAPI.notifyName || dadosAPI.verifiedName || \"Cliente\";\n} catch (error) {\n phoneReal = $json.body.payload.from.split('@')[0];\n}\n\n// FALLBACK: Se ainda n\u00c3\u00a3o pegou o nome, tenta do payload direto\nif (nomeReal === \"Cliente\") {\n try {\n const payload = $json.body.payload || {};\n const payloadData = payload._data || {};\n nomeReal = payloadData.notifyName || payload.notifyName || payload.pushname || \"Cliente\";\n } catch(e) {}\n}\n\n// ==========================================================\n// 2. RECUPERA\u00c3\u2021\u00c3\u0192O DOS DADOS BRUTOS (Do Webhook/Input)\n// ==========================================================\nconst inputData = $json; \nconst body = inputData.body || inputData;\nconst payload = body.payload || body || {};\n\nconst chatIdTecnico = payload.from || body.from;\nconst isGroup = String(chatIdTecnico).includes('@g.us');\n\nlet sessionId = body.session || 'default';\nif (!sessionId || sessionId === phoneReal) sessionId = 'default';\n\nlet userText = payload.body || \"\";\ntry {\n const buffer = $('Agrupar_Texto').first().json;\n if (buffer && buffer.texto_combinado) {\n userText = buffer.texto_combinado;\n }\n} catch(e) {}\n\n// ==========================================================\n// 3. MONTAGEM DO JSON FINAL\n// ==========================================================\nreturn {\n json: {\n \"tecnico\": {\n \"session_id\": sessionId,\n \"chat_id\": chatIdTecnico,\n \"isGroup\": isGroup,\n \"source\": body.source || \"api\",\n \"fromMe\": payload.fromMe || false\n },\n \"cliente\": {\n \"phoneNumber\": phoneReal,\n \"nome_whatsapp\": nomeReal // \u00e2\u0153\u2026 AGORA COM CAPTURA ROBUSTA\n },\n \"input_bruto\": {\n \"user_text\": userText,\n \"mediaType\": payload.type || \"chat\",\n \"payload_id\": payload.id\n }\n }\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-320,
5408
],
"id": "e0d7b1ff-6001-4844-b8a5-324e757cbc05",
"name": "CheckOrigem"
},
{
"parameters": {
"operation": "get",
"propertyName": "sessaoExistente",
"key": "=status_2fa:{{ $('Limpar Telefone').first().json.PhoneNumber }}",
"options": {
"dotNotation": true
}
},
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
1376,
5392
],
"id": "49aa9477-d4ed-460d-8576-594140d1f4ba",
"name": "Buscar_Sessao_Existente",
"credentials": {
"redis": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "// =========================================================\n// 1. RECUPERA\u00c3\u2021\u00c3\u0192O DE DADOS (BANCO + WHATSAPP)\n// =========================================================\nlet cliente = {};\ntry { \n const busca = $(\"Busca_Cliente\").first();\n if (busca && busca.json && busca.json.id_cliente) {\n cliente = busca.json;\n }\n} catch(e) {}\n\nlet dadosWhatsApp = {};\ntry {\n dadosWhatsApp = $(\"CheckOrigem\").first().json.cliente || {};\n} catch(e) {}\n\nconst phoneNumber = dadosWhatsApp.phoneNumber || \"\";\nconst nomeWhatsApp = dadosWhatsApp.nome_whatsapp || \"Cliente\";\nconst isClienteCadastrado = !!(cliente && cliente.id_cliente);\n\nlet consolidado = {};\ntry { consolidado = $(\"ConsolidarMensagem\").first().json || {}; } catch(e) {}\n\nlet perguntaAgente = consolidado.mensagem_final;\n\n// BLINDAGEM MASTER\nif (consolidado.teve_erro_ia) {\n const msgRecomendada = (consolidado.tipo_entrada === \"image\") \n ? \"Houve um erro t\u00c3\u00a9cnico ao processar a imagem do cliente. Pe\u00c3\u00a7a educadamente para ele descrever o que deseja ou reenviar.\"\n : \"Houve um erro t\u00c3\u00a9cnico ao transcrever o \u00c3\u00a1udio do cliente. Pe\u00c3\u00a7a para ele digitar ou enviar o \u00c3\u00a1udio novamente.\";\n perguntaAgente = `[SISTEMA]: ${msgRecomendada}`;\n} \nelse if (!perguntaAgente) {\n perguntaAgente = (consolidado.tipo_entrada === \"chat\") ? \"Ol\u00c3\u00a1\" : \"...\";\n}\n\nlet isValid = false;\ntry {\n const nodeRedis = $(\"Buscar_Sessao_Existente\").first();\n if (nodeRedis && nodeRedis.json && nodeRedis.json.sessaoExistente === \"VALIDADO\") {\n isValid = true;\n }\n} catch (e) { isValid = false; }\n\n// =========================================================\n// 2. PROCESSAMENTO DE AP\u00c3\u201cLICES (V13 INTEGRAL)\n// =========================================================\nlet apolicesRaw = (cliente.apolices_detalhadas || []).filter(ap => \n ap && ap.status_apolice && String(ap.status_apolice).toUpperCase() === 'ATIVA'\n);\n\nconst apolicesContexto = apolicesRaw.map(ap => ({\n seguradora: ap.seguradora,\n tipo: ap.ramo,\n status: ap.status_apolice,\n apolice: isValid ? ap.numero_apolice : \"(Bloqueado)\",\n vencimento: isValid ? ap.vigencia_fim : \"(Bloqueado)\",\n placa: (ap.placa && isValid) ? ap.placa : (ap.placa ? \"***-****\" : \"N/A\"),\n endereco: isValid ? (ap.endereco_apolice || \"N\u00c3\u00a3o informado\") : \"Endere\u00c3\u00a7o Oculto\",\n cidade: isValid ? (ap.cidade_apolice || \"N\u00c3\u00a3o informado\") : \"Mascarado\",\n uf: isValid ? (ap.uf_apolice || \"\") : \"\"\n}));\n\n// =========================================================\n// 3. CLASSIFICA\u00c3\u2021\u00c3\u0192O DE ROTA\n// =========================================================\nconst securityKeywords = /^(3|5|6|seguro|contratado|boleto|financeiro|sinistro|segunda|2a)|apolice|ap[o\u00c3\u00b3]lice|meus dados|minha ap/i;\nconst rota_num = securityKeywords.test(perguntaAgente || \"\") ? 1 : 0;\n\n// =========================================================\n// 4. RETORNO\n// =========================================================\nreturn [{\n json: {\n pergunta_cliente: perguntaAgente,\n rota_num: rota_num,\n contexto: {\n nome: cliente.nome_completo || nomeWhatsApp,\n cpf_real: cliente.cpf || \"\",\n cpf_exibicao: (cliente.cpf) ? `***.***.***-${cliente.cpf.replace(/\\D/g, \"\").slice(-2)}` : \"N\u00c3\u00a3o informado\",\n telefone: cliente.celular || phoneNumber,\n email_cadastrado: cliente.email || \"\",\n token_validado: isValid ? \"SIM\" : \"N\u00c3\u0192O\",\n cadastrado: isClienteCadastrado,\n id_cliente: cliente.id_cliente || null,\n input_user: perguntaAgente,\n resumo_apolices: apolicesContexto,\n tipo_cliente: isClienteCadastrado ? \"CADASTRADO\" : \"NOVO\",\n mensagem_sistema: isClienteCadastrado \n ? \"Cliente encontrado no sistema.\" \n : \"Cliente novo - n\u00c3\u00a3o encontrado no cadastro.\"\n }\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2576,
5440
],
"id": "58a88635-946b-49f8-bf5a-9b55c4856b1f",
"name": "DefinirContextoAI",
"alwaysOutputData": true
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $('Dados').first().json.type || 'chat' }}",
"rightValue": "audio|ptt",
"operator": {
"type": "string",
"operation": "regex"
},
"id": "0dc49c03-4595-4987-ac0a-3fc9d3a6155c"
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "6b2eacf3-4648-4f87-855c-274503889af2",
"leftValue": "={{ $('Dados').first().json.type || 'chat' }}",
"rightValue": "image",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "7102c9ec-2c9e-4121-856c-7e44d524cbb7",
"leftValue": "={{ $('Dados').first().json.type || 'chat' }}",
"rightValue": "chat",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
}
}
]
},
"looseTypeValidation": true,
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.3,
"position": [
1536,
5376
],
"id": "85c9cec0-8417-42f6-ba1d-1efaae532c89",
"name": "SwitchMidia"
},
{
"parameters": {
"url": "={{ ($node[\"Dados\"].json.media_url || $node[\"Webhook\"].json.body.payload.url || \"\").replace(/https?:\\/\\/[^\\/]+/, 'http://waha:3000') }}",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "wahaApi",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
1808,
5024
],
"id": "2ca4918e-b8d8-4a6d-babc-e814bf531394",
"name": "DownloadMidia",
"alwaysOutputData": false,
"credentials": {
"wahaApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"resource": "audio",
"modelId": {
"__rl": true,
"value": "models/gemini-2.0-flash",
"mode": "list",
"cachedResultName": "models/gemini-2.0-flash"
},
"inputType": "binary",
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"typeVersion": 1,
"position": [
1984,
5024
],
"id": "48ccbcde-0249-4506-8f6f-ac5b495fbbf5",
"name": "IA_Processar_Audio",
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"onError": "continueErrorOutput",
"notes": "Em caso de erro, prossiga o chat."
},
{
"parameters": {
"url": "={{ ($node[\"Dados\"].json.media_url || $node[\"Webhook\"].json.body.payload.url || \"\").replace(/https?:\\/\\/[^\\/]+/, 'http://waha:3000') }}",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "wahaApi",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
1808,
5184
],
"id": "0c0a5083-c7a7-4e21-aa7c-f3ecc5c7198c",
"name": "ImageDownload",
"credentials": {
"wahaApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"resource": "image",
"operation": "analyze",
"modelId": {
"__rl": true,
"value": "models/gemini-2.0-flash",
"mode": "list",
"cachedResultName": "models/gemini-2.0-flash"
},
"text": "=Analise a imagem e d\u00c3\u00aa detalhes do que tem nela:\n - Se for a imagem de um documento, procure fazer o reconhecimento atrav\u00c3\u00a9s do t\u00c3\u00adtulo ou qualquer coisa permita a correta identifica\u00c3\u00a7\u00c3\u00a3o\n - Se for a imagem de um veiculo, procure identifica-lo (moto, carro ou caminh\u00c3\u00a3o), al\u00c3\u00a9m de modelo, cor e principalmente se identificado alguma batida etc. \n - Se for qualquer outro tipo de imagem, analise de forma geral\n\nA pessoa que enviou essa imagem mencionou: {{ $node[\"CheckOrigem\"].json.input_bruto.user_text || \"N\u00c3\u00a3o mencionou nada\" }}",
"inputType": "binary",
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"typeVersion": 1,
"position": [
1984,
5184
],
"id": "870d662c-796e-405f-bf9b-227885792ab6",
"name": "IA_Processar_Imagem",
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"onError": "continueErrorOutput",
"notes": "Em caso de erro, prossiga o chat."
},
{
"parameters": {
"jsCode": "// 1. Pega dados de origem para saber o tipo real (chat ou m\u00c3\u00addia)\nlet origem = {};\ntry { origem = $(\"CheckOrigem\").first().json; } catch(e) {}\nconst tipoReal = origem.input_bruto?.mediaType || \"chat\";\nconst textoOriginal = origem.input_bruto?.user_text || \"\";\n\nlet transcricao = \"\";\nlet erroIA = false;\n\n// 2. S\u00c3\u201c BUSCA ERRO DE IA SE REALMENTE FOR M\u00c3\u008dDIA\nif (tipoReal === \"audio\" || tipoReal === \"ptt\") {\n try {\n const nodeAudio = $(\"IA_Processar_Audio\").first();\n if (nodeAudio.json.error || !nodeAudio.json.text || nodeAudio.json.text.includes(\"Erro\")) erroIA = true;\n else transcricao = nodeAudio.json.text;\n } catch(e) { erroIA = true; }\n} \nelse if (tipoReal === \"image\") {\n try {\n const nodeImagem = $(\"IA_Processar_Imagem\").first();\n if (nodeImagem.json.error || !nodeImagem.json.text) erroIA = true;\n else transcricao = nodeImagem.json.text;\n } catch(e) { erroIA = true; }\n}\n\n// 3. Resultado Final\nlet mensagemFinal = (tipoReal === \"chat\") ? textoOriginal : transcricao;\n\n// Limpeza de placeholders residuais\nif (mensagemFinal === \"MIDIA_INPUT\") mensagemFinal = \"\";\n\nreturn [{\n json: {\n mensagem_final: mensagemFinal.trim(),\n tipo_entrada: tipoReal,\n teve_erro_ia: erroIA && (tipoReal !== \"chat\"),\n houve_erro_midia: false \n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2240,
5424
],
"id": "1a6c5c5c-39fe-41df-896f-99da0c8a6fb8",
"name": "ConsolidarMensagem",
"alwaysOutputData": true
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 3
},
"conditions": [
{
"leftValue": "={{ $json.houve_erro_midia }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"id": "5280c04a-9876-4f53-a252-2e8fff6d4d73"
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 3
},
"conditions": [
{
"id": "63991c1f-d6e5-45f5-a188-beca30f15b0c",
"leftValue": "={{ $json.houve_erro_midia }}",
"rightValue": "If Boolean is False: Conecte ao n\u00c3\u00b3 DefinirContextoAI.",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
}
}
],
"combinator": "and"
}
}
]
},
"looseTypeValidation": true,
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.4,
"position": [
2384,
5424
],
"id": "ff50d6d9-f861-4062-a737-6e0e1435ccd0",
"name": "TratamentoErroMidia"
},
{
"parameters": {
"model": "google/gemini-2.0-flash",
"options": {
"maxTokens": 2048
}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"typeVersion": 1,
"position": [
1216,
6400
],
"id": "601e9013-1ee8-4a16-99ec-58de67cc33dd",
"name": "OpenRouter Chat Model1",
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"sessionIdType": "customKey",
"sessionKey": "={{ (() => { try { const tel = $('DefinirContextoAI').first().json.contexto.telefone; if (tel) return tel + '_v2'; } catch(e) {} try { return $('MontarContextoPortal').first().json.session_key; } catch(e) {} return 'FALLBACK_' + Date.now(); })() }}",
"contextWindowLength": 6
},
"type": "@n8n/n8n-nodes-langchain.memoryPostgresChat",
"typeVersion": 1.3,
"position": [
1408,
6400
],
"id": "9b34d508-4c86-4809-a642-9053d09114de",
"name": "PostgresChatMemory",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"descriptionType": "manual",
"toolDescription": "NOME: Validador_Cliente_Brokeria\n\nQUANDO USAR:\n1. Quando um usu\u00c3\u00a1rio desconhecido (telefone n\u00c3\u00a3o cadastrado) afirma ser cliente e fornece o documento ou e-mail.\n2. Quando um usu\u00c3\u00a1rio j\u00c3\u00a1 identificado solicita acesso a dados sens\u00c3\u00adveis (ap\u00c3\u00b3lices, altera\u00c3\u00a7\u00c3\u00b5es cadastrais) e precisa revalidar a identidade.\n\nINPUT:\nO cliente deve fornecer o CPF (11 d\u00c3\u00adgitos), CNPJ (14 d\u00c3\u00adgitos) OU o E-mail cadastrado.\nExemplo JSON: { \"documento\": \"12345678900\" } ou { \"documento\": \"teste@email.com\" }\n\nRETORNO:\nRetorna os dados cadastrais completos do cliente. Retorna vazio se n\u00c3\u00a3o encontrar.",
"operation": "executeQuery",
"query": "SELECT * FROM public.clientes_brokeria\nWHERE cpf = '{{\n (() => {\n const val = $json.documento ? $json.documento.toString() : \"\";\n if (val.includes(\"@\")) return \"EMAIL_QUERY_BYPASS\";\n const doc = val.replace(/\\D/g, \"\");\n if (doc.length !== 11 && doc.length !== 14) return \"000\";\n return doc;\n })()\n}}' OR email = '{{ $json.documento }}';",
"options": {}
},
"type": "n8n-nodes-base.postgresTool",
"typeVersion": 2.6,
"position": [
1600,
6400
],
"id": "5cb24bdd-0c72-4faa-b26e-68e08b6025f2",
"name": "QueryPostgresClientes",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"mode": "combine",
"combineBy": "combineByPosition",
"options": {}
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
624,
4848
],
"id": "a930ebb6-c18a-44c2-ad00-41a0ae411e6b",
"name": "MergeDados"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "f8eff236-eaf4-4910-8750-745c585893f0",
"name": "wa.provider",
"value": "waha",
"type": "string"
},
{
"id": "952635f9-b937-4076-8b4c-87e5b4dfaf5e",
"name": "wa.session",
"value": "={{$json.body.session}}",
"type": "string"
},
{
"id": "01f5d87e-4994-4995-a882-477a14b85ab6",
"name": "wa.messageId",
"value": "={{$json.body.payload.id}}",
"type": "string"
},
{
"id": "d875c982-ad5d-4098-b28f-ea116c24b210",
"name": "wa.chatId",
"value": "={{ $json.body.payload.from }}",
"type": "string"
},
{
"id": "16637790-2905-445d-a3f5-a280a1d7726c",
"name": "wa.text",
"value": "={{$json.body.payload.body}}",
"type": "string"
}
]
},
"includeOtherFields": true,
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
208,
5024
],
"id": "b344e8bf-7fbe-4e3c-92ad-3b375a2402fd",
"name": "NormalizeInbound"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "f13f09b9-af26-4aa3-9761-edc7d76ff32e",
"name": "wa.chatId",
"value": "={{ $json.pn || $json.wa.chatId }}",
"type": "string"
}
]
},
"includeOtherFields": true,
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
800,
4832
],
"id": "6edeffd4-b8b8-46d4-89f2-7674b07c8caa",
"name": "FinalizeChatId"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "final_text",
"name": "response_text",
"value": "={{ $json.mensagem_final || $json.mensagem || $json.output || \"Erro: N\u00c3\u00a3o foi poss\u00c3\u00advel recuperar a mensagem.\" }}",
"type": "string"
},
{
"id": "target_chat",
"name": "target_chat_id",
"value": "={{ $('DefinirContextoAI').first().json.contexto.telefone + '@c.us' }}",
"type": "string"
},
{
"id": "target_session",
"name": "target_session",
"value": "={{ $('CheckOrigem').first().json.tecnico.session_id }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
2352,
5872
],
"id": "c93daf48-fa9d-4dd1-8ac2-a43727f66d34",
"name": "Padronizar Output"
},
{
"parameters": {
"jsCode": "// Este n\u00c3\u00b3 apenas coleta o que chegar e passa adiante\nreturn $input.all();"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2144,
5872
],
"id": "ded3cf27-6768-4d33-b995-5c624a6f530e",
"name": "FunilRespostas"
},
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO public.brokeria_registros_brokeria (\n telefone_whatsapp, \n nome_cliente, \n resumo_conversa, \n assunto_principal,\n subtipo_solicitacao,\n tipo_seguro,\n prioridade,\n tipo_cliente,\n status_atendimento, \n data_atendimento,\n canal,\n qtde_mensagens\n)\nVALUES (\n '{{ $(\"DefinirContextoAI\").first().json.contexto.telefone }}',\n '{{ $(\"DefinirContextoAI\").first().json.contexto.nome }}',\n E'[' || TO_CHAR(NOW() AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo', 'HH24:MI, DD/MM/YYYY') || E'] ' || '{{ $(\"DefinirContextoAI\").first().json.contexto.nome }}' || E': ' || '{{ ($(\"DefinirContextoAI\").first().json.contexto.input_user || \"\").replace(/'/g, \"''\") }}' || E'\\n' ||\n E'[' || TO_CHAR(NOW() AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo', 'HH24:MI, DD/MM/YYYY') || E'] BrokerIA: ' || '{{ ($json.text || $json.output || \"\").replace(/'/g, \"''\") }}' || E'\\n',\n '{{ $json.tipo_solicitacao || \"Informa\u00c3\u00a7\u00c3\u00b5es Gerais\" }}',\n '{{ $json.subtipo_solicitacao || \"D\u00c3\u00bavidas gerais\" }}',\n '{{ $json.tipo_seguro || \"OUTROS\" }}',\n '{{ $json.prioridade || \"M\u00c3\u00a9dia\" }}',\n '{{ $json.tipo_cliente || \"N\u00c3\u00a3o cliente\" }}',\n 'PENDENTE',\n CURRENT_DATE,\n '{{ $(\"DefinirContextoAI\").first().json.contexto.origem || \"WHATSAPP\" }}',\n 1\n)\nON CONFLICT (telefone_whatsapp, data_atendimento) \nDO UPDATE SET \n resumo_conversa = brokeria_registros_brokeria.resumo_conversa || \n E'\\n' ||\n E'[' || TO_CHAR(NOW() AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo', 'HH24:MI, DD/MM/YYYY') || E'] ' || EXCLUDED.nome_cliente || E': ' || '{{ ($(\"DefinirContextoAI\").first().json.contexto.input_user || \"\").replace(/'/g, \"''\") }}' || E'\\n' ||\n E'[' || TO_CHAR(NOW() AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo', 'HH24:MI, DD/MM/YYYY') || E'] BrokerIA: ' || '{{ ($json.text || $json.output || \"\").replace(/'/g, \"''\") }}' || E'\\n',\n qtde_mensagens = COALESCE(brokeria_registros_brokeria.qtde_mensagens, 0) + 1,\n assunto_principal = EXCLUDED.assunto_principal,\n subtipo_solicitacao = EXCLUDED.subtipo_solicitacao,\n prioridade = EXCLUDED.prioridade,\n data_ultima_atualizacao = NOW();",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
2528,
5648
],
"id": "ba46fd4c-9c39-44c2-842f-7f4741308505",
"name": "Registrar_Interacao",
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "// 1. Coleta a mensagem de todas as fontes poss\u00c3\u00adveis\nlet mensagem = $json.response_text || $json.mensagem_final || $json.output || \"\";\n\n// 2. Se a mensagem ainda estiver vazia, tenta buscar diretamente nos n\u00c3\u00b3s de origem\nif (!mensagem || mensagem === \"\") {\n try {\n mensagem = $(\"LimparOutputValidacao\").first().json.mensagem_final;\n } catch(e) {\n try {\n mensagem = $(\"BrokerIA_Master\").first().json.output;\n } catch(e2) {}\n }\n}\n\n// 3. Limpeza de logs t\u00c3\u00a9cnicos\nmensagem = mensagem.replace(/\\[Used tools:.*?\\]/gis, '');\nmensagem = mensagem.replace(/Tool:.*?(?=\\n|$)/gi, '');\nmensagem = mensagem.replace(/Input:.*?(?=\\n|$)/gi, '');\nmensagem = mensagem.replace(/Result:.*?(?=\\n|$)/gi, '');\nmensagem = mensagem.replace(/\\{.*?\"accepted\".*?\\}/gs, '');\nmensagem = mensagem.replace(/\\n{3,}/g, '\\n\\n').trim();\n\n// 4. \u00e2\u0153\u2026 CORRE\u00c3\u2021\u00c3\u0192O: Fallback adequado baseado no tipo de cliente (Agora via v9)\nif (!mensagem) {\n try {\n const contexto = $(\"DefinirContextoAI\").first().json.contexto;\n const nome = contexto.nome || \"Cliente\";\n const primeiroNome = nome.split(' ')[0];\n const isClienteCadastrado = contexto.cadastrado; \n \n if (isClienteCadastrado) {\n mensagem = `Ol\u00c3\u00a1 ${primeiroNome}! Como posso te ajudar hoje?`;\n } else {\n mensagem = `Ol\u00c3\u00a1 ${primeiroNome}! Seja bem-vindo \u00c3\u00a0 DWF Seguros! \u00f0\u0178\u02dc\u0160\\n\\nEstou aqui para te ajudar com:\\n\u00e2\u20ac\u00a2 Cota\u00c3\u00a7\u00c3\u00b5es de seguros\\n\u00e2\u20ac\u00a2 Informa\u00c3\u00a7\u00c3\u00b5es sobre nossos produtos\\n\u00e2\u20ac\u00a2 D\u00c3\u00bavidas sobre seguros em geral\\n\\nComo posso te ajudar?`;\n }\n } catch(e) {\n mensagem = \"Ol\u00c3\u00a1! Seja bem-vindo \u00c3\u00a0 DWF Seguros! Como posso te ajudar?\";\n }\n}\n\nreturn {\n json: {\n ...$json,\n text: mensagem\n }\n}"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2944,
5872
],
"id": "8b9e05b3-810c-4e71-8a1a-f5b425b38fe9",
"name": "FiltrarResposta"
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.pergunta_cliente }}\n\n",
"options": {
"systemMessage": "===================================================================\n\u00f0\u0178\u201c\u00b1 FORMATO DE RESPOSTA (ESTILO MENU/URA) - CR\u00c3\u008dTICO\n==================================================================\nPara melhorar a experi\u00c3\u00aancia do usu\u00c3\u00a1rio, sempre que o cliente solicitar algo que tenha categorias ou variantes, APRESENTE AS OP\u00c3\u2021\u00c3\u2022ES EM LISTA NUMERADA.\nO objetivo \u00c3\u00a9 permitir que o cliente responda de forma r\u00c3\u00a1pida e simples (apenas digitando o n\u00c3\u00bamero).\nEXEMPLO DE INTERA\u00c3\u2021\u00c3\u0192O OBRIGAT\u00c3\u201cRIA:\nCliente: \"Quero uma cota\u00c3\u00a7\u00c3\u00a3o\"\nVoc\u00c3\u00aa: \"\u00c3\u201ctimo! Para qual tipo de seguro voc\u00c3\u00aa deseja cota\u00c3\u00a7\u00c3\u00a3o?\n1. Autom\u00c3\u00b3vel \u00f0\u0178\u0161\u2014\n2. Residencial \u00f0\u0178\u008f\u00a0\n3. Vida \u00e2\u009d\u00a4\u00ef\u00b8\u008f\n4. Empresarial \u00f0\u0178\u008f\u00a2\n5. Outros Assist\u00c3\u00aancias\"\nPRIORIZE SEMPRE esse formato de \"URA Inteligente/Menu\" em vez de perguntas abertas.\n\n=================================================================\n\u00f0\u0178\u0152\u0090 PORTAL DO CLIENTE - ACESSO SEGURO A DADOS\n=================================================================\nQualquer solicita\u00c3\u00a7\u00c3\u00a3o abaixo DEVE ser respondida com redirecionamento ao portal:\n- Documentos (PDFs de ap\u00c3\u00b3lices, cartas verdes, 2\u00c2\u00aa via)\n- Dados detalhados al\u00c3\u00a9m do resumo dispon\u00c3\u00advel no contexto\n- Altera\u00c3\u00a7\u00c3\u00b5es cadastrais\n\n\u00e2\u0153\u2026 RESPOSTA PADR\u00c3\u0192O para esses casos:\n\"Para acessar seus dados e documentos com seguran\u00c3\u00a7a, acesse nosso portal:\n\u00f0\u0178\u201d\u2014 https://brokeria-api-brokeriaweb.cx0m9g.easypanel.host/login.html\n\nL\u00c3\u00a1 voc\u00c3\u00aa encontra todas as suas informa\u00c3\u00a7\u00c3\u00b5es e pode baixar seus documentos! \u00f0\u0178\u02dc\u0160\"\n\nEXEMPLOS que devem ir ao portal:\n- \"Me envie a 2\u00c2\u00aa via da ap\u00c3\u00b3lice\" \u00e2\u2020\u2019 portal\n- \"Qual o endere\u00c3\u00a7o da minha ap\u00c3\u00b3lice?\" \u00e2\u2020\u2019 portal\n- \"Quero alterar meu cadastro\" \u00e2\u2020\u2019 portal\n\nEXEMPLOS que N\u00c3\u0192O v\u00c3\u00a3o ao portal (responda com o contexto):\n- \"Qual o n\u00c3\u00bamero da minha ap\u00c3\u00b3lice?\" \u00e2\u2020\u2019 use resumo_apolices do contexto\n- \"Quando vence meu seguro?\" \u00e2\u2020\u2019 use resumo_apolices do contexto\n- \"Qual a placa do meu carro segurado?\" \u00e2\u2020\u2019 use resumo_apolices do contexto\n- Sauda\u00c3\u00a7\u00c3\u00b5es e perguntas gerais \u00e2\u2020\u2019 responda normalmente\n\n=================================================================\n\u00f0\u0178\u201c\u009d REGISTRO DE COTA\u00c3\u2021\u00c3\u2022ES - REGRA OBRIGAT\u00c3\u201cRIA\n=================================================================\nQuando o cliente solicitar uma cota\u00c3\u00a7\u00c3\u00a3o E voc\u00c3\u00aa j\u00c3\u00a1 tiver: Nome, Email e Tipo de seguro:\n1. Chame IMEDIATAMENTE a ferramenta 'EspecialistaRegistroCotacao'\n2. Passe: nome, email, tipo_seguro, mensagem\n3. AGUARDE a confirma\u00c3\u00a7\u00c3\u00a3o da tool\n4. S\u00c3\u00b3 DEPOIS confirme: \"\u00e2\u0153\u2026 Sua solicita\u00c3\u00a7\u00c3\u00a3o foi registrada! O corretor Washington entrar\u00c3\u00a1 em contato em breve.\"\n\nNUNCA confirme sem a tool retornar sucesso.\n\n-----------------------------------------------------------------\n\u00f0\u0178\u201d\u017d CONTEXTO DIN\u00c3\u201aMICO\n-----------------------------------------------------------------\n- Cliente: {{ $json.contexto.nome }}\n- CPF: {{ $json.contexto.cpf_exibicao }}\n- Telefone: {{ $json.contexto.telefone }}\n- Email Cadastrado: {{ $json.contexto.email_cadastrado }}\n\n\u00f0\u0178\u201c\u00b1 CEN\u00c3\u0081RIO A: CLIENTE CADASTRADO\n- Sa\u00c3\u00bade pelo PRIMEIRO NOME: \"Ol\u00c3\u00a1, {{ $json.contexto.nome }}!\"\n- Dados b\u00c3\u00a1sicos das ap\u00c3\u00b3lices dispon\u00c3\u00adveis no contexto abaixo\n- Para documentos/dados detalhados \u00e2\u2020\u2019 portal\n- N\u00c3\u0192O pe\u00c3\u00a7a CPF se j\u00c3\u00a1 estiver no contexto\n\n\u00f0\u0178\u201c\u00b1 CEN\u00c3\u0081RIO B: TELEFONE N\u00c3\u0192O CADASTRADO (novo contato)\n- Use o nome do WhatsApp: \"Ol\u00c3\u00a1, {{ $json.contexto.nome }}!\"\n- Se mencionar que \u00c3\u2030 cliente, pe\u00c3\u00a7a CPF \u00e2\u2020\u2019 use QueryPostgresClientes\n- Ap\u00c3\u00b3s encontrar: \"Perfeito! Localizei seu cadastro. Como posso ajudar?\"\n\n\u00f0\u0178\u201c\u00b1 CEN\u00c3\u0081RIO C: CLIENTE MENCIONA CPF ESPONTANEAMENTE\n- Use QueryPostgresClientes com { \"documento\": \"CPF_INFORMADO\" }\n- Se encontrar \u00e2\u2020\u2019 trate como CEN\u00c3\u0081RIO A\n\n\u00e2\u0161\u00a0\u00ef\u00b8\u008f SAUDA\u00c3\u2021\u00c3\u0192O: NUNCA diga \"Ol\u00c3\u00a1, N\u00c3\u00a3o Identificado!\" ou \"Ol\u00c3\u00a1, Cliente!\"\n\n\u00f0\u0178\u201c\u0160 RESUMO DE AP\u00c3\u201cLICES (dados b\u00c3\u00a1sicos dispon\u00c3\u00adveis):\n{{ JSON.stringify($json.contexto.resumo_apolices) }}\n\n=================================================================\n\u00f0\u0178\u201c\u2039 INFORMA\u00c3\u2021\u00c3\u2022ES INSTITUCIONAIS\n=================================================================\nRespons\u00c3\u00a1vel: Washington William de Oliveira \u00e2\u20ac\u201c Susep: 10.2028529.9\nEmail: contato@dwfseguros.com.br\nWebsite: https://www.dwfseguros.com.br\nTIPOS DE SEGUROS: Autom\u00c3\u00b3vel, Residencial, Vida, Sa\u00c3\u00bade, Previd\u00c3\u00aancia, Frota, Resp. Civil, Transportes, Garantia, etc.\n\n=================================================================\n\u00f0\u0178\u0161\u00ab LIMITES E LGPD\n=================================================================\n- COTA\u00c3\u2021\u00c3\u2022ES: NUNCA forne\u00c3\u00a7a valores de pr\u00c3\u00aamio. Diga: \"A cota\u00c3\u00a7\u00c3\u00a3o oficial ser\u00c3\u00a1 feita pelo corretor Washington\".\n- CPF/CNPJ: SEMPRE mascarado. Ex: ***.***.***-22\n- Para dados al\u00c3\u00a9m do contexto \u00e2\u2020\u2019 redirecione ao portal\n- Seja formal, consultivo e simp\u00c3\u00a1tico. Saude sempre pelo primeiro nome.\n- NUNCA invente informa\u00c3\u00a7\u00c3\u00b5es.",
"maxIterations": 10
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3,
"position": [
1424,
5872
],
"id": "6e2a1dbc-1160-4c29-a70a-a070ba8af73b",
"name": "BrokerIA_Master",
"executeOnce": false,
"alwaysOutputData": false,
"notesInFlow": false
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"leftValue": "={{ $json.contexto.tipo_cliente }}",
"rightValue": "=NOVO",
"operator": {
"type": "string",
"operation": "equals"
},
"id": "75010c00-aec1-42d3-ae87-3d67526f7c3b"
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "d4159ee7-e43a-477d-90f3-a5e7ac29d9d5",
"leftValue": "={{ $json.contexto.tipo_cliente }}",
"rightValue": "=CADASTRADO",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
}
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.4,
"position": [
3936,
5424
],
"id": "98738341-404f-4110-b56f-f3c3dcff6286",
"name": "Check_Cliente_Novo"
},
{
"parameters": {
"jsCode": "// RECUPERA DADOS\nconst contexto = $json.contexto || {};\nconst pergunta = $json.pergunta_cliente || \"\";\nconst perguntaLower = pergunta.toLowerCase();\n\n// ==========================================\n// 1. RAMO DO SEGURO (Mapeamento Inteligente)\n// ==========================================\nlet ramoSeguro = \"OUTROS\";\nif (perguntaLower.match(/auto|carro|veic|moto|placa/)) ramoSeguro = \"AUTOMOVEL\";\nelse if (perguntaLower.match(/casa|resid|apart|lar|home/)) ramoSeguro = \"RESIDENCIAL\";\nelse if (perguntaLower.match(/vida|falec|morte|funeral/)) ramoSeguro = \"VIDA\";\nelse if (perguntaLower.match(/saude|m\u00c3\u00a9dic|plano|hospit/)) ramoSeguro = \"SAUDE\";\nelse if (perguntaLower.match(/empresa|pj|comercial/)) ramoSeguro = \"EMPRESARIAL\";\n\n// ==========================================\n// 2. N\u00c3\u008dVEL 1 E N\u00c3\u008dVEL 2 (Classifica\u00c3\u00a7\u00c3\u00a3o)\n// ==========================================\nlet nivel1 = \"Informa\u00c3\u00a7\u00c3\u00b5es Gerais\";\nlet nivel2 = \"D\u00c3\u00bavidas gerais sobre seguros\";\nlet prioridade = \"M\u00c3\u00a9dia\";\n\n// L\u00c3\u00b3gica de Classifica\u00c3\u00a7\u00c3\u00a3o Hier\u00c3\u00a1rquica\nif (perguntaLower.match(/cota\u00c3\u00a7\u00c3\u00a3o|cotacao|quanto custa|valor|pre\u00c3\u00a7o|contratar/)) {\n nivel1 = \"Vendas e Cota\u00c3\u00a7\u00c3\u00b5es\";\n nivel2 = \"Cota\u00c3\u00a7\u00c3\u00a3o de seguro novo\";\n prioridade = \"Alta\";\n} else if (perguntaLower.match(/sinistro|bati|acidente|roubo|furto|colis\u00c3\u00a3o/)) {\n nivel1 = \"Sinistros\";\n nivel2 = \"Aviso de sinistro\";\n prioridade = \"Cr\u00c3\u00adtica\";\n} else if (perguntaLower.match(/boleto|pagar|parcela|paguei|segunda via|2 via/)) {\n nivel1 = \"Financeiro\";\n nivel2 = \"Segunda via de boleto\";\n} else if (perguntaLower.match(/renovar|renova\u00c3\u00a7\u00c3\u00a3o|vencimento|venceu/)) {\n nivel1 = \"Renova\u00c3\u00a7\u00c3\u00a3o\";\n nivel2 = \"Renova\u00c3\u00a7\u00c3\u00a3o autom\u00c3\u00a1tica\";\n prioridade = \"Alta\";\n} else if (perguntaLower.match(/cancelar|cancelamento|desistir/)) {\n nivel1 = \"Cancelamento\";\n nivel2 = \"Cancelamento a pedido do cliente\";\n prioridade = \"Alta\";\n} else if (perguntaLower.match(/apolice|ap\u00c3\u00b3lice|documento|certificado|carta/)) {\n nivel1 = \"Documentos\";\n nivel2 = \"Segunda via de ap\u00c3\u00b3lice\";\n} else if (perguntaLower.match(/mudei|endere\u00c3\u00a7o|troquei|dados|alterar/)) {\n nivel1 = \"Altera\u00c3\u00a7\u00c3\u00b5es de Ap\u00c3\u00b3lice (Endossos)\";\n nivel2 = \"Atualiza\u00c3\u00a7\u00c3\u00a3o de dados cadastrais\";\n}\n\n// ==========================================\n// 3. RETORNO FORMATADO PARA O POSTGRES\n// ==========================================\nreturn {\n json: {\n telefone: (contexto.telefone || \"\").replace(/\\D/g, ''),\n nome_whatsapp: (contexto.nome || \"Cliente\"),\n mensagem_inicial: pergunta,\n \n // Padroniza\u00c3\u00a7\u00c3\u00a3o solicitada\n tipo_solicitacao: nivel1, // N\u00c3\u00advel 1\n subtipo_solicitacao: nivel2, // N\u00c3\u00advel 2\n tipo_seguro: ramoSeguro, // Ramo\n prioridade: prioridade, // Prioridade\n tipo_cliente: contexto.cadastrado ? \"Cliente existente\" : \"N\u00c3\u00a3o cliente\",\n \n status_atendimento: \"PENDENTE\",\n origem: \"WHATSAPP\",\n data_contato: new Date()\n }\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
4064,
5264
],
"id": "bf04d374-7095-4254-8afe-987824f6a1c8",
"name": "Preparar_Registro_Cliente"
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT \n id_atendimento,\n telefone_whatsapp,\n nome_cliente,\n resumo_conversa,\n assunto_principal,\n status_atendimento,\n qtde_mensagens,\n data_atendimento\nFROM public.brokeria_registros_brokeria\nWHERE telefone_whatsapp = '{{ $json.telefone }}'\n AND data_atendimento > NOW() - INTERVAL '24 hours'\nORDER BY data_atendimento DESC\nLIMIT 1;",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
4272,
5264
],
"id": "f61b9514-92e4-466b-bd88-1d16448d70fa",
"name": "Verificar_Registro_Existente",
"alwaysOutputData": true,
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 3
},
"conditions": [
{
"leftValue": "={{ $json.id_atendimento ? String($json.id_atendimento) : '' }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"id": "f21a439a-8d76-455e-8135-20ce8
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googlePalmApiopenRouterApipostgresrediswahaApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
This workflow automates sophisticated customer interactions via WhatsApp, enabling businesses to deliver personalised, AI-driven responses that enhance engagement and efficiency without manual intervention. It suits sales teams, support agents, or e-commerce managers seeking to scale conversations seamlessly. The core step involves receiving incoming messages through a webhook, processing them with Google Gemini for intelligent replies, and dispatching them back via the WAHA integration, all while managing session states in Postgres and Redis for reliability.
Use this workflow for high-volume WhatsApp support where quick, context-aware replies are essential, such as handling inquiries in real-time during business hours. Avoid it for simple one-off notifications or when compliance requires human oversight in sensitive sectors like finance. Common variations include swapping Google Gemini for OpenRouter models to customise response styles or adding branching logic for multilingual support.
About this workflow
Brokeria-v15. Uses n8n-nodes-waha, httpRequest, postgres, redis. Webhook trigger; 55 nodes.
Source: https://github.com/RogerioCelli/BrokerIAWeb/blob/701836f1142288856bd26776a6cb3653e91f640b/n8n/Brokeria-v15.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
secretaria. Uses postgres, n8n-nodes-evolution-api, openAi, httpRequest. Webhook trigger; 71 nodes.
Brokeria-v20. Uses n8n-nodes-waha, httpRequest, redis, googleGemini. Webhook trigger; 56 nodes.
'Elena AI' is a powerful n8n workflow that transforms your automation platform into a full-fledged, multi-agent AI hub. 🤖✨ By combining Redis state management with specialized “tool” sub-workflows, yo
Complete PostgreSQL-backed system: Keyword scoring → AI research → Multi-part content generation → fal.ai Nano Banana image generation → WordPress publishing
Flux. Uses lmChatOpenAi, agent, googleGemini, httpRequest. Webhook trigger; 67 nodes.