cdp-edge 2.5.6 → 2.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -8
- package/contracts/agent-versions.json +2 -2
- package/contracts/api-versions.json +8 -8
- package/extracted-skill/tracking-events-generator/INSTALACAO-CDPEDGE.md +2 -2
- package/extracted-skill/tracking-events-generator/INTEGRACAO-COMPLETA.md +3 -3
- package/extracted-skill/tracking-events-generator/Premium-Tracking-Intelligence-Resumo.md +1 -1
- package/extracted-skill/tracking-events-generator/SKILL.md +4 -4
- package/extracted-skill/tracking-events-generator/advanced-matching.js +1 -1
- package/extracted-skill/tracking-events-generator/agents/attribution-agent.md +3 -3
- package/extracted-skill/tracking-events-generator/agents/browser-tracking.md +2 -2
- package/extracted-skill/tracking-events-generator/agents/code-guardian-agent.md +2 -2
- package/extracted-skill/tracking-events-generator/agents/debug-agent.md +1 -1
- package/extracted-skill/tracking-events-generator/agents/intelligence-agent.md +6 -6
- package/extracted-skill/tracking-events-generator/agents/master-orchestrator.md +12 -12
- package/extracted-skill/tracking-events-generator/agents/match-quality-agent.md +1 -1
- package/extracted-skill/tracking-events-generator/agents/memory-agent.md +8 -8
- package/extracted-skill/tracking-events-generator/agents/meta-agent.md +8 -8
- package/extracted-skill/tracking-events-generator/agents/page-analyzer.md +1 -1
- package/extracted-skill/tracking-events-generator/agents/premium-tracking-intelligence-agent.md +1 -1
- package/extracted-skill/tracking-events-generator/agents/security-enterprise-agent.md +1 -1
- package/extracted-skill/tracking-events-generator/agents/server-tracking.md +9 -9
- package/extracted-skill/tracking-events-generator/agents/spotify-agent.md +1 -1
- package/extracted-skill/tracking-events-generator/agents/tracking-plan-agent.md +5 -5
- package/extracted-skill/tracking-events-generator/agents/validator-agent.md +8 -8
- package/extracted-skill/tracking-events-generator/agents/webhook-agent.md +4 -4
- package/extracted-skill/tracking-events-generator/agents/whatsapp-agent.md +7 -7
- package/extracted-skill/tracking-events-generator/agents/whatsapp-ctwa-setup-agent.md +16 -16
- package/extracted-skill/tracking-events-generator/agents/zapman-agent.md +189 -0
- package/extracted-skill/tracking-events-generator/cdpTrack.js +1 -1
- package/extracted-skill/tracking-events-generator/contracts/api-versions.json +8 -8
- package/extracted-skill/tracking-events-generator/docs/guia-cloudflare-iniciante.md +2 -2
- package/extracted-skill/tracking-events-generator/evals/evals.json +5 -5
- package/extracted-skill/tracking-events-generator/knowledge-base.md +5 -5
- package/extracted-skill/tracking-events-generator/models/captura-de-lead.md +1 -1
- package/extracted-skill/tracking-events-generator/models/checkout-proprio.md +1 -1
- package/extracted-skill/tracking-events-generator/models/lancamento-imobiliario.md +1 -1
- package/extracted-skill/tracking-events-generator/models/multi-step-checkout.md +5 -5
- package/extracted-skill/tracking-events-generator/models/pagina-obrigado.md +1 -1
- package/extracted-skill/tracking-events-generator/models/trafego-direto.md +4 -4
- package/extracted-skill/tracking-events-generator/models/webinar-registration.md +1 -1
- package/package.json +1 -1
- package/server-edge-tracker/INSTALAR.md +3 -3
- package/server-edge-tracker/index.ts +37 -36
- package/server-edge-tracker/modules/db.ts +1 -1
- package/server-edge-tracker/modules/dispatch/crm.ts +26 -363
- package/server-edge-tracker/modules/dispatch/meta.ts +2 -2
- package/server-edge-tracker/modules/dispatch/whatsapp.ts +7 -18
- package/server-edge-tracker/modules/intelligence.ts +4 -4
- package/server-edge-tracker/modules/nurture.ts +1 -1
- package/server-edge-tracker/schema.sql +1 -1
- package/server-edge-tracker/types.ts +6 -8
- package/server-edge-tracker/wrangler.toml +143 -140
- package/templates/captura-de-lead.md +1 -1
- package/templates/checkout-proprio.md +1 -1
- package/templates/lancamento-imobiliario.md +1 -1
- package/templates/multi-step-checkout.md +5 -5
- package/templates/pagina-obrigado.md +1 -1
- package/templates/trafego-direto.md +4 -4
- package/templates/webinar-registration.md +1 -1
- package/extracted-skill/tracking-events-generator/agents/evo-crm-agent.md +0 -253
|
@@ -1,382 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CDP Edge —
|
|
3
|
-
*
|
|
2
|
+
* CDP Edge — ZapMan SDR Integration
|
|
3
|
+
* Cria lead direto no Kanban do ZapMan SDR via API.
|
|
4
4
|
*
|
|
5
5
|
* Secrets necessários (wrangler secret put):
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* EVO_CRM_INBOX_ID → ID do inbox onde as conversas serão criadas
|
|
10
|
-
*
|
|
11
|
-
* Secrets opcionais (defaults BR):
|
|
12
|
-
* EVO_CRM_DEFAULT_COUNTRY → Dial code para números locais sem +cc (default "55")
|
|
13
|
-
* EVO_CRM_LOCALE → "pt-BR" | "en-US" | "es-ES" (default "pt-BR")
|
|
14
|
-
*
|
|
15
|
-
* Uso (qualquer origem de lead):
|
|
16
|
-
* await pushLeadToCrm(env, {
|
|
17
|
-
* phone: '5511999990000',
|
|
18
|
-
* name: 'João Silva',
|
|
19
|
-
* email: 'joao@exemplo.com',
|
|
20
|
-
* utmSource: 'facebook',
|
|
21
|
-
* utmCampaign: 'campanha-x',
|
|
22
|
-
* fbclid: 'AbC123...',
|
|
23
|
-
* intentScore: 85,
|
|
24
|
-
* ltvClass: 'high',
|
|
25
|
-
* pageUrl: 'https://exemplo.com/lp',
|
|
26
|
-
* });
|
|
6
|
+
* ZAPMAN_API_URL → Base URL: https://zapman-api.arkitekt.space
|
|
7
|
+
* ZAPMAN_API_KEY → DASHBOARD_SECRET do ZapMan (X-API-Key)
|
|
8
|
+
* ZAPMAN_CRM_INSTANCE → ID da instância ZapMan (ex: minha-instancia)
|
|
27
9
|
*/
|
|
28
10
|
|
|
29
11
|
import { Env } from '../../types.js';
|
|
30
12
|
|
|
31
|
-
// ──
|
|
32
|
-
|
|
33
|
-
export interface CrmLeadData {
|
|
34
|
-
// Identificação
|
|
35
|
-
phone: string;
|
|
36
|
-
name?: string | null;
|
|
37
|
-
email?: string | null;
|
|
38
|
-
|
|
39
|
-
// Meta CAPI
|
|
40
|
-
fbclid?: string | null;
|
|
41
|
-
fbc?: string | null;
|
|
42
|
-
fbp?: string | null;
|
|
43
|
-
ctwaClid?: string | null;
|
|
44
|
-
adId?: string | null;
|
|
45
|
-
messageBody?: string | null;
|
|
46
|
-
headline?: string | null;
|
|
47
|
-
|
|
48
|
-
// UTMs
|
|
49
|
-
utmSource?: string | null;
|
|
50
|
-
utmMedium?: string | null;
|
|
51
|
-
utmCampaign?: string | null;
|
|
52
|
-
utmContent?: string | null;
|
|
53
|
-
utmTerm?: string | null;
|
|
54
|
-
|
|
55
|
-
// Contexto
|
|
56
|
-
eventName?: string | null;
|
|
57
|
-
pageUrl?: string | null;
|
|
58
|
-
formName?: string | null;
|
|
59
|
-
|
|
60
|
-
// Scoring (Quantum Tracking)
|
|
61
|
-
intentScore?: number | null;
|
|
62
|
-
ltvClass?: string | null;
|
|
63
|
-
funnelStage?: string | null;
|
|
64
|
-
botScore?: number | null;
|
|
65
|
-
|
|
66
|
-
// Monetário
|
|
67
|
-
value?: number | null;
|
|
68
|
-
currency?: string | null;
|
|
13
|
+
// ── ZapMan CRM — cria lead direto no Kanban do ZapMan SDR ────────────────────
|
|
69
14
|
|
|
70
|
-
|
|
71
|
-
attributes?: Record<string, string>;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** @deprecated Use CrmLeadData + pushLeadToCrm */
|
|
75
|
-
export interface CTWALeadData {
|
|
76
|
-
phone: string;
|
|
77
|
-
name?: string | null;
|
|
78
|
-
messageBody?: string | null;
|
|
79
|
-
ctwaClid?: string | null;
|
|
80
|
-
adId?: string | null;
|
|
81
|
-
sourceUrl?: string | null;
|
|
82
|
-
headline?: string | null;
|
|
83
|
-
wamid?: string | null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** @deprecated Use CrmLeadData + pushLeadToCrm */
|
|
87
|
-
export interface FormLeadData {
|
|
15
|
+
export async function pushLeadToZapmanCrm(env: Env, data: {
|
|
88
16
|
phone: string;
|
|
89
17
|
name?: string | null;
|
|
90
|
-
email?: string
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// ── Helpers internos ──────────────────────────────────────────────────────────
|
|
98
|
-
|
|
99
|
-
function isCrmConfigured(env: Env): boolean {
|
|
100
|
-
return !!(env.EVO_CRM_BASE_URL && env.EVO_CRM_CLIENT_ID && env.EVO_CRM_CLIENT_SECRET);
|
|
101
|
-
}
|
|
18
|
+
email?: string;
|
|
19
|
+
empresa?: string;
|
|
20
|
+
campanha?: string;
|
|
21
|
+
origem?: string;
|
|
22
|
+
}): Promise<void> {
|
|
23
|
+
if (!env.ZAPMAN_API_URL || !env.ZAPMAN_API_KEY) return;
|
|
102
24
|
|
|
103
|
-
function normalizePhone(phone: string, defaultCountryCode = '55'): string {
|
|
104
|
-
const trimmed = phone.trim();
|
|
105
|
-
const cc = (defaultCountryCode || '55').replace(/\D/g, '') || '55';
|
|
106
|
-
|
|
107
|
-
// Already in E.164 (starts with +): só limpa e re-prefixa
|
|
108
|
-
if (trimmed.startsWith('+')) return '+' + trimmed.replace(/\D/g, '');
|
|
109
|
-
|
|
110
|
-
const digits = trimmed.replace(/\D/g, '');
|
|
111
|
-
|
|
112
|
-
// Comportamento BR canônico (preservado): 55 + 10 ou 11 dígitos = válido
|
|
113
|
-
if (cc === '55' && digits.startsWith('55') && (digits.length === 12 || digits.length === 13)) {
|
|
114
|
-
return `+${digits}`;
|
|
115
|
-
}
|
|
116
|
-
if (cc === '55' && (digits.length === 10 || digits.length === 11)) {
|
|
117
|
-
return `+55${digits}`;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Genérico: já tem o country code
|
|
121
|
-
if (digits.startsWith(cc) && digits.length > cc.length + 6) return `+${digits}`;
|
|
122
|
-
|
|
123
|
-
// Genérico: número local → prepend country code
|
|
124
|
-
if (digits.length >= 7 && digits.length <= 11) return `+${cc}${digits}`;
|
|
125
|
-
|
|
126
|
-
// Fallback: assume que já tem algum cc
|
|
127
|
-
return `+${digits}`;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// ── Localização da nota interna ───────────────────────────────────────────────
|
|
131
|
-
|
|
132
|
-
type Locale = 'pt-BR' | 'en-US' | 'es-ES';
|
|
133
|
-
|
|
134
|
-
const NOTE_LABELS: Record<Locale, Record<string, string>> = {
|
|
135
|
-
'pt-BR': {
|
|
136
|
-
title: '📊 *Novo Lead*',
|
|
137
|
-
name: 'Nome',
|
|
138
|
-
source: 'Origem',
|
|
139
|
-
campaign: 'Campanha',
|
|
140
|
-
form: 'Formulário',
|
|
141
|
-
ad: 'Anúncio',
|
|
142
|
-
message: 'Mensagem',
|
|
143
|
-
score: 'Score',
|
|
144
|
-
ltv: 'LTV',
|
|
145
|
-
},
|
|
146
|
-
'en-US': {
|
|
147
|
-
title: '📊 *New Lead*',
|
|
148
|
-
name: 'Name',
|
|
149
|
-
source: 'Source',
|
|
150
|
-
campaign: 'Campaign',
|
|
151
|
-
form: 'Form',
|
|
152
|
-
ad: 'Ad',
|
|
153
|
-
message: 'Message',
|
|
154
|
-
score: 'Score',
|
|
155
|
-
ltv: 'LTV',
|
|
156
|
-
},
|
|
157
|
-
'es-ES': {
|
|
158
|
-
title: '📊 *Nuevo Lead*',
|
|
159
|
-
name: 'Nombre',
|
|
160
|
-
source: 'Origen',
|
|
161
|
-
campaign: 'Campaña',
|
|
162
|
-
form: 'Formulario',
|
|
163
|
-
ad: 'Anuncio',
|
|
164
|
-
message: 'Mensaje',
|
|
165
|
-
score: 'Score',
|
|
166
|
-
ltv: 'LTV',
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
function resolveLocale(input?: string | null): Locale {
|
|
171
|
-
if (input && input in NOTE_LABELS) return input as Locale;
|
|
172
|
-
return 'pt-BR';
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async function getAccessToken(env: Env): Promise<string | null> {
|
|
176
25
|
try {
|
|
177
|
-
|
|
26
|
+
await fetch(`${env.ZAPMAN_API_URL}/crm/leads`, {
|
|
178
27
|
method: 'POST',
|
|
179
|
-
headers: {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
client_secret: env.EVO_CRM_CLIENT_SECRET!,
|
|
184
|
-
}),
|
|
185
|
-
});
|
|
186
|
-
if (!res.ok) return null;
|
|
187
|
-
const data: any = await res.json();
|
|
188
|
-
return data.access_token ?? null;
|
|
189
|
-
} catch {
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/** Nota interna para o agente: apenas o essencial para atender o lead. */
|
|
195
|
-
function buildAgentNote(data: CrmLeadData, locale: Locale = 'pt-BR'): string {
|
|
196
|
-
const L = NOTE_LABELS[locale];
|
|
197
|
-
const lines: string[] = [L.title];
|
|
198
|
-
|
|
199
|
-
const name = data.name || data.phone;
|
|
200
|
-
lines.push(`• ${L.name}: ${name}`);
|
|
201
|
-
|
|
202
|
-
const source = [data.utmSource, data.utmMedium].filter(Boolean).join(' / ');
|
|
203
|
-
if (source) lines.push(`• ${L.source}: ${source}`);
|
|
204
|
-
|
|
205
|
-
if (data.utmCampaign) lines.push(`• ${L.campaign}: ${data.utmCampaign}`);
|
|
206
|
-
if (data.formName && data.formName !== data.eventName) lines.push(`• ${L.form}: ${data.formName}`);
|
|
207
|
-
if (data.headline) lines.push(`• ${L.ad}: ${data.headline}`);
|
|
208
|
-
if (data.messageBody) lines.push(`• ${L.message}: "${data.messageBody}"`);
|
|
209
|
-
|
|
210
|
-
if (data.intentScore != null) {
|
|
211
|
-
const score = data.intentScore > 1 ? data.intentScore : Math.round(data.intentScore * 100);
|
|
212
|
-
const stage = data.funnelStage ? ` · ${data.funnelStage}` : '';
|
|
213
|
-
lines.push(`• ${L.score}: ${score}${stage}`);
|
|
214
|
-
}
|
|
215
|
-
if (data.ltvClass) lines.push(`• ${L.ltv}: ${data.ltvClass}`);
|
|
216
|
-
|
|
217
|
-
return lines.join('\n');
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/** Monta additional_attributes para o contato no CRM. */
|
|
221
|
-
function buildContactAttributes(data: CrmLeadData): Record<string, string> {
|
|
222
|
-
const attrs: Record<string, string> = {};
|
|
223
|
-
|
|
224
|
-
if (data.utmSource) attrs['utm_source'] = data.utmSource;
|
|
225
|
-
if (data.utmMedium) attrs['utm_medium'] = data.utmMedium;
|
|
226
|
-
if (data.utmCampaign) attrs['utm_campaign'] = data.utmCampaign;
|
|
227
|
-
if (data.utmContent) attrs['utm_content'] = data.utmContent;
|
|
228
|
-
if (data.utmTerm) attrs['utm_term'] = data.utmTerm;
|
|
229
|
-
if (data.pageUrl) attrs['pagina'] = data.pageUrl;
|
|
230
|
-
if (data.formName) attrs['formulario'] = data.formName;
|
|
231
|
-
if (data.fbclid) attrs['fbclid'] = data.fbclid;
|
|
232
|
-
if (data.fbc) attrs['fbc'] = data.fbc;
|
|
233
|
-
if (data.fbp) attrs['fbp'] = data.fbp;
|
|
234
|
-
if (data.ctwaClid) attrs['ctwa_clid'] = data.ctwaClid;
|
|
235
|
-
if (data.adId) attrs['ad_id'] = data.adId;
|
|
236
|
-
if (data.messageBody) attrs['mensagem'] = data.messageBody;
|
|
237
|
-
if (data.headline) attrs['anuncio'] = data.headline;
|
|
238
|
-
if (data.ltvClass) attrs['ltv_class'] = data.ltvClass;
|
|
239
|
-
if (data.funnelStage) attrs['funil'] = data.funnelStage;
|
|
240
|
-
if (data.intentScore != null) attrs['intencao'] = String(data.intentScore);
|
|
241
|
-
if (data.eventName) attrs['evento'] = data.eventName;
|
|
242
|
-
|
|
243
|
-
// Atributos livres
|
|
244
|
-
if (data.attributes) Object.assign(attrs, data.attributes);
|
|
245
|
-
|
|
246
|
-
return attrs;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// ── API principal (recomendada) ───────────────────────────────────────────────
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Cria contato + conversa aberta + nota interna no EVO CRM.
|
|
253
|
-
* Silencioso se os secrets não estiverem configurados.
|
|
254
|
-
* Retorna o ID do contato criado ou null em caso de erro/duplicata.
|
|
255
|
-
*/
|
|
256
|
-
export async function pushLeadToCrm(env: Env, data: CrmLeadData): Promise<string | null> {
|
|
257
|
-
if (!isCrmConfigured(env)) return null;
|
|
258
|
-
|
|
259
|
-
const token = await getAccessToken(env);
|
|
260
|
-
if (!token) {
|
|
261
|
-
console.warn('[EVO CRM] Failed to get access token');
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const locale = resolveLocale(env.EVO_CRM_LOCALE);
|
|
266
|
-
const defaultCountry = env.EVO_CRM_DEFAULT_COUNTRY || '55';
|
|
267
|
-
|
|
268
|
-
const attrs = buildContactAttributes(data);
|
|
269
|
-
const contactPayload: Record<string, any> = {
|
|
270
|
-
name: data.name || data.phone,
|
|
271
|
-
phone_number: normalizePhone(data.phone, defaultCountry),
|
|
272
|
-
};
|
|
273
|
-
if (data.email) contactPayload.email = data.email;
|
|
274
|
-
if (Object.keys(attrs).length) contactPayload.additional_attributes = attrs;
|
|
275
|
-
|
|
276
|
-
// 1. Criar contato
|
|
277
|
-
let contactId: string | null = null;
|
|
278
|
-
try {
|
|
279
|
-
const res = await fetch(`${env.EVO_CRM_BASE_URL}/api/v1/contacts`, {
|
|
280
|
-
method: 'POST',
|
|
281
|
-
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
|
282
|
-
body: JSON.stringify(contactPayload),
|
|
283
|
-
});
|
|
284
|
-
if (!res.ok) {
|
|
285
|
-
const body = await res.text();
|
|
286
|
-
console.warn('[EVO CRM] createContact non-ok:', res.status, body.slice(0, 200));
|
|
287
|
-
// 422 = contato já existe — tentar extrair o ID do payload de erro
|
|
288
|
-
if (res.status === 422) {
|
|
289
|
-
try {
|
|
290
|
-
const errJson: any = JSON.parse(body);
|
|
291
|
-
const rawId = errJson?.id ?? errJson?.data?.id ?? errJson?.contact?.id ?? errJson?.data?.contact?.id;
|
|
292
|
-
if (rawId != null) contactId = String(rawId);
|
|
293
|
-
} catch { /* body não é JSON */ }
|
|
294
|
-
}
|
|
295
|
-
if (!contactId) return null;
|
|
296
|
-
} else {
|
|
297
|
-
const result: any = await res.json();
|
|
298
|
-
// EVO CRM pode retornar { id } direto ou { data: { id } } ou { data: { contact: { id } } }
|
|
299
|
-
const rawId = result?.id ?? result?.data?.id ?? result?.data?.contact?.id;
|
|
300
|
-
contactId = rawId != null ? String(rawId) : null;
|
|
301
|
-
}
|
|
302
|
-
} catch (err: any) {
|
|
303
|
-
console.warn('[EVO CRM] createContact failed:', err?.message || String(err));
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (!contactId || !env.EVO_CRM_INBOX_ID) return contactId;
|
|
308
|
-
|
|
309
|
-
// 2. Criar conversa
|
|
310
|
-
let conversationId: string | null = null;
|
|
311
|
-
try {
|
|
312
|
-
const res = await fetch(`${env.EVO_CRM_BASE_URL}/api/v1/conversations`, {
|
|
313
|
-
method: 'POST',
|
|
314
|
-
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
|
28
|
+
headers: {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
'X-API-Key': env.ZAPMAN_API_KEY,
|
|
31
|
+
},
|
|
315
32
|
body: JSON.stringify({
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
33
|
+
telefone: data.phone,
|
|
34
|
+
nome: data.name || data.phone,
|
|
35
|
+
email: data.email || '',
|
|
36
|
+
empresa: data.empresa || '',
|
|
37
|
+
campanha: data.campanha || '',
|
|
38
|
+
origem: data.origem || 'whatsapp',
|
|
39
|
+
instancia_id: env.ZAPMAN_CRM_INSTANCE || '',
|
|
319
40
|
}),
|
|
320
41
|
});
|
|
321
|
-
if (!res.ok) {
|
|
322
|
-
const err = await res.text();
|
|
323
|
-
console.warn('[EVO CRM] createConversation non-ok:', res.status, err.slice(0, 200));
|
|
324
|
-
} else {
|
|
325
|
-
const result: any = await res.json();
|
|
326
|
-
const rawConvId = result?.id ?? result?.data?.id;
|
|
327
|
-
conversationId = rawConvId != null ? String(rawConvId) : null;
|
|
328
|
-
}
|
|
329
42
|
} catch (err: any) {
|
|
330
|
-
console.
|
|
43
|
+
console.error('[ZapMan CRM] Erro ao criar lead:', err?.message || String(err));
|
|
331
44
|
}
|
|
332
|
-
|
|
333
|
-
// 3. Criar nota interna com contexto do lead
|
|
334
|
-
if (conversationId) {
|
|
335
|
-
try {
|
|
336
|
-
await fetch(`${env.EVO_CRM_BASE_URL}/api/v1/conversations/${conversationId}/messages`, {
|
|
337
|
-
method: 'POST',
|
|
338
|
-
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
|
339
|
-
body: JSON.stringify({
|
|
340
|
-
content: buildAgentNote(data, locale),
|
|
341
|
-
message_type: 'activity',
|
|
342
|
-
private: true,
|
|
343
|
-
}),
|
|
344
|
-
});
|
|
345
|
-
} catch {
|
|
346
|
-
// nota interna é best-effort
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return contactId;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// ── Compat: CTWA (WhatsApp click-to-chat) ────────────────────────────────────
|
|
354
|
-
|
|
355
|
-
export async function notifyEvolutionCTWA(env: Env, data: CTWALeadData): Promise<void> {
|
|
356
|
-
await pushLeadToCrm(env, {
|
|
357
|
-
phone: data.phone,
|
|
358
|
-
name: data.name,
|
|
359
|
-
messageBody: data.messageBody,
|
|
360
|
-
ctwaClid: data.ctwaClid,
|
|
361
|
-
adId: data.adId,
|
|
362
|
-
pageUrl: data.sourceUrl,
|
|
363
|
-
headline: data.headline,
|
|
364
|
-
utmSource: 'whatsapp',
|
|
365
|
-
eventName: 'CTWA',
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// ── Compat: Formulário ────────────────────────────────────────────────────────
|
|
370
|
-
|
|
371
|
-
export async function notifyEvolutionForm(env: Env, data: FormLeadData): Promise<void> {
|
|
372
|
-
await pushLeadToCrm(env, {
|
|
373
|
-
phone: data.phone,
|
|
374
|
-
name: data.name,
|
|
375
|
-
email: data.email,
|
|
376
|
-
formName: data.formName,
|
|
377
|
-
utmSource: data.utmSource,
|
|
378
|
-
utmCampaign: data.utmCampaign,
|
|
379
|
-
pageUrl: data.pageUrl,
|
|
380
|
-
eventName: 'Lead',
|
|
381
|
-
});
|
|
382
45
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CDP Edge — Meta Conversions API
|
|
2
|
+
* CDP Edge — Meta Conversions API v25.0
|
|
3
3
|
* Envia eventos server-side para a Meta CAPI.
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -104,7 +104,7 @@ export async function sendMetaCapi(env: Env, eventName: string, payload: TrackPa
|
|
|
104
104
|
logMatchQuality(env.DB, eventName, payload, recovered).catch(() => {});
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
const endpoint = `https://graph.facebook.com/
|
|
107
|
+
const endpoint = `https://graph.facebook.com/v25.0/${env.META_PIXEL_ID}/events`;
|
|
108
108
|
|
|
109
109
|
try {
|
|
110
110
|
const res = await fetch(endpoint, {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CDP Edge — WhatsApp Cloud API
|
|
2
|
+
* CDP Edge — WhatsApp Cloud API v25.0 + HMAC Verification
|
|
3
3
|
* sendWhatsApp, processWhatsAppWebhook, verifyHmac, sendCallMeBot
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@ import { sha256, normalizePhone } from '../utils.js';
|
|
|
7
7
|
import { saveLead, logApiFailure } from '../db.js';
|
|
8
8
|
import { Env, TrackPayload } from '../../types.js';
|
|
9
9
|
import { ExecutionContext } from '@cloudflare/workers-types';
|
|
10
|
-
import {
|
|
10
|
+
import { pushLeadToZapmanCrm } from './crm.js';
|
|
11
11
|
|
|
12
12
|
// ── Tipos ───────────────────────────────────────────────────────────────────────
|
|
13
13
|
interface WhatsAppOptions {
|
|
@@ -42,7 +42,7 @@ interface WhatsAppMessage {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// ── Resolvedores de secrets (canônico + legado) ────────────────────────────────
|
|
45
|
-
// Meta Cloud API
|
|
45
|
+
// Meta Cloud API v25.0 usa PHONE_NUMBER_ID e ACCESS_TOKEN como termos oficiais.
|
|
46
46
|
// Suportamos ambos os nomes para compatibilidade com secrets já configurados.
|
|
47
47
|
function resolvePhoneNumberId(env: Env): string | undefined {
|
|
48
48
|
return env.WHATSAPP_PHONE_NUMBER_ID || env.WA_PHONE_ID;
|
|
@@ -129,7 +129,7 @@ async function _sendWARequest(env: Env, body: Record<string, any>): Promise<any>
|
|
|
129
129
|
try {
|
|
130
130
|
const phoneNumberId = resolvePhoneNumberId(env);
|
|
131
131
|
const accessToken = resolveAccessToken(env);
|
|
132
|
-
const res = await fetch(`https://graph.facebook.com/
|
|
132
|
+
const res = await fetch(`https://graph.facebook.com/v25.0/${phoneNumberId}/messages`, {
|
|
133
133
|
method: 'POST',
|
|
134
134
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` },
|
|
135
135
|
body: JSON.stringify(body),
|
|
@@ -223,7 +223,7 @@ export async function processWhatsAppWebhook(env: Env, body: any, request: Reque
|
|
|
223
223
|
if (env.META_TEST_CODE) requestBody.test_event_code = env.META_TEST_CODE;
|
|
224
224
|
|
|
225
225
|
const res = await fetch(
|
|
226
|
-
`https://graph.facebook.com/
|
|
226
|
+
`https://graph.facebook.com/v25.0/${env.META_PIXEL_ID}/events`,
|
|
227
227
|
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestBody) }
|
|
228
228
|
);
|
|
229
229
|
const data = await res.json();
|
|
@@ -249,20 +249,9 @@ export async function processWhatsAppWebhook(env: Env, body: any, request: Reque
|
|
|
249
249
|
}, request, 'whatsapp')
|
|
250
250
|
);
|
|
251
251
|
|
|
252
|
-
// ──
|
|
253
|
-
// Cria/atualiza o contato no Evolution e abre a conversa para o vendedor.
|
|
254
|
-
// Silencioso se EVOLUTION_BASE_URL / EVOLUTION_INSTANCE / EVOLUTION_API_KEY
|
|
255
|
-
// não estiverem configurados.
|
|
252
|
+
// ── ZapMan CRM — cria lead direto no Kanban ──────────────────────────────
|
|
256
253
|
ctx.waitUntil(
|
|
257
|
-
|
|
258
|
-
phone: phoneNorm,
|
|
259
|
-
messageBody: messageBody || undefined,
|
|
260
|
-
ctwaClid,
|
|
261
|
-
adId,
|
|
262
|
-
sourceUrl,
|
|
263
|
-
headline,
|
|
264
|
-
wamid,
|
|
265
|
-
})
|
|
254
|
+
pushLeadToZapmanCrm(env, { phone: phoneNorm, name: undefined, origem: 'whatsapp' })
|
|
266
255
|
);
|
|
267
256
|
|
|
268
257
|
results.push({ ok: true, phone: phoneNorm.slice(0, 4) + '****', ctwa_clid: ctwaClid ? 'present' : 'absent', event_id: eventId });
|
|
@@ -94,7 +94,7 @@ export interface GoogleCustomerMatchExport {
|
|
|
94
94
|
|
|
95
95
|
// ── Versões esperadas das APIs ────────────────────────────────────────────────
|
|
96
96
|
const EXPECTED_API_VERSIONS: Record<string, string> = {
|
|
97
|
-
meta: '
|
|
97
|
+
meta: 'v25.0',
|
|
98
98
|
ga4: 'latest',
|
|
99
99
|
tiktok: 'v1.3',
|
|
100
100
|
pinterest: 'v5',
|
|
@@ -126,7 +126,7 @@ export async function checkApiVersionsIntelligence(
|
|
|
126
126
|
const results: ApiVersionCheck[] = [];
|
|
127
127
|
|
|
128
128
|
for (const [platform, expected] of Object.entries(EXPECTED_API_VERSIONS)) {
|
|
129
|
-
const currentMap: Record<string, string> = { meta: '
|
|
129
|
+
const currentMap: Record<string, string> = { meta: 'v25.0', tiktok: 'v1.3', ga4: 'latest', pinterest: 'v5', reddit: 'v2.0' };
|
|
130
130
|
const current = currentMap[platform] || 'unknown';
|
|
131
131
|
const isOk = current === expected || expected === 'latest';
|
|
132
132
|
const status = isOk ? 'ok' : 'warning';
|
|
@@ -436,7 +436,7 @@ export async function syncMetaCustomAudience(env: Env): Promise<CustomerMatchRes
|
|
|
436
436
|
);
|
|
437
437
|
|
|
438
438
|
const body = { payload: { schema: ['EMAIL_SHA256', 'PHONE_SHA256'], data } };
|
|
439
|
-
const endpoint = `https://graph.facebook.com/
|
|
439
|
+
const endpoint = `https://graph.facebook.com/v25.0/${env.META_AUDIENCE_ID}/users`;
|
|
440
440
|
|
|
441
441
|
const res = await fetch(endpoint, {
|
|
442
442
|
method: 'POST',
|
|
@@ -506,7 +506,7 @@ export async function syncMetaLookalikeSeed(env: Env): Promise<{
|
|
|
506
506
|
);
|
|
507
507
|
|
|
508
508
|
const body = { payload: { schema: ['EMAIL_SHA256', 'PHONE_SHA256'], data } };
|
|
509
|
-
const endpoint = `https://graph.facebook.com/
|
|
509
|
+
const endpoint = `https://graph.facebook.com/v25.0/${env.META_AUDIENCE_ID}/users`;
|
|
510
510
|
|
|
511
511
|
const res = await fetch(endpoint, {
|
|
512
512
|
method: 'POST',
|
|
@@ -201,7 +201,7 @@ export async function runNurtureQueue(env: Env): Promise<NurtureRunResult> {
|
|
|
201
201
|
const e164 = digits.startsWith('55') ? `+${digits}` : `+55${digits}`;
|
|
202
202
|
|
|
203
203
|
const res = await fetch(
|
|
204
|
-
`https://graph.facebook.com/
|
|
204
|
+
`https://graph.facebook.com/v25.0/${env.WHATSAPP_PHONE_NUMBER_ID}/messages`,
|
|
205
205
|
{
|
|
206
206
|
method: 'POST',
|
|
207
207
|
headers: { 'Authorization': `Bearer ${env.WHATSAPP_ACCESS_TOKEN}`, 'Content-Type': 'application/json' },
|
|
@@ -198,7 +198,7 @@ CREATE INDEX IF NOT EXISTS idx_intel_logs_status ON intelligence_logs(status)
|
|
|
198
198
|
CREATE INDEX IF NOT EXISTS idx_intel_logs_platform ON intelligence_logs(platform);
|
|
199
199
|
|
|
200
200
|
-- ── Meta Ads Dashboard — Tabelas (conectadas ao mesmo D1) ────────────────────
|
|
201
|
-
-- Alimentadas pelo sync diário da Meta Marketing API
|
|
201
|
+
-- Alimentadas pelo sync diário da Meta Marketing API v25.0
|
|
202
202
|
|
|
203
203
|
CREATE TABLE IF NOT EXISTS meta_account (
|
|
204
204
|
account_id TEXT PRIMARY KEY,
|
|
@@ -36,7 +36,7 @@ export interface Env {
|
|
|
36
36
|
GA4_API_SECRET?: string;
|
|
37
37
|
TIKTOK_ACCESS_TOKEN?: string;
|
|
38
38
|
WA_WEBHOOK_VERIFY_TOKEN?: string;
|
|
39
|
-
// WhatsApp Cloud API — nomes canônicos (Meta Cloud API
|
|
39
|
+
// WhatsApp Cloud API — nomes canônicos (Meta Cloud API v25.0)
|
|
40
40
|
WHATSAPP_ACCESS_TOKEN?: string; // canonical: Bearer token do System User
|
|
41
41
|
WHATSAPP_PHONE_NUMBER_ID?: string; // canonical: ID numérico do número no Meta Business
|
|
42
42
|
// WhatsApp Cloud API — nomes legados (backwards compat)
|
|
@@ -65,13 +65,11 @@ export interface Env {
|
|
|
65
65
|
RESEND_FROM_EMAIL?: string;
|
|
66
66
|
CALLMEBOT_APIKEY?: string;
|
|
67
67
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
EVO_CRM_DEFAULT_COUNTRY?: string; // Country dial code para phones locais (default "55" = Brasil)
|
|
74
|
-
EVO_CRM_LOCALE?: string; // Locale da nota interna: "pt-BR" | "en-US" | "es-ES" (default "pt-BR")
|
|
68
|
+
// ZapMan SDR — forward WhatsApp webhooks para qualificação de leads
|
|
69
|
+
ZAPMAN_WEBHOOK_URL?: string; // URL completa: https://zapman-api.arkitekt.space/webhook/{instance_id}?token={secret}
|
|
70
|
+
ZAPMAN_API_URL?: string; // Base URL do ZapMan: https://zapman-api.arkitekt.space
|
|
71
|
+
ZAPMAN_API_KEY?: string; // DASHBOARD_SECRET do ZapMan (X-API-Key)
|
|
72
|
+
ZAPMAN_CRM_INSTANCE?: string; // ID da instância ZapMan para associar o lead (ex: Ramon-SDR)
|
|
75
73
|
|
|
76
74
|
// Fraud Gate — defaults parametrizáveis por projeto (vazio = comportamento legado)
|
|
77
75
|
ALLOWED_COUNTRIES?: string; // CSV ISO-2, ex: "BR" ou "BR,PT,AO". Vazio = sem geo-fence
|