From c1327f3e320e6252c8ee1eb1bf99a89ab2a18dec Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Mar 2026 00:56:04 +0000 Subject: [PATCH 01/16] feat: atualiza workflow Alicia Meta Ads com novo token e melhorias MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Substitui token Meta Ads expirado pelo novo token em todos os nós - Corrige detecção de relatório: remove obrigatoriedade da palavra 'semanal' (agora 'Alicia relatório da itag' ou 'Alicia relatório da batata' funcionam) - Expande clientMap com aliases parciais: 'batata', 'itag', 'caribbean', 'larissa', 'gabriel', 'dani', 'antonio', 'escola', 'dogz' - Atualiza formato do relatório com espaçamento correto entre métricas - Adiciona Impressões e Cliques ao relatório automático - Formata números com separador de milhar pt-BR (ex: 43.976) - Nome do cliente em MAIÚSCULAS no cabeçalho - Investimento em destaque: *INVESTIMENTO TOTAL: R$ X.XXX,XX* - Relatórios de vendas (Hi Dogz, Batata Bistrô) usam 'Vendas/Custo por venda' https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 532 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 532 insertions(+) create mode 100644 n8n-alicia-meta-ads.json diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json new file mode 100644 index 000000000..ab7af0cb1 --- /dev/null +++ b/n8n-alicia-meta-ads.json @@ -0,0 +1,532 @@ +{ + "name": "Alicia - Meta Ads v2 (Clientes + Relatórios)", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "alicia-whatsapp", + "options": {} + }, + "id": "7623fe0f-091f-4cde-a0ff-7b53998c4fe1", + "name": "Webhook Evolution API", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2, + "position": [-336, 112], + "webhookId": "alicia-meta-ads-001" + }, + { + "parameters": { + "jsCode": "const raw = $input.first().json;\nconst body = raw.body || raw;\n\nconst data = body.data || {};\nconst key = data.key || {};\nconst msg = data.message || {};\n\nconst text =\n msg.conversation ||\n msg?.extendedTextMessage?.text ||\n msg?.imageMessage?.caption ||\n msg?.videoMessage?.caption ||\n msg?.documentMessage?.caption ||\n '';\n\nconst remoteJid = key.remoteJid || '';\nconst fromMe = key.fromMe || false;\nconst instance = body.instance || 'Teste';\n\nif (fromMe || !text.trim()) return [];\nif (remoteJid.includes('@g.us')) return [];\n\nconst phone = remoteJid\n .replace('@s.whatsapp.net', '')\n .replace('@c.us', '');\n\nreturn [{ json: { text: text.trim(), phone, remoteJid, instance } }];" + }, + "id": "24a967e7-7602-4f55-bea1-a4dff46d1330", + "name": "Parse Message", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [-112, 112] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 1 + }, + "conditions": [ + { + "id": "cond-alicia", + "leftValue": "={{ $json.text.toLowerCase() }}", + "rightValue": "alicia", + "operator": { + "type": "string", + "operation": "contains", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "accdd347-7076-4d05-b4c7-ae43aa1fc4b7", + "name": "Menciona Alicia?", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [112, 112] + }, + { + "parameters": { + "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\nconst fmt = d => d.toISOString().split('T')[0];\nconst addDays = (n) => { const d = new Date(now); d.setDate(d.getDate() + n); return fmt(d); };\nconst dow = now.getDay();\nconst daysToMon = dow === 0 ? 6 : dow - 1;\nconst monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\nconst clientMap = {\n 'antonio neto': { id: 'act_497795903676192', nome: 'Antonio Neto' },\n 'antonio': { id: 'act_497795903676192', nome: 'Antonio Neto' },\n 'gabriel jacinto': { id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n 'gabriel': { id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n 'dani escudero': { id: 'act_2145598605527232', nome: 'Dani Escudero' },\n 'dani': { id: 'act_2145598605527232', nome: 'Dani Escudero' },\n 'agro': { id: 'act_929521557422139', nome: 'Agro' },\n 'escola de musica': { id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n 'escola de m\\u00fasica': { id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n 'escola': { id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n 'panmalhas': { id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil' },\n 'gfix store': { id: 'act_2811791829124905', nome: 'GFiX Store' },\n 'gfix': { id: 'act_2811791829124905', nome: 'GFiX Store' },\n 'arte em gelo': { id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n 'arte em gelo': { id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n 'itag tecnologia': { id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n 'itag': { id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n 'hi dogz': { id: 'act_873195821346004', nome: 'Hi Dogz' },\n 'dogz': { id: 'act_873195821346004', nome: 'Hi Dogz' },\n 'batata bistro': { id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n 'batata bistr\\u00f4': { id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n 'batata': { id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n 'caribbean bronze': { id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n 'caribbean': { id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n 'larissa kelleter': { id: 'act_738280412237298', nome: 'Larissa Kelleter' },\n 'larissa': { id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n};\n\n// Detecta pedido de relatório sem exigir a palavra 'semanal'\nconst isRelatorio = text.includes('relat');\n\nlet clientFound = null;\nconst sortedKeys = Object.keys(clientMap).sort((a, b) => b.length - a.length);\nfor (const key of sortedKeys) {\n if (text.includes(key)) {\n clientFound = clientMap[key];\n break;\n }\n}\n\nconst today = fmt(now);\nconst yesterday = addDays(-1);\nconst last7Start = addDays(-7);\nconst isRelatorioWithClient = isRelatorio && clientFound !== null;\n\nreturn [{\n json: {\n ...input,\n today,\n yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start,\n last7End: yesterday,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio,\n isRelatorioWithClient,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null\n }\n}];" + }, + "id": "5b953b43-14ca-4ed3-b8da-3ec455f58b92", + "name": "Calcular Datas", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [320, 112] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 1 + }, + "conditions": [ + { + "id": "cond-relatorio", + "leftValue": "={{ $json.isRelatorioWithClient }}", + "rightValue": true, + "operator": { + "type": "boolean", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "151d4a8d-966e-4ca1-a145-cc2692dec81e", + "name": "É Relatório Semanal?", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [544, 112] + }, + { + "parameters": { + "url": "={{ 'https://graph.facebook.com/v19.0/' + $json.clientFound + '/insights' }}", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "date_preset", + "value": "last_7d" + }, + { + "name": "fields", + "value": "spend,impressions,clicks,ctr,actions,reach,campaign_name" + }, + { + "name": "level", + "value": "campaign" + }, + { + "name": "access_token", + "value": "={{ $json.metaToken }}" + } + ] + }, + "options": { + "timeout": 15000 + } + }, + "id": "0ad35b21-41cf-4cca-a7a3-f8bfe29f4467", + "name": "Buscar Insights 7 Dias", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [768, -32] + }, + { + "parameters": { + "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\nif (!campaigns.length) {\n return [{ json: { ...ctx, output: '\\u274c Sem dados para *' + (ctx.clientNome || '').toUpperCase() + '* nos \\u00faltimos 7 dias.' } }];\n}\n\n// Contas de compra direta (e-commerce / vendas)\nconst purchaseAccountIds = ['act_873195821346004', 'act_1673012226810411']; // Hi Dogz, Batata Bistr\\u00f4\nconst isPurchase = purchaseAccountIds.includes(ctx.clientFound);\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nconst fmtMoney = (val) => {\n return val.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n};\n\nconst fmtNum = (val) => {\n return parseInt(val || 0).toLocaleString('pt-BR');\n};\n\nlet totalSpend = 0;\nlet totalImpressions = 0;\nlet totalClicks = 0;\n\nfor (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n}\n\nconst clientNameUpper = (ctx.clientNome || '').toUpperCase();\nlet reportText;\n\nif (isPurchase) {\n let totalPurchases = 0;\n for (const c of campaigns) {\n const purchaseAction = (c.actions || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (purchaseAction) totalPurchases += parseInt(purchaseAction.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientNameUpper + '*\\n\\n' +\n '_\\ud83d\\udcc6 ' + fmtBR(ctx.last7Start) + ' a ' + fmtBR(ctx.last7End) + ' (\\u00faltimos 7 dias)_\\n\\n' +\n '*Meta Ads*\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n const LEAD_TYPES = [\n 'lead',\n 'onsite_conversion.lead_grouped',\n 'onsite_conversion.messaging_conversation_started_7d',\n 'onsite_conversion.total_messaging_connection'\n ];\n\n let leadSpend = 0;\n let totalLeads = 0;\n for (const c of campaigns) {\n const leadAction = (c.actions || []).find(a => LEAD_TYPES.includes(a.action_type));\n if (leadAction) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(leadAction.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientNameUpper + '*\\n\\n' +\n '_\\ud83d\\udcc6 ' + fmtBR(ctx.last7Start) + ' a ' + fmtBR(ctx.last7End) + ' (\\u00faltimos 7 dias)_\\n\\n' +\n '*Meta Ads*\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { ...ctx, output: reportText } }];" + }, + "id": "2b3745d9-82ef-4d9a-aa8d-db402408a07e", + "name": "Formatar Relatório", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [992, -32] + }, + { + "parameters": { + "method": "POST", + "url": "https://evolution-evolution-api.rcxmr5.easypanel.host/message/sendText/Teste", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "apikey", + "value": "BE670335A833-4FE4-84CB-29FA65A8ED73" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + { + "name": "number", + "value": "={{ $json.phone }}" + }, + { + "name": "text", + "value": "={{ $json.output }}" + } + ] + }, + "options": { + "timeout": 10000 + } + }, + "id": "b339c96a-4c60-4a45-98ea-4091d5ec847d", + "name": "Enviar Relatório WhatsApp", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [1216, -32] + }, + { + "parameters": { + "url": "https://graph.facebook.com/v19.0/me/adaccounts", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "fields", + "value": "id,name,account_status,currency,amount_spent,balance" + }, + { + "name": "limit", + "value": "50" + }, + { + "name": "access_token", + "value": "={{ $json.metaToken }}" + } + ] + }, + "options": { + "timeout": 10000 + } + }, + "id": "1daa387b-bc23-4e31-b837-e8dcc048044e", + "name": "Buscar Contas Meta", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [768, 256] + }, + { + "parameters": { + "jsCode": "const metaResp = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst allAccounts = metaResp.data || [];\nconst accountsList = allAccounts.map(a => {\n const status = a.account_status === 1 ? '\\u2705 Ativa' : '\\u23f8 Inativa';\n return '\\u2022 ' + a.name + ' | ID: ' + a.id + ' | ' + a.currency + ' | ' + status;\n}).join('\\n') || 'Nenhuma conta encontrada';\nreturn [{ json: { ...ctx, accounts: allAccounts, accountsList } }];" + }, + "id": "9c2de825-ffbd-4870-b0e5-3f4a72c6c7b1", + "name": "Montar Contexto", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [992, 256] + }, + { + "parameters": { + "promptType": "define", + "text": "={{ $json.text }}", + "options": { + "systemMessage": "=Você é Alicia, assistente especializada em Meta Ads. Ajuda gestores de tráfego a analisar métricas de campanhas de forma clara e direta.\n\n📅 DATA ATUAL: {{ $json.today }}\n📅 ONTEM: {{ $json.yesterday }}\n📅 INÍCIO DA SEMANA (seg): {{ $json.weekStart }}\n📅 7 DIAS ATRÁS: {{ $json.last7 }}\n📅 30 DIAS ATRÁS: {{ $json.last30 }}\n\n🧑‍💼 MAPEAMENTO DE CLIENTES → CONTAS:\n• Antonio Neto → act_497795903676192\n• Gabriel Jacinto → act_2493856707309984\n• Dani Escudero → act_2145598605527232\n• Agro → act_929521557422139\n• Escola de Música → act_544347952854095\n• Panmalhas → act_1509005182799187\n• GFiX Store → act_2811791829124905\n• Arte em Gelo → act_5585082641598366\n• ITAG Tecnologia → act_738466861151636\n• Hi Dogz → act_873195821346004\n• Batata Bistrô → act_1673012226810411\n• Caribbean Bronze → act_1082397873705862\n• Larissa Kelleter → act_738280412237298\n\n🏢 CONTAS DISPONÍVEIS (status atual):\n{{ $json.accountsList }}\n\n📋 REGRAS:\n1. Quando o usuário mencionar um cliente pelo nome (parcial ou completo), use o ID de conta do mapeamento acima\n2. Exemplos de variações aceitas: 'itag' = ITAG Tecnologia, 'batata' = Batata Bistrô, 'gfix' = GFiX Store, 'caribbean' = Caribbean Bronze\n3. Se não especificou cliente, busque de TODAS as contas ativas e consolide os valores\n4. date_presets válidos: today, yesterday, this_week_mon_today, last_7d, last_14d, last_30d, last_month\n5. Para LEADS: filtre actions por action_type = 'lead' OU 'onsite_conversion.lead_grouped'\n6. CPL = gasto_total ÷ quantidade_leads\n7. Budgets da Meta vêm em CENTAVOS - divida por 100 para exibir em reais\n8. Responda SEMPRE em português do Brasil\n9. Use emojis para tornar a resposta visual e amigável\n10. Seja concisa mas completa\n\n🎨 FORMATO DE RESPOSTA PARA RELATÓRIO:\n*NOME DO CLIENTE EM MAIÚSCULAS*\n\n_📆 DD/MM a DD/MM (últimos 7 dias)_\n\n*Meta Ads*\n\nImpressões: X.XXX\n\nCliques: X.XXX\n\nLeads: XX (ou Vendas: XX para Hi Dogz e Batata Bistrô)\n\nCusto por lead: R$ XX,XX (ou Custo por venda)\n\n*INVESTIMENTO TOTAL: R$ X.XXX,XX*\n\n⚠️ Se não houver dados para o período, informe o usuário claramente." + } + }, + "id": "36e08f36-8634-478d-a32c-8bf22671531e", + "name": "Alicia Agent", + "type": "@n8n/n8n-nodes-langchain.agent", + "typeVersion": 1.7, + "position": [1216, 256] + }, + { + "parameters": { + "model": "claude-opus-4-5", + "options": { + "maxTokensToSample": 2048, + "temperature": 0.1 + } + }, + "id": "b8f7ba35-80c0-451b-899d-a40a05352a7b", + "name": "Claude Opus", + "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic", + "typeVersion": 1.3, + "position": [960, 480], + "credentials": { + "anthropicApi": { + "id": "7WwG495lbMecbH5G", + "name": "Anthropic API" + } + } + }, + { + "parameters": { + "name": "buscar_insights", + "description": "Busca métricas/insights do Meta Ads. Use para: gasto (spend), leads (actions), impressões, cliques, CTR, CPC, alcance. Envie JSON com: object_id (ID da conta ex: act_123456 OU ID de campanha), date_preset (today/yesterday/this_week_mon_today/last_7d/last_14d/last_30d/last_month), fields (ex: spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type), level opcional (account/campaign/adset/ad).", + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com object_id e date_preset'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst objectId = params.object_id;\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type';\nconst level = params.level || '';\nif (!objectId) return 'Erro: object_id é obrigatório';\nlet url = `https://graph.facebook.com/v19.0/${objectId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\nif (level) url += `&level=${encodeURIComponent(level)}`;\ntry {\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message} (código: ${data.error.code})`;\n if (!data.data || data.data.length === 0) return 'Nenhum dado encontrado para o período solicitado.';\n return JSON.stringify(data.data);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + }, + "id": "13abddfb-207b-4d02-aa60-c7b8658c7860", + "name": "Tool: Buscar Insights", + "type": "@n8n/n8n-nodes-langchain.toolCode", + "typeVersion": 1.1, + "position": [1136, 480] + }, + { + "parameters": { + "name": "listar_campanhas", + "description": "Lista todas as campanhas de uma conta de anúncios. Envie JSON com: account_id (formato: act_XXXXXXXXX). Retorna: id, nome, status, objetivo, budget diário e total. Use para encontrar o ID de uma campanha específica antes de buscar os insights.", + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nif (!accountId) return 'Erro: account_id é obrigatório (formato: act_XXXXXXXXX)';\ntry {\n const url = `https://graph.facebook.com/v19.0/${accountId}/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhuma campanha encontrada nessa conta';\n const campaigns = data.data.map(c => ({\n id: c.id,\n nome: c.name,\n status: c.status,\n objetivo: c.objective,\n budget_diario_reais: c.daily_budget ? (parseInt(c.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: c.lifetime_budget ? (parseInt(c.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(campaigns);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + }, + "id": "731c36fc-e0b1-44a4-a13e-fcda6b6f2a2e", + "name": "Tool: Listar Campanhas", + "type": "@n8n/n8n-nodes-langchain.toolCode", + "typeVersion": 1.1, + "position": [1296, 480] + }, + { + "parameters": { + "name": "listar_conjuntos", + "description": "Lista conjuntos de anúncios (adsets) de uma conta ou campanha. Envie JSON com: account_id (act_XXXXXXXXX) OU campaign_id (ID numérico). Retorna: id, nome, status e budget.", + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id ou campaign_id'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nconst campaignId = params.campaign_id;\nif (!accountId && !campaignId) return 'Erro: forneça account_id ou campaign_id';\nconst baseId = campaignId || accountId;\ntry {\n const url = `https://graph.facebook.com/v19.0/${baseId}/adsets?fields=id,name,status,daily_budget,lifetime_budget,campaign_id&limit=100&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhum conjunto de anúncios encontrado';\n const adsets = data.data.map(a => ({\n id: a.id,\n nome: a.name,\n status: a.status,\n budget_diario_reais: a.daily_budget ? (parseInt(a.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: a.lifetime_budget ? (parseInt(a.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(adsets);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + }, + "id": "a8b12824-f38e-4296-87fe-49bd6e9f934b", + "name": "Tool: Listar Conjuntos", + "type": "@n8n/n8n-nodes-langchain.toolCode", + "typeVersion": 1.1, + "position": [1408, 480] + }, + { + "parameters": { + "name": "buscar_insights_multiplas_contas", + "description": "Busca insights de MÚLTIPLAS contas ao mesmo tempo. Use quando o usuário não especificar cliente. Envie JSON com: account_ids (array de IDs ex: ['act_111','act_222']), date_preset, fields.", + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_ids (array), date_preset e fields'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountIds = params.account_ids || [];\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (accountIds.length === 0) return 'Erro: account_ids deve ser um array com pelo menos 1 conta';\ntry {\n const results = [];\n for (const accountId of accountIds) {\n const url = `https://graph.facebook.com/v19.0/${accountId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (!data.error && data.data && data.data.length > 0) {\n results.push({ account_id: accountId, data: data.data[0] });\n } else if (data.error) {\n results.push({ account_id: accountId, error: data.error.message });\n } else {\n results.push({ account_id: accountId, data: null, note: 'sem dados no período' });\n }\n }\n return JSON.stringify(results);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + }, + "id": "81aa87da-6607-4ffc-95b9-1be17e23ec68", + "name": "Tool: Insights Multiplas Contas", + "type": "@n8n/n8n-nodes-langchain.toolCode", + "typeVersion": 1.1, + "position": [1520, 480] + }, + { + "parameters": { + "method": "POST", + "url": "https://evolution-evolution-api.rcxmr5.easypanel.host/message/sendText/Teste", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "apikey", + "value": "BE670335A833-4FE4-84CB-29FA65A8ED73" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + { + "name": "number", + "value": "={{ $json.phone }}" + }, + { + "name": "text", + "value": "={{ $json.output }}" + } + ] + }, + "options": { + "timeout": 10000 + } + }, + "id": "767c226e-c649-487e-be1f-5a19db725162", + "name": "Enviar Resposta WhatsApp", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [1520, 256] + } + ], + "pinData": {}, + "connections": { + "Webhook Evolution API": { + "main": [ + [ + { + "node": "Parse Message", + "type": "main", + "index": 0 + } + ] + ] + }, + "Parse Message": { + "main": [ + [ + { + "node": "Menciona Alicia?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Menciona Alicia?": { + "main": [ + [ + { + "node": "Calcular Datas", + "type": "main", + "index": 0 + } + ] + ] + }, + "Calcular Datas": { + "main": [ + [ + { + "node": "É Relatório Semanal?", + "type": "main", + "index": 0 + } + ] + ] + }, + "É Relatório Semanal?": { + "main": [ + [ + { + "node": "Buscar Insights 7 Dias", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Buscar Contas Meta", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Insights 7 Dias": { + "main": [ + [ + { + "node": "Formatar Relatório", + "type": "main", + "index": 0 + } + ] + ] + }, + "Formatar Relatório": { + "main": [ + [ + { + "node": "Enviar Relatório WhatsApp", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Contas Meta": { + "main": [ + [ + { + "node": "Montar Contexto", + "type": "main", + "index": 0 + } + ] + ] + }, + "Montar Contexto": { + "main": [ + [ + { + "node": "Alicia Agent", + "type": "main", + "index": 0 + } + ] + ] + }, + "Alicia Agent": { + "main": [ + [ + { + "node": "Enviar Resposta WhatsApp", + "type": "main", + "index": 0 + } + ] + ] + }, + "Claude Opus": { + "ai_languageModel": [ + [ + { + "node": "Alicia Agent", + "type": "ai_languageModel", + "index": 0 + } + ] + ] + }, + "Tool: Buscar Insights": { + "ai_tool": [ + [ + { + "node": "Alicia Agent", + "type": "ai_tool", + "index": 0 + } + ] + ] + }, + "Tool: Listar Campanhas": { + "ai_tool": [ + [ + { + "node": "Alicia Agent", + "type": "ai_tool", + "index": 0 + } + ] + ] + }, + "Tool: Listar Conjuntos": { + "ai_tool": [ + [ + { + "node": "Alicia Agent", + "type": "ai_tool", + "index": 0 + } + ] + ] + }, + "Tool: Insights Multiplas Contas": { + "ai_tool": [ + [ + { + "node": "Alicia Agent", + "type": "ai_tool", + "index": 0 + } + ] + ] + } + }, + "active": true, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "availableInMCP": false + }, + "versionId": "be0265a4-12a2-4b9c-9261-6f0a5ccb0059", + "meta": { + "instanceId": "e41a2f460652c42ec14d34a1f52c8950eaafd8f9e5a90fe9db358faaccf046d7" + }, + "id": "uIUtsjBWTxjZObX3", + "tags": [] +} From b1d23e1a8176b62e1001f537a8b21c7c39af4741 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Mar 2026 01:21:01 +0000 Subject: [PATCH 02/16] fix: corrige todos os erros do workflow Alicia Meta Ads - Remove chave duplicada 'arte em gelo' no clientMap (causava erro JS) - Substitui toLocaleString('pt-BR') por formatacao manual com regex (Node.js do n8n nao tem ICU completo para locale pt-BR) - Corrige spread do objeto: last7Start e last7End declarados explicitamente - Reescreve loops com var em vez de const/let dentro de loops aninhados - Montar Contexto passa phone explicitamente para garantir chegada no Agent - Formatar Relatorio retorna apenas phone + output para simplificar o fluxo - Remove acentos dos nomes dos nos para evitar problemas de encoding - Parse Message usa compatibilidade sem optional chaining (?.) https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index ab7af0cb1..cc925f1a6 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -16,7 +16,7 @@ }, { "parameters": { - "jsCode": "const raw = $input.first().json;\nconst body = raw.body || raw;\n\nconst data = body.data || {};\nconst key = data.key || {};\nconst msg = data.message || {};\n\nconst text =\n msg.conversation ||\n msg?.extendedTextMessage?.text ||\n msg?.imageMessage?.caption ||\n msg?.videoMessage?.caption ||\n msg?.documentMessage?.caption ||\n '';\n\nconst remoteJid = key.remoteJid || '';\nconst fromMe = key.fromMe || false;\nconst instance = body.instance || 'Teste';\n\nif (fromMe || !text.trim()) return [];\nif (remoteJid.includes('@g.us')) return [];\n\nconst phone = remoteJid\n .replace('@s.whatsapp.net', '')\n .replace('@c.us', '');\n\nreturn [{ json: { text: text.trim(), phone, remoteJid, instance } }];" + "jsCode": "const raw = $input.first().json;\nconst body = raw.body || raw;\n\nconst data = body.data || {};\nconst key = data.key || {};\nconst msg = data.message || {};\n\nconst text =\n msg.conversation ||\n (msg.extendedTextMessage && msg.extendedTextMessage.text) ||\n (msg.imageMessage && msg.imageMessage.caption) ||\n (msg.videoMessage && msg.videoMessage.caption) ||\n (msg.documentMessage && msg.documentMessage.caption) ||\n '';\n\nconst remoteJid = key.remoteJid || '';\nconst fromMe = key.fromMe || false;\nconst instance = body.instance || 'Teste';\n\nif (fromMe || !text.trim()) return [];\nif (remoteJid.includes('@g.us')) return [];\n\nconst phone = remoteJid\n .replace('@s.whatsapp.net', '')\n .replace('@c.us', '');\n\nreturn [{ json: { text: text.trim(), phone, remoteJid, instance } }];" }, "id": "24a967e7-7602-4f55-bea1-a4dff46d1330", "name": "Parse Message", @@ -57,7 +57,7 @@ }, { "parameters": { - "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\nconst fmt = d => d.toISOString().split('T')[0];\nconst addDays = (n) => { const d = new Date(now); d.setDate(d.getDate() + n); return fmt(d); };\nconst dow = now.getDay();\nconst daysToMon = dow === 0 ? 6 : dow - 1;\nconst monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\nconst clientMap = {\n 'antonio neto': { id: 'act_497795903676192', nome: 'Antonio Neto' },\n 'antonio': { id: 'act_497795903676192', nome: 'Antonio Neto' },\n 'gabriel jacinto': { id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n 'gabriel': { id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n 'dani escudero': { id: 'act_2145598605527232', nome: 'Dani Escudero' },\n 'dani': { id: 'act_2145598605527232', nome: 'Dani Escudero' },\n 'agro': { id: 'act_929521557422139', nome: 'Agro' },\n 'escola de musica': { id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n 'escola de m\\u00fasica': { id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n 'escola': { id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n 'panmalhas': { id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil' },\n 'gfix store': { id: 'act_2811791829124905', nome: 'GFiX Store' },\n 'gfix': { id: 'act_2811791829124905', nome: 'GFiX Store' },\n 'arte em gelo': { id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n 'arte em gelo': { id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n 'itag tecnologia': { id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n 'itag': { id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n 'hi dogz': { id: 'act_873195821346004', nome: 'Hi Dogz' },\n 'dogz': { id: 'act_873195821346004', nome: 'Hi Dogz' },\n 'batata bistro': { id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n 'batata bistr\\u00f4': { id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n 'batata': { id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n 'caribbean bronze': { id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n 'caribbean': { id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n 'larissa kelleter': { id: 'act_738280412237298', nome: 'Larissa Kelleter' },\n 'larissa': { id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n};\n\n// Detecta pedido de relatório sem exigir a palavra 'semanal'\nconst isRelatorio = text.includes('relat');\n\nlet clientFound = null;\nconst sortedKeys = Object.keys(clientMap).sort((a, b) => b.length - a.length);\nfor (const key of sortedKeys) {\n if (text.includes(key)) {\n clientFound = clientMap[key];\n break;\n }\n}\n\nconst today = fmt(now);\nconst yesterday = addDays(-1);\nconst last7Start = addDays(-7);\nconst isRelatorioWithClient = isRelatorio && clientFound !== null;\n\nreturn [{\n json: {\n ...input,\n today,\n yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start,\n last7End: yesterday,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio,\n isRelatorioWithClient,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null\n }\n}];" + "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\n\nconst fmt = function(d) { return d.toISOString().split('T')[0]; };\nconst addDays = function(n) {\n const d = new Date(now);\n d.setDate(d.getDate() + n);\n return fmt(d);\n};\n\nconst dow = now.getDay();\nconst daysToMon = dow === 0 ? 6 : dow - 1;\nconst monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\nconst clientMap = {\n 'antonio neto': { id: 'act_497795903676192', nome: 'Antonio Neto' },\n 'antonio': { id: 'act_497795903676192', nome: 'Antonio Neto' },\n 'gabriel jacinto': { id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n 'gabriel': { id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n 'dani escudero': { id: 'act_2145598605527232', nome: 'Dani Escudero' },\n 'dani': { id: 'act_2145598605527232', nome: 'Dani Escudero' },\n 'agro': { id: 'act_929521557422139', nome: 'Agro' },\n 'escola de musica': { id: 'act_544347952854095', nome: 'Escola de Musica' },\n 'escola': { id: 'act_544347952854095', nome: 'Escola de Musica' },\n 'panmalhas': { id: 'act_1509005182799187', nome: 'Panmalhas' },\n 'gfix store': { id: 'act_2811791829124905', nome: 'GFiX Store' },\n 'gfix': { id: 'act_2811791829124905', nome: 'GFiX Store' },\n 'arte em gelo': { id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n 'arte': { id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n 'itag tecnologia': { id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n 'itag': { id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n 'hi dogz': { id: 'act_873195821346004', nome: 'Hi Dogz' },\n 'dogz': { id: 'act_873195821346004', nome: 'Hi Dogz' },\n 'batata bistro': { id: 'act_1673012226810411', nome: 'Batata Bistro' },\n 'batata': { id: 'act_1673012226810411', nome: 'Batata Bistro' },\n 'caribbean bronze': { id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n 'caribbean': { id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n 'larissa kelleter': { id: 'act_738280412237298', nome: 'Larissa Kelleter' },\n 'larissa': { id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n};\n\nconst isRelatorio = text.includes('relat');\n\nvar clientFound = null;\nvar sortedKeys = Object.keys(clientMap).sort(function(a, b) { return b.length - a.length; });\nfor (var i = 0; i < sortedKeys.length; i++) {\n if (text.includes(sortedKeys[i])) {\n clientFound = clientMap[sortedKeys[i]];\n break;\n }\n}\n\nconst today = fmt(now);\nconst yesterday = addDays(-1);\nconst last7Start = addDays(-7);\nconst last7End = yesterday;\nconst isRelatorioWithClient = isRelatorio && clientFound !== null;\n\nreturn [{\n json: {\n text: input.text,\n phone: input.phone,\n remoteJid: input.remoteJid,\n instance: input.instance,\n today: today,\n yesterday: yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start: last7Start,\n last7End: last7End,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio: isRelatorio,\n isRelatorioWithClient: isRelatorioWithClient,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null\n }\n}];" }, "id": "5b953b43-14ca-4ed3-b8da-3ec455f58b92", "name": "Calcular Datas", @@ -90,7 +90,7 @@ "options": {} }, "id": "151d4a8d-966e-4ca1-a145-cc2692dec81e", - "name": "É Relatório Semanal?", + "name": "E Relatorio Semanal?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [544, 112] @@ -131,10 +131,10 @@ }, { "parameters": { - "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\nif (!campaigns.length) {\n return [{ json: { ...ctx, output: '\\u274c Sem dados para *' + (ctx.clientNome || '').toUpperCase() + '* nos \\u00faltimos 7 dias.' } }];\n}\n\n// Contas de compra direta (e-commerce / vendas)\nconst purchaseAccountIds = ['act_873195821346004', 'act_1673012226810411']; // Hi Dogz, Batata Bistr\\u00f4\nconst isPurchase = purchaseAccountIds.includes(ctx.clientFound);\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nconst fmtMoney = (val) => {\n return val.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n};\n\nconst fmtNum = (val) => {\n return parseInt(val || 0).toLocaleString('pt-BR');\n};\n\nlet totalSpend = 0;\nlet totalImpressions = 0;\nlet totalClicks = 0;\n\nfor (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n}\n\nconst clientNameUpper = (ctx.clientNome || '').toUpperCase();\nlet reportText;\n\nif (isPurchase) {\n let totalPurchases = 0;\n for (const c of campaigns) {\n const purchaseAction = (c.actions || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (purchaseAction) totalPurchases += parseInt(purchaseAction.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientNameUpper + '*\\n\\n' +\n '_\\ud83d\\udcc6 ' + fmtBR(ctx.last7Start) + ' a ' + fmtBR(ctx.last7End) + ' (\\u00faltimos 7 dias)_\\n\\n' +\n '*Meta Ads*\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n const LEAD_TYPES = [\n 'lead',\n 'onsite_conversion.lead_grouped',\n 'onsite_conversion.messaging_conversation_started_7d',\n 'onsite_conversion.total_messaging_connection'\n ];\n\n let leadSpend = 0;\n let totalLeads = 0;\n for (const c of campaigns) {\n const leadAction = (c.actions || []).find(a => LEAD_TYPES.includes(a.action_type));\n if (leadAction) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(leadAction.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientNameUpper + '*\\n\\n' +\n '_\\ud83d\\udcc6 ' + fmtBR(ctx.last7Start) + ' a ' + fmtBR(ctx.last7End) + ' (\\u00faltimos 7 dias)_\\n\\n' +\n '*Meta Ads*\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { ...ctx, output: reportText } }];" + "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\nfunction fmtMoney(val) {\n var n = parseFloat(val) || 0;\n var fixed = n.toFixed(2);\n var parts = fixed.split('.');\n parts[0] = parts[0].replace(/\\B(?=(\\d{3})+(?!\\d))/g, '.');\n return parts[0] + ',' + parts[1];\n}\n\nfunction fmtNum(val) {\n var n = parseInt(val) || 0;\n return String(n).replace(/\\B(?=(\\d{3})+(?!\\d))/g, '.');\n}\n\nfunction fmtBR(dateStr) {\n var d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n}\n\nif (!campaigns.length) {\n return [{ json: { phone: ctx.phone, output: 'Sem dados para *' + (ctx.clientNome || '').toUpperCase() + '* nos ultimos 7 dias.' } }];\n}\n\nvar purchaseAccountIds = ['act_873195821346004', 'act_1673012226810411'];\nvar isPurchase = purchaseAccountIds.indexOf(ctx.clientFound) !== -1;\n\nvar totalSpend = 0;\nvar totalImpressions = 0;\nvar totalClicks = 0;\n\nfor (var i = 0; i < campaigns.length; i++) {\n totalSpend += parseFloat(campaigns[i].spend || '0');\n totalImpressions += parseInt(campaigns[i].impressions || '0');\n totalClicks += parseInt(campaigns[i].clicks || '0');\n}\n\nvar clientNameUpper = (ctx.clientNome || '').toUpperCase();\nvar reportText;\n\nif (isPurchase) {\n var totalPurchases = 0;\n for (var j = 0; j < campaigns.length; j++) {\n var actions = campaigns[j].actions || [];\n for (var k = 0; k < actions.length; k++) {\n if (actions[k].action_type === 'purchase' || actions[k].action_type === 'offsite_conversion.fb_pixel_purchase') {\n totalPurchases += parseInt(actions[k].value) || 0;\n }\n }\n }\n var cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientNameUpper + '*\\n\\n' +\n '_\\ud83d\\udcc6 ' + fmtBR(ctx.last7Start) + ' a ' + fmtBR(ctx.last7End) + ' (ultimos 7 dias)_\\n\\n' +\n '*Meta Ads*\\n\\n' +\n 'Impressoes: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n var LEAD_TYPES = [\n 'lead',\n 'onsite_conversion.lead_grouped',\n 'onsite_conversion.messaging_conversation_started_7d',\n 'onsite_conversion.total_messaging_connection'\n ];\n var leadSpend = 0;\n var totalLeads = 0;\n for (var j = 0; j < campaigns.length; j++) {\n var actions = campaigns[j].actions || [];\n for (var k = 0; k < actions.length; k++) {\n if (LEAD_TYPES.indexOf(actions[k].action_type) !== -1) {\n leadSpend += parseFloat(campaigns[j].spend || '0');\n totalLeads += parseInt(actions[k].value) || 0;\n break;\n }\n }\n }\n var cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientNameUpper + '*\\n\\n' +\n '_\\ud83d\\udcc6 ' + fmtBR(ctx.last7Start) + ' a ' + fmtBR(ctx.last7End) + ' (ultimos 7 dias)_\\n\\n' +\n '*Meta Ads*\\n\\n' +\n 'Impressoes: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { phone: ctx.phone, output: reportText } }];" }, "id": "2b3745d9-82ef-4d9a-aa8d-db402408a07e", - "name": "Formatar Relatório", + "name": "Formatar Relatorio", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [992, -32] @@ -174,7 +174,7 @@ } }, "id": "b339c96a-4c60-4a45-98ea-4091d5ec847d", - "name": "Enviar Relatório WhatsApp", + "name": "Enviar Relatorio WhatsApp", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [1216, -32] @@ -211,7 +211,7 @@ }, { "parameters": { - "jsCode": "const metaResp = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst allAccounts = metaResp.data || [];\nconst accountsList = allAccounts.map(a => {\n const status = a.account_status === 1 ? '\\u2705 Ativa' : '\\u23f8 Inativa';\n return '\\u2022 ' + a.name + ' | ID: ' + a.id + ' | ' + a.currency + ' | ' + status;\n}).join('\\n') || 'Nenhuma conta encontrada';\nreturn [{ json: { ...ctx, accounts: allAccounts, accountsList } }];" + "jsCode": "const metaResp = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst allAccounts = metaResp.data || [];\nconst accountsList = allAccounts.map(function(a) {\n var status = a.account_status === 1 ? 'Ativa' : 'Inativa';\n return '- ' + a.name + ' | ID: ' + a.id + ' | ' + a.currency + ' | ' + status;\n}).join('\\n') || 'Nenhuma conta encontrada';\nreturn [{ json: {\n text: ctx.text,\n phone: ctx.phone,\n remoteJid: ctx.remoteJid,\n instance: ctx.instance,\n today: ctx.today,\n yesterday: ctx.yesterday,\n weekStart: ctx.weekStart,\n last7: ctx.last7,\n last30: ctx.last30,\n last7Start: ctx.last7Start,\n last7End: ctx.last7End,\n metaToken: ctx.metaToken,\n isRelatorio: ctx.isRelatorio,\n isRelatorioWithClient: ctx.isRelatorioWithClient,\n clientFound: ctx.clientFound,\n clientNome: ctx.clientNome,\n accounts: allAccounts,\n accountsList: accountsList\n} }];" }, "id": "9c2de825-ffbd-4870-b0e5-3f4a72c6c7b1", "name": "Montar Contexto", @@ -224,7 +224,7 @@ "promptType": "define", "text": "={{ $json.text }}", "options": { - "systemMessage": "=Você é Alicia, assistente especializada em Meta Ads. Ajuda gestores de tráfego a analisar métricas de campanhas de forma clara e direta.\n\n📅 DATA ATUAL: {{ $json.today }}\n📅 ONTEM: {{ $json.yesterday }}\n📅 INÍCIO DA SEMANA (seg): {{ $json.weekStart }}\n📅 7 DIAS ATRÁS: {{ $json.last7 }}\n📅 30 DIAS ATRÁS: {{ $json.last30 }}\n\n🧑‍💼 MAPEAMENTO DE CLIENTES → CONTAS:\n• Antonio Neto → act_497795903676192\n• Gabriel Jacinto → act_2493856707309984\n• Dani Escudero → act_2145598605527232\n• Agro → act_929521557422139\n• Escola de Música → act_544347952854095\n• Panmalhas → act_1509005182799187\n• GFiX Store → act_2811791829124905\n• Arte em Gelo → act_5585082641598366\n• ITAG Tecnologia → act_738466861151636\n• Hi Dogz → act_873195821346004\n• Batata Bistrô → act_1673012226810411\n• Caribbean Bronze → act_1082397873705862\n• Larissa Kelleter → act_738280412237298\n\n🏢 CONTAS DISPONÍVEIS (status atual):\n{{ $json.accountsList }}\n\n📋 REGRAS:\n1. Quando o usuário mencionar um cliente pelo nome (parcial ou completo), use o ID de conta do mapeamento acima\n2. Exemplos de variações aceitas: 'itag' = ITAG Tecnologia, 'batata' = Batata Bistrô, 'gfix' = GFiX Store, 'caribbean' = Caribbean Bronze\n3. Se não especificou cliente, busque de TODAS as contas ativas e consolide os valores\n4. date_presets válidos: today, yesterday, this_week_mon_today, last_7d, last_14d, last_30d, last_month\n5. Para LEADS: filtre actions por action_type = 'lead' OU 'onsite_conversion.lead_grouped'\n6. CPL = gasto_total ÷ quantidade_leads\n7. Budgets da Meta vêm em CENTAVOS - divida por 100 para exibir em reais\n8. Responda SEMPRE em português do Brasil\n9. Use emojis para tornar a resposta visual e amigável\n10. Seja concisa mas completa\n\n🎨 FORMATO DE RESPOSTA PARA RELATÓRIO:\n*NOME DO CLIENTE EM MAIÚSCULAS*\n\n_📆 DD/MM a DD/MM (últimos 7 dias)_\n\n*Meta Ads*\n\nImpressões: X.XXX\n\nCliques: X.XXX\n\nLeads: XX (ou Vendas: XX para Hi Dogz e Batata Bistrô)\n\nCusto por lead: R$ XX,XX (ou Custo por venda)\n\n*INVESTIMENTO TOTAL: R$ X.XXX,XX*\n\n⚠️ Se não houver dados para o período, informe o usuário claramente." + "systemMessage": "=Voce e Alicia, assistente especializada em Meta Ads. Ajuda gestores de trafego a analisar metricas de campanhas.\n\nDATA ATUAL: {{ $json.today }}\nONTEM: {{ $json.yesterday }}\nINICIO DA SEMANA: {{ $json.weekStart }}\n7 DIAS ATRAS: {{ $json.last7 }}\n30 DIAS ATRAS: {{ $json.last30 }}\n\nMAPEAMENTO CLIENTES - CONTAS:\n- Antonio Neto = act_497795903676192\n- Gabriel Jacinto = act_2493856707309984\n- Dani Escudero = act_2145598605527232\n- Agro = act_929521557422139\n- Escola de Musica = act_544347952854095\n- Panmalhas = act_1509005182799187\n- GFiX Store = act_2811791829124905\n- Arte em Gelo = act_5585082641598366\n- ITAG Tecnologia = act_738466861151636\n- Hi Dogz = act_873195821346004\n- Batata Bistro = act_1673012226810411\n- Caribbean Bronze = act_1082397873705862\n- Larissa Kelleter = act_738280412237298\n\nCONTAS DISPONIVEIS:\n{{ $json.accountsList }}\n\nREGRAS:\n1. Aceite nomes parciais: 'itag'=ITAG, 'batata'=Batata Bistro, 'gfix'=GFiX, 'caribbean'=Caribbean Bronze\n2. Se sem cliente especifico, busque todas as contas ativas\n3. date_presets: today, yesterday, this_week_mon_today, last_7d, last_14d, last_30d, last_month\n4. Para LEADS: action_type = 'lead' OU 'onsite_conversion.lead_grouped'\n5. CPL = gasto / leads\n6. Budgets em CENTAVOS - divida por 100\n7. Responda SEMPRE em portugues\n8. Use emojis\n\nFORMATO DO RELATORIO:\n*NOME DO CLIENTE EM MAIUSCULAS*\n\n_data inicio a data fim (ultimos 7 dias)_\n\n*Meta Ads*\n\nImpressoes: X.XXX\n\nCliques: X.XXX\n\nLeads: XX\n\nCusto por lead: R$ XX,XX\n\n*INVESTIMENTO TOTAL: R$ X.XXX,XX*\n\nPara Hi Dogz e Batata Bistro usar Vendas e Custo por venda." } }, "id": "36e08f36-8634-478d-a32c-8bf22671531e", @@ -256,8 +256,8 @@ { "parameters": { "name": "buscar_insights", - "description": "Busca métricas/insights do Meta Ads. Use para: gasto (spend), leads (actions), impressões, cliques, CTR, CPC, alcance. Envie JSON com: object_id (ID da conta ex: act_123456 OU ID de campanha), date_preset (today/yesterday/this_week_mon_today/last_7d/last_14d/last_30d/last_month), fields (ex: spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type), level opcional (account/campaign/adset/ad).", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com object_id e date_preset'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst objectId = params.object_id;\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type';\nconst level = params.level || '';\nif (!objectId) return 'Erro: object_id é obrigatório';\nlet url = `https://graph.facebook.com/v19.0/${objectId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\nif (level) url += `&level=${encodeURIComponent(level)}`;\ntry {\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message} (código: ${data.error.code})`;\n if (!data.data || data.data.length === 0) return 'Nenhum dado encontrado para o período solicitado.';\n return JSON.stringify(data.data);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "description": "Busca metricas/insights do Meta Ads. Use para: gasto (spend), leads (actions), impressoes, cliques, CTR, CPC, alcance. Envie JSON com: object_id (ID da conta ex: act_123456 OU ID de campanha), date_preset (today/yesterday/this_week_mon_today/last_7d/last_14d/last_30d/last_month), fields (ex: spend,impressions,clicks,ctr,cpc,actions,reach), level opcional (account/campaign/adset/ad).", + "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com object_id e date_preset'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar objectId = params.object_id;\nvar datePreset = params.date_preset || 'today';\nvar fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type';\nvar level = params.level || '';\nif (!objectId) return 'Erro: object_id e obrigatorio';\nvar url = 'https://graph.facebook.com/v19.0/' + objectId + '/insights?date_preset=' + encodeURIComponent(datePreset) + '&fields=' + encodeURIComponent(fields) + '&access_token=' + token;\nif (level) url += '&level=' + encodeURIComponent(level);\ntry {\n var res = await fetch(url);\n var data = await res.json();\n if (data.error) return 'Erro Meta API: ' + data.error.message + ' (codigo: ' + data.error.code + ')';\n if (!data.data || data.data.length === 0) return 'Nenhum dado encontrado para o periodo solicitado.';\n return JSON.stringify(data.data);\n} catch(e) { return 'Erro de conexao: ' + e.message; }" }, "id": "13abddfb-207b-4d02-aa60-c7b8658c7860", "name": "Tool: Buscar Insights", @@ -268,8 +268,8 @@ { "parameters": { "name": "listar_campanhas", - "description": "Lista todas as campanhas de uma conta de anúncios. Envie JSON com: account_id (formato: act_XXXXXXXXX). Retorna: id, nome, status, objetivo, budget diário e total. Use para encontrar o ID de uma campanha específica antes de buscar os insights.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nif (!accountId) return 'Erro: account_id é obrigatório (formato: act_XXXXXXXXX)';\ntry {\n const url = `https://graph.facebook.com/v19.0/${accountId}/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhuma campanha encontrada nessa conta';\n const campaigns = data.data.map(c => ({\n id: c.id,\n nome: c.name,\n status: c.status,\n objetivo: c.objective,\n budget_diario_reais: c.daily_budget ? (parseInt(c.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: c.lifetime_budget ? (parseInt(c.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(campaigns);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "description": "Lista todas as campanhas de uma conta de anuncios. Envie JSON com: account_id (formato: act_XXXXXXXXX). Retorna: id, nome, status, objetivo, budget diario e total.", + "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar accountId = params.account_id;\nif (!accountId) return 'Erro: account_id e obrigatorio (formato: act_XXXXXXXXX)';\ntry {\n var url = 'https://graph.facebook.com/v19.0/' + accountId + '/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=' + token;\n var res = await fetch(url);\n var data = await res.json();\n if (data.error) return 'Erro Meta API: ' + data.error.message;\n if (!data.data || data.data.length === 0) return 'Nenhuma campanha encontrada nessa conta';\n var campaigns = data.data.map(function(c) {\n return {\n id: c.id,\n nome: c.name,\n status: c.status,\n objetivo: c.objective,\n budget_diario_reais: c.daily_budget ? (parseInt(c.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: c.lifetime_budget ? (parseInt(c.lifetime_budget) / 100).toFixed(2) : null\n };\n });\n return JSON.stringify(campaigns);\n} catch(e) { return 'Erro de conexao: ' + e.message; }" }, "id": "731c36fc-e0b1-44a4-a13e-fcda6b6f2a2e", "name": "Tool: Listar Campanhas", @@ -280,8 +280,8 @@ { "parameters": { "name": "listar_conjuntos", - "description": "Lista conjuntos de anúncios (adsets) de uma conta ou campanha. Envie JSON com: account_id (act_XXXXXXXXX) OU campaign_id (ID numérico). Retorna: id, nome, status e budget.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id ou campaign_id'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nconst campaignId = params.campaign_id;\nif (!accountId && !campaignId) return 'Erro: forneça account_id ou campaign_id';\nconst baseId = campaignId || accountId;\ntry {\n const url = `https://graph.facebook.com/v19.0/${baseId}/adsets?fields=id,name,status,daily_budget,lifetime_budget,campaign_id&limit=100&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhum conjunto de anúncios encontrado';\n const adsets = data.data.map(a => ({\n id: a.id,\n nome: a.name,\n status: a.status,\n budget_diario_reais: a.daily_budget ? (parseInt(a.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: a.lifetime_budget ? (parseInt(a.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(adsets);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "description": "Lista conjuntos de anuncios (adsets) de uma conta ou campanha. Envie JSON com: account_id (act_XXXXXXXXX) OU campaign_id (ID numerico). Retorna: id, nome, status e budget.", + "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id ou campaign_id'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar accountId = params.account_id;\nvar campaignId = params.campaign_id;\nif (!accountId && !campaignId) return 'Erro: forneca account_id ou campaign_id';\nvar baseId = campaignId || accountId;\ntry {\n var url = 'https://graph.facebook.com/v19.0/' + baseId + '/adsets?fields=id,name,status,daily_budget,lifetime_budget,campaign_id&limit=100&access_token=' + token;\n var res = await fetch(url);\n var data = await res.json();\n if (data.error) return 'Erro Meta API: ' + data.error.message;\n if (!data.data || data.data.length === 0) return 'Nenhum conjunto de anuncios encontrado';\n var adsets = data.data.map(function(a) {\n return {\n id: a.id,\n nome: a.name,\n status: a.status,\n budget_diario_reais: a.daily_budget ? (parseInt(a.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: a.lifetime_budget ? (parseInt(a.lifetime_budget) / 100).toFixed(2) : null\n };\n });\n return JSON.stringify(adsets);\n} catch(e) { return 'Erro de conexao: ' + e.message; }" }, "id": "a8b12824-f38e-4296-87fe-49bd6e9f934b", "name": "Tool: Listar Conjuntos", @@ -292,8 +292,8 @@ { "parameters": { "name": "buscar_insights_multiplas_contas", - "description": "Busca insights de MÚLTIPLAS contas ao mesmo tempo. Use quando o usuário não especificar cliente. Envie JSON com: account_ids (array de IDs ex: ['act_111','act_222']), date_preset, fields.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_ids (array), date_preset e fields'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountIds = params.account_ids || [];\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (accountIds.length === 0) return 'Erro: account_ids deve ser um array com pelo menos 1 conta';\ntry {\n const results = [];\n for (const accountId of accountIds) {\n const url = `https://graph.facebook.com/v19.0/${accountId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (!data.error && data.data && data.data.length > 0) {\n results.push({ account_id: accountId, data: data.data[0] });\n } else if (data.error) {\n results.push({ account_id: accountId, error: data.error.message });\n } else {\n results.push({ account_id: accountId, data: null, note: 'sem dados no período' });\n }\n }\n return JSON.stringify(results);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "description": "Busca insights de MULTIPLAS contas ao mesmo tempo. Use quando o usuario nao especificar cliente. Envie JSON com: account_ids (array de IDs ex: ['act_111','act_222']), date_preset, fields.", + "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_ids (array), date_preset e fields'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar accountIds = params.account_ids || [];\nvar datePreset = params.date_preset || 'today';\nvar fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (accountIds.length === 0) return 'Erro: account_ids deve ser um array com pelo menos 1 conta';\ntry {\n var results = [];\n for (var i = 0; i < accountIds.length; i++) {\n var accountId = accountIds[i];\n var url = 'https://graph.facebook.com/v19.0/' + accountId + '/insights?date_preset=' + encodeURIComponent(datePreset) + '&fields=' + encodeURIComponent(fields) + '&access_token=' + token;\n var res = await fetch(url);\n var data = await res.json();\n if (!data.error && data.data && data.data.length > 0) {\n results.push({ account_id: accountId, data: data.data[0] });\n } else if (data.error) {\n results.push({ account_id: accountId, error: data.error.message });\n } else {\n results.push({ account_id: accountId, data: null, note: 'sem dados no periodo' });\n }\n }\n return JSON.stringify(results);\n} catch(e) { return 'Erro de conexao: ' + e.message; }" }, "id": "81aa87da-6607-4ffc-95b9-1be17e23ec68", "name": "Tool: Insights Multiplas Contas", @@ -381,14 +381,14 @@ "main": [ [ { - "node": "É Relatório Semanal?", + "node": "E Relatorio Semanal?", "type": "main", "index": 0 } ] ] }, - "É Relatório Semanal?": { + "E Relatorio Semanal?": { "main": [ [ { @@ -410,18 +410,18 @@ "main": [ [ { - "node": "Formatar Relatório", + "node": "Formatar Relatorio", "type": "main", "index": 0 } ] ] }, - "Formatar Relatório": { + "Formatar Relatorio": { "main": [ [ { - "node": "Enviar Relatório WhatsApp", + "node": "Enviar Relatorio WhatsApp", "type": "main", "index": 0 } From 20e838aa544044c231f6f32546623eda2b422944 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Mar 2026 01:42:20 +0000 Subject: [PATCH 03/16] fix: corrige Parse Message e IF nodes para funcionar com Evolution API - Parse Message: detecta payload em formato body aninhado (typeVersion 2) e direto (typeVersion 1) automaticamente - Parse Message: filtra eventos nao-mensagem (connection.update, read receipt, etc.) verificando event !== 'messages.upsert' - Parse Message: fromMe verificado com === true para evitar truthy bug - Menciona Alicia?: muda typeValidation para 'loose' e caseSensitive para false para nao rejeitar 'Alicia' com letra maiuscula - E Relatorio Semanal?: muda comparacao boolean para string 'true' com loose validation, corrige problema de tipo que impedia o branch correto - Calcular Datas: reescreve clientMap como array de objetos para evitar chave duplicada e facilitar busca por multiplos aliases - Formatar Relatorio: corrige regex do fmtMoney/fmtNum para pt-BR https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 197 ++++++++------------------------------- 1 file changed, 37 insertions(+), 160 deletions(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index cc925f1a6..f2f0a4105 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -16,7 +16,7 @@ }, { "parameters": { - "jsCode": "const raw = $input.first().json;\nconst body = raw.body || raw;\n\nconst data = body.data || {};\nconst key = data.key || {};\nconst msg = data.message || {};\n\nconst text =\n msg.conversation ||\n (msg.extendedTextMessage && msg.extendedTextMessage.text) ||\n (msg.imageMessage && msg.imageMessage.caption) ||\n (msg.videoMessage && msg.videoMessage.caption) ||\n (msg.documentMessage && msg.documentMessage.caption) ||\n '';\n\nconst remoteJid = key.remoteJid || '';\nconst fromMe = key.fromMe || false;\nconst instance = body.instance || 'Teste';\n\nif (fromMe || !text.trim()) return [];\nif (remoteJid.includes('@g.us')) return [];\n\nconst phone = remoteJid\n .replace('@s.whatsapp.net', '')\n .replace('@c.us', '');\n\nreturn [{ json: { text: text.trim(), phone, remoteJid, instance } }];" + "jsCode": "// Suporta: n8n webhook typeVersion 1 (body direto) e typeVersion 2 (body aninhado)\nconst raw = $input.first().json;\nconst payload = (raw.body && typeof raw.body === 'object' && raw.body !== null) ? raw.body : raw;\n\n// Ignora eventos que nao sao mensagens recebidas\nconst event = payload.event || '';\nif (event !== '' && event !== 'messages.upsert') return [];\n\nconst data = payload.data || {};\nconst key = data.key || {};\nconst msg = data.message || {};\n\n// Extrai texto de todos os tipos de mensagem suportados\nconst text = (\n msg.conversation ||\n (msg.extendedTextMessage && msg.extendedTextMessage.text) ||\n (msg.imageMessage && msg.imageMessage.caption) ||\n (msg.videoMessage && msg.videoMessage.caption) ||\n (msg.documentMessage && msg.documentMessage.caption) ||\n ''\n);\n\nconst remoteJid = key.remoteJid || '';\nconst fromMe = key.fromMe === true;\nconst instance = payload.instance || 'Teste';\n\n// Filtra: mensagem propria, texto vazio, grupo\nif (fromMe) return [];\nif (!text.trim()) return [];\nif (remoteJid.includes('@g.us')) return [];\n\nconst phone = remoteJid\n .replace('@s.whatsapp.net', '')\n .replace('@c.us', '');\n\nreturn [{ json: { text: text.trim(), phone: phone, remoteJid: remoteJid, instance: instance } }];" }, "id": "24a967e7-7602-4f55-bea1-a4dff46d1330", "name": "Parse Message", @@ -28,15 +28,15 @@ "parameters": { "conditions": { "options": { - "caseSensitive": true, + "caseSensitive": false, "leftValue": "", - "typeValidation": "strict", + "typeValidation": "loose", "version": 1 }, "conditions": [ { "id": "cond-alicia", - "leftValue": "={{ $json.text.toLowerCase() }}", + "leftValue": "={{ $json.text }}", "rightValue": "alicia", "operator": { "type": "string", @@ -57,7 +57,7 @@ }, { "parameters": { - "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\n\nconst fmt = function(d) { return d.toISOString().split('T')[0]; };\nconst addDays = function(n) {\n const d = new Date(now);\n d.setDate(d.getDate() + n);\n return fmt(d);\n};\n\nconst dow = now.getDay();\nconst daysToMon = dow === 0 ? 6 : dow - 1;\nconst monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\nconst clientMap = {\n 'antonio neto': { id: 'act_497795903676192', nome: 'Antonio Neto' },\n 'antonio': { id: 'act_497795903676192', nome: 'Antonio Neto' },\n 'gabriel jacinto': { id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n 'gabriel': { id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n 'dani escudero': { id: 'act_2145598605527232', nome: 'Dani Escudero' },\n 'dani': { id: 'act_2145598605527232', nome: 'Dani Escudero' },\n 'agro': { id: 'act_929521557422139', nome: 'Agro' },\n 'escola de musica': { id: 'act_544347952854095', nome: 'Escola de Musica' },\n 'escola': { id: 'act_544347952854095', nome: 'Escola de Musica' },\n 'panmalhas': { id: 'act_1509005182799187', nome: 'Panmalhas' },\n 'gfix store': { id: 'act_2811791829124905', nome: 'GFiX Store' },\n 'gfix': { id: 'act_2811791829124905', nome: 'GFiX Store' },\n 'arte em gelo': { id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n 'arte': { id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n 'itag tecnologia': { id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n 'itag': { id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n 'hi dogz': { id: 'act_873195821346004', nome: 'Hi Dogz' },\n 'dogz': { id: 'act_873195821346004', nome: 'Hi Dogz' },\n 'batata bistro': { id: 'act_1673012226810411', nome: 'Batata Bistro' },\n 'batata': { id: 'act_1673012226810411', nome: 'Batata Bistro' },\n 'caribbean bronze': { id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n 'caribbean': { id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n 'larissa kelleter': { id: 'act_738280412237298', nome: 'Larissa Kelleter' },\n 'larissa': { id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n};\n\nconst isRelatorio = text.includes('relat');\n\nvar clientFound = null;\nvar sortedKeys = Object.keys(clientMap).sort(function(a, b) { return b.length - a.length; });\nfor (var i = 0; i < sortedKeys.length; i++) {\n if (text.includes(sortedKeys[i])) {\n clientFound = clientMap[sortedKeys[i]];\n break;\n }\n}\n\nconst today = fmt(now);\nconst yesterday = addDays(-1);\nconst last7Start = addDays(-7);\nconst last7End = yesterday;\nconst isRelatorioWithClient = isRelatorio && clientFound !== null;\n\nreturn [{\n json: {\n text: input.text,\n phone: input.phone,\n remoteJid: input.remoteJid,\n instance: input.instance,\n today: today,\n yesterday: yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start: last7Start,\n last7End: last7End,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio: isRelatorio,\n isRelatorioWithClient: isRelatorioWithClient,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null\n }\n}];" + "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\n\nfunction fmt(d) { return d.toISOString().split('T')[0]; }\nfunction addDays(n) {\n var d = new Date(now);\n d.setDate(d.getDate() + n);\n return fmt(d);\n}\n\nvar dow = now.getDay();\nvar daysToMon = (dow === 0) ? 6 : (dow - 1);\nvar monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\nvar clientMap = [\n { keys: ['antonio neto', 'antonio'], id: 'act_497795903676192', nome: 'Antonio Neto' },\n { keys: ['gabriel jacinto', 'gabriel'], id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n { keys: ['dani escudero', 'dani'], id: 'act_2145598605527232', nome: 'Dani Escudero' },\n { keys: ['agro'], id: 'act_929521557422139', nome: 'Agro' },\n { keys: ['escola de musica', 'escola de música', 'escola'], id: 'act_544347952854095', nome: 'Escola de Musica' },\n { keys: ['panmalhas'], id: 'act_1509005182799187', nome: 'Panmalhas' },\n { keys: ['gfix store', 'gfix'], id: 'act_2811791829124905', nome: 'GFiX Store' },\n { keys: ['arte em gelo', 'arte em'], id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n { keys: ['itag tecnologia', 'itag'], id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n { keys: ['hi dogz', 'dogz'], id: 'act_873195821346004', nome: 'Hi Dogz' },\n { keys: ['batata bistro', 'batata bistrô', 'batata'], id: 'act_1673012226810411', nome: 'Batata Bistro' },\n { keys: ['caribbean bronze', 'caribbean'], id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n { keys: ['larissa kelleter', 'larissa'], id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n];\n\n// Ordena por comprimento decrescente de chaves para priorizar matches mais especificos\nclientMap.sort(function(a, b) {\n return b.keys[0].length - a.keys[0].length;\n});\n\nvar isRelatorio = text.indexOf('relat') !== -1;\n\nvar clientFound = null;\nfor (var i = 0; i < clientMap.length; i++) {\n var entry = clientMap[i];\n for (var j = 0; j < entry.keys.length; j++) {\n if (text.indexOf(entry.keys[j]) !== -1) {\n clientFound = entry;\n break;\n }\n }\n if (clientFound) break;\n}\n\nvar today = fmt(now);\nvar yesterday = addDays(-1);\nvar last7Start = addDays(-7);\nvar last7End = yesterday;\nvar isRelatorioWithClient = (isRelatorio && clientFound !== null);\n\nreturn [{\n json: {\n text: input.text,\n phone: input.phone,\n remoteJid: input.remoteJid,\n instance: input.instance,\n today: today,\n yesterday: yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start: last7Start,\n last7End: last7End,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio: isRelatorio,\n isRelatorioWithClient: isRelatorioWithClient,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null\n }\n}];" }, "id": "5b953b43-14ca-4ed3-b8da-3ec455f58b92", "name": "Calcular Datas", @@ -71,17 +71,18 @@ "options": { "caseSensitive": true, "leftValue": "", - "typeValidation": "strict", + "typeValidation": "loose", "version": 1 }, "conditions": [ { "id": "cond-relatorio", "leftValue": "={{ $json.isRelatorioWithClient }}", - "rightValue": true, + "rightValue": "true", "operator": { - "type": "boolean", - "operation": "equals" + "type": "string", + "operation": "equals", + "singleValue": true } } ], @@ -131,7 +132,7 @@ }, { "parameters": { - "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\nfunction fmtMoney(val) {\n var n = parseFloat(val) || 0;\n var fixed = n.toFixed(2);\n var parts = fixed.split('.');\n parts[0] = parts[0].replace(/\\B(?=(\\d{3})+(?!\\d))/g, '.');\n return parts[0] + ',' + parts[1];\n}\n\nfunction fmtNum(val) {\n var n = parseInt(val) || 0;\n return String(n).replace(/\\B(?=(\\d{3})+(?!\\d))/g, '.');\n}\n\nfunction fmtBR(dateStr) {\n var d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n}\n\nif (!campaigns.length) {\n return [{ json: { phone: ctx.phone, output: 'Sem dados para *' + (ctx.clientNome || '').toUpperCase() + '* nos ultimos 7 dias.' } }];\n}\n\nvar purchaseAccountIds = ['act_873195821346004', 'act_1673012226810411'];\nvar isPurchase = purchaseAccountIds.indexOf(ctx.clientFound) !== -1;\n\nvar totalSpend = 0;\nvar totalImpressions = 0;\nvar totalClicks = 0;\n\nfor (var i = 0; i < campaigns.length; i++) {\n totalSpend += parseFloat(campaigns[i].spend || '0');\n totalImpressions += parseInt(campaigns[i].impressions || '0');\n totalClicks += parseInt(campaigns[i].clicks || '0');\n}\n\nvar clientNameUpper = (ctx.clientNome || '').toUpperCase();\nvar reportText;\n\nif (isPurchase) {\n var totalPurchases = 0;\n for (var j = 0; j < campaigns.length; j++) {\n var actions = campaigns[j].actions || [];\n for (var k = 0; k < actions.length; k++) {\n if (actions[k].action_type === 'purchase' || actions[k].action_type === 'offsite_conversion.fb_pixel_purchase') {\n totalPurchases += parseInt(actions[k].value) || 0;\n }\n }\n }\n var cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientNameUpper + '*\\n\\n' +\n '_\\ud83d\\udcc6 ' + fmtBR(ctx.last7Start) + ' a ' + fmtBR(ctx.last7End) + ' (ultimos 7 dias)_\\n\\n' +\n '*Meta Ads*\\n\\n' +\n 'Impressoes: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n var LEAD_TYPES = [\n 'lead',\n 'onsite_conversion.lead_grouped',\n 'onsite_conversion.messaging_conversation_started_7d',\n 'onsite_conversion.total_messaging_connection'\n ];\n var leadSpend = 0;\n var totalLeads = 0;\n for (var j = 0; j < campaigns.length; j++) {\n var actions = campaigns[j].actions || [];\n for (var k = 0; k < actions.length; k++) {\n if (LEAD_TYPES.indexOf(actions[k].action_type) !== -1) {\n leadSpend += parseFloat(campaigns[j].spend || '0');\n totalLeads += parseInt(actions[k].value) || 0;\n break;\n }\n }\n }\n var cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientNameUpper + '*\\n\\n' +\n '_\\ud83d\\udcc6 ' + fmtBR(ctx.last7Start) + ' a ' + fmtBR(ctx.last7End) + ' (ultimos 7 dias)_\\n\\n' +\n '*Meta Ads*\\n\\n' +\n 'Impressoes: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { phone: ctx.phone, output: reportText } }];" + "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\nfunction fmtMoney(val) {\n var n = parseFloat(val) || 0;\n var fixed = n.toFixed(2);\n var parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n}\n\nfunction fmtNum(val) {\n var n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n}\n\nfunction fmtBR(dateStr) {\n var d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n}\n\nif (!campaigns.length) {\n return [{ json: {\n phone: ctx.phone,\n output: 'Sem dados de campanhas para *' + (ctx.clientNome || '').toUpperCase() + '* nos ultimos 7 dias.'\n } }];\n}\n\nvar purchaseIds = ['act_873195821346004', 'act_1673012226810411'];\nvar isPurchase = purchaseIds.indexOf(ctx.clientFound) !== -1;\n\nvar totalSpend = 0;\nvar totalImpressions = 0;\nvar totalClicks = 0;\n\nfor (var i = 0; i < campaigns.length; i++) {\n totalSpend += parseFloat(campaigns[i].spend || '0');\n totalImpressions += parseInt(campaigns[i].impressions || '0');\n totalClicks += parseInt(campaigns[i].clicks || '0');\n}\n\nvar clientUpper = (ctx.clientNome || '').toUpperCase();\nvar dateRange = '_\\ud83d\\udcc6 ' + fmtBR(ctx.last7Start) + ' a ' + fmtBR(ctx.last7End) + ' (ultimos 7 dias)_';\nvar reportText;\n\nif (isPurchase) {\n var totalPurchases = 0;\n for (var i = 0; i < campaigns.length; i++) {\n var acts = campaigns[i].actions || [];\n for (var k = 0; k < acts.length; k++) {\n if (acts[k].action_type === 'purchase' || acts[k].action_type === 'offsite_conversion.fb_pixel_purchase') {\n totalPurchases += parseInt(acts[k].value) || 0;\n }\n }\n }\n var cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impressoes: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n var LEAD_TYPES = ['lead', 'onsite_conversion.lead_grouped', 'onsite_conversion.messaging_conversation_started_7d', 'onsite_conversion.total_messaging_connection'];\n var leadSpend = 0;\n var totalLeads = 0;\n for (var i = 0; i < campaigns.length; i++) {\n var acts = campaigns[i].actions || [];\n for (var k = 0; k < acts.length; k++) {\n if (LEAD_TYPES.indexOf(acts[k].action_type) !== -1) {\n leadSpend += parseFloat(campaigns[i].spend || '0');\n totalLeads += parseInt(acts[k].value) || 0;\n break;\n }\n }\n }\n var cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impressoes: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { phone: ctx.phone, output: reportText } }];" }, "id": "2b3745d9-82ef-4d9a-aa8d-db402408a07e", "name": "Formatar Relatorio", @@ -211,7 +212,7 @@ }, { "parameters": { - "jsCode": "const metaResp = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst allAccounts = metaResp.data || [];\nconst accountsList = allAccounts.map(function(a) {\n var status = a.account_status === 1 ? 'Ativa' : 'Inativa';\n return '- ' + a.name + ' | ID: ' + a.id + ' | ' + a.currency + ' | ' + status;\n}).join('\\n') || 'Nenhuma conta encontrada';\nreturn [{ json: {\n text: ctx.text,\n phone: ctx.phone,\n remoteJid: ctx.remoteJid,\n instance: ctx.instance,\n today: ctx.today,\n yesterday: ctx.yesterday,\n weekStart: ctx.weekStart,\n last7: ctx.last7,\n last30: ctx.last30,\n last7Start: ctx.last7Start,\n last7End: ctx.last7End,\n metaToken: ctx.metaToken,\n isRelatorio: ctx.isRelatorio,\n isRelatorioWithClient: ctx.isRelatorioWithClient,\n clientFound: ctx.clientFound,\n clientNome: ctx.clientNome,\n accounts: allAccounts,\n accountsList: accountsList\n} }];" + "jsCode": "const metaResp = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst allAccounts = metaResp.data || [];\nconst accountsList = allAccounts.map(function(a) {\n var status = a.account_status === 1 ? 'Ativa' : 'Inativa';\n return '- ' + a.name + ' | ID: ' + a.id + ' | ' + status;\n}).join('\\n') || 'Nenhuma conta encontrada';\nreturn [{ json: {\n text: ctx.text,\n phone: ctx.phone,\n remoteJid: ctx.remoteJid,\n instance: ctx.instance,\n today: ctx.today,\n yesterday: ctx.yesterday,\n weekStart: ctx.weekStart,\n last7: ctx.last7,\n last30: ctx.last30,\n last7Start: ctx.last7Start,\n last7End: ctx.last7End,\n metaToken: ctx.metaToken,\n isRelatorio: ctx.isRelatorio,\n isRelatorioWithClient: ctx.isRelatorioWithClient,\n clientFound: ctx.clientFound,\n clientNome: ctx.clientNome,\n accounts: allAccounts,\n accountsList: accountsList\n} }];" }, "id": "9c2de825-ffbd-4870-b0e5-3f4a72c6c7b1", "name": "Montar Contexto", @@ -224,7 +225,7 @@ "promptType": "define", "text": "={{ $json.text }}", "options": { - "systemMessage": "=Voce e Alicia, assistente especializada em Meta Ads. Ajuda gestores de trafego a analisar metricas de campanhas.\n\nDATA ATUAL: {{ $json.today }}\nONTEM: {{ $json.yesterday }}\nINICIO DA SEMANA: {{ $json.weekStart }}\n7 DIAS ATRAS: {{ $json.last7 }}\n30 DIAS ATRAS: {{ $json.last30 }}\n\nMAPEAMENTO CLIENTES - CONTAS:\n- Antonio Neto = act_497795903676192\n- Gabriel Jacinto = act_2493856707309984\n- Dani Escudero = act_2145598605527232\n- Agro = act_929521557422139\n- Escola de Musica = act_544347952854095\n- Panmalhas = act_1509005182799187\n- GFiX Store = act_2811791829124905\n- Arte em Gelo = act_5585082641598366\n- ITAG Tecnologia = act_738466861151636\n- Hi Dogz = act_873195821346004\n- Batata Bistro = act_1673012226810411\n- Caribbean Bronze = act_1082397873705862\n- Larissa Kelleter = act_738280412237298\n\nCONTAS DISPONIVEIS:\n{{ $json.accountsList }}\n\nREGRAS:\n1. Aceite nomes parciais: 'itag'=ITAG, 'batata'=Batata Bistro, 'gfix'=GFiX, 'caribbean'=Caribbean Bronze\n2. Se sem cliente especifico, busque todas as contas ativas\n3. date_presets: today, yesterday, this_week_mon_today, last_7d, last_14d, last_30d, last_month\n4. Para LEADS: action_type = 'lead' OU 'onsite_conversion.lead_grouped'\n5. CPL = gasto / leads\n6. Budgets em CENTAVOS - divida por 100\n7. Responda SEMPRE em portugues\n8. Use emojis\n\nFORMATO DO RELATORIO:\n*NOME DO CLIENTE EM MAIUSCULAS*\n\n_data inicio a data fim (ultimos 7 dias)_\n\n*Meta Ads*\n\nImpressoes: X.XXX\n\nCliques: X.XXX\n\nLeads: XX\n\nCusto por lead: R$ XX,XX\n\n*INVESTIMENTO TOTAL: R$ X.XXX,XX*\n\nPara Hi Dogz e Batata Bistro usar Vendas e Custo por venda." + "systemMessage": "=Voce e Alicia, assistente especializada em Meta Ads. Ajuda gestores de trafego a analisar metricas.\n\nDATA ATUAL: {{ $json.today }}\nONTEM: {{ $json.yesterday }}\nINICIO DA SEMANA: {{ $json.weekStart }}\n7 DIAS ATRAS: {{ $json.last7 }}\n30 DIAS ATRAS: {{ $json.last30 }}\n\nMAPEAMENTO CLIENTES:\n- Antonio Neto = act_497795903676192\n- Gabriel Jacinto = act_2493856707309984\n- Dani Escudero = act_2145598605527232\n- Agro = act_929521557422139\n- Escola de Musica = act_544347952854095\n- Panmalhas = act_1509005182799187\n- GFiX Store = act_2811791829124905\n- Arte em Gelo = act_5585082641598366\n- ITAG Tecnologia = act_738466861151636\n- Hi Dogz = act_873195821346004\n- Batata Bistro = act_1673012226810411\n- Caribbean Bronze = act_1082397873705862\n- Larissa Kelleter = act_738280412237298\n\nCONTAS:\n{{ $json.accountsList }}\n\nREGRAS:\n1. Aceite nomes parciais: itag, batata, gfix, caribbean, larissa, etc\n2. Se sem cliente, busque todas as ativas\n3. date_presets validos: today, yesterday, this_week_mon_today, last_7d, last_14d, last_30d\n4. Leads: action_type = lead OU onsite_conversion.lead_grouped\n5. CPL = gasto / leads\n6. Budgets em CENTAVOS - divida por 100\n7. Responda em portugues\n8. Use emojis\n\nFORMATO RELATORIO:\n*CLIENTE EM MAIUSCULAS*\n\n_data a data (ultimos 7 dias)_\n\n*Meta Ads*\n\nImpressoes: X.XXX\n\nCliques: X.XXX\n\nLeads: XX (ou Vendas para Hi Dogz e Batata Bistro)\n\nCusto por lead: R$ XX,XX\n\n*INVESTIMENTO TOTAL: R$ X.XXX,XX*" } }, "id": "36e08f36-8634-478d-a32c-8bf22671531e", @@ -256,8 +257,8 @@ { "parameters": { "name": "buscar_insights", - "description": "Busca metricas/insights do Meta Ads. Use para: gasto (spend), leads (actions), impressoes, cliques, CTR, CPC, alcance. Envie JSON com: object_id (ID da conta ex: act_123456 OU ID de campanha), date_preset (today/yesterday/this_week_mon_today/last_7d/last_14d/last_30d/last_month), fields (ex: spend,impressions,clicks,ctr,cpc,actions,reach), level opcional (account/campaign/adset/ad).", - "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com object_id e date_preset'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar objectId = params.object_id;\nvar datePreset = params.date_preset || 'today';\nvar fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type';\nvar level = params.level || '';\nif (!objectId) return 'Erro: object_id e obrigatorio';\nvar url = 'https://graph.facebook.com/v19.0/' + objectId + '/insights?date_preset=' + encodeURIComponent(datePreset) + '&fields=' + encodeURIComponent(fields) + '&access_token=' + token;\nif (level) url += '&level=' + encodeURIComponent(level);\ntry {\n var res = await fetch(url);\n var data = await res.json();\n if (data.error) return 'Erro Meta API: ' + data.error.message + ' (codigo: ' + data.error.code + ')';\n if (!data.data || data.data.length === 0) return 'Nenhum dado encontrado para o periodo solicitado.';\n return JSON.stringify(data.data);\n} catch(e) { return 'Erro de conexao: ' + e.message; }" + "description": "Busca metricas do Meta Ads. Envie JSON com: object_id (ID da conta ex: act_123456), date_preset (today/yesterday/last_7d/last_30d), fields (spend,impressions,clicks,ctr,cpc,actions,reach), level opcional (account/campaign/adset/ad).", + "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: JSON invalido. Envie object_id e date_preset'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar objectId = params.object_id;\nvar datePreset = params.date_preset || 'today';\nvar fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach';\nvar level = params.level || '';\nif (!objectId) return 'Erro: object_id obrigatorio';\nvar url = 'https://graph.facebook.com/v19.0/' + objectId + '/insights?date_preset=' + encodeURIComponent(datePreset) + '&fields=' + encodeURIComponent(fields) + '&access_token=' + token;\nif (level) url += '&level=' + encodeURIComponent(level);\ntry {\n var res = await fetch(url);\n var data = await res.json();\n if (data.error) return 'Erro Meta: ' + data.error.message;\n if (!data.data || data.data.length === 0) return 'Sem dados para o periodo.';\n return JSON.stringify(data.data);\n} catch(e) { return 'Erro conexao: ' + e.message; }" }, "id": "13abddfb-207b-4d02-aa60-c7b8658c7860", "name": "Tool: Buscar Insights", @@ -268,8 +269,8 @@ { "parameters": { "name": "listar_campanhas", - "description": "Lista todas as campanhas de uma conta de anuncios. Envie JSON com: account_id (formato: act_XXXXXXXXX). Retorna: id, nome, status, objetivo, budget diario e total.", - "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar accountId = params.account_id;\nif (!accountId) return 'Erro: account_id e obrigatorio (formato: act_XXXXXXXXX)';\ntry {\n var url = 'https://graph.facebook.com/v19.0/' + accountId + '/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=' + token;\n var res = await fetch(url);\n var data = await res.json();\n if (data.error) return 'Erro Meta API: ' + data.error.message;\n if (!data.data || data.data.length === 0) return 'Nenhuma campanha encontrada nessa conta';\n var campaigns = data.data.map(function(c) {\n return {\n id: c.id,\n nome: c.name,\n status: c.status,\n objetivo: c.objective,\n budget_diario_reais: c.daily_budget ? (parseInt(c.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: c.lifetime_budget ? (parseInt(c.lifetime_budget) / 100).toFixed(2) : null\n };\n });\n return JSON.stringify(campaigns);\n} catch(e) { return 'Erro de conexao: ' + e.message; }" + "description": "Lista campanhas de uma conta. Envie JSON com: account_id (formato: act_XXXXXXXXX).", + "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar accountId = params.account_id;\nif (!accountId) return 'Erro: account_id obrigatorio';\ntry {\n var url = 'https://graph.facebook.com/v19.0/' + accountId + '/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=' + token;\n var res = await fetch(url);\n var data = await res.json();\n if (data.error) return 'Erro Meta: ' + data.error.message;\n if (!data.data || !data.data.length) return 'Nenhuma campanha encontrada';\n return JSON.stringify(data.data.map(function(c) {\n return { id: c.id, nome: c.name, status: c.status, objetivo: c.objective,\n budget_diario: c.daily_budget ? (parseInt(c.daily_budget)/100).toFixed(2) : null,\n budget_total: c.lifetime_budget ? (parseInt(c.lifetime_budget)/100).toFixed(2) : null };\n }));\n} catch(e) { return 'Erro conexao: ' + e.message; }" }, "id": "731c36fc-e0b1-44a4-a13e-fcda6b6f2a2e", "name": "Tool: Listar Campanhas", @@ -280,8 +281,8 @@ { "parameters": { "name": "listar_conjuntos", - "description": "Lista conjuntos de anuncios (adsets) de uma conta ou campanha. Envie JSON com: account_id (act_XXXXXXXXX) OU campaign_id (ID numerico). Retorna: id, nome, status e budget.", - "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id ou campaign_id'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar accountId = params.account_id;\nvar campaignId = params.campaign_id;\nif (!accountId && !campaignId) return 'Erro: forneca account_id ou campaign_id';\nvar baseId = campaignId || accountId;\ntry {\n var url = 'https://graph.facebook.com/v19.0/' + baseId + '/adsets?fields=id,name,status,daily_budget,lifetime_budget,campaign_id&limit=100&access_token=' + token;\n var res = await fetch(url);\n var data = await res.json();\n if (data.error) return 'Erro Meta API: ' + data.error.message;\n if (!data.data || data.data.length === 0) return 'Nenhum conjunto de anuncios encontrado';\n var adsets = data.data.map(function(a) {\n return {\n id: a.id,\n nome: a.name,\n status: a.status,\n budget_diario_reais: a.daily_budget ? (parseInt(a.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: a.lifetime_budget ? (parseInt(a.lifetime_budget) / 100).toFixed(2) : null\n };\n });\n return JSON.stringify(adsets);\n} catch(e) { return 'Erro de conexao: ' + e.message; }" + "description": "Lista adsets de uma conta ou campanha. Envie JSON com: account_id (act_XXXXXXXXX) OU campaign_id.", + "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie account_id ou campaign_id'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar baseId = params.campaign_id || params.account_id;\nif (!baseId) return 'Erro: forneca account_id ou campaign_id';\ntry {\n var url = 'https://graph.facebook.com/v19.0/' + baseId + '/adsets?fields=id,name,status,daily_budget,lifetime_budget&limit=100&access_token=' + token;\n var res = await fetch(url);\n var data = await res.json();\n if (data.error) return 'Erro Meta: ' + data.error.message;\n if (!data.data || !data.data.length) return 'Nenhum adset encontrado';\n return JSON.stringify(data.data.map(function(a) {\n return { id: a.id, nome: a.name, status: a.status,\n budget_diario: a.daily_budget ? (parseInt(a.daily_budget)/100).toFixed(2) : null,\n budget_total: a.lifetime_budget ? (parseInt(a.lifetime_budget)/100).toFixed(2) : null };\n }));\n} catch(e) { return 'Erro conexao: ' + e.message; }" }, "id": "a8b12824-f38e-4296-87fe-49bd6e9f934b", "name": "Tool: Listar Conjuntos", @@ -292,8 +293,8 @@ { "parameters": { "name": "buscar_insights_multiplas_contas", - "description": "Busca insights de MULTIPLAS contas ao mesmo tempo. Use quando o usuario nao especificar cliente. Envie JSON com: account_ids (array de IDs ex: ['act_111','act_222']), date_preset, fields.", - "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_ids (array), date_preset e fields'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar accountIds = params.account_ids || [];\nvar datePreset = params.date_preset || 'today';\nvar fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (accountIds.length === 0) return 'Erro: account_ids deve ser um array com pelo menos 1 conta';\ntry {\n var results = [];\n for (var i = 0; i < accountIds.length; i++) {\n var accountId = accountIds[i];\n var url = 'https://graph.facebook.com/v19.0/' + accountId + '/insights?date_preset=' + encodeURIComponent(datePreset) + '&fields=' + encodeURIComponent(fields) + '&access_token=' + token;\n var res = await fetch(url);\n var data = await res.json();\n if (!data.error && data.data && data.data.length > 0) {\n results.push({ account_id: accountId, data: data.data[0] });\n } else if (data.error) {\n results.push({ account_id: accountId, error: data.error.message });\n } else {\n results.push({ account_id: accountId, data: null, note: 'sem dados no periodo' });\n }\n }\n return JSON.stringify(results);\n} catch(e) { return 'Erro de conexao: ' + e.message; }" + "description": "Busca insights de multiplas contas. Envie JSON com: account_ids (array), date_preset, fields.", + "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: JSON invalido'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar accountIds = params.account_ids || [];\nvar datePreset = params.date_preset || 'today';\nvar fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (!accountIds.length) return 'Erro: account_ids vazio';\ntry {\n var results = [];\n for (var i = 0; i < accountIds.length; i++) {\n var url = 'https://graph.facebook.com/v19.0/' + accountIds[i] + '/insights?date_preset=' + encodeURIComponent(datePreset) + '&fields=' + encodeURIComponent(fields) + '&access_token=' + token;\n var res = await fetch(url);\n var data = await res.json();\n if (data.error) results.push({ account_id: accountIds[i], error: data.error.message });\n else if (data.data && data.data.length) results.push({ account_id: accountIds[i], data: data.data[0] });\n else results.push({ account_id: accountIds[i], data: null });\n }\n return JSON.stringify(results);\n} catch(e) { return 'Erro conexao: ' + e.message; }" }, "id": "81aa87da-6607-4ffc-95b9-1be17e23ec68", "name": "Tool: Insights Multiplas Contas", @@ -345,176 +346,52 @@ "pinData": {}, "connections": { "Webhook Evolution API": { - "main": [ - [ - { - "node": "Parse Message", - "type": "main", - "index": 0 - } - ] - ] + "main": [[{ "node": "Parse Message", "type": "main", "index": 0 }]] }, "Parse Message": { - "main": [ - [ - { - "node": "Menciona Alicia?", - "type": "main", - "index": 0 - } - ] - ] + "main": [[{ "node": "Menciona Alicia?", "type": "main", "index": 0 }]] }, "Menciona Alicia?": { - "main": [ - [ - { - "node": "Calcular Datas", - "type": "main", - "index": 0 - } - ] - ] + "main": [[{ "node": "Calcular Datas", "type": "main", "index": 0 }]] }, "Calcular Datas": { - "main": [ - [ - { - "node": "E Relatorio Semanal?", - "type": "main", - "index": 0 - } - ] - ] + "main": [[{ "node": "E Relatorio Semanal?", "type": "main", "index": 0 }]] }, "E Relatorio Semanal?": { "main": [ - [ - { - "node": "Buscar Insights 7 Dias", - "type": "main", - "index": 0 - } - ], - [ - { - "node": "Buscar Contas Meta", - "type": "main", - "index": 0 - } - ] + [{ "node": "Buscar Insights 7 Dias", "type": "main", "index": 0 }], + [{ "node": "Buscar Contas Meta", "type": "main", "index": 0 }] ] }, "Buscar Insights 7 Dias": { - "main": [ - [ - { - "node": "Formatar Relatorio", - "type": "main", - "index": 0 - } - ] - ] + "main": [[{ "node": "Formatar Relatorio", "type": "main", "index": 0 }]] }, "Formatar Relatorio": { - "main": [ - [ - { - "node": "Enviar Relatorio WhatsApp", - "type": "main", - "index": 0 - } - ] - ] + "main": [[{ "node": "Enviar Relatorio WhatsApp", "type": "main", "index": 0 }]] }, "Buscar Contas Meta": { - "main": [ - [ - { - "node": "Montar Contexto", - "type": "main", - "index": 0 - } - ] - ] + "main": [[{ "node": "Montar Contexto", "type": "main", "index": 0 }]] }, "Montar Contexto": { - "main": [ - [ - { - "node": "Alicia Agent", - "type": "main", - "index": 0 - } - ] - ] + "main": [[{ "node": "Alicia Agent", "type": "main", "index": 0 }]] }, "Alicia Agent": { - "main": [ - [ - { - "node": "Enviar Resposta WhatsApp", - "type": "main", - "index": 0 - } - ] - ] + "main": [[{ "node": "Enviar Resposta WhatsApp", "type": "main", "index": 0 }]] }, "Claude Opus": { - "ai_languageModel": [ - [ - { - "node": "Alicia Agent", - "type": "ai_languageModel", - "index": 0 - } - ] - ] + "ai_languageModel": [[{ "node": "Alicia Agent", "type": "ai_languageModel", "index": 0 }]] }, "Tool: Buscar Insights": { - "ai_tool": [ - [ - { - "node": "Alicia Agent", - "type": "ai_tool", - "index": 0 - } - ] - ] + "ai_tool": [[{ "node": "Alicia Agent", "type": "ai_tool", "index": 0 }]] }, "Tool: Listar Campanhas": { - "ai_tool": [ - [ - { - "node": "Alicia Agent", - "type": "ai_tool", - "index": 0 - } - ] - ] + "ai_tool": [[{ "node": "Alicia Agent", "type": "ai_tool", "index": 0 }]] }, "Tool: Listar Conjuntos": { - "ai_tool": [ - [ - { - "node": "Alicia Agent", - "type": "ai_tool", - "index": 0 - } - ] - ] + "ai_tool": [[{ "node": "Alicia Agent", "type": "ai_tool", "index": 0 }]] }, "Tool: Insights Multiplas Contas": { - "ai_tool": [ - [ - { - "node": "Alicia Agent", - "type": "ai_tool", - "index": 0 - } - ] - ] + "ai_tool": [[{ "node": "Alicia Agent", "type": "ai_tool", "index": 0 }]] } }, "active": true, From ecc5d188b276c1458165b4d5b9e9ee06b276a706 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Mar 2026 01:51:42 +0000 Subject: [PATCH 04/16] fix: restaura formatacao verbose do JSON para 584 linhas - Conexoes expandidas em multiplas linhas (estilo original) - Posicoes dos nos em linhas separadas - jsCode com comentarios e estrutura legivel - Todos os 17 nos presentes com logica completa e corrigida https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 277 ++++++++++++++++++++++++++++++++------- 1 file changed, 226 insertions(+), 51 deletions(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index f2f0a4105..86b06b178 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -11,18 +11,24 @@ "name": "Webhook Evolution API", "type": "n8n-nodes-base.webhook", "typeVersion": 2, - "position": [-336, 112], + "position": [ + -336, + 112 + ], "webhookId": "alicia-meta-ads-001" }, { "parameters": { - "jsCode": "// Suporta: n8n webhook typeVersion 1 (body direto) e typeVersion 2 (body aninhado)\nconst raw = $input.first().json;\nconst payload = (raw.body && typeof raw.body === 'object' && raw.body !== null) ? raw.body : raw;\n\n// Ignora eventos que nao sao mensagens recebidas\nconst event = payload.event || '';\nif (event !== '' && event !== 'messages.upsert') return [];\n\nconst data = payload.data || {};\nconst key = data.key || {};\nconst msg = data.message || {};\n\n// Extrai texto de todos os tipos de mensagem suportados\nconst text = (\n msg.conversation ||\n (msg.extendedTextMessage && msg.extendedTextMessage.text) ||\n (msg.imageMessage && msg.imageMessage.caption) ||\n (msg.videoMessage && msg.videoMessage.caption) ||\n (msg.documentMessage && msg.documentMessage.caption) ||\n ''\n);\n\nconst remoteJid = key.remoteJid || '';\nconst fromMe = key.fromMe === true;\nconst instance = payload.instance || 'Teste';\n\n// Filtra: mensagem propria, texto vazio, grupo\nif (fromMe) return [];\nif (!text.trim()) return [];\nif (remoteJid.includes('@g.us')) return [];\n\nconst phone = remoteJid\n .replace('@s.whatsapp.net', '')\n .replace('@c.us', '');\n\nreturn [{ json: { text: text.trim(), phone: phone, remoteJid: remoteJid, instance: instance } }];" + "jsCode": "// Suporta webhook typeVersion 1 (body direto) e typeVersion 2 (body aninhado)\nconst raw = $input.first().json;\nconst payload = (raw.body && typeof raw.body === 'object' && raw.body !== null) ? raw.body : raw;\n\n// Ignora eventos que nao sao mensagens recebidas (connection.update, read receipts, etc)\nconst event = payload.event || '';\nif (event !== '' && event !== 'messages.upsert') return [];\n\nconst data = payload.data || {};\nconst key = data.key || {};\nconst msg = data.message || {};\n\nconst text =\n msg.conversation ||\n (msg.extendedTextMessage && msg.extendedTextMessage.text) ||\n (msg.imageMessage && msg.imageMessage.caption) ||\n (msg.videoMessage && msg.videoMessage.caption) ||\n (msg.documentMessage && msg.documentMessage.caption) ||\n '';\n\nconst remoteJid = key.remoteJid || '';\nconst fromMe = key.fromMe === true;\nconst instance = payload.instance || 'Teste';\n\nif (fromMe) return [];\nif (!text.trim()) return [];\nif (remoteJid.includes('@g.us')) return [];\n\nconst phone = remoteJid\n .replace('@s.whatsapp.net', '')\n .replace('@c.us', '');\n\nreturn [{ json: { text: text.trim(), phone: phone, remoteJid: remoteJid, instance: instance } }];" }, "id": "24a967e7-7602-4f55-bea1-a4dff46d1330", "name": "Parse Message", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [-112, 112] + "position": [ + -112, + 112 + ] }, { "parameters": { @@ -53,17 +59,23 @@ "name": "Menciona Alicia?", "type": "n8n-nodes-base.if", "typeVersion": 2, - "position": [112, 112] + "position": [ + 112, + 112 + ] }, { "parameters": { - "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\n\nfunction fmt(d) { return d.toISOString().split('T')[0]; }\nfunction addDays(n) {\n var d = new Date(now);\n d.setDate(d.getDate() + n);\n return fmt(d);\n}\n\nvar dow = now.getDay();\nvar daysToMon = (dow === 0) ? 6 : (dow - 1);\nvar monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\nvar clientMap = [\n { keys: ['antonio neto', 'antonio'], id: 'act_497795903676192', nome: 'Antonio Neto' },\n { keys: ['gabriel jacinto', 'gabriel'], id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n { keys: ['dani escudero', 'dani'], id: 'act_2145598605527232', nome: 'Dani Escudero' },\n { keys: ['agro'], id: 'act_929521557422139', nome: 'Agro' },\n { keys: ['escola de musica', 'escola de música', 'escola'], id: 'act_544347952854095', nome: 'Escola de Musica' },\n { keys: ['panmalhas'], id: 'act_1509005182799187', nome: 'Panmalhas' },\n { keys: ['gfix store', 'gfix'], id: 'act_2811791829124905', nome: 'GFiX Store' },\n { keys: ['arte em gelo', 'arte em'], id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n { keys: ['itag tecnologia', 'itag'], id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n { keys: ['hi dogz', 'dogz'], id: 'act_873195821346004', nome: 'Hi Dogz' },\n { keys: ['batata bistro', 'batata bistrô', 'batata'], id: 'act_1673012226810411', nome: 'Batata Bistro' },\n { keys: ['caribbean bronze', 'caribbean'], id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n { keys: ['larissa kelleter', 'larissa'], id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n];\n\n// Ordena por comprimento decrescente de chaves para priorizar matches mais especificos\nclientMap.sort(function(a, b) {\n return b.keys[0].length - a.keys[0].length;\n});\n\nvar isRelatorio = text.indexOf('relat') !== -1;\n\nvar clientFound = null;\nfor (var i = 0; i < clientMap.length; i++) {\n var entry = clientMap[i];\n for (var j = 0; j < entry.keys.length; j++) {\n if (text.indexOf(entry.keys[j]) !== -1) {\n clientFound = entry;\n break;\n }\n }\n if (clientFound) break;\n}\n\nvar today = fmt(now);\nvar yesterday = addDays(-1);\nvar last7Start = addDays(-7);\nvar last7End = yesterday;\nvar isRelatorioWithClient = (isRelatorio && clientFound !== null);\n\nreturn [{\n json: {\n text: input.text,\n phone: input.phone,\n remoteJid: input.remoteJid,\n instance: input.instance,\n today: today,\n yesterday: yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start: last7Start,\n last7End: last7End,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio: isRelatorio,\n isRelatorioWithClient: isRelatorioWithClient,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null\n }\n}];" + "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\nconst fmt = d => d.toISOString().split('T')[0];\nconst addDays = (n) => { const d = new Date(now); d.setDate(d.getDate() + n); return fmt(d); };\nconst dow = now.getDay();\nconst daysToMon = dow === 0 ? 6 : dow - 1;\nconst monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\n// clientMap como array para evitar chave duplicada e suportar multiplos aliases\nconst clientMap = [\n { keys: ['antonio neto', 'antonio'], id: 'act_497795903676192', nome: 'Antonio Neto' },\n { keys: ['gabriel jacinto', 'gabriel'], id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n { keys: ['dani escudero', 'dani'], id: 'act_2145598605527232', nome: 'Dani Escudero' },\n { keys: ['agro'], id: 'act_929521557422139', nome: 'Agro' },\n { keys: ['escola de musica', 'escola de m\\u00fasica', 'escola'], id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n { keys: ['panmalhas'], id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil' },\n { keys: ['gfix store', 'gfix'], id: 'act_2811791829124905', nome: 'GFiX Store' },\n { keys: ['arte em gelo', 'arte em'], id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n { keys: ['itag tecnologia', 'itag'], id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n { keys: ['hi dogz', 'dogz'], id: 'act_873195821346004', nome: 'Hi Dogz' },\n { keys: ['batata bistro', 'batata bistr\\u00f4', 'batata'], id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n { keys: ['caribbean bronze', 'caribbean'], id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n { keys: ['larissa kelleter', 'larissa'], id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n];\n\n// Ordena por comprimento da primeira chave (mais especifico primeiro)\nclientMap.sort((a, b) => b.keys[0].length - a.keys[0].length);\n\n// Detecta pedido de relatorio sem exigir a palavra 'semanal'\nconst isRelatorio = text.includes('relat');\n\nlet clientFound = null;\nfor (let i = 0; i < clientMap.length; i++) {\n const entry = clientMap[i];\n for (let j = 0; j < entry.keys.length; j++) {\n if (text.includes(entry.keys[j])) {\n clientFound = entry;\n break;\n }\n }\n if (clientFound) break;\n}\n\nconst today = fmt(now);\nconst yesterday = addDays(-1);\nconst last7Start = addDays(-7);\nconst last7End = yesterday;\nconst isRelatorioWithClient = isRelatorio && clientFound !== null;\n\nreturn [{\n json: {\n text: input.text,\n phone: input.phone,\n remoteJid: input.remoteJid,\n instance: input.instance,\n today: today,\n yesterday: yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start: last7Start,\n last7End: last7End,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio: isRelatorio,\n isRelatorioWithClient: isRelatorioWithClient,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null\n }\n}];" }, "id": "5b953b43-14ca-4ed3-b8da-3ec455f58b92", "name": "Calcular Datas", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [320, 112] + "position": [ + 320, + 112 + ] }, { "parameters": { @@ -91,10 +103,13 @@ "options": {} }, "id": "151d4a8d-966e-4ca1-a145-cc2692dec81e", - "name": "E Relatorio Semanal?", + "name": "É Relatório Semanal?", "type": "n8n-nodes-base.if", "typeVersion": 2, - "position": [544, 112] + "position": [ + 544, + 112 + ] }, { "parameters": { @@ -128,17 +143,23 @@ "name": "Buscar Insights 7 Dias", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, - "position": [768, -32] + "position": [ + 768, + -32 + ] }, { "parameters": { - "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\nfunction fmtMoney(val) {\n var n = parseFloat(val) || 0;\n var fixed = n.toFixed(2);\n var parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n}\n\nfunction fmtNum(val) {\n var n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n}\n\nfunction fmtBR(dateStr) {\n var d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n}\n\nif (!campaigns.length) {\n return [{ json: {\n phone: ctx.phone,\n output: 'Sem dados de campanhas para *' + (ctx.clientNome || '').toUpperCase() + '* nos ultimos 7 dias.'\n } }];\n}\n\nvar purchaseIds = ['act_873195821346004', 'act_1673012226810411'];\nvar isPurchase = purchaseIds.indexOf(ctx.clientFound) !== -1;\n\nvar totalSpend = 0;\nvar totalImpressions = 0;\nvar totalClicks = 0;\n\nfor (var i = 0; i < campaigns.length; i++) {\n totalSpend += parseFloat(campaigns[i].spend || '0');\n totalImpressions += parseInt(campaigns[i].impressions || '0');\n totalClicks += parseInt(campaigns[i].clicks || '0');\n}\n\nvar clientUpper = (ctx.clientNome || '').toUpperCase();\nvar dateRange = '_\\ud83d\\udcc6 ' + fmtBR(ctx.last7Start) + ' a ' + fmtBR(ctx.last7End) + ' (ultimos 7 dias)_';\nvar reportText;\n\nif (isPurchase) {\n var totalPurchases = 0;\n for (var i = 0; i < campaigns.length; i++) {\n var acts = campaigns[i].actions || [];\n for (var k = 0; k < acts.length; k++) {\n if (acts[k].action_type === 'purchase' || acts[k].action_type === 'offsite_conversion.fb_pixel_purchase') {\n totalPurchases += parseInt(acts[k].value) || 0;\n }\n }\n }\n var cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impressoes: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n var LEAD_TYPES = ['lead', 'onsite_conversion.lead_grouped', 'onsite_conversion.messaging_conversation_started_7d', 'onsite_conversion.total_messaging_connection'];\n var leadSpend = 0;\n var totalLeads = 0;\n for (var i = 0; i < campaigns.length; i++) {\n var acts = campaigns[i].actions || [];\n for (var k = 0; k < acts.length; k++) {\n if (LEAD_TYPES.indexOf(acts[k].action_type) !== -1) {\n leadSpend += parseFloat(campaigns[i].spend || '0');\n totalLeads += parseInt(acts[k].value) || 0;\n break;\n }\n }\n }\n var cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impressoes: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { phone: ctx.phone, output: reportText } }];" + "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\n// Formata valor monetario em pt-BR sem depender de ICU/locale\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\n// Formata numero inteiro com separador de milhar pt-BR\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nif (!campaigns.length) {\n return [{ json: {\n phone: ctx.phone,\n output: '\\u274c Sem dados para *' + (ctx.clientNome || '').toUpperCase() + '* nos \\u00faltimos 7 dias.'\n } }];\n}\n\n// Contas cujo objetivo principal \u00e9 compra direta (e-commerce / vendas)\nconst purchaseAccountIds = ['act_873195821346004', 'act_1673012226810411']; // Hi Dogz, Batata Bistr\\u00f4\nconst isPurchase = purchaseAccountIds.includes(ctx.clientFound);\n\nlet totalSpend = 0;\nlet totalImpressions = 0;\nlet totalClicks = 0;\n\nfor (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n}\n\nconst clientNameUpper = (ctx.clientNome || '').toUpperCase();\nconst dateRange = '_\\ud83d\\udcc6 ' + fmtBR(ctx.last7Start) + ' a ' + fmtBR(ctx.last7End) + ' (\\u00faltimos 7 dias)_';\nlet reportText;\n\nif (isPurchase) {\n // Campanha de vendas: mostra compras e custo por venda\n let totalPurchases = 0;\n for (const c of campaigns) {\n const purchaseAction = (c.actions || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (purchaseAction) totalPurchases += parseInt(purchaseAction.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n // Campanha de leads: verifica multiplos tipos de conversao\n const LEAD_TYPES = [\n 'lead',\n 'onsite_conversion.lead_grouped',\n 'onsite_conversion.messaging_conversation_started_7d',\n 'onsite_conversion.total_messaging_connection'\n ];\n\n let leadSpend = 0;\n let totalLeads = 0;\n for (const c of campaigns) {\n const leadAction = (c.actions || []).find(a => LEAD_TYPES.includes(a.action_type));\n if (leadAction) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(leadAction.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { phone: ctx.phone, output: reportText } }];" }, "id": "2b3745d9-82ef-4d9a-aa8d-db402408a07e", - "name": "Formatar Relatorio", + "name": "Formatar Relatório", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [992, -32] + "position": [ + 992, + -32 + ] }, { "parameters": { @@ -175,10 +196,13 @@ } }, "id": "b339c96a-4c60-4a45-98ea-4091d5ec847d", - "name": "Enviar Relatorio WhatsApp", + "name": "Enviar Relatório WhatsApp", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, - "position": [1216, -32] + "position": [ + 1216, + -32 + ] }, { "parameters": { @@ -208,31 +232,40 @@ "name": "Buscar Contas Meta", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, - "position": [768, 256] + "position": [ + 768, + 256 + ] }, { "parameters": { - "jsCode": "const metaResp = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst allAccounts = metaResp.data || [];\nconst accountsList = allAccounts.map(function(a) {\n var status = a.account_status === 1 ? 'Ativa' : 'Inativa';\n return '- ' + a.name + ' | ID: ' + a.id + ' | ' + status;\n}).join('\\n') || 'Nenhuma conta encontrada';\nreturn [{ json: {\n text: ctx.text,\n phone: ctx.phone,\n remoteJid: ctx.remoteJid,\n instance: ctx.instance,\n today: ctx.today,\n yesterday: ctx.yesterday,\n weekStart: ctx.weekStart,\n last7: ctx.last7,\n last30: ctx.last30,\n last7Start: ctx.last7Start,\n last7End: ctx.last7End,\n metaToken: ctx.metaToken,\n isRelatorio: ctx.isRelatorio,\n isRelatorioWithClient: ctx.isRelatorioWithClient,\n clientFound: ctx.clientFound,\n clientNome: ctx.clientNome,\n accounts: allAccounts,\n accountsList: accountsList\n} }];" + "jsCode": "const metaResp = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst allAccounts = metaResp.data || [];\nconst accountsList = allAccounts.map(a => {\n const status = a.account_status === 1 ? '\\u2705 Ativa' : '\\u23f8 Inativa';\n return '\\u2022 ' + a.name + ' | ID: ' + a.id + ' | ' + a.currency + ' | ' + status;\n}).join('\\n') || 'Nenhuma conta encontrada';\nreturn [{ json: {\n text: ctx.text,\n phone: ctx.phone,\n remoteJid: ctx.remoteJid,\n instance: ctx.instance,\n today: ctx.today,\n yesterday: ctx.yesterday,\n weekStart: ctx.weekStart,\n last7: ctx.last7,\n last30: ctx.last30,\n last7Start: ctx.last7Start,\n last7End: ctx.last7End,\n metaToken: ctx.metaToken,\n isRelatorio: ctx.isRelatorio,\n isRelatorioWithClient: ctx.isRelatorioWithClient,\n clientFound: ctx.clientFound,\n clientNome: ctx.clientNome,\n accounts: allAccounts,\n accountsList: accountsList\n} }];" }, "id": "9c2de825-ffbd-4870-b0e5-3f4a72c6c7b1", "name": "Montar Contexto", "type": "n8n-nodes-base.code", "typeVersion": 2, - "position": [992, 256] + "position": [ + 992, + 256 + ] }, { "parameters": { "promptType": "define", "text": "={{ $json.text }}", "options": { - "systemMessage": "=Voce e Alicia, assistente especializada em Meta Ads. Ajuda gestores de trafego a analisar metricas.\n\nDATA ATUAL: {{ $json.today }}\nONTEM: {{ $json.yesterday }}\nINICIO DA SEMANA: {{ $json.weekStart }}\n7 DIAS ATRAS: {{ $json.last7 }}\n30 DIAS ATRAS: {{ $json.last30 }}\n\nMAPEAMENTO CLIENTES:\n- Antonio Neto = act_497795903676192\n- Gabriel Jacinto = act_2493856707309984\n- Dani Escudero = act_2145598605527232\n- Agro = act_929521557422139\n- Escola de Musica = act_544347952854095\n- Panmalhas = act_1509005182799187\n- GFiX Store = act_2811791829124905\n- Arte em Gelo = act_5585082641598366\n- ITAG Tecnologia = act_738466861151636\n- Hi Dogz = act_873195821346004\n- Batata Bistro = act_1673012226810411\n- Caribbean Bronze = act_1082397873705862\n- Larissa Kelleter = act_738280412237298\n\nCONTAS:\n{{ $json.accountsList }}\n\nREGRAS:\n1. Aceite nomes parciais: itag, batata, gfix, caribbean, larissa, etc\n2. Se sem cliente, busque todas as ativas\n3. date_presets validos: today, yesterday, this_week_mon_today, last_7d, last_14d, last_30d\n4. Leads: action_type = lead OU onsite_conversion.lead_grouped\n5. CPL = gasto / leads\n6. Budgets em CENTAVOS - divida por 100\n7. Responda em portugues\n8. Use emojis\n\nFORMATO RELATORIO:\n*CLIENTE EM MAIUSCULAS*\n\n_data a data (ultimos 7 dias)_\n\n*Meta Ads*\n\nImpressoes: X.XXX\n\nCliques: X.XXX\n\nLeads: XX (ou Vendas para Hi Dogz e Batata Bistro)\n\nCusto por lead: R$ XX,XX\n\n*INVESTIMENTO TOTAL: R$ X.XXX,XX*" + "systemMessage": "=Voc\u00ea \u00e9 Alicia, assistente especializada em Meta Ads. Ajuda gestores de tr\u00e1fego a analisar m\u00e9tricas de campanhas de forma clara e direta.\n\n\ud83d\udcc5 DATA ATUAL: {{ $json.today }}\n\ud83d\udcc5 ONTEM: {{ $json.yesterday }}\n\ud83d\udcc5 IN\u00cdCIO DA SEMANA (seg): {{ $json.weekStart }}\n\ud83d\udcc5 7 DIAS ATR\u00c1S: {{ $json.last7 }}\n\ud83d\udcc5 30 DIAS ATR\u00c1S: {{ $json.last30 }}\n\n\ud83e\uddd1\u200d\ud83d\udcbc MAPEAMENTO DE CLIENTES \u2192 CONTAS:\n\u2022 Antonio Neto \u2192 act_497795903676192\n\u2022 Gabriel Jacinto \u2192 act_2493856707309984\n\u2022 Dani Escudero \u2192 act_2145598605527232\n\u2022 Agro \u2192 act_929521557422139\n\u2022 Escola de M\u00fasica \u2192 act_544347952854095\n\u2022 Panmalhas \u2192 act_1509005182799187\n\u2022 GFiX Store \u2192 act_2811791829124905\n\u2022 Arte em Gelo \u2192 act_5585082641598366\n\u2022 ITAG Tecnologia \u2192 act_738466861151636\n\u2022 Hi Dogz \u2192 act_873195821346004\n\u2022 Batata Bistr\u00f4 \u2192 act_1673012226810411\n\u2022 Caribbean Bronze \u2192 act_1082397873705862\n\u2022 Larissa Kelleter \u2192 act_738280412237298\n\n\ud83c\udfe2 CONTAS DISPON\u00cdVEIS (status atual):\n{{ $json.accountsList }}\n\n\ud83d\udccb REGRAS:\n1. Aceite nomes parciais: 'itag' = ITAG Tecnologia, 'batata' = Batata Bistr\u00f4, 'gfix' = GFiX Store, 'caribbean' = Caribbean Bronze\n2. Se n\u00e3o especificou cliente, busque de TODAS as contas ativas e consolide os valores\n3. date_presets v\u00e1lidos: today, yesterday, this_week_mon_today, last_7d, last_14d, last_30d, last_month\n4. Para LEADS: filtre actions por action_type = 'lead' OU 'onsite_conversion.lead_grouped'\n5. CPL = gasto_total \u00f7 quantidade_leads\n6. Budgets da Meta v\u00eam em CENTAVOS - divida por 100 para exibir em reais\n7. Responda SEMPRE em portugu\u00eas do Brasil\n8. Use emojis para tornar a resposta visual e amig\u00e1vel\n9. Seja concisa mas completa\n\n\ud83c\udfa8 FORMATO DE RESPOSTA:\n*NOME DO CLIENTE EM MAI\u00daSCULAS*\n\n_\ud83d\udcc6 DD/MM a DD/MM (\u00faltimos 7 dias)_\n\n*Meta Ads*\n\nImpress\u00f5es: X.XXX\n\nCliques: X.XXX\n\nLeads: XX (ou Vendas para Hi Dogz e Batata Bistr\u00f4)\n\nCusto por lead: R$ XX,XX (ou Custo por venda)\n\n*INVESTIMENTO TOTAL: R$ X.XXX,XX*\n\n\u26a0\ufe0f Se n\u00e3o houver dados para o per\u00edodo, informe o usu\u00e1rio claramente." } }, "id": "36e08f36-8634-478d-a32c-8bf22671531e", "name": "Alicia Agent", "type": "@n8n/n8n-nodes-langchain.agent", "typeVersion": 1.7, - "position": [1216, 256] + "position": [ + 1216, + 256 + ] }, { "parameters": { @@ -246,7 +279,10 @@ "name": "Claude Opus", "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic", "typeVersion": 1.3, - "position": [960, 480], + "position": [ + 960, + 480 + ], "credentials": { "anthropicApi": { "id": "7WwG495lbMecbH5G", @@ -257,50 +293,62 @@ { "parameters": { "name": "buscar_insights", - "description": "Busca metricas do Meta Ads. Envie JSON com: object_id (ID da conta ex: act_123456), date_preset (today/yesterday/last_7d/last_30d), fields (spend,impressions,clicks,ctr,cpc,actions,reach), level opcional (account/campaign/adset/ad).", - "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: JSON invalido. Envie object_id e date_preset'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar objectId = params.object_id;\nvar datePreset = params.date_preset || 'today';\nvar fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach';\nvar level = params.level || '';\nif (!objectId) return 'Erro: object_id obrigatorio';\nvar url = 'https://graph.facebook.com/v19.0/' + objectId + '/insights?date_preset=' + encodeURIComponent(datePreset) + '&fields=' + encodeURIComponent(fields) + '&access_token=' + token;\nif (level) url += '&level=' + encodeURIComponent(level);\ntry {\n var res = await fetch(url);\n var data = await res.json();\n if (data.error) return 'Erro Meta: ' + data.error.message;\n if (!data.data || data.data.length === 0) return 'Sem dados para o periodo.';\n return JSON.stringify(data.data);\n} catch(e) { return 'Erro conexao: ' + e.message; }" + "description": "Busca m\u00e9tricas/insights do Meta Ads. Use para: gasto (spend), leads (actions), impress\u00f5es, cliques, CTR, CPC, alcan\u00e7e. Envie JSON com: object_id (ID da conta ex: act_123456 OU ID de campanha), date_preset (today/yesterday/this_week_mon_today/last_7d/last_14d/last_30d/last_month), fields (ex: spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type), level opcional (account/campaign/adset/ad).", + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com object_id e date_preset'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst objectId = params.object_id;\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type';\nconst level = params.level || '';\nif (!objectId) return 'Erro: object_id \u00e9 obrigat\u00f3rio';\nlet url = `https://graph.facebook.com/v19.0/${objectId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\nif (level) url += `&level=${encodeURIComponent(level)}`;\ntry {\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message} (c\u00f3digo: ${data.error.code})`;\n if (!data.data || data.data.length === 0) return 'Nenhum dado encontrado para o per\u00edodo solicitado.';\n return JSON.stringify(data.data);\n} catch(e) { return `Erro de conex\u00e3o: ${e.message}`; }" }, "id": "13abddfb-207b-4d02-aa60-c7b8658c7860", "name": "Tool: Buscar Insights", "type": "@n8n/n8n-nodes-langchain.toolCode", "typeVersion": 1.1, - "position": [1136, 480] + "position": [ + 1136, + 480 + ] }, { "parameters": { "name": "listar_campanhas", - "description": "Lista campanhas de uma conta. Envie JSON com: account_id (formato: act_XXXXXXXXX).", - "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar accountId = params.account_id;\nif (!accountId) return 'Erro: account_id obrigatorio';\ntry {\n var url = 'https://graph.facebook.com/v19.0/' + accountId + '/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=' + token;\n var res = await fetch(url);\n var data = await res.json();\n if (data.error) return 'Erro Meta: ' + data.error.message;\n if (!data.data || !data.data.length) return 'Nenhuma campanha encontrada';\n return JSON.stringify(data.data.map(function(c) {\n return { id: c.id, nome: c.name, status: c.status, objetivo: c.objective,\n budget_diario: c.daily_budget ? (parseInt(c.daily_budget)/100).toFixed(2) : null,\n budget_total: c.lifetime_budget ? (parseInt(c.lifetime_budget)/100).toFixed(2) : null };\n }));\n} catch(e) { return 'Erro conexao: ' + e.message; }" + "description": "Lista todas as campanhas de uma conta de an\u00fancios. Envie JSON com: account_id (formato: act_XXXXXXXXX). Retorna: id, nome, status, objetivo, budget di\u00e1rio e total. Use para encontrar o ID de uma campanha espec\u00edfica antes de buscar os insights.", + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nif (!accountId) return 'Erro: account_id \u00e9 obrigat\u00f3rio (formato: act_XXXXXXXXX)';\ntry {\n const url = `https://graph.facebook.com/v19.0/${accountId}/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhuma campanha encontrada nessa conta';\n const campaigns = data.data.map(c => ({\n id: c.id,\n nome: c.name,\n status: c.status,\n objetivo: c.objective,\n budget_diario_reais: c.daily_budget ? (parseInt(c.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: c.lifetime_budget ? (parseInt(c.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(campaigns);\n} catch(e) { return `Erro de conex\u00e3o: ${e.message}`; }" }, "id": "731c36fc-e0b1-44a4-a13e-fcda6b6f2a2e", "name": "Tool: Listar Campanhas", "type": "@n8n/n8n-nodes-langchain.toolCode", "typeVersion": 1.1, - "position": [1296, 480] + "position": [ + 1296, + 480 + ] }, { "parameters": { "name": "listar_conjuntos", - "description": "Lista adsets de uma conta ou campanha. Envie JSON com: account_id (act_XXXXXXXXX) OU campaign_id.", - "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie account_id ou campaign_id'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar baseId = params.campaign_id || params.account_id;\nif (!baseId) return 'Erro: forneca account_id ou campaign_id';\ntry {\n var url = 'https://graph.facebook.com/v19.0/' + baseId + '/adsets?fields=id,name,status,daily_budget,lifetime_budget&limit=100&access_token=' + token;\n var res = await fetch(url);\n var data = await res.json();\n if (data.error) return 'Erro Meta: ' + data.error.message;\n if (!data.data || !data.data.length) return 'Nenhum adset encontrado';\n return JSON.stringify(data.data.map(function(a) {\n return { id: a.id, nome: a.name, status: a.status,\n budget_diario: a.daily_budget ? (parseInt(a.daily_budget)/100).toFixed(2) : null,\n budget_total: a.lifetime_budget ? (parseInt(a.lifetime_budget)/100).toFixed(2) : null };\n }));\n} catch(e) { return 'Erro conexao: ' + e.message; }" + "description": "Lista conjuntos de an\u00fancios (adsets) de uma conta ou campanha. Envie JSON com: account_id (act_XXXXXXXXX) OU campaign_id (ID num\u00e9rico). Retorna: id, nome, status e budget.", + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id ou campaign_id'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nconst campaignId = params.campaign_id;\nif (!accountId && !campaignId) return 'Erro: forne\u00e7a account_id ou campaign_id';\nconst baseId = campaignId || accountId;\ntry {\n const url = `https://graph.facebook.com/v19.0/${baseId}/adsets?fields=id,name,status,daily_budget,lifetime_budget,campaign_id&limit=100&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhum conjunto de an\u00fancios encontrado';\n const adsets = data.data.map(a => ({\n id: a.id,\n nome: a.name,\n status: a.status,\n budget_diario_reais: a.daily_budget ? (parseInt(a.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: a.lifetime_budget ? (parseInt(a.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(adsets);\n} catch(e) { return `Erro de conex\u00e3o: ${e.message}`; }" }, "id": "a8b12824-f38e-4296-87fe-49bd6e9f934b", "name": "Tool: Listar Conjuntos", "type": "@n8n/n8n-nodes-langchain.toolCode", "typeVersion": 1.1, - "position": [1408, 480] + "position": [ + 1408, + 480 + ] }, { "parameters": { "name": "buscar_insights_multiplas_contas", - "description": "Busca insights de multiplas contas. Envie JSON com: account_ids (array), date_preset, fields.", - "jsCode": "var params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: JSON invalido'; }\nvar token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nvar accountIds = params.account_ids || [];\nvar datePreset = params.date_preset || 'today';\nvar fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (!accountIds.length) return 'Erro: account_ids vazio';\ntry {\n var results = [];\n for (var i = 0; i < accountIds.length; i++) {\n var url = 'https://graph.facebook.com/v19.0/' + accountIds[i] + '/insights?date_preset=' + encodeURIComponent(datePreset) + '&fields=' + encodeURIComponent(fields) + '&access_token=' + token;\n var res = await fetch(url);\n var data = await res.json();\n if (data.error) results.push({ account_id: accountIds[i], error: data.error.message });\n else if (data.data && data.data.length) results.push({ account_id: accountIds[i], data: data.data[0] });\n else results.push({ account_id: accountIds[i], data: null });\n }\n return JSON.stringify(results);\n} catch(e) { return 'Erro conexao: ' + e.message; }" + "description": "Busca insights de M\u00daLTIPLAS contas ao mesmo tempo. Use quando o usu\u00e1rio n\u00e3o especificar cliente. Envie JSON com: account_ids (array de IDs ex: ['act_111','act_222']), date_preset, fields.", + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_ids (array), date_preset e fields'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountIds = params.account_ids || [];\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (accountIds.length === 0) return 'Erro: account_ids deve ser um array com pelo menos 1 conta';\ntry {\n const results = [];\n for (const accountId of accountIds) {\n const url = `https://graph.facebook.com/v19.0/${accountId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (!data.error && data.data && data.data.length > 0) {\n results.push({ account_id: accountId, data: data.data[0] });\n } else if (data.error) {\n results.push({ account_id: accountId, error: data.error.message });\n } else {\n results.push({ account_id: accountId, data: null, note: 'sem dados no per\u00edodo' });\n }\n }\n return JSON.stringify(results);\n} catch(e) { return `Erro de conex\u00e3o: ${e.message}`; }" }, "id": "81aa87da-6607-4ffc-95b9-1be17e23ec68", "name": "Tool: Insights Multiplas Contas", "type": "@n8n/n8n-nodes-langchain.toolCode", "typeVersion": 1.1, - "position": [1520, 480] + "position": [ + 1520, + 480 + ] }, { "parameters": { @@ -340,58 +388,185 @@ "name": "Enviar Resposta WhatsApp", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, - "position": [1520, 256] + "position": [ + 1520, + 256 + ] } ], "pinData": {}, "connections": { "Webhook Evolution API": { - "main": [[{ "node": "Parse Message", "type": "main", "index": 0 }]] + "main": [ + [ + { + "node": "Parse Message", + "type": "main", + "index": 0 + } + ] + ] }, "Parse Message": { - "main": [[{ "node": "Menciona Alicia?", "type": "main", "index": 0 }]] + "main": [ + [ + { + "node": "Menciona Alicia?", + "type": "main", + "index": 0 + } + ] + ] }, "Menciona Alicia?": { - "main": [[{ "node": "Calcular Datas", "type": "main", "index": 0 }]] + "main": [ + [ + { + "node": "Calcular Datas", + "type": "main", + "index": 0 + } + ] + ] }, "Calcular Datas": { - "main": [[{ "node": "E Relatorio Semanal?", "type": "main", "index": 0 }]] + "main": [ + [ + { + "node": "É Relatório Semanal?", + "type": "main", + "index": 0 + } + ] + ] }, - "E Relatorio Semanal?": { + "É Relatório Semanal?": { "main": [ - [{ "node": "Buscar Insights 7 Dias", "type": "main", "index": 0 }], - [{ "node": "Buscar Contas Meta", "type": "main", "index": 0 }] + [ + { + "node": "Buscar Insights 7 Dias", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Buscar Contas Meta", + "type": "main", + "index": 0 + } + ] ] }, "Buscar Insights 7 Dias": { - "main": [[{ "node": "Formatar Relatorio", "type": "main", "index": 0 }]] + "main": [ + [ + { + "node": "Formatar Relatório", + "type": "main", + "index": 0 + } + ] + ] }, - "Formatar Relatorio": { - "main": [[{ "node": "Enviar Relatorio WhatsApp", "type": "main", "index": 0 }]] + "Formatar Relatório": { + "main": [ + [ + { + "node": "Enviar Relatório WhatsApp", + "type": "main", + "index": 0 + } + ] + ] }, "Buscar Contas Meta": { - "main": [[{ "node": "Montar Contexto", "type": "main", "index": 0 }]] + "main": [ + [ + { + "node": "Montar Contexto", + "type": "main", + "index": 0 + } + ] + ] }, "Montar Contexto": { - "main": [[{ "node": "Alicia Agent", "type": "main", "index": 0 }]] + "main": [ + [ + { + "node": "Alicia Agent", + "type": "main", + "index": 0 + } + ] + ] }, "Alicia Agent": { - "main": [[{ "node": "Enviar Resposta WhatsApp", "type": "main", "index": 0 }]] + "main": [ + [ + { + "node": "Enviar Resposta WhatsApp", + "type": "main", + "index": 0 + } + ] + ] }, "Claude Opus": { - "ai_languageModel": [[{ "node": "Alicia Agent", "type": "ai_languageModel", "index": 0 }]] + "ai_languageModel": [ + [ + { + "node": "Alicia Agent", + "type": "ai_languageModel", + "index": 0 + } + ] + ] }, "Tool: Buscar Insights": { - "ai_tool": [[{ "node": "Alicia Agent", "type": "ai_tool", "index": 0 }]] + "ai_tool": [ + [ + { + "node": "Alicia Agent", + "type": "ai_tool", + "index": 0 + } + ] + ] }, "Tool: Listar Campanhas": { - "ai_tool": [[{ "node": "Alicia Agent", "type": "ai_tool", "index": 0 }]] + "ai_tool": [ + [ + { + "node": "Alicia Agent", + "type": "ai_tool", + "index": 0 + } + ] + ] }, "Tool: Listar Conjuntos": { - "ai_tool": [[{ "node": "Alicia Agent", "type": "ai_tool", "index": 0 }]] + "ai_tool": [ + [ + { + "node": "Alicia Agent", + "type": "ai_tool", + "index": 0 + } + ] + ] }, "Tool: Insights Multiplas Contas": { - "ai_tool": [[{ "node": "Alicia Agent", "type": "ai_tool", "index": 0 }]] + "ai_tool": [ + [ + { + "node": "Alicia Agent", + "type": "ai_tool", + "index": 0 + } + ] + ] } }, "active": true, From d9508b0837683a6a18fb1da40aaae97d9fa1ab93 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Mar 2026 02:09:37 +0000 Subject: [PATCH 05/16] =?UTF-8?q?feat:=20adiciona=20detec=C3=A7=C3=A3o=20d?= =?UTF-8?q?in=C3=A2mica=20de=20per=C3=ADodo=20nos=20relat=C3=B3rios=20Meta?= =?UTF-8?q?=20Ads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Calcular Datas: detecta palavras-chave no texto (ontem, hoje, essa semana, 14 dias, 30 dias, esse mês, mês passado) e mapeia para date_preset do Meta Ads - Buscar Insights 7 Dias: usa datePreset dinâmico em vez de last_7d fixo - Formatar Relatório: exibe label e intervalo de datas corretos conforme período solicitado (ex: "ontem", "últimos 14 dias", "mês passado") https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index 86b06b178..d2937d47b 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -66,7 +66,7 @@ }, { "parameters": { - "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\nconst fmt = d => d.toISOString().split('T')[0];\nconst addDays = (n) => { const d = new Date(now); d.setDate(d.getDate() + n); return fmt(d); };\nconst dow = now.getDay();\nconst daysToMon = dow === 0 ? 6 : dow - 1;\nconst monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\n// clientMap como array para evitar chave duplicada e suportar multiplos aliases\nconst clientMap = [\n { keys: ['antonio neto', 'antonio'], id: 'act_497795903676192', nome: 'Antonio Neto' },\n { keys: ['gabriel jacinto', 'gabriel'], id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n { keys: ['dani escudero', 'dani'], id: 'act_2145598605527232', nome: 'Dani Escudero' },\n { keys: ['agro'], id: 'act_929521557422139', nome: 'Agro' },\n { keys: ['escola de musica', 'escola de m\\u00fasica', 'escola'], id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n { keys: ['panmalhas'], id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil' },\n { keys: ['gfix store', 'gfix'], id: 'act_2811791829124905', nome: 'GFiX Store' },\n { keys: ['arte em gelo', 'arte em'], id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n { keys: ['itag tecnologia', 'itag'], id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n { keys: ['hi dogz', 'dogz'], id: 'act_873195821346004', nome: 'Hi Dogz' },\n { keys: ['batata bistro', 'batata bistr\\u00f4', 'batata'], id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n { keys: ['caribbean bronze', 'caribbean'], id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n { keys: ['larissa kelleter', 'larissa'], id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n];\n\n// Ordena por comprimento da primeira chave (mais especifico primeiro)\nclientMap.sort((a, b) => b.keys[0].length - a.keys[0].length);\n\n// Detecta pedido de relatorio sem exigir a palavra 'semanal'\nconst isRelatorio = text.includes('relat');\n\nlet clientFound = null;\nfor (let i = 0; i < clientMap.length; i++) {\n const entry = clientMap[i];\n for (let j = 0; j < entry.keys.length; j++) {\n if (text.includes(entry.keys[j])) {\n clientFound = entry;\n break;\n }\n }\n if (clientFound) break;\n}\n\nconst today = fmt(now);\nconst yesterday = addDays(-1);\nconst last7Start = addDays(-7);\nconst last7End = yesterday;\nconst isRelatorioWithClient = isRelatorio && clientFound !== null;\n\nreturn [{\n json: {\n text: input.text,\n phone: input.phone,\n remoteJid: input.remoteJid,\n instance: input.instance,\n today: today,\n yesterday: yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start: last7Start,\n last7End: last7End,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio: isRelatorio,\n isRelatorioWithClient: isRelatorioWithClient,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null\n }\n}];" + "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\nconst fmt = d => d.toISOString().split('T')[0];\nconst addDays = (n) => { const d = new Date(now); d.setDate(d.getDate() + n); return fmt(d); };\nconst dow = now.getDay();\nconst daysToMon = dow === 0 ? 6 : dow - 1;\nconst monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\n// clientMap como array para evitar chave duplicada e suportar multiplos aliases\nconst clientMap = [\n { keys: ['antonio neto', 'antonio'], id: 'act_497795903676192', nome: 'Antonio Neto' },\n { keys: ['gabriel jacinto', 'gabriel'], id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n { keys: ['dani escudero', 'dani'], id: 'act_2145598605527232', nome: 'Dani Escudero' },\n { keys: ['agro'], id: 'act_929521557422139', nome: 'Agro' },\n { keys: ['escola de musica', 'escola de m\\u00fasica', 'escola'], id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n { keys: ['panmalhas'], id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil' },\n { keys: ['gfix store', 'gfix'], id: 'act_2811791829124905', nome: 'GFiX Store' },\n { keys: ['arte em gelo', 'arte em'], id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n { keys: ['itag tecnologia', 'itag'], id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n { keys: ['hi dogz', 'dogz'], id: 'act_873195821346004', nome: 'Hi Dogz' },\n { keys: ['batata bistro', 'batata bistr\\u00f4', 'batata'], id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n { keys: ['caribbean bronze', 'caribbean'], id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n { keys: ['larissa kelleter', 'larissa'], id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n];\n\n// Ordena por comprimento da primeira chave (mais especifico primeiro)\nclientMap.sort((a, b) => b.keys[0].length - a.keys[0].length);\n\n// Detecta pedido de relatorio sem exigir a palavra 'semanal'\nconst isRelatorio = text.includes('relat');\n\nlet clientFound = null;\nfor (let i = 0; i < clientMap.length; i++) {\n const entry = clientMap[i];\n for (let j = 0; j < entry.keys.length; j++) {\n if (text.includes(entry.keys[j])) {\n clientFound = entry;\n break;\n }\n }\n if (clientFound) break;\n}\n\nconst today = fmt(now);\nconst yesterday = addDays(-1);\nconst last7Start = addDays(-7);\nconst last7End = yesterday;\nconst isRelatorioWithClient = isRelatorio && clientFound !== null;\n\n// Detecta periodo solicitado e mapeia para date_preset do Meta Ads\nconst hasMesPassado = text.includes('m\\u00eas passado') || text.includes('mes passado');\nconst hasEsseMes = text.includes('esse m\\u00eas') || text.includes('este m\\u00eas') || text.includes('esse mes') || text.includes('este mes');\n\nlet datePreset = 'last_7d';\nlet periodoLabel = '\\u00faltimos 7 dias';\nlet periodStart = last7Start;\nlet periodEnd = last7End;\n\nif (hasMesPassado) {\n datePreset = 'last_month';\n periodoLabel = 'm\\u00eas passado';\n const firstOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n const lastOfPrevMonth = new Date(firstOfMonth.getTime() - 86400000);\n const firstOfPrevMonth = new Date(lastOfPrevMonth.getFullYear(), lastOfPrevMonth.getMonth(), 1);\n periodStart = fmt(firstOfPrevMonth);\n periodEnd = fmt(lastOfPrevMonth);\n} else if (text.includes('ontem')) {\n datePreset = 'yesterday';\n periodoLabel = 'ontem';\n periodStart = yesterday;\n periodEnd = yesterday;\n} else if (text.includes('hoje')) {\n datePreset = 'today';\n periodoLabel = 'hoje';\n periodStart = today;\n periodEnd = today;\n} else if (text.includes('essa semana') || text.includes('esta semana')) {\n datePreset = 'this_week_mon_today';\n periodoLabel = 'essa semana';\n periodStart = fmt(monday);\n periodEnd = today;\n} else if (text.includes('14 dias')) {\n datePreset = 'last_14d';\n periodoLabel = '\\u00faltimos 14 dias';\n periodStart = addDays(-14);\n periodEnd = yesterday;\n} else if (text.includes('30 dias') || hasEsseMes) {\n datePreset = 'last_30d';\n periodoLabel = '\\u00faltimos 30 dias';\n periodStart = addDays(-30);\n periodEnd = yesterday;\n}\n\nreturn [{\n json: {\n text: input.text,\n phone: input.phone,\n remoteJid: input.remoteJid,\n instance: input.instance,\n today: today,\n yesterday: yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start: last7Start,\n last7End: last7End,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio: isRelatorio,\n isRelatorioWithClient: isRelatorioWithClient,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null,\n datePreset: datePreset,\n periodoLabel: periodoLabel,\n periodStart: periodStart,\n periodEnd: periodEnd\n }\n}];" }, "id": "5b953b43-14ca-4ed3-b8da-3ec455f58b92", "name": "Calcular Datas", @@ -119,7 +119,7 @@ "parameters": [ { "name": "date_preset", - "value": "last_7d" + "value": "={{ $('Calcular Datas').first().json.datePreset }}" }, { "name": "fields", @@ -150,7 +150,7 @@ }, { "parameters": { - "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\n// Formata valor monetario em pt-BR sem depender de ICU/locale\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\n// Formata numero inteiro com separador de milhar pt-BR\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nif (!campaigns.length) {\n return [{ json: {\n phone: ctx.phone,\n output: '\\u274c Sem dados para *' + (ctx.clientNome || '').toUpperCase() + '* nos \\u00faltimos 7 dias.'\n } }];\n}\n\n// Contas cujo objetivo principal \u00e9 compra direta (e-commerce / vendas)\nconst purchaseAccountIds = ['act_873195821346004', 'act_1673012226810411']; // Hi Dogz, Batata Bistr\\u00f4\nconst isPurchase = purchaseAccountIds.includes(ctx.clientFound);\n\nlet totalSpend = 0;\nlet totalImpressions = 0;\nlet totalClicks = 0;\n\nfor (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n}\n\nconst clientNameUpper = (ctx.clientNome || '').toUpperCase();\nconst dateRange = '_\\ud83d\\udcc6 ' + fmtBR(ctx.last7Start) + ' a ' + fmtBR(ctx.last7End) + ' (\\u00faltimos 7 dias)_';\nlet reportText;\n\nif (isPurchase) {\n // Campanha de vendas: mostra compras e custo por venda\n let totalPurchases = 0;\n for (const c of campaigns) {\n const purchaseAction = (c.actions || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (purchaseAction) totalPurchases += parseInt(purchaseAction.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n // Campanha de leads: verifica multiplos tipos de conversao\n const LEAD_TYPES = [\n 'lead',\n 'onsite_conversion.lead_grouped',\n 'onsite_conversion.messaging_conversation_started_7d',\n 'onsite_conversion.total_messaging_connection'\n ];\n\n let leadSpend = 0;\n let totalLeads = 0;\n for (const c of campaigns) {\n const leadAction = (c.actions || []).find(a => LEAD_TYPES.includes(a.action_type));\n if (leadAction) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(leadAction.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { phone: ctx.phone, output: reportText } }];" + "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\n// Formata valor monetario em pt-BR sem depender de ICU/locale\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\n// Formata numero inteiro com separador de milhar pt-BR\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nif (!campaigns.length) {\n return [{ json: {\n phone: ctx.phone,\n output: '\\u274c Sem dados para *' + (ctx.clientNome || '').toUpperCase() + '* nos \\u00faltimos 7 dias.'\n } }];\n}\n\n// Contas cujo objetivo principal \u00e9 compra direta (e-commerce / vendas)\nconst purchaseAccountIds = ['act_873195821346004', 'act_1673012226810411']; // Hi Dogz, Batata Bistr\\u00f4\nconst isPurchase = purchaseAccountIds.includes(ctx.clientFound);\n\nlet totalSpend = 0;\nlet totalImpressions = 0;\nlet totalClicks = 0;\n\nfor (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n}\n\nconst clientNameUpper = (ctx.clientNome || '').toUpperCase();\nconst periodoLabel = ctx.periodoLabel || '\\u00faltimos 7 dias';\nconst periodStart = ctx.periodStart || ctx.last7Start;\nconst periodEnd = ctx.periodEnd || ctx.last7End;\nconst dateRange = periodStart === periodEnd\n ? '_\\ud83d\\udcc6 ' + fmtBR(periodStart) + ' (' + periodoLabel + ')_'\n : '_\\ud83d\\udcc6 ' + fmtBR(periodStart) + ' a ' + fmtBR(periodEnd) + ' (' + periodoLabel + ')_';\nlet reportText;\n\nif (isPurchase) {\n // Campanha de vendas: mostra compras e custo por venda\n let totalPurchases = 0;\n for (const c of campaigns) {\n const purchaseAction = (c.actions || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (purchaseAction) totalPurchases += parseInt(purchaseAction.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n // Campanha de leads: verifica multiplos tipos de conversao\n const LEAD_TYPES = [\n 'lead',\n 'onsite_conversion.lead_grouped',\n 'onsite_conversion.messaging_conversation_started_7d',\n 'onsite_conversion.total_messaging_connection'\n ];\n\n let leadSpend = 0;\n let totalLeads = 0;\n for (const c of campaigns) {\n const leadAction = (c.actions || []).find(a => LEAD_TYPES.includes(a.action_type));\n if (leadAction) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(leadAction.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { phone: ctx.phone, output: reportText } }];" }, "id": "2b3745d9-82ef-4d9a-aa8d-db402408a07e", "name": "Formatar Relatório", From e856a1128e4a4c4aa787dd3a20591fd9f6476eb6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Mar 2026 02:17:07 +0000 Subject: [PATCH 06/16] =?UTF-8?q?feat:=20adiciona=20faturamento=20(valor?= =?UTF-8?q?=20de=20convers=C3=A3o=20de=20compra)=20no=20relat=C3=B3rio=20d?= =?UTF-8?q?e=20vendas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nas contas de vendas (Hi Dogz, Batata Bistrô), o relatório agora exibe o campo 'Faturamento' calculado a partir de action_values da Meta Ads API. https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index d2937d47b..15151d27e 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -123,7 +123,7 @@ }, { "name": "fields", - "value": "spend,impressions,clicks,ctr,actions,reach,campaign_name" + "value": "spend,impressions,clicks,ctr,actions,action_values,reach,campaign_name" }, { "name": "level", @@ -150,7 +150,7 @@ }, { "parameters": { - "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\n// Formata valor monetario em pt-BR sem depender de ICU/locale\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\n// Formata numero inteiro com separador de milhar pt-BR\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nif (!campaigns.length) {\n return [{ json: {\n phone: ctx.phone,\n output: '\\u274c Sem dados para *' + (ctx.clientNome || '').toUpperCase() + '* nos \\u00faltimos 7 dias.'\n } }];\n}\n\n// Contas cujo objetivo principal \u00e9 compra direta (e-commerce / vendas)\nconst purchaseAccountIds = ['act_873195821346004', 'act_1673012226810411']; // Hi Dogz, Batata Bistr\\u00f4\nconst isPurchase = purchaseAccountIds.includes(ctx.clientFound);\n\nlet totalSpend = 0;\nlet totalImpressions = 0;\nlet totalClicks = 0;\n\nfor (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n}\n\nconst clientNameUpper = (ctx.clientNome || '').toUpperCase();\nconst periodoLabel = ctx.periodoLabel || '\\u00faltimos 7 dias';\nconst periodStart = ctx.periodStart || ctx.last7Start;\nconst periodEnd = ctx.periodEnd || ctx.last7End;\nconst dateRange = periodStart === periodEnd\n ? '_\\ud83d\\udcc6 ' + fmtBR(periodStart) + ' (' + periodoLabel + ')_'\n : '_\\ud83d\\udcc6 ' + fmtBR(periodStart) + ' a ' + fmtBR(periodEnd) + ' (' + periodoLabel + ')_';\nlet reportText;\n\nif (isPurchase) {\n // Campanha de vendas: mostra compras e custo por venda\n let totalPurchases = 0;\n for (const c of campaigns) {\n const purchaseAction = (c.actions || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (purchaseAction) totalPurchases += parseInt(purchaseAction.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n // Campanha de leads: verifica multiplos tipos de conversao\n const LEAD_TYPES = [\n 'lead',\n 'onsite_conversion.lead_grouped',\n 'onsite_conversion.messaging_conversation_started_7d',\n 'onsite_conversion.total_messaging_connection'\n ];\n\n let leadSpend = 0;\n let totalLeads = 0;\n for (const c of campaigns) {\n const leadAction = (c.actions || []).find(a => LEAD_TYPES.includes(a.action_type));\n if (leadAction) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(leadAction.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { phone: ctx.phone, output: reportText } }];" + "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\n// Formata valor monetario em pt-BR sem depender de ICU/locale\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\n// Formata numero inteiro com separador de milhar pt-BR\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nif (!campaigns.length) {\n return [{ json: {\n phone: ctx.phone,\n output: '\\u274c Sem dados para *' + (ctx.clientNome || '').toUpperCase() + '* nos \\u00faltimos 7 dias.'\n } }];\n}\n\n// Contas cujo objetivo principal \u00e9 compra direta (e-commerce / vendas)\nconst purchaseAccountIds = ['act_873195821346004', 'act_1673012226810411']; // Hi Dogz, Batata Bistr\\u00f4\nconst isPurchase = purchaseAccountIds.includes(ctx.clientFound);\n\nlet totalSpend = 0;\nlet totalImpressions = 0;\nlet totalClicks = 0;\n\nfor (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n}\n\nconst clientNameUpper = (ctx.clientNome || '').toUpperCase();\nconst periodoLabel = ctx.periodoLabel || '\\u00faltimos 7 dias';\nconst periodStart = ctx.periodStart || ctx.last7Start;\nconst periodEnd = ctx.periodEnd || ctx.last7End;\nconst dateRange = periodStart === periodEnd\n ? '_\\ud83d\\udcc6 ' + fmtBR(periodStart) + ' (' + periodoLabel + ')_'\n : '_\\ud83d\\udcc6 ' + fmtBR(periodStart) + ' a ' + fmtBR(periodEnd) + ' (' + periodoLabel + ')_';\nlet reportText;\n\nif (isPurchase) {\n // Campanha de vendas: mostra compras e custo por venda\n let totalPurchases = 0;\n let totalRevenue = 0;\n for (const c of campaigns) {\n const purchaseAction = (c.actions || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (purchaseAction) totalPurchases += parseInt(purchaseAction.value);\n const revenueAction = (c.action_values || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (revenueAction) totalRevenue += parseFloat(revenueAction.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Faturamento: R$ ' + fmtMoney(totalRevenue) + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n // Campanha de leads: verifica multiplos tipos de conversao\n const LEAD_TYPES = [\n 'lead',\n 'onsite_conversion.lead_grouped',\n 'onsite_conversion.messaging_conversation_started_7d',\n 'onsite_conversion.total_messaging_connection'\n ];\n\n let leadSpend = 0;\n let totalLeads = 0;\n for (const c of campaigns) {\n const leadAction = (c.actions || []).find(a => LEAD_TYPES.includes(a.action_type));\n if (leadAction) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(leadAction.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { phone: ctx.phone, output: reportText } }];" }, "id": "2b3745d9-82ef-4d9a-aa8d-db402408a07e", "name": "Formatar Relatório", From 806a1a83ad90bb0a4581b2f7b03a33ab076dc46c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Mar 2026 02:20:19 +0000 Subject: [PATCH 07/16] =?UTF-8?q?fix:=20corrige=20intervalo=20de=20datas?= =?UTF-8?q?=20para=20bater=20com=20o=20padr=C3=A3o=20do=20Meta=20Ads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit last_7d: termina 2 dias atrás (não ontem), pois dados de ontem podem estar incompletos. Mesmo padrão aplicado a last_14d e last_30d. Ex: hoje 15/03 → últimos 7 dias exibe 07/03 a 13/03. https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index 15151d27e..845518092 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -66,7 +66,7 @@ }, { "parameters": { - "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\nconst fmt = d => d.toISOString().split('T')[0];\nconst addDays = (n) => { const d = new Date(now); d.setDate(d.getDate() + n); return fmt(d); };\nconst dow = now.getDay();\nconst daysToMon = dow === 0 ? 6 : dow - 1;\nconst monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\n// clientMap como array para evitar chave duplicada e suportar multiplos aliases\nconst clientMap = [\n { keys: ['antonio neto', 'antonio'], id: 'act_497795903676192', nome: 'Antonio Neto' },\n { keys: ['gabriel jacinto', 'gabriel'], id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n { keys: ['dani escudero', 'dani'], id: 'act_2145598605527232', nome: 'Dani Escudero' },\n { keys: ['agro'], id: 'act_929521557422139', nome: 'Agro' },\n { keys: ['escola de musica', 'escola de m\\u00fasica', 'escola'], id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n { keys: ['panmalhas'], id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil' },\n { keys: ['gfix store', 'gfix'], id: 'act_2811791829124905', nome: 'GFiX Store' },\n { keys: ['arte em gelo', 'arte em'], id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n { keys: ['itag tecnologia', 'itag'], id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n { keys: ['hi dogz', 'dogz'], id: 'act_873195821346004', nome: 'Hi Dogz' },\n { keys: ['batata bistro', 'batata bistr\\u00f4', 'batata'], id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n { keys: ['caribbean bronze', 'caribbean'], id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n { keys: ['larissa kelleter', 'larissa'], id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n];\n\n// Ordena por comprimento da primeira chave (mais especifico primeiro)\nclientMap.sort((a, b) => b.keys[0].length - a.keys[0].length);\n\n// Detecta pedido de relatorio sem exigir a palavra 'semanal'\nconst isRelatorio = text.includes('relat');\n\nlet clientFound = null;\nfor (let i = 0; i < clientMap.length; i++) {\n const entry = clientMap[i];\n for (let j = 0; j < entry.keys.length; j++) {\n if (text.includes(entry.keys[j])) {\n clientFound = entry;\n break;\n }\n }\n if (clientFound) break;\n}\n\nconst today = fmt(now);\nconst yesterday = addDays(-1);\nconst last7Start = addDays(-7);\nconst last7End = yesterday;\nconst isRelatorioWithClient = isRelatorio && clientFound !== null;\n\n// Detecta periodo solicitado e mapeia para date_preset do Meta Ads\nconst hasMesPassado = text.includes('m\\u00eas passado') || text.includes('mes passado');\nconst hasEsseMes = text.includes('esse m\\u00eas') || text.includes('este m\\u00eas') || text.includes('esse mes') || text.includes('este mes');\n\nlet datePreset = 'last_7d';\nlet periodoLabel = '\\u00faltimos 7 dias';\nlet periodStart = last7Start;\nlet periodEnd = last7End;\n\nif (hasMesPassado) {\n datePreset = 'last_month';\n periodoLabel = 'm\\u00eas passado';\n const firstOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n const lastOfPrevMonth = new Date(firstOfMonth.getTime() - 86400000);\n const firstOfPrevMonth = new Date(lastOfPrevMonth.getFullYear(), lastOfPrevMonth.getMonth(), 1);\n periodStart = fmt(firstOfPrevMonth);\n periodEnd = fmt(lastOfPrevMonth);\n} else if (text.includes('ontem')) {\n datePreset = 'yesterday';\n periodoLabel = 'ontem';\n periodStart = yesterday;\n periodEnd = yesterday;\n} else if (text.includes('hoje')) {\n datePreset = 'today';\n periodoLabel = 'hoje';\n periodStart = today;\n periodEnd = today;\n} else if (text.includes('essa semana') || text.includes('esta semana')) {\n datePreset = 'this_week_mon_today';\n periodoLabel = 'essa semana';\n periodStart = fmt(monday);\n periodEnd = today;\n} else if (text.includes('14 dias')) {\n datePreset = 'last_14d';\n periodoLabel = '\\u00faltimos 14 dias';\n periodStart = addDays(-14);\n periodEnd = yesterday;\n} else if (text.includes('30 dias') || hasEsseMes) {\n datePreset = 'last_30d';\n periodoLabel = '\\u00faltimos 30 dias';\n periodStart = addDays(-30);\n periodEnd = yesterday;\n}\n\nreturn [{\n json: {\n text: input.text,\n phone: input.phone,\n remoteJid: input.remoteJid,\n instance: input.instance,\n today: today,\n yesterday: yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start: last7Start,\n last7End: last7End,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio: isRelatorio,\n isRelatorioWithClient: isRelatorioWithClient,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null,\n datePreset: datePreset,\n periodoLabel: periodoLabel,\n periodStart: periodStart,\n periodEnd: periodEnd\n }\n}];" + "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\nconst fmt = d => d.toISOString().split('T')[0];\nconst addDays = (n) => { const d = new Date(now); d.setDate(d.getDate() + n); return fmt(d); };\nconst dow = now.getDay();\nconst daysToMon = dow === 0 ? 6 : dow - 1;\nconst monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\n// clientMap como array para evitar chave duplicada e suportar multiplos aliases\nconst clientMap = [\n { keys: ['antonio neto', 'antonio'], id: 'act_497795903676192', nome: 'Antonio Neto' },\n { keys: ['gabriel jacinto', 'gabriel'], id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n { keys: ['dani escudero', 'dani'], id: 'act_2145598605527232', nome: 'Dani Escudero' },\n { keys: ['agro'], id: 'act_929521557422139', nome: 'Agro' },\n { keys: ['escola de musica', 'escola de m\\u00fasica', 'escola'], id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n { keys: ['panmalhas'], id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil' },\n { keys: ['gfix store', 'gfix'], id: 'act_2811791829124905', nome: 'GFiX Store' },\n { keys: ['arte em gelo', 'arte em'], id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n { keys: ['itag tecnologia', 'itag'], id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n { keys: ['hi dogz', 'dogz'], id: 'act_873195821346004', nome: 'Hi Dogz' },\n { keys: ['batata bistro', 'batata bistr\\u00f4', 'batata'], id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n { keys: ['caribbean bronze', 'caribbean'], id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n { keys: ['larissa kelleter', 'larissa'], id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n];\n\n// Ordena por comprimento da primeira chave (mais especifico primeiro)\nclientMap.sort((a, b) => b.keys[0].length - a.keys[0].length);\n\n// Detecta pedido de relatorio sem exigir a palavra 'semanal'\nconst isRelatorio = text.includes('relat');\n\nlet clientFound = null;\nfor (let i = 0; i < clientMap.length; i++) {\n const entry = clientMap[i];\n for (let j = 0; j < entry.keys.length; j++) {\n if (text.includes(entry.keys[j])) {\n clientFound = entry;\n break;\n }\n }\n if (clientFound) break;\n}\n\nconst today = fmt(now);\nconst yesterday = addDays(-1);\n// Meta Ads last_7d termina 2 dias atras (dados de ontem ainda podem estar incompletos)\nconst last7End = addDays(-2);\nconst last7Start = addDays(-8);\nconst isRelatorioWithClient = isRelatorio && clientFound !== null;\n\n// Detecta periodo solicitado e mapeia para date_preset do Meta Ads\nconst hasMesPassado = text.includes('m\\u00eas passado') || text.includes('mes passado');\nconst hasEsseMes = text.includes('esse m\\u00eas') || text.includes('este m\\u00eas') || text.includes('esse mes') || text.includes('este mes');\n\nlet datePreset = 'last_7d';\nlet periodoLabel = '\\u00faltimos 7 dias';\nlet periodStart = last7Start;\nlet periodEnd = last7End;\n\nif (hasMesPassado) {\n datePreset = 'last_month';\n periodoLabel = 'm\\u00eas passado';\n const firstOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n const lastOfPrevMonth = new Date(firstOfMonth.getTime() - 86400000);\n const firstOfPrevMonth = new Date(lastOfPrevMonth.getFullYear(), lastOfPrevMonth.getMonth(), 1);\n periodStart = fmt(firstOfPrevMonth);\n periodEnd = fmt(lastOfPrevMonth);\n} else if (text.includes('ontem')) {\n datePreset = 'yesterday';\n periodoLabel = 'ontem';\n periodStart = yesterday;\n periodEnd = yesterday;\n} else if (text.includes('hoje')) {\n datePreset = 'today';\n periodoLabel = 'hoje';\n periodStart = today;\n periodEnd = today;\n} else if (text.includes('essa semana') || text.includes('esta semana')) {\n datePreset = 'this_week_mon_today';\n periodoLabel = 'essa semana';\n periodStart = fmt(monday);\n periodEnd = today;\n} else if (text.includes('14 dias')) {\n datePreset = 'last_14d';\n periodoLabel = '\\u00faltimos 14 dias';\n periodStart = addDays(-15);\n periodEnd = addDays(-2);\n} else if (text.includes('30 dias') || hasEsseMes) {\n datePreset = 'last_30d';\n periodoLabel = '\\u00faltimos 30 dias';\n periodStart = addDays(-31);\n periodEnd = addDays(-2);\n}\n\nreturn [{\n json: {\n text: input.text,\n phone: input.phone,\n remoteJid: input.remoteJid,\n instance: input.instance,\n today: today,\n yesterday: yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start: last7Start,\n last7End: last7End,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio: isRelatorio,\n isRelatorioWithClient: isRelatorioWithClient,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null,\n datePreset: datePreset,\n periodoLabel: periodoLabel,\n periodStart: periodStart,\n periodEnd: periodEnd\n }\n}];" }, "id": "5b953b43-14ca-4ed3-b8da-3ec455f58b92", "name": "Calcular Datas", From 368c46fd4a1ba1f33b75a7ee6f551d2ef22f2ad0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 18:54:38 +0000 Subject: [PATCH 08/16] chore: atualiza credenciais para novo ambiente (xeq31m.easypanel.host) - Evolution API URL e instancia atualizados (Clarice) - API key e ID da credencial Anthropic marcados como placeholder https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index 845518092..01f63a099 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -164,13 +164,13 @@ { "parameters": { "method": "POST", - "url": "https://evolution-evolution-api.rcxmr5.easypanel.host/message/sendText/Teste", + "url": "https://n8n-evolution-api.xeq31m.easypanel.host/message/sendText/Clarice", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "apikey", - "value": "BE670335A833-4FE4-84CB-29FA65A8ED73" + "value": "SUA_APIKEY_AQUI" }, { "name": "Content-Type", @@ -285,7 +285,7 @@ ], "credentials": { "anthropicApi": { - "id": "7WwG495lbMecbH5G", + "id": "SEU_ID_ANTHROPIC_AQUI", "name": "Anthropic API" } } @@ -353,13 +353,13 @@ { "parameters": { "method": "POST", - "url": "https://evolution-evolution-api.rcxmr5.easypanel.host/message/sendText/Teste", + "url": "https://n8n-evolution-api.xeq31m.easypanel.host/message/sendText/Clarice", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "apikey", - "value": "BE670335A833-4FE4-84CB-29FA65A8ED73" + "value": "SUA_APIKEY_AQUI" }, { "name": "Content-Type", From 6c924864b16db27b2bc0928fae28533cbe2db35d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 19:00:05 +0000 Subject: [PATCH 09/16] chore: preenche credenciais finais do novo ambiente - API Key Evolution API: 07CA5E59D86C... - ID credencial Anthropic: G8q6yzOfEbfoOtOT https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index 01f63a099..eb6088f48 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -170,7 +170,7 @@ "parameters": [ { "name": "apikey", - "value": "SUA_APIKEY_AQUI" + "value": "07CA5E59D86C-43F3-95B4-BCBC3CE2E5E4" }, { "name": "Content-Type", @@ -285,7 +285,7 @@ ], "credentials": { "anthropicApi": { - "id": "SEU_ID_ANTHROPIC_AQUI", + "id": "G8q6yzOfEbfoOtOT", "name": "Anthropic API" } } @@ -359,7 +359,7 @@ "parameters": [ { "name": "apikey", - "value": "SUA_APIKEY_AQUI" + "value": "07CA5E59D86C-43F3-95B4-BCBC3CE2E5E4" }, { "name": "Content-Type", From aad00e4b16e3ead6fc93ffe18d7dce8286d758fe Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 19:10:23 +0000 Subject: [PATCH 10/16] fix: remove hardcoded webhookId to prevent import conflict in n8n https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index eb6088f48..4105ef745 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -14,8 +14,7 @@ "position": [ -336, 112 - ], - "webhookId": "alicia-meta-ads-001" + ] }, { "parameters": { From d94e5428384464a1eafcd5f05d6107ed702f578b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 19:49:39 +0000 Subject: [PATCH 11/16] =?UTF-8?q?fix:=20corrige=20datas,=20leads=20e=20adi?= =?UTF-8?q?ciona=20modo=20relat=C3=B3rios=20de=20todos=20os=20clientes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Datas: last_7d agora vai de D-7 a D-1 (igual ao Meta Ads), corrigindo offset de 1 dia - Leads: usa action_type onsite_conversion.total_messaging_connection (novos contatos de mensagem) - CPL: calculado apenas sobre campanhas com destino WhatsApp (que possuem a métrica de novos contatos) - Novo fluxo: 'Alicia, relatórios da semana' sem cliente específico busca todos os 13 clientes e envia cada relatório como mensagem separada - Novo nó 'É Relatório de Todos?' roteia entre todos-os-clientes e Alicia Agent - Novo nó 'Buscar e Formatar Todos' faz fetch paralelo de todos os clientes em um único code node - Alicia Agent: system prompt atualizado com contexto detalhado de cada cliente, regras de leads e CPL https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 116 ++++++++++++++++++++++++++++++++------- 1 file changed, 96 insertions(+), 20 deletions(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index 4105ef745..e83d37014 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -65,7 +65,7 @@ }, { "parameters": { - "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\nconst fmt = d => d.toISOString().split('T')[0];\nconst addDays = (n) => { const d = new Date(now); d.setDate(d.getDate() + n); return fmt(d); };\nconst dow = now.getDay();\nconst daysToMon = dow === 0 ? 6 : dow - 1;\nconst monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\n// clientMap como array para evitar chave duplicada e suportar multiplos aliases\nconst clientMap = [\n { keys: ['antonio neto', 'antonio'], id: 'act_497795903676192', nome: 'Antonio Neto' },\n { keys: ['gabriel jacinto', 'gabriel'], id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n { keys: ['dani escudero', 'dani'], id: 'act_2145598605527232', nome: 'Dani Escudero' },\n { keys: ['agro'], id: 'act_929521557422139', nome: 'Agro' },\n { keys: ['escola de musica', 'escola de m\\u00fasica', 'escola'], id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n { keys: ['panmalhas'], id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil' },\n { keys: ['gfix store', 'gfix'], id: 'act_2811791829124905', nome: 'GFiX Store' },\n { keys: ['arte em gelo', 'arte em'], id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n { keys: ['itag tecnologia', 'itag'], id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n { keys: ['hi dogz', 'dogz'], id: 'act_873195821346004', nome: 'Hi Dogz' },\n { keys: ['batata bistro', 'batata bistr\\u00f4', 'batata'], id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n { keys: ['caribbean bronze', 'caribbean'], id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n { keys: ['larissa kelleter', 'larissa'], id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n];\n\n// Ordena por comprimento da primeira chave (mais especifico primeiro)\nclientMap.sort((a, b) => b.keys[0].length - a.keys[0].length);\n\n// Detecta pedido de relatorio sem exigir a palavra 'semanal'\nconst isRelatorio = text.includes('relat');\n\nlet clientFound = null;\nfor (let i = 0; i < clientMap.length; i++) {\n const entry = clientMap[i];\n for (let j = 0; j < entry.keys.length; j++) {\n if (text.includes(entry.keys[j])) {\n clientFound = entry;\n break;\n }\n }\n if (clientFound) break;\n}\n\nconst today = fmt(now);\nconst yesterday = addDays(-1);\n// Meta Ads last_7d termina 2 dias atras (dados de ontem ainda podem estar incompletos)\nconst last7End = addDays(-2);\nconst last7Start = addDays(-8);\nconst isRelatorioWithClient = isRelatorio && clientFound !== null;\n\n// Detecta periodo solicitado e mapeia para date_preset do Meta Ads\nconst hasMesPassado = text.includes('m\\u00eas passado') || text.includes('mes passado');\nconst hasEsseMes = text.includes('esse m\\u00eas') || text.includes('este m\\u00eas') || text.includes('esse mes') || text.includes('este mes');\n\nlet datePreset = 'last_7d';\nlet periodoLabel = '\\u00faltimos 7 dias';\nlet periodStart = last7Start;\nlet periodEnd = last7End;\n\nif (hasMesPassado) {\n datePreset = 'last_month';\n periodoLabel = 'm\\u00eas passado';\n const firstOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n const lastOfPrevMonth = new Date(firstOfMonth.getTime() - 86400000);\n const firstOfPrevMonth = new Date(lastOfPrevMonth.getFullYear(), lastOfPrevMonth.getMonth(), 1);\n periodStart = fmt(firstOfPrevMonth);\n periodEnd = fmt(lastOfPrevMonth);\n} else if (text.includes('ontem')) {\n datePreset = 'yesterday';\n periodoLabel = 'ontem';\n periodStart = yesterday;\n periodEnd = yesterday;\n} else if (text.includes('hoje')) {\n datePreset = 'today';\n periodoLabel = 'hoje';\n periodStart = today;\n periodEnd = today;\n} else if (text.includes('essa semana') || text.includes('esta semana')) {\n datePreset = 'this_week_mon_today';\n periodoLabel = 'essa semana';\n periodStart = fmt(monday);\n periodEnd = today;\n} else if (text.includes('14 dias')) {\n datePreset = 'last_14d';\n periodoLabel = '\\u00faltimos 14 dias';\n periodStart = addDays(-15);\n periodEnd = addDays(-2);\n} else if (text.includes('30 dias') || hasEsseMes) {\n datePreset = 'last_30d';\n periodoLabel = '\\u00faltimos 30 dias';\n periodStart = addDays(-31);\n periodEnd = addDays(-2);\n}\n\nreturn [{\n json: {\n text: input.text,\n phone: input.phone,\n remoteJid: input.remoteJid,\n instance: input.instance,\n today: today,\n yesterday: yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start: last7Start,\n last7End: last7End,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio: isRelatorio,\n isRelatorioWithClient: isRelatorioWithClient,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null,\n datePreset: datePreset,\n periodoLabel: periodoLabel,\n periodStart: periodStart,\n periodEnd: periodEnd\n }\n}];" + "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\nconst fmt = d => d.toISOString().split('T')[0];\nconst addDays = (n) => { const d = new Date(now); d.setDate(d.getDate() + n); return fmt(d); };\nconst dow = now.getDay();\nconst daysToMon = dow === 0 ? 6 : dow - 1;\nconst monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\n// clientMap como array para evitar chave duplicada e suportar multiplos aliases\nconst clientMap = [\n { keys: ['antonio neto', 'antonio'], id: 'act_497795903676192', nome: 'Antonio Neto' },\n { keys: ['gabriel jacinto', 'gabriel'], id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n { keys: ['dani escudero', 'dani'], id: 'act_2145598605527232', nome: 'Dani Escudero' },\n { keys: ['agro'], id: 'act_929521557422139', nome: 'Agro' },\n { keys: ['escola de musica', 'escola de m\\u00fasica', 'escola'], id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n { keys: ['panmalhas'], id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil' },\n { keys: ['gfix store', 'gfix'], id: 'act_2811791829124905', nome: 'GFiX Store' },\n { keys: ['arte em gelo', 'arte em'], id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n { keys: ['itag tecnologia', 'itag'], id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n { keys: ['hi dogz', 'dogz'], id: 'act_873195821346004', nome: 'Hi Dogz' },\n { keys: ['batata bistro', 'batata bistr\\u00f4', 'batata'], id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n { keys: ['caribbean bronze', 'caribbean'], id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n { keys: ['larissa kelleter', 'larissa'], id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n];\n\n// Ordena por comprimento da primeira chave (mais especifico primeiro)\nclientMap.sort((a, b) => b.keys[0].length - a.keys[0].length);\n\n// Detecta pedido de relatorio\nconst isRelatorio = text.includes('relat');\n\nlet clientFound = null;\nfor (let i = 0; i < clientMap.length; i++) {\n const entry = clientMap[i];\n for (let j = 0; j < entry.keys.length; j++) {\n if (text.includes(entry.keys[j])) {\n clientFound = entry;\n break;\n }\n }\n if (clientFound) break;\n}\n\nconst today = fmt(now);\nconst yesterday = addDays(-1);\n// Corrigido: ultimos 7 dias = D-7 ate D-1 (igual ao Meta Ads)\nconst last7End = addDays(-1);\nconst last7Start = addDays(-7);\n\nconst isRelatorioWithClient = isRelatorio && clientFound !== null;\n// Modo todos: pede relatorio sem especificar cliente\nconst isRelatorioTodos = isRelatorio && clientFound === null;\n\n// Detecta periodo solicitado e mapeia para date_preset do Meta Ads\nconst hasMesPassado = text.includes('m\\u00eas passado') || text.includes('mes passado');\nconst hasEsseMes = text.includes('esse m\\u00eas') || text.includes('este m\\u00eas') || text.includes('esse mes') || text.includes('este mes');\n\nlet datePreset = 'last_7d';\nlet periodoLabel = '\\u00faltimos 7 dias';\nlet periodStart = last7Start;\nlet periodEnd = last7End;\n\nif (hasMesPassado) {\n datePreset = 'last_month';\n periodoLabel = 'm\\u00eas passado';\n const firstOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n const lastOfPrevMonth = new Date(firstOfMonth.getTime() - 86400000);\n const firstOfPrevMonth = new Date(lastOfPrevMonth.getFullYear(), lastOfPrevMonth.getMonth(), 1);\n periodStart = fmt(firstOfPrevMonth);\n periodEnd = fmt(lastOfPrevMonth);\n} else if (text.includes('ontem')) {\n datePreset = 'yesterday';\n periodoLabel = 'ontem';\n periodStart = yesterday;\n periodEnd = yesterday;\n} else if (text.includes('hoje')) {\n datePreset = 'today';\n periodoLabel = 'hoje';\n periodStart = today;\n periodEnd = today;\n} else if (text.includes('essa semana') || text.includes('esta semana')) {\n datePreset = 'this_week_mon_today';\n periodoLabel = 'essa semana';\n periodStart = fmt(monday);\n periodEnd = today;\n} else if (text.includes('14 dias')) {\n datePreset = 'last_14d';\n periodoLabel = '\\u00faltimos 14 dias';\n periodStart = addDays(-14);\n periodEnd = addDays(-1);\n} else if (text.includes('30 dias') || hasEsseMes) {\n datePreset = 'last_30d';\n periodoLabel = '\\u00faltimos 30 dias';\n periodStart = addDays(-30);\n periodEnd = addDays(-1);\n}\n\nreturn [{\n json: {\n text: input.text,\n phone: input.phone,\n remoteJid: input.remoteJid,\n instance: input.instance,\n today: today,\n yesterday: yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start: last7Start,\n last7End: last7End,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio: isRelatorio,\n isRelatorioWithClient: isRelatorioWithClient,\n isRelatorioTodos: isRelatorioTodos,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null,\n datePreset: datePreset,\n periodoLabel: periodoLabel,\n periodStart: periodStart,\n periodEnd: periodEnd\n }\n}];" }, "id": "5b953b43-14ca-4ed3-b8da-3ec455f58b92", "name": "Calcular Datas", @@ -110,6 +110,53 @@ 112 ] }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 1 + }, + "conditions": [ + { + "id": "cond-todos", + "leftValue": "={{ $json.isRelatorioTodos }}", + "rightValue": "true", + "operator": { + "type": "string", + "operation": "equals", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "e2f1a3b4-5c6d-7e8f-9a0b-1c2d3e4f5a6b", + "name": "É Relatório de Todos?", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [ + 768, + 112 + ] + }, + { + "parameters": { + "jsCode": "const ctx = $('Calcular Datas').first().json;\nconst token = ctx.metaToken;\nconst datePreset = ctx.datePreset || 'last_7d';\nconst periodStart = ctx.periodStart;\nconst periodEnd = ctx.periodEnd;\nconst periodoLabel = ctx.periodoLabel || '\\u00faltimos 7 dias';\nconst phone = ctx.phone;\n\nconst clientMap = [\n { id: 'act_497795903676192', nome: 'Antonio Neto', type: 'lead' },\n { id: 'act_2493856707309984', nome: 'Gabriel Jacinto', type: 'lead' },\n { id: 'act_2145598605527232', nome: 'Dani Escudero', type: 'lead' },\n { id: 'act_929521557422139', nome: 'Agro', type: 'lead' },\n { id: 'act_544347952854095', nome: 'Escola de M\\u00fasica', type: 'lead' },\n { id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil', type: 'lead' },\n { id: 'act_2811791829124905', nome: 'GFiX Store', type: 'lead' },\n { id: 'act_5585082641598366', nome: 'Arte em Gelo', type: 'lead' },\n { id: 'act_738466861151636', nome: 'ITAG Tecnologia', type: 'lead' },\n { id: 'act_873195821346004', nome: 'Hi Dogz', type: 'purchase' },\n { id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4', type: 'purchase' },\n { id: 'act_1082397873705862', nome: 'Caribbean Bronze', type: 'lead' },\n { id: 'act_738280412237298', nome: 'Larissa Kelleter', type: 'lead' }\n];\n\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nconst LEAD_ACTION = 'onsite_conversion.total_messaging_connection';\nconst LEAD_FALLBACK = 'onsite_conversion.messaging_conversation_started_7d';\nconst fields = 'spend,impressions,clicks,actions,action_values';\n\nconst dateRange = periodStart === periodEnd\n ? '_\\ud83d\\udcc5 ' + fmtBR(periodStart) + ' (' + periodoLabel + ')_'\n : '_\\ud83d\\udcc5 ' + fmtBR(periodStart) + ' a ' + fmtBR(periodEnd) + ' (' + periodoLabel + ')_';\n\nconst results = [];\n\nfor (const client of clientMap) {\n try {\n const url = 'https://graph.facebook.com/v19.0/' + client.id + '/insights?date_preset=' + encodeURIComponent(datePreset) + '&fields=' + encodeURIComponent(fields) + '&level=campaign&access_token=' + token;\n const res = await fetch(url);\n const data = await res.json();\n const campaigns = (data.data) || [];\n\n if (!campaigns.length) {\n results.push({ json: { phone: phone, output: '*' + client.nome.toUpperCase() + '*\\n\\n' + dateRange + '\\n\\n*Meta Ads*\\n\\n\\u274c Sem dados no per\\u00edodo.' } });\n continue;\n }\n\n let totalSpend = 0, totalImpressions = 0, totalClicks = 0;\n for (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n }\n\n let reportText;\n\n if (client.type === 'purchase') {\n let totalPurchases = 0, totalRevenue = 0;\n for (const c of campaigns) {\n const pa = (c.actions || []).find(a => a.action_type === 'purchase' || a.action_type === 'offsite_conversion.fb_pixel_purchase');\n if (pa) totalPurchases += parseInt(pa.value);\n const ra = (c.action_values || []).find(a => a.action_type === 'purchase' || a.action_type === 'offsite_conversion.fb_pixel_purchase');\n if (ra) totalRevenue += parseFloat(ra.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + client.nome.toUpperCase() + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Faturamento: R$ ' + fmtMoney(totalRevenue) + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n } else {\n // Leads = Novos contatos de mensagem (campanhas WhatsApp)\n let leadSpend = 0, totalLeads = 0;\n for (const c of campaigns) {\n const la = (c.actions || []).find(a => a.action_type === LEAD_ACTION)\n || (c.actions || []).find(a => a.action_type === LEAD_FALLBACK);\n if (la) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(la.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + client.nome.toUpperCase() + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n }\n\n results.push({ json: { phone: phone, output: reportText } });\n } catch(e) {\n results.push({ json: { phone: phone, output: '*' + client.nome.toUpperCase() + '*\\n\\n\\u274c Erro ao buscar dados: ' + e.message } });\n }\n}\n\nreturn results;" + }, + "id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890", + "name": "Buscar e Formatar Todos", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 992, + 112 + ] + }, { "parameters": { "url": "={{ 'https://graph.facebook.com/v19.0/' + $json.clientFound + '/insights' }}", @@ -149,7 +196,7 @@ }, { "parameters": { - "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\n// Formata valor monetario em pt-BR sem depender de ICU/locale\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\n// Formata numero inteiro com separador de milhar pt-BR\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nif (!campaigns.length) {\n return [{ json: {\n phone: ctx.phone,\n output: '\\u274c Sem dados para *' + (ctx.clientNome || '').toUpperCase() + '* nos \\u00faltimos 7 dias.'\n } }];\n}\n\n// Contas cujo objetivo principal \u00e9 compra direta (e-commerce / vendas)\nconst purchaseAccountIds = ['act_873195821346004', 'act_1673012226810411']; // Hi Dogz, Batata Bistr\\u00f4\nconst isPurchase = purchaseAccountIds.includes(ctx.clientFound);\n\nlet totalSpend = 0;\nlet totalImpressions = 0;\nlet totalClicks = 0;\n\nfor (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n}\n\nconst clientNameUpper = (ctx.clientNome || '').toUpperCase();\nconst periodoLabel = ctx.periodoLabel || '\\u00faltimos 7 dias';\nconst periodStart = ctx.periodStart || ctx.last7Start;\nconst periodEnd = ctx.periodEnd || ctx.last7End;\nconst dateRange = periodStart === periodEnd\n ? '_\\ud83d\\udcc6 ' + fmtBR(periodStart) + ' (' + periodoLabel + ')_'\n : '_\\ud83d\\udcc6 ' + fmtBR(periodStart) + ' a ' + fmtBR(periodEnd) + ' (' + periodoLabel + ')_';\nlet reportText;\n\nif (isPurchase) {\n // Campanha de vendas: mostra compras e custo por venda\n let totalPurchases = 0;\n let totalRevenue = 0;\n for (const c of campaigns) {\n const purchaseAction = (c.actions || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (purchaseAction) totalPurchases += parseInt(purchaseAction.value);\n const revenueAction = (c.action_values || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (revenueAction) totalRevenue += parseFloat(revenueAction.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Faturamento: R$ ' + fmtMoney(totalRevenue) + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n // Campanha de leads: verifica multiplos tipos de conversao\n const LEAD_TYPES = [\n 'lead',\n 'onsite_conversion.lead_grouped',\n 'onsite_conversion.messaging_conversation_started_7d',\n 'onsite_conversion.total_messaging_connection'\n ];\n\n let leadSpend = 0;\n let totalLeads = 0;\n for (const c of campaigns) {\n const leadAction = (c.actions || []).find(a => LEAD_TYPES.includes(a.action_type));\n if (leadAction) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(leadAction.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { phone: ctx.phone, output: reportText } }];" + "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nif (!campaigns.length) {\n return [{ json: {\n phone: ctx.phone,\n output: '\\u274c Sem dados para *' + (ctx.clientNome || '').toUpperCase() + '* no per\\u00edodo solicitado.'\n } }];\n}\n\nconst purchaseAccountIds = ['act_873195821346004', 'act_1673012226810411'];\nconst isPurchase = purchaseAccountIds.includes(ctx.clientFound);\n\nlet totalSpend = 0;\nlet totalImpressions = 0;\nlet totalClicks = 0;\n\nfor (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n}\n\nconst clientNameUpper = (ctx.clientNome || '').toUpperCase();\nconst periodoLabel = ctx.periodoLabel || '\\u00faltimos 7 dias';\nconst periodStart = ctx.periodStart || ctx.last7Start;\nconst periodEnd = ctx.periodEnd || ctx.last7End;\nconst dateRange = periodStart === periodEnd\n ? '_\\ud83d\\udcc5 ' + fmtBR(periodStart) + ' (' + periodoLabel + ')_'\n : '_\\ud83d\\udcc5 ' + fmtBR(periodStart) + ' a ' + fmtBR(periodEnd) + ' (' + periodoLabel + ')_';\n\nlet reportText;\n\nif (isPurchase) {\n let totalPurchases = 0;\n let totalRevenue = 0;\n for (const c of campaigns) {\n const purchaseAction = (c.actions || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (purchaseAction) totalPurchases += parseInt(purchaseAction.value);\n const revenueAction = (c.action_values || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (revenueAction) totalRevenue += parseFloat(revenueAction.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Faturamento: R$ ' + fmtMoney(totalRevenue) + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n // Leads = Novos contatos de mensagem (campanhas WhatsApp)\n // CPL calculado somente sobre campanhas com destino WhatsApp\n const LEAD_ACTION = 'onsite_conversion.total_messaging_connection';\n const LEAD_FALLBACK = 'onsite_conversion.messaging_conversation_started_7d';\n\n let leadSpend = 0;\n let totalLeads = 0;\n for (const c of campaigns) {\n const leadAction = (c.actions || []).find(a => a.action_type === LEAD_ACTION)\n || (c.actions || []).find(a => a.action_type === LEAD_FALLBACK);\n if (leadAction) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(leadAction.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { phone: ctx.phone, output: reportText } }];" }, "id": "2b3745d9-82ef-4d9a-aa8d-db402408a07e", "name": "Formatar Relatório", @@ -233,7 +280,7 @@ "typeVersion": 4.2, "position": [ 768, - 256 + 320 ] }, { @@ -246,7 +293,7 @@ "typeVersion": 2, "position": [ 992, - 256 + 320 ] }, { @@ -254,7 +301,7 @@ "promptType": "define", "text": "={{ $json.text }}", "options": { - "systemMessage": "=Voc\u00ea \u00e9 Alicia, assistente especializada em Meta Ads. Ajuda gestores de tr\u00e1fego a analisar m\u00e9tricas de campanhas de forma clara e direta.\n\n\ud83d\udcc5 DATA ATUAL: {{ $json.today }}\n\ud83d\udcc5 ONTEM: {{ $json.yesterday }}\n\ud83d\udcc5 IN\u00cdCIO DA SEMANA (seg): {{ $json.weekStart }}\n\ud83d\udcc5 7 DIAS ATR\u00c1S: {{ $json.last7 }}\n\ud83d\udcc5 30 DIAS ATR\u00c1S: {{ $json.last30 }}\n\n\ud83e\uddd1\u200d\ud83d\udcbc MAPEAMENTO DE CLIENTES \u2192 CONTAS:\n\u2022 Antonio Neto \u2192 act_497795903676192\n\u2022 Gabriel Jacinto \u2192 act_2493856707309984\n\u2022 Dani Escudero \u2192 act_2145598605527232\n\u2022 Agro \u2192 act_929521557422139\n\u2022 Escola de M\u00fasica \u2192 act_544347952854095\n\u2022 Panmalhas \u2192 act_1509005182799187\n\u2022 GFiX Store \u2192 act_2811791829124905\n\u2022 Arte em Gelo \u2192 act_5585082641598366\n\u2022 ITAG Tecnologia \u2192 act_738466861151636\n\u2022 Hi Dogz \u2192 act_873195821346004\n\u2022 Batata Bistr\u00f4 \u2192 act_1673012226810411\n\u2022 Caribbean Bronze \u2192 act_1082397873705862\n\u2022 Larissa Kelleter \u2192 act_738280412237298\n\n\ud83c\udfe2 CONTAS DISPON\u00cdVEIS (status atual):\n{{ $json.accountsList }}\n\n\ud83d\udccb REGRAS:\n1. Aceite nomes parciais: 'itag' = ITAG Tecnologia, 'batata' = Batata Bistr\u00f4, 'gfix' = GFiX Store, 'caribbean' = Caribbean Bronze\n2. Se n\u00e3o especificou cliente, busque de TODAS as contas ativas e consolide os valores\n3. date_presets v\u00e1lidos: today, yesterday, this_week_mon_today, last_7d, last_14d, last_30d, last_month\n4. Para LEADS: filtre actions por action_type = 'lead' OU 'onsite_conversion.lead_grouped'\n5. CPL = gasto_total \u00f7 quantidade_leads\n6. Budgets da Meta v\u00eam em CENTAVOS - divida por 100 para exibir em reais\n7. Responda SEMPRE em portugu\u00eas do Brasil\n8. Use emojis para tornar a resposta visual e amig\u00e1vel\n9. Seja concisa mas completa\n\n\ud83c\udfa8 FORMATO DE RESPOSTA:\n*NOME DO CLIENTE EM MAI\u00daSCULAS*\n\n_\ud83d\udcc6 DD/MM a DD/MM (\u00faltimos 7 dias)_\n\n*Meta Ads*\n\nImpress\u00f5es: X.XXX\n\nCliques: X.XXX\n\nLeads: XX (ou Vendas para Hi Dogz e Batata Bistr\u00f4)\n\nCusto por lead: R$ XX,XX (ou Custo por venda)\n\n*INVESTIMENTO TOTAL: R$ X.XXX,XX*\n\n\u26a0\ufe0f Se n\u00e3o houver dados para o per\u00edodo, informe o usu\u00e1rio claramente." + "systemMessage": "=Você é Alicia, assistente especializada em Meta Ads. Você gerencia uma carteira de 13 clientes ativos e ajuda gestores de tráfego a analisar métricas de campanhas com clareza e precisão.\n\n📅 DATA ATUAL: {{ $json.today }}\n📅 ONTEM: {{ $json.yesterday }}\n📅 INÍCIO DA SEMANA (seg): {{ $json.weekStart }}\n📅 7 DIAS ATRÁS: {{ $json.last7 }}\n📅 30 DIAS ATRÁS: {{ $json.last30 }}\n\n👨‍💼 CARTEIRA DE CLIENTES (13 clientes ativos):\n• Antonio Neto → act_497795903676192 | Tipo: Lead Gen (WhatsApp)\n• Gabriel Jacinto → act_2493856707309984 | Tipo: Lead Gen (WhatsApp)\n• Dani Escudero → act_2145598605527232 | Tipo: Lead Gen (WhatsApp)\n• Agro → act_929521557422139 | Tipo: Lead Gen (WhatsApp)\n• Escola de Música → act_544347952854095 | Tipo: Lead Gen (WhatsApp)\n• Panmalhas → act_1509005182799187 | Tipo: Lead Gen (WhatsApp)\n• GFiX Store → act_2811791829124905 | Tipo: Lead Gen (WhatsApp)\n• Arte em Gelo → act_5585082641598366 | Tipo: Lead Gen (WhatsApp)\n• ITAG Tecnologia → act_738466861151636 | Tipo: Lead Gen (WhatsApp)\n• Hi Dogz → act_873195821346004 | Tipo: VENDAS (e-commerce)\n• Batata Bistrô → act_1673012226810411 | Tipo: VENDAS (e-commerce)\n• Caribbean Bronze → act_1082397873705862 | Tipo: Lead Gen (WhatsApp)\n• Larissa Kelleter → act_738280412237298 | Tipo: Lead Gen (WhatsApp)\n\n🏢 CONTAS DISPONÍVEIS (status atual):\n{{ $json.accountsList }}\n\n📋 REGRAS IMPORTANTES:\n1. Aceite nomes parciais: 'itag' = ITAG Tecnologia, 'batata' = Batata Bistrô, 'gfix' = GFiX Store, 'caribbean' = Caribbean Bronze\n2. LEADS = métrica \"Novos contatos de mensagem\" = action_type: onsite_conversion.total_messaging_connection (fallback: onsite_conversion.messaging_conversation_started_7d)\n3. CPL (Custo por Lead) = gasto APENAS das campanhas com destino WhatsApp (campanhas que possuem a métrica novos contatos de mensagem) ÷ total de leads. NÃO divide o gasto total por leads.\n4. VENDAS (Hi Dogz e Batata Bistrô) = action_type: purchase ou offsite_conversion.fb_pixel_purchase. Mostrar: vendas, faturamento, custo por venda.\n5. date_presets válidos: today, yesterday, this_week_mon_today, last_7d, last_14d, last_30d, last_month\n6. Budgets da Meta vêm em CENTAVOS - divida por 100 para exibir em reais\n7. Responda SEMPRE em português do Brasil\n8. Use emojis para tornar a resposta visual e amigável\n9. Seja concisa mas completa\n10. Período padrão: últimos 7 dias (D-7 até D-1, igual ao Meta Ads)\n\n🎨 FORMATO DE RESPOSTA PADRÃO:\n*NOME DO CLIENTE EM MAIÚSCULAS*\n\n_📅 DD/MM a DD/MM (período)_\n\n*Meta Ads*\n\nImpressões: X.XXX\n\nCliques: X.XXX\n\nLeads: XX (ou Vendas/Faturamento para Hi Dogz e Batata Bistrô)\n\nCusto por lead: R$ XX,XX (ou Custo por venda)\n\n*INVESTIMENTO TOTAL: R$ X.XXX,XX*\n\n⚠️ Se não houver dados para o período, informe o usuário claramente." } }, "id": "36e08f36-8634-478d-a32c-8bf22671531e", @@ -263,7 +310,7 @@ "typeVersion": 1.7, "position": [ 1216, - 256 + 320 ] }, { @@ -280,7 +327,7 @@ "typeVersion": 1.3, "position": [ 960, - 480 + 540 ], "credentials": { "anthropicApi": { @@ -292,8 +339,8 @@ { "parameters": { "name": "buscar_insights", - "description": "Busca m\u00e9tricas/insights do Meta Ads. Use para: gasto (spend), leads (actions), impress\u00f5es, cliques, CTR, CPC, alcan\u00e7e. Envie JSON com: object_id (ID da conta ex: act_123456 OU ID de campanha), date_preset (today/yesterday/this_week_mon_today/last_7d/last_14d/last_30d/last_month), fields (ex: spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type), level opcional (account/campaign/adset/ad).", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com object_id e date_preset'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst objectId = params.object_id;\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type';\nconst level = params.level || '';\nif (!objectId) return 'Erro: object_id \u00e9 obrigat\u00f3rio';\nlet url = `https://graph.facebook.com/v19.0/${objectId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\nif (level) url += `&level=${encodeURIComponent(level)}`;\ntry {\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message} (c\u00f3digo: ${data.error.code})`;\n if (!data.data || data.data.length === 0) return 'Nenhum dado encontrado para o per\u00edodo solicitado.';\n return JSON.stringify(data.data);\n} catch(e) { return `Erro de conex\u00e3o: ${e.message}`; }" + "description": "Busca métricas/insights do Meta Ads. Use para: gasto (spend), leads (actions), impressões, cliques, CTR, CPC, alcance. Envie JSON com: object_id (ID da conta ex: act_123456 OU ID de campanha), date_preset (today/yesterday/this_week_mon_today/last_7d/last_14d/last_30d/last_month), fields (ex: spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type), level opcional (account/campaign/adset/ad). IMPORTANTE: Para leads, inclua 'actions' nos fields e filtre por action_type = onsite_conversion.total_messaging_connection.", + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com object_id e date_preset'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst objectId = params.object_id;\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type';\nconst level = params.level || '';\nif (!objectId) return 'Erro: object_id é obrigatório';\nlet url = `https://graph.facebook.com/v19.0/${objectId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\nif (level) url += `&level=${encodeURIComponent(level)}`;\ntry {\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message} (código: ${data.error.code})`;\n if (!data.data || data.data.length === 0) return 'Nenhum dado encontrado para o período solicitado.';\n return JSON.stringify(data.data);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "13abddfb-207b-4d02-aa60-c7b8658c7860", "name": "Tool: Buscar Insights", @@ -301,14 +348,14 @@ "typeVersion": 1.1, "position": [ 1136, - 480 + 540 ] }, { "parameters": { "name": "listar_campanhas", - "description": "Lista todas as campanhas de uma conta de an\u00fancios. Envie JSON com: account_id (formato: act_XXXXXXXXX). Retorna: id, nome, status, objetivo, budget di\u00e1rio e total. Use para encontrar o ID de uma campanha espec\u00edfica antes de buscar os insights.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nif (!accountId) return 'Erro: account_id \u00e9 obrigat\u00f3rio (formato: act_XXXXXXXXX)';\ntry {\n const url = `https://graph.facebook.com/v19.0/${accountId}/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhuma campanha encontrada nessa conta';\n const campaigns = data.data.map(c => ({\n id: c.id,\n nome: c.name,\n status: c.status,\n objetivo: c.objective,\n budget_diario_reais: c.daily_budget ? (parseInt(c.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: c.lifetime_budget ? (parseInt(c.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(campaigns);\n} catch(e) { return `Erro de conex\u00e3o: ${e.message}`; }" + "description": "Lista todas as campanhas de uma conta de anúncios. Envie JSON com: account_id (formato: act_XXXXXXXXX). Retorna: id, nome, status, objetivo, budget diário e total. Use para encontrar o ID de uma campanha específica antes de buscar os insights.", + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nif (!accountId) return 'Erro: account_id é obrigatório (formato: act_XXXXXXXXX)';\ntry {\n const url = `https://graph.facebook.com/v19.0/${accountId}/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhuma campanha encontrada nessa conta';\n const campaigns = data.data.map(c => ({\n id: c.id,\n nome: c.name,\n status: c.status,\n objetivo: c.objective,\n budget_diario_reais: c.daily_budget ? (parseInt(c.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: c.lifetime_budget ? (parseInt(c.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(campaigns);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "731c36fc-e0b1-44a4-a13e-fcda6b6f2a2e", "name": "Tool: Listar Campanhas", @@ -316,14 +363,14 @@ "typeVersion": 1.1, "position": [ 1296, - 480 + 540 ] }, { "parameters": { "name": "listar_conjuntos", - "description": "Lista conjuntos de an\u00fancios (adsets) de uma conta ou campanha. Envie JSON com: account_id (act_XXXXXXXXX) OU campaign_id (ID num\u00e9rico). Retorna: id, nome, status e budget.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id ou campaign_id'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nconst campaignId = params.campaign_id;\nif (!accountId && !campaignId) return 'Erro: forne\u00e7a account_id ou campaign_id';\nconst baseId = campaignId || accountId;\ntry {\n const url = `https://graph.facebook.com/v19.0/${baseId}/adsets?fields=id,name,status,daily_budget,lifetime_budget,campaign_id&limit=100&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhum conjunto de an\u00fancios encontrado';\n const adsets = data.data.map(a => ({\n id: a.id,\n nome: a.name,\n status: a.status,\n budget_diario_reais: a.daily_budget ? (parseInt(a.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: a.lifetime_budget ? (parseInt(a.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(adsets);\n} catch(e) { return `Erro de conex\u00e3o: ${e.message}`; }" + "description": "Lista conjuntos de anúncios (adsets) de uma conta ou campanha. Envie JSON com: account_id (act_XXXXXXXXX) OU campaign_id (ID numérico). Retorna: id, nome, status e budget.", + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id ou campaign_id'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nconst campaignId = params.campaign_id;\nif (!accountId && !campaignId) return 'Erro: forneça account_id ou campaign_id';\nconst baseId = campaignId || accountId;\ntry {\n const url = `https://graph.facebook.com/v19.0/${baseId}/adsets?fields=id,name,status,daily_budget,lifetime_budget,campaign_id&limit=100&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhum conjunto de anúncios encontrado';\n const adsets = data.data.map(a => ({\n id: a.id,\n nome: a.name,\n status: a.status,\n budget_diario_reais: a.daily_budget ? (parseInt(a.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: a.lifetime_budget ? (parseInt(a.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(adsets);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "a8b12824-f38e-4296-87fe-49bd6e9f934b", "name": "Tool: Listar Conjuntos", @@ -331,14 +378,14 @@ "typeVersion": 1.1, "position": [ 1408, - 480 + 540 ] }, { "parameters": { "name": "buscar_insights_multiplas_contas", - "description": "Busca insights de M\u00daLTIPLAS contas ao mesmo tempo. Use quando o usu\u00e1rio n\u00e3o especificar cliente. Envie JSON com: account_ids (array de IDs ex: ['act_111','act_222']), date_preset, fields.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_ids (array), date_preset e fields'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountIds = params.account_ids || [];\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (accountIds.length === 0) return 'Erro: account_ids deve ser um array com pelo menos 1 conta';\ntry {\n const results = [];\n for (const accountId of accountIds) {\n const url = `https://graph.facebook.com/v19.0/${accountId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (!data.error && data.data && data.data.length > 0) {\n results.push({ account_id: accountId, data: data.data[0] });\n } else if (data.error) {\n results.push({ account_id: accountId, error: data.error.message });\n } else {\n results.push({ account_id: accountId, data: null, note: 'sem dados no per\u00edodo' });\n }\n }\n return JSON.stringify(results);\n} catch(e) { return `Erro de conex\u00e3o: ${e.message}`; }" + "description": "Busca insights de MÚLTIPLAS contas ao mesmo tempo. Use quando o usuário não especificar cliente. Envie JSON com: account_ids (array de IDs ex: ['act_111','act_222']), date_preset, fields.", + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_ids (array), date_preset e fields'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountIds = params.account_ids || [];\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (accountIds.length === 0) return 'Erro: account_ids deve ser um array com pelo menos 1 conta';\ntry {\n const results = [];\n for (const accountId of accountIds) {\n const url = `https://graph.facebook.com/v19.0/${accountId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (!data.error && data.data && data.data.length > 0) {\n results.push({ account_id: accountId, data: data.data[0] });\n } else if (data.error) {\n results.push({ account_id: accountId, error: data.error.message });\n } else {\n results.push({ account_id: accountId, data: null, note: 'sem dados no período' });\n }\n }\n return JSON.stringify(results);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "81aa87da-6607-4ffc-95b9-1be17e23ec68", "name": "Tool: Insights Multiplas Contas", @@ -346,7 +393,7 @@ "typeVersion": 1.1, "position": [ 1520, - 480 + 540 ] }, { @@ -389,7 +436,7 @@ "typeVersion": 4.2, "position": [ 1520, - 256 + 320 ] } ], @@ -448,6 +495,24 @@ "index": 0 } ], + [ + { + "node": "É Relatório de Todos?", + "type": "main", + "index": 0 + } + ] + ] + }, + "É Relatório de Todos?": { + "main": [ + [ + { + "node": "Buscar e Formatar Todos", + "type": "main", + "index": 0 + } + ], [ { "node": "Buscar Contas Meta", @@ -457,6 +522,17 @@ ] ] }, + "Buscar e Formatar Todos": { + "main": [ + [ + { + "node": "Enviar Relatório WhatsApp", + "type": "main", + "index": 0 + } + ] + ] + }, "Buscar Insights 7 Dias": { "main": [ [ From 24e0ae64099c9b65bb47282362eb2f3859b6953f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 20:02:37 +0000 Subject: [PATCH 12/16] =?UTF-8?q?fix:=20corrige=20fetch=20indispon=C3=ADve?= =?UTF-8?q?l=20e=20remove=20fallback=20de=20lead=20metric?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Substitui fetch() por helper https nativo no node "Buscar e Formatar Todos" para compatibilidade com versões do n8n que não expõem fetch globalmente - Remove fallback onsite_conversion.messaging_conversation_started_7d nos nodes "Buscar e Formatar Todos" e "Formatar Relatório" — o fallback usava janela de atribuição diferente e inflava o total de leads (ex: 45 vs 34) - Agora ambos os nodes usam exclusivamente onsite_conversion.total_messaging_connection para consistência com o Gerenciador de Anúncios da Meta https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index e83d37014..ca10b75f1 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -146,7 +146,7 @@ }, { "parameters": { - "jsCode": "const ctx = $('Calcular Datas').first().json;\nconst token = ctx.metaToken;\nconst datePreset = ctx.datePreset || 'last_7d';\nconst periodStart = ctx.periodStart;\nconst periodEnd = ctx.periodEnd;\nconst periodoLabel = ctx.periodoLabel || '\\u00faltimos 7 dias';\nconst phone = ctx.phone;\n\nconst clientMap = [\n { id: 'act_497795903676192', nome: 'Antonio Neto', type: 'lead' },\n { id: 'act_2493856707309984', nome: 'Gabriel Jacinto', type: 'lead' },\n { id: 'act_2145598605527232', nome: 'Dani Escudero', type: 'lead' },\n { id: 'act_929521557422139', nome: 'Agro', type: 'lead' },\n { id: 'act_544347952854095', nome: 'Escola de M\\u00fasica', type: 'lead' },\n { id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil', type: 'lead' },\n { id: 'act_2811791829124905', nome: 'GFiX Store', type: 'lead' },\n { id: 'act_5585082641598366', nome: 'Arte em Gelo', type: 'lead' },\n { id: 'act_738466861151636', nome: 'ITAG Tecnologia', type: 'lead' },\n { id: 'act_873195821346004', nome: 'Hi Dogz', type: 'purchase' },\n { id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4', type: 'purchase' },\n { id: 'act_1082397873705862', nome: 'Caribbean Bronze', type: 'lead' },\n { id: 'act_738280412237298', nome: 'Larissa Kelleter', type: 'lead' }\n];\n\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nconst LEAD_ACTION = 'onsite_conversion.total_messaging_connection';\nconst LEAD_FALLBACK = 'onsite_conversion.messaging_conversation_started_7d';\nconst fields = 'spend,impressions,clicks,actions,action_values';\n\nconst dateRange = periodStart === periodEnd\n ? '_\\ud83d\\udcc5 ' + fmtBR(periodStart) + ' (' + periodoLabel + ')_'\n : '_\\ud83d\\udcc5 ' + fmtBR(periodStart) + ' a ' + fmtBR(periodEnd) + ' (' + periodoLabel + ')_';\n\nconst results = [];\n\nfor (const client of clientMap) {\n try {\n const url = 'https://graph.facebook.com/v19.0/' + client.id + '/insights?date_preset=' + encodeURIComponent(datePreset) + '&fields=' + encodeURIComponent(fields) + '&level=campaign&access_token=' + token;\n const res = await fetch(url);\n const data = await res.json();\n const campaigns = (data.data) || [];\n\n if (!campaigns.length) {\n results.push({ json: { phone: phone, output: '*' + client.nome.toUpperCase() + '*\\n\\n' + dateRange + '\\n\\n*Meta Ads*\\n\\n\\u274c Sem dados no per\\u00edodo.' } });\n continue;\n }\n\n let totalSpend = 0, totalImpressions = 0, totalClicks = 0;\n for (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n }\n\n let reportText;\n\n if (client.type === 'purchase') {\n let totalPurchases = 0, totalRevenue = 0;\n for (const c of campaigns) {\n const pa = (c.actions || []).find(a => a.action_type === 'purchase' || a.action_type === 'offsite_conversion.fb_pixel_purchase');\n if (pa) totalPurchases += parseInt(pa.value);\n const ra = (c.action_values || []).find(a => a.action_type === 'purchase' || a.action_type === 'offsite_conversion.fb_pixel_purchase');\n if (ra) totalRevenue += parseFloat(ra.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + client.nome.toUpperCase() + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Faturamento: R$ ' + fmtMoney(totalRevenue) + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n } else {\n // Leads = Novos contatos de mensagem (campanhas WhatsApp)\n let leadSpend = 0, totalLeads = 0;\n for (const c of campaigns) {\n const la = (c.actions || []).find(a => a.action_type === LEAD_ACTION)\n || (c.actions || []).find(a => a.action_type === LEAD_FALLBACK);\n if (la) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(la.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + client.nome.toUpperCase() + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n }\n\n results.push({ json: { phone: phone, output: reportText } });\n } catch(e) {\n results.push({ json: { phone: phone, output: '*' + client.nome.toUpperCase() + '*\\n\\n\\u274c Erro ao buscar dados: ' + e.message } });\n }\n}\n\nreturn results;" + "jsCode": "const https = require('https');\nconst httpGet = (url) => new Promise((resolve, reject) => { https.get(url, (res) => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch(e) { reject(e); } }); }).on('error', reject); });\nconst ctx = $('Calcular Datas').first().json;\nconst token = ctx.metaToken;\nconst datePreset = ctx.datePreset || 'last_7d';\nconst periodStart = ctx.periodStart;\nconst periodEnd = ctx.periodEnd;\nconst periodoLabel = ctx.periodoLabel || '\\u00faltimos 7 dias';\nconst phone = ctx.phone;\n\nconst clientMap = [\n { id: 'act_497795903676192', nome: 'Antonio Neto', type: 'lead' },\n { id: 'act_2493856707309984', nome: 'Gabriel Jacinto', type: 'lead' },\n { id: 'act_2145598605527232', nome: 'Dani Escudero', type: 'lead' },\n { id: 'act_929521557422139', nome: 'Agro', type: 'lead' },\n { id: 'act_544347952854095', nome: 'Escola de M\\u00fasica', type: 'lead' },\n { id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil', type: 'lead' },\n { id: 'act_2811791829124905', nome: 'GFiX Store', type: 'lead' },\n { id: 'act_5585082641598366', nome: 'Arte em Gelo', type: 'lead' },\n { id: 'act_738466861151636', nome: 'ITAG Tecnologia', type: 'lead' },\n { id: 'act_873195821346004', nome: 'Hi Dogz', type: 'purchase' },\n { id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4', type: 'purchase' },\n { id: 'act_1082397873705862', nome: 'Caribbean Bronze', type: 'lead' },\n { id: 'act_738280412237298', nome: 'Larissa Kelleter', type: 'lead' }\n];\n\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nconst LEAD_ACTION = 'onsite_conversion.total_messaging_connection';\nconst fields = 'spend,impressions,clicks,actions,action_values';\n\nconst dateRange = periodStart === periodEnd\n ? '_\\ud83d\\udcc5 ' + fmtBR(periodStart) + ' (' + periodoLabel + ')_'\n : '_\\ud83d\\udcc5 ' + fmtBR(periodStart) + ' a ' + fmtBR(periodEnd) + ' (' + periodoLabel + ')_';\n\nconst results = [];\n\nfor (const client of clientMap) {\n try {\n const url = 'https://graph.facebook.com/v19.0/' + client.id + '/insights?date_preset=' + encodeURIComponent(datePreset) + '&fields=' + encodeURIComponent(fields) + '&level=campaign&access_token=' + token;\n const data = await httpGet(url);\n const campaigns = (data.data) || [];\n\n if (!campaigns.length) {\n results.push({ json: { phone: phone, output: '*' + client.nome.toUpperCase() + '*\\n\\n' + dateRange + '\\n\\n*Meta Ads*\\n\\n\\u274c Sem dados no per\\u00edodo.' } });\n continue;\n }\n\n let totalSpend = 0, totalImpressions = 0, totalClicks = 0;\n for (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n }\n\n let reportText;\n\n if (client.type === 'purchase') {\n let totalPurchases = 0, totalRevenue = 0;\n for (const c of campaigns) {\n const pa = (c.actions || []).find(a => a.action_type === 'purchase' || a.action_type === 'offsite_conversion.fb_pixel_purchase');\n if (pa) totalPurchases += parseInt(pa.value);\n const ra = (c.action_values || []).find(a => a.action_type === 'purchase' || a.action_type === 'offsite_conversion.fb_pixel_purchase');\n if (ra) totalRevenue += parseFloat(ra.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + client.nome.toUpperCase() + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Faturamento: R$ ' + fmtMoney(totalRevenue) + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n } else {\n // Leads = Novos contatos de mensagem (campanhas WhatsApp)\n let leadSpend = 0, totalLeads = 0;\n for (const c of campaigns) {\n const la = (c.actions || []).find(a => a.action_type === LEAD_ACTION);\n if (la) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(la.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + client.nome.toUpperCase() + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n }\n\n results.push({ json: { phone: phone, output: reportText } });\n } catch(e) {\n results.push({ json: { phone: phone, output: '*' + client.nome.toUpperCase() + '*\\n\\n\\u274c Erro ao buscar dados: ' + e.message } });\n }\n}\n\nreturn results;" }, "id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890", "name": "Buscar e Formatar Todos", @@ -196,7 +196,7 @@ }, { "parameters": { - "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nif (!campaigns.length) {\n return [{ json: {\n phone: ctx.phone,\n output: '\\u274c Sem dados para *' + (ctx.clientNome || '').toUpperCase() + '* no per\\u00edodo solicitado.'\n } }];\n}\n\nconst purchaseAccountIds = ['act_873195821346004', 'act_1673012226810411'];\nconst isPurchase = purchaseAccountIds.includes(ctx.clientFound);\n\nlet totalSpend = 0;\nlet totalImpressions = 0;\nlet totalClicks = 0;\n\nfor (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n}\n\nconst clientNameUpper = (ctx.clientNome || '').toUpperCase();\nconst periodoLabel = ctx.periodoLabel || '\\u00faltimos 7 dias';\nconst periodStart = ctx.periodStart || ctx.last7Start;\nconst periodEnd = ctx.periodEnd || ctx.last7End;\nconst dateRange = periodStart === periodEnd\n ? '_\\ud83d\\udcc5 ' + fmtBR(periodStart) + ' (' + periodoLabel + ')_'\n : '_\\ud83d\\udcc5 ' + fmtBR(periodStart) + ' a ' + fmtBR(periodEnd) + ' (' + periodoLabel + ')_';\n\nlet reportText;\n\nif (isPurchase) {\n let totalPurchases = 0;\n let totalRevenue = 0;\n for (const c of campaigns) {\n const purchaseAction = (c.actions || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (purchaseAction) totalPurchases += parseInt(purchaseAction.value);\n const revenueAction = (c.action_values || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (revenueAction) totalRevenue += parseFloat(revenueAction.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Faturamento: R$ ' + fmtMoney(totalRevenue) + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n // Leads = Novos contatos de mensagem (campanhas WhatsApp)\n // CPL calculado somente sobre campanhas com destino WhatsApp\n const LEAD_ACTION = 'onsite_conversion.total_messaging_connection';\n const LEAD_FALLBACK = 'onsite_conversion.messaging_conversation_started_7d';\n\n let leadSpend = 0;\n let totalLeads = 0;\n for (const c of campaigns) {\n const leadAction = (c.actions || []).find(a => a.action_type === LEAD_ACTION)\n || (c.actions || []).find(a => a.action_type === LEAD_FALLBACK);\n if (leadAction) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(leadAction.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { phone: ctx.phone, output: reportText } }];" + "jsCode": "const metaData = $input.first().json;\nconst ctx = $('Calcular Datas').first().json;\nconst campaigns = metaData.data || [];\n\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nif (!campaigns.length) {\n return [{ json: {\n phone: ctx.phone,\n output: '\\u274c Sem dados para *' + (ctx.clientNome || '').toUpperCase() + '* no per\\u00edodo solicitado.'\n } }];\n}\n\nconst purchaseAccountIds = ['act_873195821346004', 'act_1673012226810411'];\nconst isPurchase = purchaseAccountIds.includes(ctx.clientFound);\n\nlet totalSpend = 0;\nlet totalImpressions = 0;\nlet totalClicks = 0;\n\nfor (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n}\n\nconst clientNameUpper = (ctx.clientNome || '').toUpperCase();\nconst periodoLabel = ctx.periodoLabel || '\\u00faltimos 7 dias';\nconst periodStart = ctx.periodStart || ctx.last7Start;\nconst periodEnd = ctx.periodEnd || ctx.last7End;\nconst dateRange = periodStart === periodEnd\n ? '_\\ud83d\\udcc5 ' + fmtBR(periodStart) + ' (' + periodoLabel + ')_'\n : '_\\ud83d\\udcc5 ' + fmtBR(periodStart) + ' a ' + fmtBR(periodEnd) + ' (' + periodoLabel + ')_';\n\nlet reportText;\n\nif (isPurchase) {\n let totalPurchases = 0;\n let totalRevenue = 0;\n for (const c of campaigns) {\n const purchaseAction = (c.actions || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (purchaseAction) totalPurchases += parseInt(purchaseAction.value);\n const revenueAction = (c.action_values || []).find(a =>\n a.action_type === 'purchase' ||\n a.action_type === 'offsite_conversion.fb_pixel_purchase'\n );\n if (revenueAction) totalRevenue += parseFloat(revenueAction.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Faturamento: R$ ' + fmtMoney(totalRevenue) + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n} else {\n // Leads = Novos contatos de mensagem (campanhas WhatsApp)\n // CPL calculado somente sobre campanhas com destino WhatsApp\n const LEAD_ACTION = 'onsite_conversion.total_messaging_connection';\n\n let leadSpend = 0;\n let totalLeads = 0;\n for (const c of campaigns) {\n const leadAction = (c.actions || []).find(a => a.action_type === LEAD_ACTION);\n if (leadAction) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(leadAction.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + clientNameUpper + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n}\n\nreturn [{ json: { phone: ctx.phone, output: reportText } }];" }, "id": "2b3745d9-82ef-4d9a-aa8d-db402408a07e", "name": "Formatar Relatório", From 3f49f3a1e6aa0da0416819a35f2b0636b9c39ccf Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 20:20:30 +0000 Subject: [PATCH 13/16] =?UTF-8?q?fix:=20substitui=20fetch=20por=20https.ge?= =?UTF-8?q?t=20nos=204=20n=C3=B3s=20toolCode=20do=20agente=20Alicia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fetch não está disponível no ambiente Node.js dos nós toolCode do n8n. Todos os 4 tools (buscar_insights, listar_campanhas, listar_conjuntos, buscar_insights_multiplas_contas) agora usam require('https') + httpGet, igual ao padrão já funcional no nó Buscar e Formatar Todos. https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index ca10b75f1..bea9e9bb9 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -340,7 +340,7 @@ "parameters": { "name": "buscar_insights", "description": "Busca métricas/insights do Meta Ads. Use para: gasto (spend), leads (actions), impressões, cliques, CTR, CPC, alcance. Envie JSON com: object_id (ID da conta ex: act_123456 OU ID de campanha), date_preset (today/yesterday/this_week_mon_today/last_7d/last_14d/last_30d/last_month), fields (ex: spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type), level opcional (account/campaign/adset/ad). IMPORTANTE: Para leads, inclua 'actions' nos fields e filtre por action_type = onsite_conversion.total_messaging_connection.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com object_id e date_preset'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst objectId = params.object_id;\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type';\nconst level = params.level || '';\nif (!objectId) return 'Erro: object_id é obrigatório';\nlet url = `https://graph.facebook.com/v19.0/${objectId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\nif (level) url += `&level=${encodeURIComponent(level)}`;\ntry {\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message} (código: ${data.error.code})`;\n if (!data.data || data.data.length === 0) return 'Nenhum dado encontrado para o período solicitado.';\n return JSON.stringify(data.data);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com object_id e date_preset'; }\nconst https = require('https');\nconst httpGet = (u) => new Promise((resolve, reject) => { https.get(u, (res) => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch(err) { reject(err); } }); }).on('error', reject); });\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst objectId = params.object_id;\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type';\nconst level = params.level || '';\nif (!objectId) return 'Erro: object_id é obrigatório';\nlet url = `https://graph.facebook.com/v19.0/${objectId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\nif (level) url += `&level=${encodeURIComponent(level)}`;\ntry {\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message} (código: ${data.error.code})`;\n if (!data.data || data.data.length === 0) return 'Nenhum dado encontrado para o período solicitado.';\n return JSON.stringify(data.data);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "13abddfb-207b-4d02-aa60-c7b8658c7860", "name": "Tool: Buscar Insights", @@ -355,7 +355,7 @@ "parameters": { "name": "listar_campanhas", "description": "Lista todas as campanhas de uma conta de anúncios. Envie JSON com: account_id (formato: act_XXXXXXXXX). Retorna: id, nome, status, objetivo, budget diário e total. Use para encontrar o ID de uma campanha específica antes de buscar os insights.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nif (!accountId) return 'Erro: account_id é obrigatório (formato: act_XXXXXXXXX)';\ntry {\n const url = `https://graph.facebook.com/v19.0/${accountId}/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhuma campanha encontrada nessa conta';\n const campaigns = data.data.map(c => ({\n id: c.id,\n nome: c.name,\n status: c.status,\n objetivo: c.objective,\n budget_diario_reais: c.daily_budget ? (parseInt(c.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: c.lifetime_budget ? (parseInt(c.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(campaigns);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nconst https = require('https');\nconst httpGet = (u) => new Promise((resolve, reject) => { https.get(u, (res) => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch(err) { reject(err); } }); }).on('error', reject); });\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nif (!accountId) return 'Erro: account_id é obrigatório (formato: act_XXXXXXXXX)';\ntry {\n const url = `https://graph.facebook.com/v19.0/${accountId}/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=${token}`;\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhuma campanha encontrada nessa conta';\n const campaigns = data.data.map(c => ({\n id: c.id,\n nome: c.name,\n status: c.status,\n objetivo: c.objective,\n budget_diario_reais: c.daily_budget ? (parseInt(c.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: c.lifetime_budget ? (parseInt(c.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(campaigns);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "731c36fc-e0b1-44a4-a13e-fcda6b6f2a2e", "name": "Tool: Listar Campanhas", @@ -370,7 +370,7 @@ "parameters": { "name": "listar_conjuntos", "description": "Lista conjuntos de anúncios (adsets) de uma conta ou campanha. Envie JSON com: account_id (act_XXXXXXXXX) OU campaign_id (ID numérico). Retorna: id, nome, status e budget.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id ou campaign_id'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nconst campaignId = params.campaign_id;\nif (!accountId && !campaignId) return 'Erro: forneça account_id ou campaign_id';\nconst baseId = campaignId || accountId;\ntry {\n const url = `https://graph.facebook.com/v19.0/${baseId}/adsets?fields=id,name,status,daily_budget,lifetime_budget,campaign_id&limit=100&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhum conjunto de anúncios encontrado';\n const adsets = data.data.map(a => ({\n id: a.id,\n nome: a.name,\n status: a.status,\n budget_diario_reais: a.daily_budget ? (parseInt(a.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: a.lifetime_budget ? (parseInt(a.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(adsets);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id ou campaign_id'; }\nconst https = require('https');\nconst httpGet = (u) => new Promise((resolve, reject) => { https.get(u, (res) => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch(err) { reject(err); } }); }).on('error', reject); });\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nconst campaignId = params.campaign_id;\nif (!accountId && !campaignId) return 'Erro: forneça account_id ou campaign_id';\nconst baseId = campaignId || accountId;\ntry {\n const url = `https://graph.facebook.com/v19.0/${baseId}/adsets?fields=id,name,status,daily_budget,lifetime_budget,campaign_id&limit=100&access_token=${token}`;\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhum conjunto de anúncios encontrado';\n const adsets = data.data.map(a => ({\n id: a.id,\n nome: a.name,\n status: a.status,\n budget_diario_reais: a.daily_budget ? (parseInt(a.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: a.lifetime_budget ? (parseInt(a.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(adsets);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "a8b12824-f38e-4296-87fe-49bd6e9f934b", "name": "Tool: Listar Conjuntos", @@ -385,7 +385,7 @@ "parameters": { "name": "buscar_insights_multiplas_contas", "description": "Busca insights de MÚLTIPLAS contas ao mesmo tempo. Use quando o usuário não especificar cliente. Envie JSON com: account_ids (array de IDs ex: ['act_111','act_222']), date_preset, fields.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_ids (array), date_preset e fields'; }\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountIds = params.account_ids || [];\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (accountIds.length === 0) return 'Erro: account_ids deve ser um array com pelo menos 1 conta';\ntry {\n const results = [];\n for (const accountId of accountIds) {\n const url = `https://graph.facebook.com/v19.0/${accountId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\n const res = await fetch(url);\n const data = await res.json();\n if (!data.error && data.data && data.data.length > 0) {\n results.push({ account_id: accountId, data: data.data[0] });\n } else if (data.error) {\n results.push({ account_id: accountId, error: data.error.message });\n } else {\n results.push({ account_id: accountId, data: null, note: 'sem dados no período' });\n }\n }\n return JSON.stringify(results);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_ids (array), date_preset e fields'; }\nconst https = require('https');\nconst httpGet = (u) => new Promise((resolve, reject) => { https.get(u, (res) => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch(err) { reject(err); } }); }).on('error', reject); });\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountIds = params.account_ids || [];\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (accountIds.length === 0) return 'Erro: account_ids deve ser um array com pelo menos 1 conta';\ntry {\n const results = [];\n for (const accountId of accountIds) {\n const url = `https://graph.facebook.com/v19.0/${accountId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\n const data = await httpGet(url);\n if (!data.error && data.data && data.data.length > 0) {\n results.push({ account_id: accountId, data: data.data[0] });\n } else if (data.error) {\n results.push({ account_id: accountId, error: data.error.message });\n } else {\n results.push({ account_id: accountId, data: null, note: 'sem dados no período' });\n }\n }\n return JSON.stringify(results);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "81aa87da-6607-4ffc-95b9-1be17e23ec68", "name": "Tool: Insights Multiplas Contas", @@ -656,4 +656,4 @@ }, "id": "uIUtsjBWTxjZObX3", "tags": [] -} +} \ No newline at end of file From 80929ae5525813e1ecdd2a6e3504ca11bd94cb30 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 20:34:32 +0000 Subject: [PATCH 14/16] =?UTF-8?q?fix:=20corrige=203=20bugs=20no=20fluxo=20?= =?UTF-8?q?'Alicia=20relat=C3=B3rios=20da=20semana'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. isRelatorioWithClient e isRelatorioTodos agora retornam strings 'true'/'false' em vez de booleans, garantindo que o nó IF do n8n compare corretamente contra o rightValue 'true' (string) 2. Enviar Resposta WhatsApp (path Alicia Agent) agora usa $('Calcular Datas').first().json.phone em vez de $json.phone, pois o nó agent não passa o campo phone no output 3. Detecção de período 'da semana' adicionada em Calcular Datas além de 'essa semana' / 'esta semana' https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index bea9e9bb9..dcdd38945 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -65,7 +65,7 @@ }, { "parameters": { - "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\nconst fmt = d => d.toISOString().split('T')[0];\nconst addDays = (n) => { const d = new Date(now); d.setDate(d.getDate() + n); return fmt(d); };\nconst dow = now.getDay();\nconst daysToMon = dow === 0 ? 6 : dow - 1;\nconst monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\n// clientMap como array para evitar chave duplicada e suportar multiplos aliases\nconst clientMap = [\n { keys: ['antonio neto', 'antonio'], id: 'act_497795903676192', nome: 'Antonio Neto' },\n { keys: ['gabriel jacinto', 'gabriel'], id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n { keys: ['dani escudero', 'dani'], id: 'act_2145598605527232', nome: 'Dani Escudero' },\n { keys: ['agro'], id: 'act_929521557422139', nome: 'Agro' },\n { keys: ['escola de musica', 'escola de m\\u00fasica', 'escola'], id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n { keys: ['panmalhas'], id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil' },\n { keys: ['gfix store', 'gfix'], id: 'act_2811791829124905', nome: 'GFiX Store' },\n { keys: ['arte em gelo', 'arte em'], id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n { keys: ['itag tecnologia', 'itag'], id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n { keys: ['hi dogz', 'dogz'], id: 'act_873195821346004', nome: 'Hi Dogz' },\n { keys: ['batata bistro', 'batata bistr\\u00f4', 'batata'], id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n { keys: ['caribbean bronze', 'caribbean'], id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n { keys: ['larissa kelleter', 'larissa'], id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n];\n\n// Ordena por comprimento da primeira chave (mais especifico primeiro)\nclientMap.sort((a, b) => b.keys[0].length - a.keys[0].length);\n\n// Detecta pedido de relatorio\nconst isRelatorio = text.includes('relat');\n\nlet clientFound = null;\nfor (let i = 0; i < clientMap.length; i++) {\n const entry = clientMap[i];\n for (let j = 0; j < entry.keys.length; j++) {\n if (text.includes(entry.keys[j])) {\n clientFound = entry;\n break;\n }\n }\n if (clientFound) break;\n}\n\nconst today = fmt(now);\nconst yesterday = addDays(-1);\n// Corrigido: ultimos 7 dias = D-7 ate D-1 (igual ao Meta Ads)\nconst last7End = addDays(-1);\nconst last7Start = addDays(-7);\n\nconst isRelatorioWithClient = isRelatorio && clientFound !== null;\n// Modo todos: pede relatorio sem especificar cliente\nconst isRelatorioTodos = isRelatorio && clientFound === null;\n\n// Detecta periodo solicitado e mapeia para date_preset do Meta Ads\nconst hasMesPassado = text.includes('m\\u00eas passado') || text.includes('mes passado');\nconst hasEsseMes = text.includes('esse m\\u00eas') || text.includes('este m\\u00eas') || text.includes('esse mes') || text.includes('este mes');\n\nlet datePreset = 'last_7d';\nlet periodoLabel = '\\u00faltimos 7 dias';\nlet periodStart = last7Start;\nlet periodEnd = last7End;\n\nif (hasMesPassado) {\n datePreset = 'last_month';\n periodoLabel = 'm\\u00eas passado';\n const firstOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n const lastOfPrevMonth = new Date(firstOfMonth.getTime() - 86400000);\n const firstOfPrevMonth = new Date(lastOfPrevMonth.getFullYear(), lastOfPrevMonth.getMonth(), 1);\n periodStart = fmt(firstOfPrevMonth);\n periodEnd = fmt(lastOfPrevMonth);\n} else if (text.includes('ontem')) {\n datePreset = 'yesterday';\n periodoLabel = 'ontem';\n periodStart = yesterday;\n periodEnd = yesterday;\n} else if (text.includes('hoje')) {\n datePreset = 'today';\n periodoLabel = 'hoje';\n periodStart = today;\n periodEnd = today;\n} else if (text.includes('essa semana') || text.includes('esta semana')) {\n datePreset = 'this_week_mon_today';\n periodoLabel = 'essa semana';\n periodStart = fmt(monday);\n periodEnd = today;\n} else if (text.includes('14 dias')) {\n datePreset = 'last_14d';\n periodoLabel = '\\u00faltimos 14 dias';\n periodStart = addDays(-14);\n periodEnd = addDays(-1);\n} else if (text.includes('30 dias') || hasEsseMes) {\n datePreset = 'last_30d';\n periodoLabel = '\\u00faltimos 30 dias';\n periodStart = addDays(-30);\n periodEnd = addDays(-1);\n}\n\nreturn [{\n json: {\n text: input.text,\n phone: input.phone,\n remoteJid: input.remoteJid,\n instance: input.instance,\n today: today,\n yesterday: yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start: last7Start,\n last7End: last7End,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio: isRelatorio,\n isRelatorioWithClient: isRelatorioWithClient,\n isRelatorioTodos: isRelatorioTodos,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null,\n datePreset: datePreset,\n periodoLabel: periodoLabel,\n periodStart: periodStart,\n periodEnd: periodEnd\n }\n}];" + "jsCode": "const input = $input.first().json;\nconst text = (input.text || '').toLowerCase();\nconst now = new Date();\nconst fmt = d => d.toISOString().split('T')[0];\nconst addDays = (n) => { const d = new Date(now); d.setDate(d.getDate() + n); return fmt(d); };\nconst dow = now.getDay();\nconst daysToMon = dow === 0 ? 6 : dow - 1;\nconst monday = new Date(now);\nmonday.setDate(now.getDate() - daysToMon);\n\n// clientMap como array para evitar chave duplicada e suportar multiplos aliases\nconst clientMap = [\n { keys: ['antonio neto', 'antonio'], id: 'act_497795903676192', nome: 'Antonio Neto' },\n { keys: ['gabriel jacinto', 'gabriel'], id: 'act_2493856707309984', nome: 'Gabriel Jacinto' },\n { keys: ['dani escudero', 'dani'], id: 'act_2145598605527232', nome: 'Dani Escudero' },\n { keys: ['agro'], id: 'act_929521557422139', nome: 'Agro' },\n { keys: ['escola de musica', 'escola de m\\u00fasica', 'escola'], id: 'act_544347952854095', nome: 'Escola de M\\u00fasica' },\n { keys: ['panmalhas'], id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil' },\n { keys: ['gfix store', 'gfix'], id: 'act_2811791829124905', nome: 'GFiX Store' },\n { keys: ['arte em gelo', 'arte em'], id: 'act_5585082641598366', nome: 'Arte em Gelo' },\n { keys: ['itag tecnologia', 'itag'], id: 'act_738466861151636', nome: 'ITAG Tecnologia' },\n { keys: ['hi dogz', 'dogz'], id: 'act_873195821346004', nome: 'Hi Dogz' },\n { keys: ['batata bistro', 'batata bistr\\u00f4', 'batata'], id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4' },\n { keys: ['caribbean bronze', 'caribbean'], id: 'act_1082397873705862', nome: 'Caribbean Bronze' },\n { keys: ['larissa kelleter', 'larissa'], id: 'act_738280412237298', nome: 'Larissa Kelleter' }\n];\n\n// Ordena por comprimento da primeira chave (mais especifico primeiro)\nclientMap.sort((a, b) => b.keys[0].length - a.keys[0].length);\n\n// Detecta pedido de relatorio\nconst isRelatorio = text.includes('relat');\n\nlet clientFound = null;\nfor (let i = 0; i < clientMap.length; i++) {\n const entry = clientMap[i];\n for (let j = 0; j < entry.keys.length; j++) {\n if (text.includes(entry.keys[j])) {\n clientFound = entry;\n break;\n }\n }\n if (clientFound) break;\n}\n\nconst today = fmt(now);\nconst yesterday = addDays(-1);\n// Corrigido: ultimos 7 dias = D-7 ate D-1 (igual ao Meta Ads)\nconst last7End = addDays(-1);\nconst last7Start = addDays(-7);\n\nconst isRelatorioWithClient = (isRelatorio && clientFound !== null) ? 'true' : 'false';\n// Modo todos: pede relatorio sem especificar cliente\nconst isRelatorioTodos = (isRelatorio && clientFound === null) ? 'true' : 'false';\n\n// Detecta periodo solicitado e mapeia para date_preset do Meta Ads\nconst hasMesPassado = text.includes('m\\u00eas passado') || text.includes('mes passado');\nconst hasEsseMes = text.includes('esse m\\u00eas') || text.includes('este m\\u00eas') || text.includes('esse mes') || text.includes('este mes');\n\nlet datePreset = 'last_7d';\nlet periodoLabel = '\\u00faltimos 7 dias';\nlet periodStart = last7Start;\nlet periodEnd = last7End;\n\nif (hasMesPassado) {\n datePreset = 'last_month';\n periodoLabel = 'm\\u00eas passado';\n const firstOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);\n const lastOfPrevMonth = new Date(firstOfMonth.getTime() - 86400000);\n const firstOfPrevMonth = new Date(lastOfPrevMonth.getFullYear(), lastOfPrevMonth.getMonth(), 1);\n periodStart = fmt(firstOfPrevMonth);\n periodEnd = fmt(lastOfPrevMonth);\n} else if (text.includes('ontem')) {\n datePreset = 'yesterday';\n periodoLabel = 'ontem';\n periodStart = yesterday;\n periodEnd = yesterday;\n} else if (text.includes('hoje')) {\n datePreset = 'today';\n periodoLabel = 'hoje';\n periodStart = today;\n periodEnd = today;\n} else if (text.includes('essa semana') || text.includes('esta semana') || text.includes('da semana')) {\n datePreset = 'this_week_mon_today';\n periodoLabel = 'essa semana';\n periodStart = fmt(monday);\n periodEnd = today;\n} else if (text.includes('14 dias')) {\n datePreset = 'last_14d';\n periodoLabel = '\\u00faltimos 14 dias';\n periodStart = addDays(-14);\n periodEnd = addDays(-1);\n} else if (text.includes('30 dias') || hasEsseMes) {\n datePreset = 'last_30d';\n periodoLabel = '\\u00faltimos 30 dias';\n periodStart = addDays(-30);\n periodEnd = addDays(-1);\n}\n\nreturn [{\n json: {\n text: input.text,\n phone: input.phone,\n remoteJid: input.remoteJid,\n instance: input.instance,\n today: today,\n yesterday: yesterday,\n weekStart: fmt(monday),\n last7: last7Start,\n last30: addDays(-30),\n last7Start: last7Start,\n last7End: last7End,\n metaToken: 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X',\n isRelatorio: isRelatorio,\n isRelatorioWithClient: isRelatorioWithClient,\n isRelatorioTodos: isRelatorioTodos,\n clientFound: clientFound ? clientFound.id : null,\n clientNome: clientFound ? clientFound.nome : null,\n datePreset: datePreset,\n periodoLabel: periodoLabel,\n periodStart: periodStart,\n periodEnd: periodEnd\n }\n}];" }, "id": "5b953b43-14ca-4ed3-b8da-3ec455f58b92", "name": "Calcular Datas", @@ -418,7 +418,7 @@ "parameters": [ { "name": "number", - "value": "={{ $json.phone }}" + "value": "={{ $('Calcular Datas').first().json.phone }}" }, { "name": "text", From 451e23f9aa0cb69afe30c7e122b7f9878c2d5f4f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 21:02:02 +0000 Subject: [PATCH 15/16] fix: rewrite HTTP calls to use \$helpers.httpRequest in all code nodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace require('https') + httpGet with \$helpers.httpRequest() in Buscar e Formatar Todos and all 4 tool nodes (more reliable in n8n) - Switch to parallel requests (Promise.all) in Buscar e Formatar Todos for faster execution across 13 clients - Add ignoreHttpStatusErrors: true so Meta API errors (400 status) are returned as JSON and handled gracefully - Fix Enviar Relatório WhatsApp to use contentType: 'json' with explicit body template instead of bodyParameters (avoids encoding issues) - Add delay: 1500 to WhatsApp messages to avoid rate limiting https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index dcdd38945..f0b57000d 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -146,7 +146,7 @@ }, { "parameters": { - "jsCode": "const https = require('https');\nconst httpGet = (url) => new Promise((resolve, reject) => { https.get(url, (res) => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch(e) { reject(e); } }); }).on('error', reject); });\nconst ctx = $('Calcular Datas').first().json;\nconst token = ctx.metaToken;\nconst datePreset = ctx.datePreset || 'last_7d';\nconst periodStart = ctx.periodStart;\nconst periodEnd = ctx.periodEnd;\nconst periodoLabel = ctx.periodoLabel || '\\u00faltimos 7 dias';\nconst phone = ctx.phone;\n\nconst clientMap = [\n { id: 'act_497795903676192', nome: 'Antonio Neto', type: 'lead' },\n { id: 'act_2493856707309984', nome: 'Gabriel Jacinto', type: 'lead' },\n { id: 'act_2145598605527232', nome: 'Dani Escudero', type: 'lead' },\n { id: 'act_929521557422139', nome: 'Agro', type: 'lead' },\n { id: 'act_544347952854095', nome: 'Escola de M\\u00fasica', type: 'lead' },\n { id: 'act_1509005182799187', nome: 'Panmalhas Assessoria T\\u00eaxtil', type: 'lead' },\n { id: 'act_2811791829124905', nome: 'GFiX Store', type: 'lead' },\n { id: 'act_5585082641598366', nome: 'Arte em Gelo', type: 'lead' },\n { id: 'act_738466861151636', nome: 'ITAG Tecnologia', type: 'lead' },\n { id: 'act_873195821346004', nome: 'Hi Dogz', type: 'purchase' },\n { id: 'act_1673012226810411', nome: 'Batata Bistr\\u00f4', type: 'purchase' },\n { id: 'act_1082397873705862', nome: 'Caribbean Bronze', type: 'lead' },\n { id: 'act_738280412237298', nome: 'Larissa Kelleter', type: 'lead' }\n];\n\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nconst LEAD_ACTION = 'onsite_conversion.total_messaging_connection';\nconst fields = 'spend,impressions,clicks,actions,action_values';\n\nconst dateRange = periodStart === periodEnd\n ? '_\\ud83d\\udcc5 ' + fmtBR(periodStart) + ' (' + periodoLabel + ')_'\n : '_\\ud83d\\udcc5 ' + fmtBR(periodStart) + ' a ' + fmtBR(periodEnd) + ' (' + periodoLabel + ')_';\n\nconst results = [];\n\nfor (const client of clientMap) {\n try {\n const url = 'https://graph.facebook.com/v19.0/' + client.id + '/insights?date_preset=' + encodeURIComponent(datePreset) + '&fields=' + encodeURIComponent(fields) + '&level=campaign&access_token=' + token;\n const data = await httpGet(url);\n const campaigns = (data.data) || [];\n\n if (!campaigns.length) {\n results.push({ json: { phone: phone, output: '*' + client.nome.toUpperCase() + '*\\n\\n' + dateRange + '\\n\\n*Meta Ads*\\n\\n\\u274c Sem dados no per\\u00edodo.' } });\n continue;\n }\n\n let totalSpend = 0, totalImpressions = 0, totalClicks = 0;\n for (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n }\n\n let reportText;\n\n if (client.type === 'purchase') {\n let totalPurchases = 0, totalRevenue = 0;\n for (const c of campaigns) {\n const pa = (c.actions || []).find(a => a.action_type === 'purchase' || a.action_type === 'offsite_conversion.fb_pixel_purchase');\n if (pa) totalPurchases += parseInt(pa.value);\n const ra = (c.action_values || []).find(a => a.action_type === 'purchase' || a.action_type === 'offsite_conversion.fb_pixel_purchase');\n if (ra) totalRevenue += parseFloat(ra.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + client.nome.toUpperCase() + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Faturamento: R$ ' + fmtMoney(totalRevenue) + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n } else {\n // Leads = Novos contatos de mensagem (campanhas WhatsApp)\n let leadSpend = 0, totalLeads = 0;\n for (const c of campaigns) {\n const la = (c.actions || []).find(a => a.action_type === LEAD_ACTION);\n if (la) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(la.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + client.nome.toUpperCase() + '*' + '\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*' + '\\n\\n' +\n 'Impress\\u00f5es: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n }\n\n results.push({ json: { phone: phone, output: reportText } });\n } catch(e) {\n results.push({ json: { phone: phone, output: '*' + client.nome.toUpperCase() + '*\\n\\n\\u274c Erro ao buscar dados: ' + e.message } });\n }\n}\n\nreturn results;" + "jsCode": "const ctx = $('Calcular Datas').first().json;\nconst token = ctx.metaToken;\nconst datePreset = ctx.datePreset || 'last_7d';\nconst periodStart = ctx.periodStart;\nconst periodEnd = ctx.periodEnd;\nconst periodoLabel = ctx.periodoLabel || 'últimos 7 dias';\nconst phone = ctx.phone;\n\nconst clientMap = [\n { id: 'act_497795903676192', nome: 'Antonio Neto', type: 'lead' },\n { id: 'act_2493856707309984', nome: 'Gabriel Jacinto', type: 'lead' },\n { id: 'act_2145598605527232', nome: 'Dani Escudero', type: 'lead' },\n { id: 'act_929521557422139', nome: 'Agro', type: 'lead' },\n { id: 'act_544347952854095', nome: 'Escola de Música', type: 'lead' },\n { id: 'act_1509005182799187', nome: 'Panmalhas Assessoria Têxtil', type: 'lead' },\n { id: 'act_2811791829124905', nome: 'GFiX Store', type: 'lead' },\n { id: 'act_5585082641598366', nome: 'Arte em Gelo', type: 'lead' },\n { id: 'act_738466861151636', nome: 'ITAG Tecnologia', type: 'lead' },\n { id: 'act_873195821346004', nome: 'Hi Dogz', type: 'purchase' },\n { id: 'act_1673012226810411', nome: 'Batata Bistrô', type: 'purchase' },\n { id: 'act_1082397873705862', nome: 'Caribbean Bronze', type: 'lead' },\n { id: 'act_738280412237298', nome: 'Larissa Kelleter', type: 'lead' }\n];\n\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n if (!dateStr) return '??/??';\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nconst LEAD_ACTION = 'onsite_conversion.total_messaging_connection';\nconst fields = 'spend,impressions,clicks,actions,action_values';\n\nconst dateRange = periodStart === periodEnd\n ? '_📅 ' + fmtBR(periodStart) + ' (' + periodoLabel + ')_'\n : '_📅 ' + fmtBR(periodStart) + ' a ' + fmtBR(periodEnd) + ' (' + periodoLabel + ')_';\n\nconst fetchClient = async (client) => {\n try {\n const data = await $helpers.httpRequest({\n url: 'https://graph.facebook.com/v19.0/' + client.id + '/insights',\n method: 'GET',\n qs: {\n date_preset: datePreset,\n fields: fields,\n level: 'campaign',\n access_token: token\n },\n ignoreHttpStatusErrors: true\n });\n\n if (data.error) {\n return { json: { phone: phone, output: '*' + client.nome.toUpperCase() + '*\\n\\n' + dateRange + '\\n\\n*Meta Ads*\\n\\n❌ Erro API: ' + data.error.message } };\n }\n\n const campaigns = data.data || [];\n\n if (!campaigns.length) {\n return { json: { phone: phone, output: '*' + client.nome.toUpperCase() + '*\\n\\n' + dateRange + '\\n\\n*Meta Ads*\\n\\n❌ Sem dados no período.' } };\n }\n\n let totalSpend = 0, totalImpressions = 0, totalClicks = 0;\n for (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n }\n\n let reportText;\n\n if (client.type === 'purchase') {\n let totalPurchases = 0, totalRevenue = 0;\n for (const c of campaigns) {\n const pa = (c.actions || []).find(a => a.action_type === 'purchase' || a.action_type === 'offsite_conversion.fb_pixel_purchase');\n if (pa) totalPurchases += parseInt(pa.value);\n const ra = (c.action_values || []).find(a => a.action_type === 'purchase' || a.action_type === 'offsite_conversion.fb_pixel_purchase');\n if (ra) totalRevenue += parseFloat(ra.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + client.nome.toUpperCase() + '*\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*\\n\\n' +\n 'Impressões: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Faturamento: R$ ' + fmtMoney(totalRevenue) + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n } else {\n let leadSpend = 0, totalLeads = 0;\n for (const c of campaigns) {\n const la = (c.actions || []).find(a => a.action_type === LEAD_ACTION);\n if (la) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(la.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + client.nome.toUpperCase() + '*\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*\\n\\n' +\n 'Impressões: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n }\n\n return { json: { phone: phone, output: reportText } };\n } catch(e) {\n return { json: { phone: phone, output: '*' + client.nome.toUpperCase() + '*\\n\\n❌ Erro ao buscar dados: ' + e.message } };\n }\n};\n\nconst results = await Promise.all(clientMap.map(fetchClient));\nreturn results;" }, "id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890", "name": "Buscar e Formatar Todos", @@ -225,20 +225,10 @@ ] }, "sendBody": true, - "bodyParameters": { - "parameters": [ - { - "name": "number", - "value": "={{ $json.phone }}" - }, - { - "name": "text", - "value": "={{ $json.output }}" - } - ] - }, + "contentType": "json", + "body": "={{ {\"number\": $json.phone, \"text\": $json.output, \"delay\": 1500} }}", "options": { - "timeout": 10000 + "timeout": 15000 } }, "id": "b339c96a-4c60-4a45-98ea-4091d5ec847d", @@ -340,7 +330,7 @@ "parameters": { "name": "buscar_insights", "description": "Busca métricas/insights do Meta Ads. Use para: gasto (spend), leads (actions), impressões, cliques, CTR, CPC, alcance. Envie JSON com: object_id (ID da conta ex: act_123456 OU ID de campanha), date_preset (today/yesterday/this_week_mon_today/last_7d/last_14d/last_30d/last_month), fields (ex: spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type), level opcional (account/campaign/adset/ad). IMPORTANTE: Para leads, inclua 'actions' nos fields e filtre por action_type = onsite_conversion.total_messaging_connection.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com object_id e date_preset'; }\nconst https = require('https');\nconst httpGet = (u) => new Promise((resolve, reject) => { https.get(u, (res) => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch(err) { reject(err); } }); }).on('error', reject); });\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst objectId = params.object_id;\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type';\nconst level = params.level || '';\nif (!objectId) return 'Erro: object_id é obrigatório';\nlet url = `https://graph.facebook.com/v19.0/${objectId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\nif (level) url += `&level=${encodeURIComponent(level)}`;\ntry {\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message} (código: ${data.error.code})`;\n if (!data.data || data.data.length === 0) return 'Nenhum dado encontrado para o período solicitado.';\n return JSON.stringify(data.data);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com object_id e date_preset'; }\nconst httpGet = async (u) => { const url = new URL(u); const qs = {}; url.searchParams.forEach((v, k) => { qs[k] = v; }); const baseUrl = url.origin + url.pathname; return await $helpers.httpRequest({ url: baseUrl, method: 'GET', qs: qs, ignoreHttpStatusErrors: true }); };\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst objectId = params.object_id;\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type';\nconst level = params.level || '';\nif (!objectId) return 'Erro: object_id é obrigatório';\nlet url = `https://graph.facebook.com/v19.0/${objectId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\nif (level) url += `&level=${encodeURIComponent(level)}`;\ntry {\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message} (código: ${data.error.code})`;\n if (!data.data || data.data.length === 0) return 'Nenhum dado encontrado para o período solicitado.';\n return JSON.stringify(data.data);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "13abddfb-207b-4d02-aa60-c7b8658c7860", "name": "Tool: Buscar Insights", @@ -355,7 +345,7 @@ "parameters": { "name": "listar_campanhas", "description": "Lista todas as campanhas de uma conta de anúncios. Envie JSON com: account_id (formato: act_XXXXXXXXX). Retorna: id, nome, status, objetivo, budget diário e total. Use para encontrar o ID de uma campanha específica antes de buscar os insights.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nconst https = require('https');\nconst httpGet = (u) => new Promise((resolve, reject) => { https.get(u, (res) => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch(err) { reject(err); } }); }).on('error', reject); });\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nif (!accountId) return 'Erro: account_id é obrigatório (formato: act_XXXXXXXXX)';\ntry {\n const url = `https://graph.facebook.com/v19.0/${accountId}/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=${token}`;\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhuma campanha encontrada nessa conta';\n const campaigns = data.data.map(c => ({\n id: c.id,\n nome: c.name,\n status: c.status,\n objetivo: c.objective,\n budget_diario_reais: c.daily_budget ? (parseInt(c.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: c.lifetime_budget ? (parseInt(c.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(campaigns);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nconst httpGet = async (u) => { const url = new URL(u); const qs = {}; url.searchParams.forEach((v, k) => { qs[k] = v; }); const baseUrl = url.origin + url.pathname; return await $helpers.httpRequest({ url: baseUrl, method: 'GET', qs: qs, ignoreHttpStatusErrors: true }); };\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nif (!accountId) return 'Erro: account_id é obrigatório (formato: act_XXXXXXXXX)';\ntry {\n const url = `https://graph.facebook.com/v19.0/${accountId}/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=${token}`;\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhuma campanha encontrada nessa conta';\n const campaigns = data.data.map(c => ({\n id: c.id,\n nome: c.name,\n status: c.status,\n objetivo: c.objective,\n budget_diario_reais: c.daily_budget ? (parseInt(c.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: c.lifetime_budget ? (parseInt(c.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(campaigns);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "731c36fc-e0b1-44a4-a13e-fcda6b6f2a2e", "name": "Tool: Listar Campanhas", @@ -370,7 +360,7 @@ "parameters": { "name": "listar_conjuntos", "description": "Lista conjuntos de anúncios (adsets) de uma conta ou campanha. Envie JSON com: account_id (act_XXXXXXXXX) OU campaign_id (ID numérico). Retorna: id, nome, status e budget.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id ou campaign_id'; }\nconst https = require('https');\nconst httpGet = (u) => new Promise((resolve, reject) => { https.get(u, (res) => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch(err) { reject(err); } }); }).on('error', reject); });\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nconst campaignId = params.campaign_id;\nif (!accountId && !campaignId) return 'Erro: forneça account_id ou campaign_id';\nconst baseId = campaignId || accountId;\ntry {\n const url = `https://graph.facebook.com/v19.0/${baseId}/adsets?fields=id,name,status,daily_budget,lifetime_budget,campaign_id&limit=100&access_token=${token}`;\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhum conjunto de anúncios encontrado';\n const adsets = data.data.map(a => ({\n id: a.id,\n nome: a.name,\n status: a.status,\n budget_diario_reais: a.daily_budget ? (parseInt(a.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: a.lifetime_budget ? (parseInt(a.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(adsets);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id ou campaign_id'; }\nconst httpGet = async (u) => { const url = new URL(u); const qs = {}; url.searchParams.forEach((v, k) => { qs[k] = v; }); const baseUrl = url.origin + url.pathname; return await $helpers.httpRequest({ url: baseUrl, method: 'GET', qs: qs, ignoreHttpStatusErrors: true }); };\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nconst campaignId = params.campaign_id;\nif (!accountId && !campaignId) return 'Erro: forneça account_id ou campaign_id';\nconst baseId = campaignId || accountId;\ntry {\n const url = `https://graph.facebook.com/v19.0/${baseId}/adsets?fields=id,name,status,daily_budget,lifetime_budget,campaign_id&limit=100&access_token=${token}`;\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhum conjunto de anúncios encontrado';\n const adsets = data.data.map(a => ({\n id: a.id,\n nome: a.name,\n status: a.status,\n budget_diario_reais: a.daily_budget ? (parseInt(a.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: a.lifetime_budget ? (parseInt(a.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(adsets);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "a8b12824-f38e-4296-87fe-49bd6e9f934b", "name": "Tool: Listar Conjuntos", @@ -385,7 +375,7 @@ "parameters": { "name": "buscar_insights_multiplas_contas", "description": "Busca insights de MÚLTIPLAS contas ao mesmo tempo. Use quando o usuário não especificar cliente. Envie JSON com: account_ids (array de IDs ex: ['act_111','act_222']), date_preset, fields.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_ids (array), date_preset e fields'; }\nconst https = require('https');\nconst httpGet = (u) => new Promise((resolve, reject) => { https.get(u, (res) => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch(err) { reject(err); } }); }).on('error', reject); });\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountIds = params.account_ids || [];\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (accountIds.length === 0) return 'Erro: account_ids deve ser um array com pelo menos 1 conta';\ntry {\n const results = [];\n for (const accountId of accountIds) {\n const url = `https://graph.facebook.com/v19.0/${accountId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\n const data = await httpGet(url);\n if (!data.error && data.data && data.data.length > 0) {\n results.push({ account_id: accountId, data: data.data[0] });\n } else if (data.error) {\n results.push({ account_id: accountId, error: data.error.message });\n } else {\n results.push({ account_id: accountId, data: null, note: 'sem dados no período' });\n }\n }\n return JSON.stringify(results);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_ids (array), date_preset e fields'; }\nconst httpGet = async (u) => { const url = new URL(u); const qs = {}; url.searchParams.forEach((v, k) => { qs[k] = v; }); const baseUrl = url.origin + url.pathname; return await $helpers.httpRequest({ url: baseUrl, method: 'GET', qs: qs, ignoreHttpStatusErrors: true }); };\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountIds = params.account_ids || [];\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (accountIds.length === 0) return 'Erro: account_ids deve ser um array com pelo menos 1 conta';\ntry {\n const results = [];\n for (const accountId of accountIds) {\n const url = `https://graph.facebook.com/v19.0/${accountId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\n const data = await httpGet(url);\n if (!data.error && data.data && data.data.length > 0) {\n results.push({ account_id: accountId, data: data.data[0] });\n } else if (data.error) {\n results.push({ account_id: accountId, error: data.error.message });\n } else {\n results.push({ account_id: accountId, data: null, note: 'sem dados no período' });\n }\n }\n return JSON.stringify(results);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "81aa87da-6607-4ffc-95b9-1be17e23ec68", "name": "Tool: Insights Multiplas Contas", From a4b2239fb9cb91078255c90ffc38814bcd67c0ae Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Mar 2026 21:19:47 +0000 Subject: [PATCH 16/16] fix: revert to require(https), reduce to 9 active clients, filter zero spend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Revert Buscar e Formatar Todos back to require('https') - \$helpers is not available in n8n Code nodes - Revert Enviar Relatório WhatsApp to bodyParameters (was working for single client reports before change broke it) - Revert all 4 tool nodes back to require('https') - Reduce automatic report list from 13 to 9 active clients: Agro, Arte em Gelo, Batata Bistrô, Caribbean Bronze, Escola de Música, GFiX Store, Hi Dogz, ITAG Tecnologia, Panmalhas Assessoria Têxtil - Skip accounts with R$ 0 spend in the period (return null + filter) - Keep Promise.all for parallel requests (faster) https://claude.ai/code/session_01TSV3FcRdbPFXKZUAftjEoQ --- n8n-alicia-meta-ads.json | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/n8n-alicia-meta-ads.json b/n8n-alicia-meta-ads.json index f0b57000d..cb192c0ee 100644 --- a/n8n-alicia-meta-ads.json +++ b/n8n-alicia-meta-ads.json @@ -146,7 +146,7 @@ }, { "parameters": { - "jsCode": "const ctx = $('Calcular Datas').first().json;\nconst token = ctx.metaToken;\nconst datePreset = ctx.datePreset || 'last_7d';\nconst periodStart = ctx.periodStart;\nconst periodEnd = ctx.periodEnd;\nconst periodoLabel = ctx.periodoLabel || 'últimos 7 dias';\nconst phone = ctx.phone;\n\nconst clientMap = [\n { id: 'act_497795903676192', nome: 'Antonio Neto', type: 'lead' },\n { id: 'act_2493856707309984', nome: 'Gabriel Jacinto', type: 'lead' },\n { id: 'act_2145598605527232', nome: 'Dani Escudero', type: 'lead' },\n { id: 'act_929521557422139', nome: 'Agro', type: 'lead' },\n { id: 'act_544347952854095', nome: 'Escola de Música', type: 'lead' },\n { id: 'act_1509005182799187', nome: 'Panmalhas Assessoria Têxtil', type: 'lead' },\n { id: 'act_2811791829124905', nome: 'GFiX Store', type: 'lead' },\n { id: 'act_5585082641598366', nome: 'Arte em Gelo', type: 'lead' },\n { id: 'act_738466861151636', nome: 'ITAG Tecnologia', type: 'lead' },\n { id: 'act_873195821346004', nome: 'Hi Dogz', type: 'purchase' },\n { id: 'act_1673012226810411', nome: 'Batata Bistrô', type: 'purchase' },\n { id: 'act_1082397873705862', nome: 'Caribbean Bronze', type: 'lead' },\n { id: 'act_738280412237298', nome: 'Larissa Kelleter', type: 'lead' }\n];\n\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n if (!dateStr) return '??/??';\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nconst LEAD_ACTION = 'onsite_conversion.total_messaging_connection';\nconst fields = 'spend,impressions,clicks,actions,action_values';\n\nconst dateRange = periodStart === periodEnd\n ? '_📅 ' + fmtBR(periodStart) + ' (' + periodoLabel + ')_'\n : '_📅 ' + fmtBR(periodStart) + ' a ' + fmtBR(periodEnd) + ' (' + periodoLabel + ')_';\n\nconst fetchClient = async (client) => {\n try {\n const data = await $helpers.httpRequest({\n url: 'https://graph.facebook.com/v19.0/' + client.id + '/insights',\n method: 'GET',\n qs: {\n date_preset: datePreset,\n fields: fields,\n level: 'campaign',\n access_token: token\n },\n ignoreHttpStatusErrors: true\n });\n\n if (data.error) {\n return { json: { phone: phone, output: '*' + client.nome.toUpperCase() + '*\\n\\n' + dateRange + '\\n\\n*Meta Ads*\\n\\n❌ Erro API: ' + data.error.message } };\n }\n\n const campaigns = data.data || [];\n\n if (!campaigns.length) {\n return { json: { phone: phone, output: '*' + client.nome.toUpperCase() + '*\\n\\n' + dateRange + '\\n\\n*Meta Ads*\\n\\n❌ Sem dados no período.' } };\n }\n\n let totalSpend = 0, totalImpressions = 0, totalClicks = 0;\n for (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n }\n\n let reportText;\n\n if (client.type === 'purchase') {\n let totalPurchases = 0, totalRevenue = 0;\n for (const c of campaigns) {\n const pa = (c.actions || []).find(a => a.action_type === 'purchase' || a.action_type === 'offsite_conversion.fb_pixel_purchase');\n if (pa) totalPurchases += parseInt(pa.value);\n const ra = (c.action_values || []).find(a => a.action_type === 'purchase' || a.action_type === 'offsite_conversion.fb_pixel_purchase');\n if (ra) totalRevenue += parseFloat(ra.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + client.nome.toUpperCase() + '*\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*\\n\\n' +\n 'Impressões: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Faturamento: R$ ' + fmtMoney(totalRevenue) + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n } else {\n let leadSpend = 0, totalLeads = 0;\n for (const c of campaigns) {\n const la = (c.actions || []).find(a => a.action_type === LEAD_ACTION);\n if (la) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(la.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + client.nome.toUpperCase() + '*\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*\\n\\n' +\n 'Impressões: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n }\n\n return { json: { phone: phone, output: reportText } };\n } catch(e) {\n return { json: { phone: phone, output: '*' + client.nome.toUpperCase() + '*\\n\\n❌ Erro ao buscar dados: ' + e.message } };\n }\n};\n\nconst results = await Promise.all(clientMap.map(fetchClient));\nreturn results;" + "jsCode": "const https = require('https');\nconst httpGet = (url) => new Promise((resolve, reject) => {\n https.get(url, (res) => {\n let d = '';\n res.on('data', c => d += c);\n res.on('end', () => { try { resolve(JSON.parse(d)); } catch(e) { reject(e); } });\n }).on('error', reject);\n});\n\nconst ctx = $('Calcular Datas').first().json;\nconst token = ctx.metaToken;\nconst datePreset = ctx.datePreset || 'last_7d';\nconst periodStart = ctx.periodStart;\nconst periodEnd = ctx.periodEnd;\nconst periodoLabel = ctx.periodoLabel || 'últimos 7 dias';\nconst phone = ctx.phone;\n\n// 9 clientes ativos para relatório automático\nconst clientMap = [\n { id: 'act_929521557422139', nome: 'Agro', type: 'lead' },\n { id: 'act_5585082641598366', nome: 'Arte em Gelo', type: 'lead' },\n { id: 'act_1673012226810411', nome: 'Batata Bistrô', type: 'purchase' },\n { id: 'act_1082397873705862', nome: 'Caribbean Bronze', type: 'lead' },\n { id: 'act_544347952854095', nome: 'Escola de Música', type: 'lead' },\n { id: 'act_2811791829124905', nome: 'GFiX Store', type: 'lead' },\n { id: 'act_873195821346004', nome: 'Hi Dogz', type: 'purchase' },\n { id: 'act_738466861151636', nome: 'ITAG Tecnologia', type: 'lead' },\n { id: 'act_1509005182799187', nome: 'Panmalhas Assessoria Têxtil', type: 'lead' }\n];\n\nconst fmtMoney = (val) => {\n const n = parseFloat(val) || 0;\n const fixed = n.toFixed(2);\n const parts = fixed.split('.');\n parts[0] = parts[0].replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n return parts[0] + ',' + parts[1];\n};\n\nconst fmtNum = (val) => {\n const n = parseInt(val) || 0;\n return String(n).replace(/(\\d)(?=(\\d{3})+$)/g, '$1.');\n};\n\nconst fmtBR = (dateStr) => {\n if (!dateStr) return '??/??';\n const d = new Date(dateStr + 'T12:00:00Z');\n return String(d.getUTCDate()).padStart(2, '0') + '/' + String(d.getUTCMonth() + 1).padStart(2, '0');\n};\n\nconst LEAD_ACTION = 'onsite_conversion.total_messaging_connection';\nconst fields = 'spend,impressions,clicks,actions,action_values';\n\nconst dateRange = periodStart === periodEnd\n ? '_📅 ' + fmtBR(periodStart) + ' (' + periodoLabel + ')_'\n : '_📅 ' + fmtBR(periodStart) + ' a ' + fmtBR(periodEnd) + ' (' + periodoLabel + ')_';\n\nconst fetchClient = async (client) => {\n try {\n const url = 'https://graph.facebook.com/v19.0/' + client.id + '/insights?date_preset=' +\n encodeURIComponent(datePreset) + '&fields=' + encodeURIComponent(fields) +\n '&level=campaign&access_token=' + token;\n const data = await httpGet(url);\n\n if (data.error) return null; // ignora conta com erro de acesso\n\n const campaigns = data.data || [];\n if (!campaigns.length) return null; // sem dados no período\n\n let totalSpend = 0, totalImpressions = 0, totalClicks = 0;\n for (const c of campaigns) {\n totalSpend += parseFloat(c.spend || '0');\n totalImpressions += parseInt(c.impressions || '0');\n totalClicks += parseInt(c.clicks || '0');\n }\n\n if (totalSpend === 0) return null; // sem investimento no período, ignora\n\n let reportText;\n\n if (client.type === 'purchase') {\n let totalPurchases = 0, totalRevenue = 0;\n for (const c of campaigns) {\n const pa = (c.actions || []).find(a =>\n a.action_type === 'purchase' || a.action_type === 'offsite_conversion.fb_pixel_purchase');\n if (pa) totalPurchases += parseInt(pa.value);\n const ra = (c.action_values || []).find(a =>\n a.action_type === 'purchase' || a.action_type === 'offsite_conversion.fb_pixel_purchase');\n if (ra) totalRevenue += parseFloat(ra.value);\n }\n const cpp = totalPurchases > 0 ? totalSpend / totalPurchases : 0;\n reportText =\n '*' + client.nome.toUpperCase() + '*\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*\\n\\n' +\n 'Impressões: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Vendas: ' + totalPurchases + '\\n\\n' +\n 'Faturamento: R$ ' + fmtMoney(totalRevenue) + '\\n\\n' +\n 'Custo por venda: R$ ' + fmtMoney(cpp) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n } else {\n let leadSpend = 0, totalLeads = 0;\n for (const c of campaigns) {\n const la = (c.actions || []).find(a => a.action_type === LEAD_ACTION);\n if (la) {\n leadSpend += parseFloat(c.spend || '0');\n totalLeads += parseInt(la.value);\n }\n }\n const cpl = totalLeads > 0 ? leadSpend / totalLeads : 0;\n reportText =\n '*' + client.nome.toUpperCase() + '*\\n\\n' +\n dateRange + '\\n\\n' +\n '*Meta Ads*\\n\\n' +\n 'Impressões: ' + fmtNum(totalImpressions) + '\\n\\n' +\n 'Cliques: ' + fmtNum(totalClicks) + '\\n\\n' +\n 'Leads: ' + totalLeads + '\\n\\n' +\n 'Custo por lead: R$ ' + fmtMoney(cpl) + '\\n\\n' +\n '*INVESTIMENTO TOTAL: R$ ' + fmtMoney(totalSpend) + '*';\n }\n\n return { json: { phone: phone, output: reportText } };\n } catch(e) {\n return null; // ignora clientes com erro de conexão\n }\n};\n\nconst allResults = await Promise.all(clientMap.map(fetchClient));\nconst results = allResults.filter(r => r !== null);\n\nif (results.length === 0) {\n return [{ json: { phone: phone, output: '❌ Nenhum dado encontrado para o período (' + periodoLabel + ').' } }];\n}\n\nreturn results;" }, "id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890", "name": "Buscar e Formatar Todos", @@ -225,8 +225,18 @@ ] }, "sendBody": true, - "contentType": "json", - "body": "={{ {\"number\": $json.phone, \"text\": $json.output, \"delay\": 1500} }}", + "bodyParameters": { + "parameters": [ + { + "name": "number", + "value": "={{ $json.phone }}" + }, + { + "name": "text", + "value": "={{ $json.output }}" + } + ] + }, "options": { "timeout": 15000 } @@ -330,7 +340,7 @@ "parameters": { "name": "buscar_insights", "description": "Busca métricas/insights do Meta Ads. Use para: gasto (spend), leads (actions), impressões, cliques, CTR, CPC, alcance. Envie JSON com: object_id (ID da conta ex: act_123456 OU ID de campanha), date_preset (today/yesterday/this_week_mon_today/last_7d/last_14d/last_30d/last_month), fields (ex: spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type), level opcional (account/campaign/adset/ad). IMPORTANTE: Para leads, inclua 'actions' nos fields e filtre por action_type = onsite_conversion.total_messaging_connection.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com object_id e date_preset'; }\nconst httpGet = async (u) => { const url = new URL(u); const qs = {}; url.searchParams.forEach((v, k) => { qs[k] = v; }); const baseUrl = url.origin + url.pathname; return await $helpers.httpRequest({ url: baseUrl, method: 'GET', qs: qs, ignoreHttpStatusErrors: true }); };\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst objectId = params.object_id;\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type';\nconst level = params.level || '';\nif (!objectId) return 'Erro: object_id é obrigatório';\nlet url = `https://graph.facebook.com/v19.0/${objectId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\nif (level) url += `&level=${encodeURIComponent(level)}`;\ntry {\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message} (código: ${data.error.code})`;\n if (!data.data || data.data.length === 0) return 'Nenhum dado encontrado para o período solicitado.';\n return JSON.stringify(data.data);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com object_id e date_preset'; }\nconst https = require('https');\nconst httpGet = (u) => new Promise((resolve, reject) => { https.get(u, (res) => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch(err) { reject(err); } }); }).on('error', reject); });\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst objectId = params.object_id;\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,ctr,cpc,actions,reach,cost_per_action_type';\nconst level = params.level || '';\nif (!objectId) return 'Erro: object_id é obrigatório';\nlet url = `https://graph.facebook.com/v19.0/${objectId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\nif (level) url += `&level=${encodeURIComponent(level)}`;\ntry {\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message} (código: ${data.error.code})`;\n if (!data.data || data.data.length === 0) return 'Nenhum dado encontrado para o período solicitado.';\n return JSON.stringify(data.data);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "13abddfb-207b-4d02-aa60-c7b8658c7860", "name": "Tool: Buscar Insights", @@ -345,7 +355,7 @@ "parameters": { "name": "listar_campanhas", "description": "Lista todas as campanhas de uma conta de anúncios. Envie JSON com: account_id (formato: act_XXXXXXXXX). Retorna: id, nome, status, objetivo, budget diário e total. Use para encontrar o ID de uma campanha específica antes de buscar os insights.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nconst httpGet = async (u) => { const url = new URL(u); const qs = {}; url.searchParams.forEach((v, k) => { qs[k] = v; }); const baseUrl = url.origin + url.pathname; return await $helpers.httpRequest({ url: baseUrl, method: 'GET', qs: qs, ignoreHttpStatusErrors: true }); };\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nif (!accountId) return 'Erro: account_id é obrigatório (formato: act_XXXXXXXXX)';\ntry {\n const url = `https://graph.facebook.com/v19.0/${accountId}/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=${token}`;\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhuma campanha encontrada nessa conta';\n const campaigns = data.data.map(c => ({\n id: c.id,\n nome: c.name,\n status: c.status,\n objetivo: c.objective,\n budget_diario_reais: c.daily_budget ? (parseInt(c.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: c.lifetime_budget ? (parseInt(c.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(campaigns);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id'; }\nconst https = require('https');\nconst httpGet = (u) => new Promise((resolve, reject) => { https.get(u, (res) => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch(err) { reject(err); } }); }).on('error', reject); });\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nif (!accountId) return 'Erro: account_id é obrigatório (formato: act_XXXXXXXXX)';\ntry {\n const url = `https://graph.facebook.com/v19.0/${accountId}/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget&limit=100&access_token=${token}`;\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhuma campanha encontrada nessa conta';\n const campaigns = data.data.map(c => ({\n id: c.id,\n nome: c.name,\n status: c.status,\n objetivo: c.objective,\n budget_diario_reais: c.daily_budget ? (parseInt(c.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: c.lifetime_budget ? (parseInt(c.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(campaigns);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "731c36fc-e0b1-44a4-a13e-fcda6b6f2a2e", "name": "Tool: Listar Campanhas", @@ -360,7 +370,7 @@ "parameters": { "name": "listar_conjuntos", "description": "Lista conjuntos de anúncios (adsets) de uma conta ou campanha. Envie JSON com: account_id (act_XXXXXXXXX) OU campaign_id (ID numérico). Retorna: id, nome, status e budget.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id ou campaign_id'; }\nconst httpGet = async (u) => { const url = new URL(u); const qs = {}; url.searchParams.forEach((v, k) => { qs[k] = v; }); const baseUrl = url.origin + url.pathname; return await $helpers.httpRequest({ url: baseUrl, method: 'GET', qs: qs, ignoreHttpStatusErrors: true }); };\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nconst campaignId = params.campaign_id;\nif (!accountId && !campaignId) return 'Erro: forneça account_id ou campaign_id';\nconst baseId = campaignId || accountId;\ntry {\n const url = `https://graph.facebook.com/v19.0/${baseId}/adsets?fields=id,name,status,daily_budget,lifetime_budget,campaign_id&limit=100&access_token=${token}`;\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhum conjunto de anúncios encontrado';\n const adsets = data.data.map(a => ({\n id: a.id,\n nome: a.name,\n status: a.status,\n budget_diario_reais: a.daily_budget ? (parseInt(a.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: a.lifetime_budget ? (parseInt(a.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(adsets);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_id ou campaign_id'; }\nconst https = require('https');\nconst httpGet = (u) => new Promise((resolve, reject) => { https.get(u, (res) => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch(err) { reject(err); } }); }).on('error', reject); });\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountId = params.account_id;\nconst campaignId = params.campaign_id;\nif (!accountId && !campaignId) return 'Erro: forneça account_id ou campaign_id';\nconst baseId = campaignId || accountId;\ntry {\n const url = `https://graph.facebook.com/v19.0/${baseId}/adsets?fields=id,name,status,daily_budget,lifetime_budget,campaign_id&limit=100&access_token=${token}`;\n const data = await httpGet(url);\n if (data.error) return `Erro Meta API: ${data.error.message}`;\n if (!data.data || data.data.length === 0) return 'Nenhum conjunto de anúncios encontrado';\n const adsets = data.data.map(a => ({\n id: a.id,\n nome: a.name,\n status: a.status,\n budget_diario_reais: a.daily_budget ? (parseInt(a.daily_budget) / 100).toFixed(2) : null,\n budget_total_reais: a.lifetime_budget ? (parseInt(a.lifetime_budget) / 100).toFixed(2) : null\n }));\n return JSON.stringify(adsets);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "a8b12824-f38e-4296-87fe-49bd6e9f934b", "name": "Tool: Listar Conjuntos", @@ -375,7 +385,7 @@ "parameters": { "name": "buscar_insights_multiplas_contas", "description": "Busca insights de MÚLTIPLAS contas ao mesmo tempo. Use quando o usuário não especificar cliente. Envie JSON com: account_ids (array de IDs ex: ['act_111','act_222']), date_preset, fields.", - "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_ids (array), date_preset e fields'; }\nconst httpGet = async (u) => { const url = new URL(u); const qs = {}; url.searchParams.forEach((v, k) => { qs[k] = v; }); const baseUrl = url.origin + url.pathname; return await $helpers.httpRequest({ url: baseUrl, method: 'GET', qs: qs, ignoreHttpStatusErrors: true }); };\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountIds = params.account_ids || [];\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (accountIds.length === 0) return 'Erro: account_ids deve ser um array com pelo menos 1 conta';\ntry {\n const results = [];\n for (const accountId of accountIds) {\n const url = `https://graph.facebook.com/v19.0/${accountId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\n const data = await httpGet(url);\n if (!data.error && data.data && data.data.length > 0) {\n results.push({ account_id: accountId, data: data.data[0] });\n } else if (data.error) {\n results.push({ account_id: accountId, error: data.error.message });\n } else {\n results.push({ account_id: accountId, data: null, note: 'sem dados no período' });\n }\n }\n return JSON.stringify(results);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" + "jsCode": "let params;\ntry { params = JSON.parse(query); } catch(e) { return 'Erro: envie JSON com account_ids (array), date_preset e fields'; }\nconst https = require('https');\nconst httpGet = (u) => new Promise((resolve, reject) => { https.get(u, (res) => { let d = ''; res.on('data', c => d += c); res.on('end', () => { try { resolve(JSON.parse(d)); } catch(err) { reject(err); } }); }).on('error', reject); });\nconst token = 'EAAeVLzZANk8oBQ79Ue5UimCCCYL9uNZCH9EZA7vZAJtqB5B9aQ6mO53EnUI1b8Cskpq9tRsYZAaDKvZB1kLZABjbqgwi3aenhyPnAtQkeineasNmaZA7vue9ndlaErc59UFZBaMBeDnBC8ZBpXtn9PdUP4OMQvkd53QZAL1jI9SC49WGZCvgopdZABkhGKCEuVStyYGRfYPrlhksx7HHkqZBNWo5gMLhREik5hvkp1JA7X';\nconst accountIds = params.account_ids || [];\nconst datePreset = params.date_preset || 'today';\nconst fields = params.fields || 'spend,impressions,clicks,actions,reach';\nif (accountIds.length === 0) return 'Erro: account_ids deve ser um array com pelo menos 1 conta';\ntry {\n const results = [];\n for (const accountId of accountIds) {\n const url = `https://graph.facebook.com/v19.0/${accountId}/insights?date_preset=${encodeURIComponent(datePreset)}&fields=${encodeURIComponent(fields)}&access_token=${token}`;\n const data = await httpGet(url);\n if (!data.error && data.data && data.data.length > 0) {\n results.push({ account_id: accountId, data: data.data[0] });\n } else if (data.error) {\n results.push({ account_id: accountId, error: data.error.message });\n } else {\n results.push({ account_id: accountId, data: null, note: 'sem dados no período' });\n }\n }\n return JSON.stringify(results);\n} catch(e) { return `Erro de conexão: ${e.message}`; }" }, "id": "81aa87da-6607-4ffc-95b9-1be17e23ec68", "name": "Tool: Insights Multiplas Contas",