Skip to content

Commit d9508b0

Browse files
committed
feat: adiciona detecção dinâmica de período nos relatórios Meta Ads
- 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
1 parent ecc5d18 commit d9508b0

File tree

1 file changed

+3
-3
lines changed

1 file changed

+3
-3
lines changed

n8n-alicia-meta-ads.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
},
6767
{
6868
"parameters": {
69-
"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}];"
69+
"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}];"
7070
},
7171
"id": "5b953b43-14ca-4ed3-b8da-3ec455f58b92",
7272
"name": "Calcular Datas",
@@ -119,7 +119,7 @@
119119
"parameters": [
120120
{
121121
"name": "date_preset",
122-
"value": "last_7d"
122+
"value": "={{ $('Calcular Datas').first().json.datePreset }}"
123123
},
124124
{
125125
"name": "fields",
@@ -150,7 +150,7 @@
150150
},
151151
{
152152
"parameters": {
153-
"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 } }];"
153+
"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 } }];"
154154
},
155155
"id": "2b3745d9-82ef-4d9a-aa8d-db402408a07e",
156156
"name": "Formatar Relatório",

0 commit comments

Comments
 (0)