AutomationFlowsAI & RAG › WhatsApp AI Chatbot with Gemini

WhatsApp AI Chatbot with Gemini

Original n8n title: Brokeria V20

Brokeria-v20. Uses n8n-nodes-waha, httpRequest, redis, googleGemini. Webhook trigger; 56 nodes.

Webhook trigger★★★★★ complexityAI-powered56 nodesN8N Nodes WahaHTTP RequestRedisGoogle GeminiOpenRouter ChatMemory Postgres ChatPostgres ToolPostgres
AI & RAG Trigger: Webhook Nodes: 56 Complexity: ★★★★★ AI nodes: yes Added:

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 →

Download .json
{
  "name": "Brokeria-v20",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "waha",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -656,
        -96
      ],
      "id": "dd67499f-0c0b-4690-a5fb-a4a0ffa5fc19",
      "name": "Webhook"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "78415b90-064b-46f3-8a36-8489bd14f43a",
              "name": "event",
              "value": "={{ $json.body.event || $json.body.action }}",
              "type": "string"
            },
            {
              "id": "6d70d6a1-58d7-416a-a388-7b2aeabd4b85",
              "name": "session",
              "value": "={{ ($json.body.session || $json.body.session_id || 'default').trim() }}",
              "type": "string"
            },
            {
              "id": "ff0bd58d-efa1-4e49-9c9c-602aed25a290",
              "name": "payload_id",
              "value": "={{ $json.body.payload.id || $json.body.id || 'web_' + $now }}",
              "type": "string"
            },
            {
              "id": "0c990094-4206-417a-be86-008c8156acf8",
              "name": "from",
              "value": "={{ $json.body.payload.from || $json.body.telefone || $json.body.target }}",
              "type": "string"
            },
            {
              "id": "d33eb4cc-078e-40b7-9c9c-8fbba1e2aa76",
              "name": "to",
              "value": "={{ $json.body.payload.to || $json.body.target }}",
              "type": "string"
            },
            {
              "id": "457f7c13-bf6f-4171-80bc-75620cfda604",
              "name": "source",
              "value": "={{ $json.body.payload.source || $json.body.origem }}",
              "type": "string"
            },
            {
              "id": "8ce79aa1-5408-48c9-8226-9ac6f875df12",
              "name": "body",
              "value": "={{ $json.body.payload?.body || $json.body.message || $json.body.pergunta_cliente }}",
              "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 || $json.body.client_name || $json.body.nome }}",
              "type": "string"
            },
            {
              "id": "6508b08d-4b94-4352-8a94-c55ffd11cc91",
              "name": "fromMe",
              "value": "={{ $json.body.payload.fromMe || false }}",
              "type": "boolean"
            },
            {
              "id": "3ada40ac-45a0-440a-9e4b-44417caadf9c",
              "name": "to",
              "value": "={{ $json.body.payload.to || $json.body.target }}",
              "type": "string"
            },
            {
              "id": "0096fa37-d92b-43ca-8c98-269cc5e1899f",
              "name": "id",
              "value": "={{ $json.body.payload?.id || $json.body.id || 'web_' + $now }}",
              "type": "string"
            },
            {
              "id": "30a509ba-e3aa-4e6a-b3ce-4f7d1fcbf46f",
              "name": "session_id",
              "value": "={{ ($json.body.session || 'portal') + ' ' + ($json.body.payload.from || $json.body.telefone || $json.body.identifier) + ' chats' }}",
              "type": "string"
            },
            {
              "id": "72d18f99-83a5-4bfb-8621-6af03d231409",
              "name": "from",
              "value": "={{ $json.body.payload.from || $json.body.telefone || $json.body.target }}",
              "type": "string"
            },
            {
              "id": "72d18f99-83a5-4bfb-8621-6af03d231410",
              "name": "pn",
              "value": "={{ $json.body.payload.from || $json.body.telefone }}",
              "type": "string"
            },
            {
              "id": "72d18f99-83a5-4bfb-8621-6af03d231409",
              "name": "user_text",
              "value": "={{ $json.body.payload.body || $json.body.message || $json.body.pergunta_cliente }}",
              "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": [
        -464,
        -96
      ],
      "id": "858a403f-0b79-471d-9c93-066b6bf306eb",
      "name": "Dados"
    },
    {
      "parameters": {
        "resource": "Chatting",
        "operation": "Send Seen",
        "session": "={{ $('Padronizar Output').first().json.target_session || 'default' }}",
        "chatId": "={{ $('Padronizar Output').first().json.target_chat_id }}",
        "messageId": "",
        "requestOptions": {}
      },
      "type": "n8n-nodes-waha.WAHA",
      "typeVersion": 202411,
      "position": [
        2800,
        896
      ],
      "id": "575c26cf-b26a-4cd1-ba01-405dd1e499e3",
      "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": [
        2944,
        896
      ],
      "id": "e3a76892-860a-47a6-b36e-411f7deb1b3f",
      "name": "Start Typing",
      "credentials": {
        "wahaApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        3088,
        896
      ],
      "id": "83c21e59-e1b2-438a-add8-58cabddca665",
      "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": [
        3392,
        896
      ],
      "id": "bcc3a72f-fa00-4643-a156-96f6a2c56df0",
      "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": "d0c6e226-75d7-4121-b51e-189f2c749c5d",
      "name": "Consultar WAHA Direto",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        320,
        -288
      ],
      "onError": "continueRegularOutput"
    },
    {
      "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": "4575405f-68cb-4ebe-a937-ca0b67c9a886",
      "name": "Limpar Telefone",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        480,
        -288
      ]
    },
    {
      "parameters": {
        "jsCode": "// BUSCA DOS DADOS: Pegamos o input que vem do CheckOrigem (o dado 'fresco' da execu\u00e7\u00e3o)\nconst input = $(\"CheckOrigem\").first().json;\nconst wa = input.wa || {}; \n\nlet mensagens = [];\ntry {\n  const redisNode = $(\"Recuperar_Buffer\").first();\n  const redis = (redisNode && redisNode.json) ? redisNode.json : {};\n  // O Redis GET retorna o buffer em Redis_Check_Buffer ou value\n  const conteudo = redis.Redis_Check_Buffer || redis.value;\n  if (conteudo) {\n      const parsed = typeof conteudo === 'string' ? JSON.parse(conteudo) : conteudo;\n      mensagens = (parsed && parsed.msgs) ? parsed.msgs : (Array.isArray(parsed) ? parsed : []);\n  }\n} catch (e) { mensagens = []; }\n\n// Garante que temos um id \u00fanico para esta mensagem\nconst messageId = wa.messageId || (\"wa_\" + Date.now());\n// Pega o texto real que o NormalizeInbound capturou\nconst textoAtual = wa.text || input.user_text || input.message || \"\";\n\n// Adiciona ao hist\u00f3rico se n\u00e3o existir\nif (!mensagens.some(m => m.id === messageId)) {\n    mensagens.push({ id: messageId, texto: textoAtual, exec_id: $execution.id });\n}\n\nreturn [{\n    json: {\n        ...input,\n        wa: {\n            ...wa,\n            messageId: messageId,\n            text: textoAtual\n        },\n        texto_combinado: mensagens.map(m => m.texto).join(' \\n '),\n        msgs: mensagens,\n        minha_execucao: $execution.id\n    }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        32,
        496
      ],
      "id": "a385b8dc-b279-49ce-be12-b8cb49b691b5",
      "name": "Agrupar_Texto"
    },
    {
      "parameters": {
        "operation": "set",
        "key": "=buffer:{{ $('CheckOrigem').first().json.wa.chatId }}",
        "value": "={{ JSON.stringify($json) }}",
        "expire": true,
        "ttl": 10
      },
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [
        176,
        592
      ],
      "id": "0cde3c3a-85d4-4c3f-a6ab-0a45760dc563",
      "name": "Atualizar_Buffer",
      "credentials": {
        "redis": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "amount": 3
      },
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        336,
        496
      ],
      "id": "6639e308-e336-4025-bc1a-2a771ea4e122",
      "name": "Espera_3_Segundos"
    },
    {
      "parameters": {
        "operation": "get",
        "propertyName": "Redis_Check_Final",
        "key": "=buffer:{{ $('CheckOrigem').first().json.wa.chatId }}",
        "options": {}
      },
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [
        480,
        576
      ],
      "id": "852c54e0-ef5b-45bd-87ed-a8957d7762ec",
      "name": "Check_Pos_Espera",
      "alwaysOutputData": true,
      "credentials": {
        "redis": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "get",
        "propertyName": "Redis_Check_Buffer",
        "key": "=buffer:{{ $('CheckOrigem').first().json.wa.chatId }}",
        "options": {}
      },
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [
        -128,
        608
      ],
      "id": "519dc24e-9d82-4b8f-8464-3827a8ee9b64",
      "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": [
        1008,
        480
      ],
      "id": "196540f4-21a8-4596-9a03-c77ae2fbfc45",
      "name": "SwitchOrigem"
    },
    {
      "parameters": {
        "jsCode": "// 1. Limpeza de Newlines e Normaliza\u00e7\u00e3o de Input\nconst rawInput = $input.first().json;\nconst input = {};\nfor (const key in rawInput) {\n    if (typeof rawInput[key] === 'string') {\n        input[key] = rawInput[key].trim();\n    } else {\n        input[key] = rawInput[key];\n    }\n}\n\nlet wa = input.wa || {};\nlet perguntaCliente = input.pergunta_cliente || input.message || \"\";\n\ntry {\n    const nodeInbound = $(\"NormalizeInbound\").first().json;\n    if (nodeInbound) {\n        if (nodeInbound.wa && nodeInbound.wa.provider) {\n             wa = { ...nodeInbound.wa, ...wa };\n        }\n        if (!perguntaCliente) perguntaCliente = nodeInbound.pergunta_cliente || \"\";\n    }\n} catch(e) {}\n\n// 2. Detecta Origem Real (Detectamos se \u00e9 Portal ou Chat P\u00fablico da Home)\nconst isPortal = (wa.provider === 'portal') || \n                 (input.source === 'PORTAL_WEB') || \n                 (input.origem === 'PORTAL_WEB') || \n                 (input.event && input.event.includes('dashboard')) || \n                 !!input.identifier || \n                 !!input.session_id; // Identifica o chat da Home\n\nconst isWhatsApp = !isPortal;\n\nif (isWhatsApp) {\n    const pn = (input.PhoneNumber || (wa.chatId ? wa.chatId.split('@')[0] : (input.from || \"\"))).toString().trim();\n    return [{\n        json: {\n            wa: {\n                ...wa,\n                provider: \"waha\",\n                session: (wa.session || input.session || \"default\").trim(),\n                chatId: (wa.chatId || (pn + \"@c.us\")).trim(),\n                name: (wa.name || input.name || \"\").trim(),\n                messageId: (wa.messageId || input.payload_id || (\"wa_\" + Date.now())).trim(),\n                text: (wa.text || perguntaCliente || \"\").trim()\n            },\n            pergunta_cliente: (perguntaCliente || wa.text || \"\").trim(),\n            cliente: {\n                phoneNumber: pn,\n                nome_whatsapp: (wa.name || input.name || \"\").trim()\n            },\n            ...input \n        }\n    }];\n} else {\n    // L\u00d3GICA PORTAL / HOME\n    const portalId = (input.session_id || input.identifier || input.cpf || \"portal_user\").toString().trim();\n    \n    return [{\n        json: {\n            wa: {\n                ...wa,\n                provider: \"portal\",\n                session: (input.session_id) ? portalId : (wa.session || portalId + \"_v2\").trim(),\n                chatId: (wa.chatId || input.chat_id || portalId).trim(),\n                name: (input.client_name || input.name || \"\").trim(),\n                messageId: (wa.messageId || \"web_\" + Date.now()).trim(),\n                text: (perguntaCliente || wa.text || \"\").trim()\n            },\n            pergunta_cliente: (perguntaCliente || wa.text || \"\").trim(),\n            ...input\n        }\n    }];\n}\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -304,
        496
      ],
      "id": "074849fb-1ea8-43a5-992f-68a9d3a82777",
      "name": "CheckOrigem"
    },
    {
      "parameters": {
        "operation": "get",
        "propertyName": "sessaoExistente",
        "key": "=status_2fa:{{ $('CheckOrigem').first().json.wa.chatId }}",
        "options": {
          "dotNotation": true
        }
      },
      "type": "n8n-nodes-base.redis",
      "typeVersion": 1,
      "position": [
        1344,
        496
      ],
      "id": "65bfc741-9977-4ef4-9985-c37b35b53b55",
      "name": "Buscar_Sessao_Existente",
      "credentials": {
        "redis": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "let cliente = {};\ntry { \n    const busca = $(\"Busca_Cliente\").first();\n    if (busca && busca.json && busca.json.id_cliente) { cliente = busca.json; }\n} catch(e) {}\n\nconst wa = $json.wa || {};\nconst chatId = wa.chatId || \"\";\nconst phoneNumber = (chatId.replace(\"@c.us\", \"\") || $json.PhoneNumber || $json.telefone || \"\").toString();\n\nlet consolidado = {};\ntry { consolidado = $(\"ConsolidarMensagem\").first().json || {}; } catch(e) {}\n\nlet perguntaAgente = consolidado.mensagem_final || wa.text || $json.pergunta_cliente || \"\";\n\n// VERIFICA\u00c7\u00c3O DE SEGURAN\u00c7A\nlet isValid = false;\ntry {\n    const nodeRedis = $(\"Buscar_Sessao_Existente\").first();\n    if (nodeRedis && nodeRedis.json && nodeRedis.json.sessaoExistente === \"VALIDADO\") { isValid = true; }\n    \n    if (wa.authenticated === true || $json.authenticated === true || $json.token_validado === \"SIM\") { \n        isValid = true; \n    }\n} catch (e) { isValid = false; }\n\n// INSTRU\u00c7\u00c3O DIN\u00c2MICA DE ACESSO\nconst isPortal = wa.provider === 'portal' || $json.origem === 'PORTAL_WEB';\nlet instrucaoAcesso = \"\";\nif (isPortal && isValid) {\n    instrucaoAcesso = \"Como voc\u00ea j\u00e1 est\u00e1 logado, voc\u00ea pode acessar todos os detalhes de suas ap\u00f3lices e baixar documentos diretamente no menu lateral esquerdo do portal! \ud83d\ude0a\";\n} else {\n    instrucaoAcesso = \"Para acessar seus dados e documentos com seguran\u00e7a, acesse nosso portal:\\n\ud83d\udd17 https://brokeria-api-brokeriaweb.cx0m9g.easypanel.host/login.html\\n\\nL\u00e1 voc\u00ea encontra todas as suas informa\u00e7\u00f5es e pode baixar seus documentos! \ud83d\ude0a\";\n}\n\n// =========================================================\n// PROCESSAMENTO DE AP\u00d3LICES\n// =========================================================\nlet todasApolices = (cliente.apolices_detalhadas || []);\n\n// LISTA PARA RESPOSTAS GERAIS\nlet listaFormatada = todasApolices.map(ap => {\n    const status = String(ap.status_apolice || 'INDEFINIDO').toUpperCase();\n    const vigencia = (ap.vigencia_inicio && ap.vigencia_fim) \n        ? `${new Date(ap.vigencia_inicio).toLocaleDateString('pt-BR')} at\u00e9 ${new Date(ap.vigencia_fim).toLocaleDateString('pt-BR')}`\n        : 'Vig\u00eancia n\u00e3o informada';\n    \n    return `- ${ap.seguradora || 'Seguradora N/A'} | Ramo: ${ap.ramo || 'Seguro'} | Status: ${status} | Vig\u00eancia: ${vigencia} | Placa: ${ap.placa || 'N/A'} | Assist\u00eancia 24h: ${ap.telefone_0800 || 'N\u00e3o informado'} / ${ap.telefone_capital || 'N\u00e3o informado'} | Site: ${ap.site_url || 'N\u00e3o informado'}`;\n}).join('\\n');\n\n// DADOS CR\u00cdTICOS PARA SINISTRO (Garantia de Match)\nlet dadosSinistroCritico = todasApolices.filter(ap => \n    String(ap.status_apolice).toUpperCase() === 'ATIVA'\n).map(ap => {\n    return `[DADOS DE EMERG\u00caNCIA] Seguradora: ${ap.seguradora.toUpperCase()} | Ramo: ${ap.ramo} | Assist\u00eancia 24h: ${ap.telefone_0800} / ${ap.telefone_capital} | Site para Sinistro: ${ap.site_url}`;\n}).join('\\n');\n\nif (todasApolices.length === 0) {\n    listaFormatada = \"Nenhuma ap\u00f3lice encontrada.\";\n    dadosSinistroCritico = \"CLIENTE SEM AP\u00d3LICES ATIVAS NO SISTEMA.\";\n}\n\nlet apolicesAtivas = todasApolices.filter(ap => \n    ap && ap.status_apolice && String(ap.status_apolice).toUpperCase() === 'ATIVA'\n);\n\nconst apolicesSeguras = apolicesAtivas.map(ap => ({\n    seguradora: ap.seguradora,\n    ramo: ap.ramo,\n    status: ap.status_apolice,\n    numero_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    link_download: isValid ? (ap.link_url_apolice || \"Nao disponivel\") : \"(Oculto)\",\n    telefone_0800: ap.telefone_0800 || \"Nao informado\",\n    telefone_capital: ap.telefone_capital || \"Nao informado\",\n    site_seguradora: ap.site_url || \"Nao informado\"\n}));\n\nreturn [{\n    json: {\n        pergunta_cliente: perguntaAgente,\n        contexto: {\n            nome: (cliente.nome_identificado || cliente.nome_completo || wa.name || $json.client_name || \"\").trim(),\n            cpf_real: (cliente.cpf || $json.cpf || \"\").toString().replace(/\\D/g, ''), \n            cpf_exibicao: (cliente.cpf || $json.cpf) ? `***.***.***-${(cliente.cpf || $json.cpf).toString().slice(-2)}` : \"Nao informado\",\n            telefone: (cliente.celular || phoneNumber || $json.telefone || \"\").toString(),\n            email_cadastrado: (cliente.email || $json.email || \"\").toString(),\n            token_validado: isValid ? \"SIM\" : \"NAO\",\n            instrucao_acesso: instrucaoAcesso,\n            cadastrado: !!cliente.id_cliente,\n            id_cliente: cliente.id_cliente || null,\n            resumo_apolices: apolicesSeguras,\n            lista_apolices_texto: listaFormatada,\n            dados_sinistro_critico: dadosSinistroCritico,\n            tipo_cliente: cliente.id_cliente ? \"CADASTRADO\" : \"NOVO\",\n            origem: wa.provider || $json.origem || \"WHATSAPP\"\n        }\n    }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2592,
        544
      ],
      "id": "e18193c3-0e64-494f-ad07-ee8e0e9c90ae",
      "name": "DefinirContextoAI",
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 2
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.wa?.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": "={{ $json.wa?.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": "={{ $json.wa?.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": [
        1552,
        480
      ],
      "id": "18f8c15c-68d3-44a5-b7a5-f4145d0178bf",
      "name": "SwitchMidia"
    },
    {
      "parameters": {
        "url": "={{ ($json.wa?.media_url || \"\").replace(/https?:\\/\\/[^\\/]+/, 'http://waha:3000') }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "wahaApi",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        1824,
        128
      ],
      "id": "0c9ce326-b51f-4022-a475-e0c311be403d",
      "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": [
        2000,
        128
      ],
      "id": "65ca2c1e-8e55-4a58-809f-d4f009d0ea02",
      "name": "IA_Processar_Audio",
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueErrorOutput",
      "notes": "Em caso de erro, prossiga o chat."
    },
    {
      "parameters": {
        "url": "={{ ($json.wa?.media_url || \"\").replace(/https?:\\/\\/[^\\/]+/, 'http://waha:3000') }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "wahaApi",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        1824,
        288
      ],
      "id": "aa40f9ea-14e5-4bc2-8e53-ee78d44cc899",
      "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": [
        2000,
        288
      ],
      "id": "0e6d0742-2385-4771-a3ca-3dba2b1172d8",
      "name": "IA_Processar_Imagem",
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueErrorOutput",
      "notes": "Em caso de erro, prossiga o chat."
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst wa = input.wa || {};\n\n// Busca em TODAS as fontes poss\u00edveis, com \u00e2ncoras fixas\nlet textoFinal = \"\";\n\ntry {\n    // 1. Tenta pegar o texto j\u00e1 agrupado (Melhor fonte para o Bot)\n    textoFinal = $(\"Agrupar_Texto\").first().json.texto_combinado;\n} catch(e) {\n    try {\n        // 2. Tenta pegar o texto que passou pelo CheckOrigem\n        textoFinal = $(\"CheckOrigem\").first().json.wa.text;\n    } catch(e2) {\n        // 3. Fallback para o input atual\n        textoFinal = wa.text || input.message || input.user_text || \"\";\n    }\n}\n\n// Verifica se houve erro em n\u00f3s de m\u00eddia anteriores (Audio/Imagem)\nconst erroMidia = !!input.houve_erro_midia;\n\nif (textoFinal) {\n    textoFinal = textoFinal.replace(/\\[\\d{2}:\\d{2}, \\d{2}\\/\\d{2}\\/\\d{4}\\] .*?: /g, \"\");\n}\n\n\nreturn [{\n    json: {\n        ...input,\n        wa: {\n            ...wa,\n            text: textoFinal || wa.text || \"\"\n        },\n        mensagem_final: (textoFinal || \"\").trim(),\n        pergunta_cliente: (textoFinal || \"\").trim(),\n        tipo_entrada: input.tipo_entrada || wa.mediaType || \"chat\",\n        teve_erro_ia: erroMidia,\n        houve_erro_midia: erroMidia // <-- AGORA O SWITCH VAI FUNCIONAR!\n    }\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2256,
        528
      ],
      "id": "f9d2844f-fb00-4f82-a59f-7c705ffe1e79",
      "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": [
        2400,
        528
      ],
      "id": "5caeeced-2fa4-4b6e-aaf7-317e84f07f7a",
      "name": "TratamentoErroMidia"
    },
    {
      "parameters": {
        "model": "google/gemini-2.5-flash",
        "options": {
          "maxTokens": 2048
        }
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "typeVersion": 1,
      "position": [
        1232,
        1504
      ],
      "id": "17e213a9-e427-4400-b935-37aaac786030",
      "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": [
        1424,
        1504
      ],
      "id": "4a4153cc-90c0-410f-85ff-4910398285cf",
      "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": [
        1616,
        1504
      ],
      "id": "2a8d694c-c721-4a2f-ac00-df0c74785cfa",
      "name": "QueryPostgresClientes",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "f13f09b9-af26-4aa3-9761-edc7d76ff32e",
              "name": "wa.chatId",
              "value": "={{ $json.PhoneNumber ? $json.PhoneNumber + '@c.us' : $json.wa.chatId }}",
              "type": "string"
            },
            {
              "id": "fdb1c7bb-c3a8-4a98-a52e-563720599d08",
              "name": "wa.session",
              "value": "={{ $json.session || 'default' }}",
              "type": "string"
            },
            {
              "id": "178fd429-e3cb-4404-ba2e-bb9a4878ec3f",
              "name": "wa.name",
              "value": "={{ $json.wa?.name || $json.name || $json.pushName || '' }}",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        688,
        -288
      ],
      "id": "590f7ca2-9a76-40ee-a9ed-e4c6af67a262",
      "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": "={{ $('CheckOrigem').first().json.wa.chatId }}",
              "type": "string"
            },
            {
              "id": "target_session",
              "name": "target_session",
              "value": "={{ $('CheckOrigem').first().json.wa.session }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2640,
        896
      ],
      "id": "1aeef07f-9af3-4858-b8ac-bb3d449b4e10",
      "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": [
        1744,
        976
      ],
      "id": "60cdd828-f544-4fa9-99d9-63b772f626ea",
      "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": [
        2448,
        896
      ],
      "id": "4c10015b-1bb8-46af-be6b-af2a82231c19",
      "name": "Registrar_Interacao",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "// 1. Coleta a mensagem de todas as fontes poss\u00edveis\nlet mensagem = $json.response_text || $json.mensagem_final || $json.output || $json.text || \"\";\n\n// 2. Se a mensagem ainda estiver vazia, tenta buscar diretamente no n\u00f3 do Agente\nif (!mensagem || mensagem === \"\") {\n    try {\n        mensagem = $(\"BrokerIA_Master\").first().json.output;\n    } catch(e) {}\n}\n\n// 3. Limpeza de logs t\u00e9cnicos\nmensagem = mensagem.replace(/\\[Used tools:.*?\\]/gis, '')\n                   .replace(/Tool:.*?(?=\\n|$)/gi, '')\n                   .replace(/Input:.*?(?=\\n|$)/gi, '')\n                   .replace(/Result:.*?(?=\\n|$)/gi, '')\n                   .replace(/\\{.*?\"accepted\".*?\\}/gs, '')\n                   .replace(/\\n{3,}/g, '\\n\\n').trim();\n\n// 4. RETORNAR APENAS A MENSAGEM (SEM FALLBACK DE OL\u00c1)\nreturn {\n  json: {\n    ...$json,\n    text: mensagem\n  }\n};\n\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3232,
        896
      ],
      "id": "c7f10e80-0c1f-4d7c-9237-8e0bf822535a",
      "name": "FiltrarResposta"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.pergunta_cliente }}\n\n",
        "options": {
          "systemMessage": "======================================================================\n\ud83d\udcf1 FORMATO DE RESPOSTA (ESTILO MENU/URA) - CR\u00cdTICO\n==================================================================\nPara melhorar a experi\u00eancia do usu\u00e1rio, sempre que o cliente solicitar algo que tenha categorias ou variantes, APRESENTE AS OP\u00c7\u00d5ES EM LISTA NUMERADA.\nO objetivo \u00e9 permitir que o cliente responda de forma r\u00e1pida e simples (apenas digitando o n\u00famero).\n\nEXEMPLO DE INTERA\u00c7\u00c3O OBRIGAT\u00d3RIA:\nCliente: \"Quero uma cota\u00e7\u00e3o\"\nVoc\u00ea: \"\u00d3timo! Para qual tipo de seguro voc\u00ea deseja cota\u00e7\u00e3o?\n1. Autom\u00f3vel \ud83d\ude97\n2. Residencial \ud83c\udfe0\n3. Vida \u2764\ufe0f\n4. Empresarial \ud83c\udfe2\n5. Outros Assist\u00eancias\"\n\nPRIORIZE SEMPRE esse formato de \"URA Inteligente/Menu\" em vez de perguntas abertas.\n\n=================================================================\n\ud83d\udea8 SINISTROS E EMERG\u00caNCIAS - PROTOCOLO \"FONTE DE VERDADE\" \ud83d\udea8\n=================================================================\nSe o cliente relatar um sinistro (acidente, roubo, furto, colis\u00e3o) ou precisar de assist\u00eancia:\n1. Demonstre empatia imediata (ex: \"Sinto muito pelo ocorrido, vou te ajudar agora.\").\n2. VOC\u00ca DEVE BUSCAR OS DADOS DE CONTATO E AP\u00d3LICE EXCLUSIVAMENTE NESTE CAMPO:\n   \ud83d\udc49 {{ $json.contexto.dados_sinistro_critico }}\n3. REGRAS DE OURO PARA SINISTRO:\n   - APRESENTE SEMPRE o n\u00famero da ap\u00f3lice (mesmo que mascarado) para facilitar a identifica\u00e7\u00e3o do cliente perante a seguradora.\n   - NUNCA mencione seguradoras que n\u00e3o estejam na lista acima.\n   - Forne\u00e7a os telefones de assist\u00eancia (0800 e Capital) e o site EXATAMENTE como constam no campo cr\u00edtico.\n   - Se a lista estiver vazia ou disser \"SEM AP\u00d3LICES ATIVAS\", diga que n\u00e3o localizou uma ap\u00f3lice ativa e pe\u00e7a o CPF para confer\u00eancia.\n\n=================================================================\n\ud83c\udf10 PORTAL DO CLIENTE vs WHATSAPP - L\u00d3GICA DE RESPOSTA\n=================================================================\nVoc\u00ea deve identificar onde o cliente est\u00e1 falando (Origem: {{ $json.contexto.origem }}).\n\n\ud83d\ude80 CEN\u00c1RIO A: CLIENTE NO PORTAL (origem: portal)\n- O cliente J\u00c1 EST\u00c1 LOGADO e SEGURO.\n- N\u00c3O ENVIE links de login ou instru\u00e7\u00f5es de acesso ao portal.\n- Responda DIRETAMENTE \u00e0s perguntas sobre ap\u00f3lices, vencimentos e dados usando o resumo dispon\u00edvel.\n- Se ele pedir documentos, diga: \"Voc\u00ea pode baixar seus documentos clicando no menu lateral esquerdo aqui no portal! \ud83d\ude0a\"\n\n\ud83d\udcf1 CEN\u00c1RIO B: CLIENTE NO WHATSAPP (origem: waha)\n- Redirecione para o portal para: COTA\u00c7\u00d5ES, Documentos detalhados ou altera\u00e7\u00f5es cadastrais.\n- Use a RESPOSTA PADR\u00c3O: \"{{ $json.contexto.instrucao_acesso }}\"\n\n=================================================================\n\ud83d\udccb REGISTRO DE COTA\u00c7\u00d5ES - REGRA OBRIGAT\u00d3RIA\n=================================================================\nQuando o cliente solicitar uma cota\u00e7\u00e3o:\n1. Primeiro, ofere\u00e7a a cota\u00e7\u00e3o pelo portal (especialmente no WhatsApp):\n\ud83d\udd17 https://brokeria-api-brokeriaweb.cx0m9g.easypanel.host/cotacao.html\n\n\u274c N\u00c3O pe\u00e7a telefone ou e-mail.\n\u274c N\u00c3O diga que vai passar para o corretor Washington para fazer a cota\u00e7\u00e3o.\nOriente sempre o autoatendimento pelo link acima.\n\n-----------------------------------------------------------------\n\ud83d\udd0d CONTEXTO DIN\u00c2MICO\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\nREGRAS DE OURO:\n1. Sempre use o nome do cliente: {{ $json.contexto.nome.split(' ')[0] }}.\n2. Se o cliente perguntar sobre seguros, ofere\u00e7a uma cota\u00e7\u00e3o usando o formato de lista e o link do portal.\n\nINSTRUCAO DE RESPOSTA:\nResponda de forma curta e objetiva. Nao invente informacoes. Se nao souber algo fora do escopo de cota\u00e7\u00f5es, diga que vai transferir para o corretor Washington.\n\n\ud83d\udcca RESUMO COMPLETO DE AP\u00d3LICES:\n{{ $json.contexto.lista_apolices_texto }}\n\n=================================================================\n\ud83d\udccb INFORMA\u00c7\u00d5ES INSTITUCIONAIS\n=================================================================\nRespons\u00e1vel: Washington William de Oliveira | Susep: 10.2028529.9\nWebsite: https://www.dwfseguros.com.br\n\n=================================================================\n\ud83d\udeab LIMITES E LGPD\n=================================================================\n- COTA\u00c7\u00d5ES: NUNCA forne\u00e7a valores de pr\u00eamio e NUNCA pe\u00e7a dados de contato. Encaminhe ao link de cota\u00e7\u00e3o.\n- CPF/CNPJ: SEMPRE mascarado.\n- Seja formal, consultivo e simp\u00e1tico. Saude sempre pelo primeiro nome.\n\n\n\n",
          "maxIterations": 10
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3,
      "position": [
        1440,
        976
      ],
      "id": "8cb54808-2c3b-44a5-bf9c-39188751c7db",
      "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": [
        2976,
        544
      ],
      "id": "6ca5643e-f13d-41d4-a8f3-2f48782bf5f2",
      "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": [
        3104,
        384
      ],
      "id": "fb4f8474-f7ae-4217-b471-dc4f8967b85f",
      "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": [
        3312,
        384
      ],
      "id": "784420cc-6f1d-4b30-a75e-b9406b8a3921",
      "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-20ce8961a9cd"
                  }
                ],
                "combinator": "and"
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 3
                },
                "conditions": [
                  {
                    "id": "32739ffd-b920-42b7-91e3-cc77369defd7",
                    "leftValue": "={{ $json.id_atendimento ? String($json.id_atendimento) : '' }}",
                    "rightValue": "",
                    "operator": {
                      "type": "string",
                      "operation": "notEmpty",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              }
            }
          ]
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.4,
      "position": [
        3520,
        384
      ],
      "id": "7ffab47d-1d20-484b-b510-5224a0374a7c",
      "name": "Switch_Registro_Existe"
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO public.brokeria_registros_brokeria (\n    telefone_whatsapp,\n    nome_cliente,\n    resumo_conversa,    -- Ajustado para o padr\u00c3\u00a3o do Dashboard\n    assunto_principal,\n    canal,\n    status_atendimento,\n    qtde_mensagens,\n    recebeu_arquivos,\n    tipos_documentos,\n    etapa_funil,\n    data_atendimento,\n    hora_inicio\n) VALUES (\n    '{{ $(\"Preparar_Registro_Cliente\").first().json.telefone }}',\n    '{{ $(\"Preparar_Registro_Cliente\").first().json.nome_whatsapp }}',\n    -- Inicia o log j\u00c3\u00a1 formatado\n    E'[' || TO_CHAR(NOW() AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo', 'HH24:MI, DD/MM/YYYY') || E'] ' || '{{ $(\"Preparar_Registro_Cliente\").first().json.nome_whatsapp }}' || E': ' || '{{ $(\"Preparar_Registro_Cliente\").first().json.mensagem_inicial.replace(/'/g, \"''\") }}' || E'\\n',\n    '{{ $(\"Preparar_Registro_Cliente\").first().json.tipo_solicitacao }}',\n    'WHATSAPP',\n    'PENDENTE',\n    1,\n    {{ $(\"Preparar_Registro_Cliente\").first().json.recebeu_arquivos || false }},\n    '{{ $(\"Preparar_Registro_Cliente\").first().json.tipos_documentos || \"\" }}',\n    'PRIMEIRO_CONTATO',\n    NOW(),\n    NOW()\n)\nRETURNING *;",
        "options": {}
      },
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        3728,
        192
      ],
      "id": "fc4264c9-7192-4ab9-b637-1983a330c3ee",
      "name": "Inserir_Novo_Registro",
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "UPDATE public.brokeria_registros_brokeria\nSET \n    -- Adiciona a nova mensagem do cliente mantendo o formato [HH:MM]\n    resumo_conversa = 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'] ' || '{{ $(\"Preparar_Registro_Cliente\").first().json.nome_whatsapp }}' || E': ' || '{{ $(\"Preparar_Registro_Cliente\").first().json.mensagem_inicial.replace(/'/g, \"''\") }}' || E'\\n',\n    assunto_principal = '{{ $(\"Preparar_Registro_Cliente\").first().json.tipo_solicitacao }}',\n    qtde_mensagens = COALESCE(qtde_mensagens, 0) + 1,\n    data_ultima_atualizacao = NOW()\nWHERE id_atendimento

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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

How this works

This workflow automates intelligent WhatsApp interactions for businesses handling customer enquiries, delivering quick, context-aware responses that enhance user satisfaction and reduce response times. It suits support teams or e-commerce operations seeking to scale conversations without constant human oversight, leveraging integrations like WAHA for messaging and Google Gemini for AI-driven replies. The key step involves processing incoming webhook triggers through a chain that simulates typing, consults AI for tailored content, and sends polished messages via WhatsApp, all while managing session data in Redis for continuity.

Use this workflow for high-volume WhatsApp support where AI can resolve routine queries, such as order status checks or FAQs, freeing staff for complex issues. Avoid it for sensitive conversations requiring human empathy, like complaints or legal matters, or when compliance demands full audit trails beyond basic logging. Common variations include swapping Google Gemini for OpenRouter models to customise tone, or integrating Postgres for persistent chat memory in long-term engagements.

About this workflow

Brokeria-v20. Uses n8n-nodes-waha, httpRequest, redis, googleGemini. Webhook trigger; 56 nodes.

Source: https://github.com/RogerioCelli/BrokerIAWeb/blob/701836f1142288856bd26776a6cb3653e91f640b/n8n/Brokeria-v21.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

secretaria. Uses postgres, n8n-nodes-evolution-api, openAi, httpRequest. Webhook trigger; 71 nodes.

Postgres, N8N Nodes Evolution Api, OpenAI +12
AI & RAG

Brokeria-v15. Uses n8n-nodes-waha, httpRequest, postgres, redis. Webhook trigger; 55 nodes.

N8N Nodes Waha, HTTP Request, Postgres +7
AI & RAG

'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

Redis, HTTP Request, Supabase +7
AI & RAG

Complete PostgreSQL-backed system: Keyword scoring → AI research → Multi-part content generation → fal.ai Nano Banana image generation → WordPress publishing

WordPress, OpenAI, Perplexity +8
AI & RAG

Flux. Uses lmChatOpenAi, agent, googleGemini, httpRequest. Webhook trigger; 67 nodes.

OpenAI Chat, Agent, Google Gemini +8