Skip to content

Commit e856a11

Browse files
committed
feat: adiciona faturamento (valor de conversão de compra) no relatório de vendas
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
1 parent d9508b0 commit e856a11

File tree

1 file changed

+2
-2
lines changed

1 file changed

+2
-2
lines changed

n8n-alicia-meta-ads.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@
123123
},
124124
{
125125
"name": "fields",
126-
"value": "spend,impressions,clicks,ctr,actions,reach,campaign_name"
126+
"value": "spend,impressions,clicks,ctr,actions,action_values,reach,campaign_name"
127127
},
128128
{
129129
"name": "level",
@@ -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 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 } }];"
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 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 } }];"
154154
},
155155
"id": "2b3745d9-82ef-4d9a-aa8d-db402408a07e",
156156
"name": "Formatar Relatório",

0 commit comments

Comments
 (0)