cdp-edge 1.18.0 → 1.18.2

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.
Files changed (34) hide show
  1. package/contracts/api-versions.json +12 -8
  2. package/dist/commands/install.js +186 -0
  3. package/dist/commands/setup.js +18 -1
  4. package/extracted-skill/tracking-events-generator/agents/attribution-agent.md +23 -23
  5. package/extracted-skill/tracking-events-generator/agents/browser-tracking.md +172 -72
  6. package/extracted-skill/tracking-events-generator/agents/compliance-agent.md +20 -0
  7. package/extracted-skill/tracking-events-generator/agents/crm-integration-agent.md +48 -16
  8. package/extracted-skill/tracking-events-generator/agents/dashboard-agent.md +7 -7
  9. package/extracted-skill/tracking-events-generator/agents/database-agent.md +8 -8
  10. package/extracted-skill/tracking-events-generator/agents/debug-agent.md +13 -13
  11. package/extracted-skill/tracking-events-generator/agents/devops-agent.md +31 -7
  12. package/extracted-skill/tracking-events-generator/agents/email-agent.md +27 -0
  13. package/extracted-skill/tracking-events-generator/agents/fingerprint-agent.md +205 -0
  14. package/extracted-skill/tracking-events-generator/agents/google-agent.md +118 -0
  15. package/extracted-skill/tracking-events-generator/agents/intelligence-agent.md +90 -4
  16. package/extracted-skill/tracking-events-generator/agents/intelligence-scheduling.md +8 -641
  17. package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +108 -0
  18. package/extracted-skill/tracking-events-generator/agents/ltv-predictor-agent.md +1 -1
  19. package/extracted-skill/tracking-events-generator/agents/master-feedback-loop.md +68 -8
  20. package/extracted-skill/tracking-events-generator/agents/master-orchestrator.md +71 -34
  21. package/extracted-skill/tracking-events-generator/agents/memory-agent.md +98 -0
  22. package/extracted-skill/tracking-events-generator/agents/performance-agent.md +29 -19
  23. package/extracted-skill/tracking-events-generator/agents/performance-optimization-agent.md +11 -1
  24. package/extracted-skill/tracking-events-generator/agents/security-enterprise-agent.md +137 -28
  25. package/extracted-skill/tracking-events-generator/agents/server-tracking.md +7 -8
  26. package/extracted-skill/tracking-events-generator/agents/tiktok-agent.md +63 -0
  27. package/extracted-skill/tracking-events-generator/agents/tracking-plan-agent.md +100 -5
  28. package/extracted-skill/tracking-events-generator/agents/webhook-agent.md +100 -0
  29. package/extracted-skill/tracking-events-generator/agents/whatsapp-agent.md +58 -5
  30. package/extracted-skill/tracking-events-generator/agents/whatsapp-ctwa-setup-agent.md +16 -16
  31. package/extracted-skill/tracking-events-generator/agents/youtube-agent.md +140 -25
  32. package/extracted-skill/tracking-events-generator/contracts/api-versions.json +12 -8
  33. package/package.json +2 -2
  34. package/server-edge-tracker/worker.js +53 -8
@@ -59,19 +59,30 @@ function validateEventCoverage(pageAnalysis, agentOutputs) {
59
59
  });
60
60
  });
61
61
 
62
- // Encontrar eventos faltantes (no plano mas implementados)
63
- const missingEvents = [];
62
+ // Encontrar eventos do Page Analyzer NÃO implementados por nenhum agente
63
+ const unimplementedEvents = [];
64
+ pageEvents.forEach(eventKey => {
65
+ if (!agentEvents.has(eventKey)) {
66
+ unimplementedEvents.push(eventKey);
67
+ }
68
+ });
69
+
70
+ // Encontrar eventos implementados pelos agentes mas SEM correspondência no Page Analyzer
71
+ const orphanEvents = [];
64
72
  agentEvents.forEach(eventKey => {
65
73
  if (!pageEvents.has(eventKey)) {
66
- missingEvents.push(eventKey);
74
+ orphanEvents.push(eventKey);
67
75
  }
68
76
  });
69
77
 
70
78
  return {
71
79
  total_page_events: pageEvents.size,
72
80
  total_implemented_events: agentEvents.size,
73
- missing_events,
74
- coverage_percentage: Math.round((agentEvents.size / pageEvents.size) * 100)
81
+ unimplemented_events: unimplementedEvents, // ← Eventos do plano sem código
82
+ orphan_events: orphanEvents, // Código sem evento no plano
83
+ coverage_percentage: Math.round(
84
+ ((pageEvents.size - unimplementedEvents.length) / Math.max(pageEvents.size, 1)) * 100
85
+ )
75
86
  };
76
87
  }
77
88
  ```
@@ -187,6 +198,90 @@ async function validateApiVersions(trackingPlan) {
187
198
  }
188
199
  ```
189
200
 
201
+ ### 1.5 Validação Cruzada Completa (runFullValidation)
202
+
203
+ ```javascript
204
+ /**
205
+ * Ponto de entrada principal — executa TODAS as validações em sequência
206
+ * e retorna um relatório consolidado com status PASS | WARN | BLOCK
207
+ *
208
+ * @param {Object} pageAnalysis - Output do Page Analyzer Agent
209
+ * @param {Object} agentOutputs - Código gerado por todos os agentes
210
+ * @param {Object} apiVersions - Conteúdo de contracts/api-versions.json
211
+ * @returns {Object} Relatório consolidado de validação
212
+ */
213
+ async function runFullValidation(pageAnalysis, agentOutputs, apiVersions) {
214
+ const report = {
215
+ status: 'PASS', // PASS | WARN | BLOCK
216
+ timestamp: new Date().toISOString(),
217
+ checks: {}
218
+ };
219
+
220
+ // CHECK 1: Cobertura de eventos
221
+ const coverage = validateEventCoverage(pageAnalysis, agentOutputs);
222
+ report.checks.event_coverage = coverage;
223
+ if (coverage.coverage_percentage < 100) {
224
+ report.status = coverage.coverage_percentage < 80 ? 'BLOCK' : 'WARN';
225
+ }
226
+
227
+ // CHECK 2: Parâmetros de conversão
228
+ const allEvents = Object.values(agentOutputs).flatMap(o => o.events || []);
229
+ const paramIssues = validateConversionParameters(allEvents, apiVersions);
230
+ report.checks.conversion_params = paramIssues;
231
+ if (paramIssues.filter(i => i.severity === 'HIGH').length > 0) {
232
+ report.status = 'BLOCK';
233
+ }
234
+
235
+ // CHECK 3: Seletores existentes no código
236
+ const missingSelectors = validateSelectorsExist({}, pageAnalysis);
237
+ report.checks.selectors = { missing: missingSelectors };
238
+ if (missingSelectors.length > 0) {
239
+ if (report.status === 'PASS') report.status = 'WARN';
240
+ }
241
+
242
+ // CHECK 4: Versões de API consistentes com api-versions.json
243
+ const trackingPlan = { events: {} };
244
+ Object.entries(agentOutputs).forEach(([agent, output]) => {
245
+ if (output.events) trackingPlan.events[agent] = output.events;
246
+ });
247
+ await validateApiVersions(trackingPlan);
248
+ const apiIssues = Object.values(trackingPlan.events)
249
+ .flatMap(events => events.filter(e => e.api_version_issue || e.api_deprecated));
250
+ report.checks.api_versions = apiIssues;
251
+ if (apiIssues.some(e => e.api_deprecated)) {
252
+ report.status = 'BLOCK'; // API depreciada = bloquear deploy
253
+ }
254
+
255
+ // CHECK 5: Regras de ouro — deduplicação event_id
256
+ const missingEventId = allEvents.filter(e => !e.event_id && !e.params?.event_id);
257
+ report.checks.deduplication = {
258
+ events_missing_event_id: missingEventId.map(e => `${e.platform}:${e.name}`)
259
+ };
260
+ if (missingEventId.length > 0) report.status = 'WARN';
261
+
262
+ // CHECK 6: SHA-256 em campos PII
263
+ const piiFields = ['em', 'ph', 'fn', 'ln'];
264
+ const unhashed = allEvents.filter(e =>
265
+ piiFields.some(field => e.user_data?.[field] && !e.user_data[field].match(/^[a-f0-9]{64}$/))
266
+ );
267
+ report.checks.pii_hashing = {
268
+ events_with_unhashed_pii: unhashed.map(e => `${e.platform}:${e.name}`)
269
+ };
270
+ if (unhashed.length > 0) {
271
+ report.status = 'BLOCK'; // PII sem hash = bloquear imediatamente
272
+ }
273
+
274
+ // Determinar mensagem de status
275
+ report.summary = {
276
+ PASS: '✅ Tracking Plan validado — pode fazer deploy',
277
+ WARN: '⚠️ Tracking Plan com alertas — revisar antes do deploy',
278
+ BLOCK: '❌ BLOQUEADO — corrigir itens críticos antes do deploy'
279
+ }[report.status];
280
+
281
+ return report;
282
+ }
283
+ ```
284
+
190
285
  ---
191
286
 
192
287
  ## PASSO 2 — GERAR O TRACKING PLAN COM VALIDAÇÃO
@@ -12,6 +12,48 @@ Você é o especialista em Webhooks do CDP Edge. Sua missão é capturar vendas
12
12
 
13
13
  ---
14
14
 
15
+ ## 🔐 NORMALIZAÇÃO E HASHING DE PII (OBRIGATÓRIO)
16
+
17
+ Antes de qualquer dispatch para CAPI, normalizar e hashear PII extraída do webhook:
18
+
19
+ ```javascript
20
+ // Hashing SHA-256 para PII — usar WebCrypto (disponível em Cloudflare Workers)
21
+ async function hashPII(value) {
22
+ if (!value) return null;
23
+ const normalized = value.toString().toLowerCase().trim();
24
+ const encoder = new TextEncoder();
25
+ const data = encoder.encode(normalized);
26
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
27
+ return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('');
28
+ }
29
+
30
+ // Normalização E.164 para telefone (Brasil)
31
+ function normalizePhone(phone) {
32
+ if (!phone) return null;
33
+ const digits = phone.replace(/\D/g, '');
34
+ // Adicionar +55 se não tiver código de país
35
+ if (digits.length === 10 || digits.length === 11) return `+55${digits}`;
36
+ if (digits.startsWith('55') && (digits.length === 12 || digits.length === 13)) return `+${digits}`;
37
+ return `+${digits}`;
38
+ }
39
+
40
+ // Exemplo de uso no handler de webhook:
41
+ async function hashWebhookUserData(webhookPayload) {
42
+ const email = webhookPayload.buyer?.email || webhookPayload.email;
43
+ const phone = webhookPayload.buyer?.phone || webhookPayload.phone;
44
+ return {
45
+ em: email ? await hashPII(email) : null, // SHA-256 lowercase+trim
46
+ ph: phone ? await hashPII(normalizePhone(phone)) : null, // SHA-256 após E.164
47
+ fn: webhookPayload.buyer?.first_name ? await hashPII(webhookPayload.buyer.first_name) : null,
48
+ ln: webhookPayload.buyer?.last_name ? await hashPII(webhookPayload.buyer.last_name) : null,
49
+ };
50
+ }
51
+ ```
52
+
53
+ > **Regra:** NUNCA enviar email ou telefone em plaintext para Meta CAPI, GA4 MP ou TikTok Events API. Sempre normalizar → hashear → enviar.
54
+
55
+ ---
56
+
15
57
  ## 🏗️ PADRÕES TÉCNICOS (Quantum Tier)
16
58
 
17
59
  1. **D1 Identity Cross-Check**: Utilize o e-mail ou telefone do webhook para buscar no banco **D1** os identificadores originais (`fbp`, `fbc`, `ttp`). Isso garante a precisão da atribuição.
@@ -29,6 +71,64 @@ Você é o especialista em Webhooks do CDP Edge. Sua missão é capturar vendas
29
71
 
30
72
  ---
31
73
 
74
+ ## 🔗 INTEGRAÇÃO COM OUTROS AGENTES (Fluxo Pós-Compra)
75
+
76
+ Após processar um webhook de compra com sucesso, o Webhook Agent DEVE disparar:
77
+
78
+ ```
79
+ Webhook (Hotmart/Kiwify/Ticto/Stripe)
80
+
81
+ ├─► [1] Validar HMAC → rejeitar 401 se inválido
82
+ ├─► [2] Dedup D1 por transaction_id
83
+ ├─► [3] Cross-check D1 por email → fbp/fbc/ttp/gclid
84
+ ├─► [4] Hashear PII (SHA-256) — ver seção acima
85
+
86
+ ├─► [5] CAPI Dispatch (ctx.waitUntil) — paralelo:
87
+ │ → Meta CAPI v22.0 (Purchase)
88
+ │ → GA4 MP (purchase)
89
+ │ → TikTok Events API v1.3 (CompletePayment)
90
+
91
+ ├─► [6] Email Agent (ctx.waitUntil) — enviar confirmação de compra:
92
+ │ → Chamar sendEmail(env, 'purchase_confirmation', { email, nome, produto, valor })
93
+ │ → Ver email-agent.md para implementação completa
94
+
95
+ └─► [7] CRM Sync (ctx.waitUntil) — sincronizar comprador:
96
+ → Chamar syncToCRM(env, 'purchase', { email, nome, produto, valor, order_id })
97
+ → Ver crm-integration-agent.md para implementação completa
98
+ ```
99
+
100
+ ### Código de Integração (webhook handler)
101
+
102
+ ```javascript
103
+ // No handler de webhook, após validação e dedup:
104
+ ctx.waitUntil(Promise.allSettled([
105
+ // [5] CAPI dispatch
106
+ dispatchToCAPI(env, hashedUserData, purchaseData),
107
+
108
+ // [6] Email Agent — confirmação de compra
109
+ sendEmail(env, 'purchase_confirmation', {
110
+ to: buyerEmail,
111
+ name: buyerName,
112
+ product: productName,
113
+ value: purchaseValue
114
+ }),
115
+
116
+ // [7] CRM Sync — criar/atualizar contato como comprador
117
+ syncToCRM(env, 'purchase', {
118
+ email: buyerEmail,
119
+ name: buyerName,
120
+ product: productName,
121
+ value: purchaseValue,
122
+ order_id: transactionId
123
+ })
124
+ ]));
125
+ ```
126
+
127
+ > **Nota:** `sendEmail()` é implementada pelo Email Agent em `cloudflare/email-service.js`.
128
+ > `syncToCRM()` é implementada pelo CRM Integration Agent em `cloudflare/crm-service.js`.
129
+
130
+ ---
131
+
32
132
  ## INPUTS RECEBIDOS
33
133
 
34
134
  - Payload JSON do webhook da plataforma de vendas (Hotmart, Kiwify, Ticto, Stripe)
@@ -9,8 +9,8 @@ Você é o **Especialista em Mensageria WhatsApp (Quantum Tier)** do CDP Edge. S
9
9
  1. **Meta Cloud API v22.0 (Eixo Vendas/Notificações ao dono)**:
10
10
  - **Público**: O dono do sistema — notificações de Nova Venda e Novo Lead em tempo real.
11
11
  - **Objetivo**: Avisar o dono quando chegar uma venda ou lead via webhook.
12
- - **Padrão**: API oficial Meta v22.0 — `POST /v22.0/{WA_PHONE_ID}/messages`.
13
- - **Secrets**: `WA_PHONE_ID`, `WA_ACCESS_TOKEN`, `WA_NOTIFY_NUMBER`.
12
+ - **Padrão**: API oficial Meta v22.0 — `POST /v22.0/{WHATSAPP_PHONE_NUMBER_ID}/messages`.
13
+ - **Secrets**: `WHATSAPP_PHONE_NUMBER_ID`, `WHATSAPP_ACCESS_TOKEN`, `WA_NOTIFY_NUMBER`.
14
14
  2. **CallMeBot (Eixo Guardião/Alertas de Sistema)**:
15
15
  - **Público**: O dono do sistema (admin).
16
16
  - **Objetivo**: Alertas internos do Cloudflare — Worker com erro, API falhando, token expirado, D1 com problema. **NÃO usado para mensagens a clientes.**
@@ -29,11 +29,64 @@ Sempre que o usuário desejar robustez na mensageria:
29
29
 
30
30
  ---
31
31
 
32
+ ## 🔗 INTEGRAÇÃO COM WHATSAPP CTWA SETUP AGENT
33
+
34
+ O **WhatsApp CTWA Setup Agent** (`whatsapp-ctwa-setup-agent.md`) é o parceiro deste agente para rastreamento de anúncios Click-to-WhatsApp. A divisão de responsabilidades é clara:
35
+
36
+ | Este agente (whatsapp-agent) | CTWA Setup Agent |
37
+ |---|---|
38
+ | Notificações de venda/lead ao dono (Meta API) | Webhook `/webhook/whatsapp` — recebe mensagens da Meta |
39
+ | Alertas de sistema ao admin (CallMeBot) | Extração de `ctwa_clid` e disparo à Meta CAPI |
40
+ | Tracking de clique em botão WhatsApp (browser) | Setup do webhook de verificação (GET + POST) |
41
+
42
+ ### Fluxo CTWA → Meta CAPI (implementado pelo CTWA Setup Agent)
43
+
44
+ ```
45
+ Usuário clica anúncio CTWA no Facebook/Instagram
46
+
47
+ WhatsApp abre com mensagem pré-preenchida
48
+
49
+ Meta faz POST ao Worker: /webhook/whatsapp
50
+ ↓ (CTWA Setup Agent processa)
51
+ Worker extrai ctwa_clid + phone do payload
52
+
53
+ Worker envia evento 'Contact' à Meta CAPI:
54
+ {
55
+ event_name: 'Contact',
56
+ action_source: 'chat', ← obrigatório para CTWA
57
+ event_source_url: ad_source_url,
58
+ user_data: { ph: sha256(phone) },
59
+ custom_data: { ctwa_clid: '...' } ← identifica o anúncio
60
+ }
61
+
62
+ Worker salva no D1: tabela whatsapp_contacts
63
+
64
+ WhatsApp Agent dispara notificação ao dono via Meta Cloud API v22.0
65
+ ```
66
+
67
+ ### O que este agente gera para o fluxo CTWA
68
+
69
+ ```javascript
70
+ // sendWhatsApp() — notificação ao dono quando chega lead CTWA
71
+ async function notifyOwnerNewCtwaLead(env, contactData) {
72
+ const message = `📲 Novo Lead CTWA!\n\nNome: ${contactData.name || 'Desconhecido'}\nTelefone: ${contactData.phone}\nAnúncio: ${contactData.headline || '-'}\nMensagem: "${contactData.messageBody?.slice(0, 80) || '-'}"`;
73
+
74
+ await sendWhatsApp(env, 'system', {
75
+ to: env.WA_NOTIFY_NUMBER,
76
+ message
77
+ });
78
+ }
79
+ ```
80
+
81
+ > **Regra de coordenação:** O CTWA Setup Agent processa o webhook e extrai ctwa_clid. Após salvar no D1 com `capi_sent = 0`, ele chama `notifyOwnerNewCtwaLead()` deste agente para notificar o dono. Manter esta divisão — nunca misturar as responsabilidades.
82
+
83
+ ---
84
+
32
85
  ## INPUTS RECEBIDOS
33
86
 
34
87
  - Tipo de disparo: `Purchase` | `Lead` (Meta Cloud API) ou falha de sistema (CallMeBot)
35
88
  - Dados do comprador/lead: `name`, `email`, `phone`, `product_name`, `value` (via webhook ou D1)
36
- - Secrets Meta: `WA_PHONE_ID`, `WA_ACCESS_TOKEN`, `WA_NOTIFY_NUMBER`
89
+ - Secrets Meta: `WHATSAPP_PHONE_NUMBER_ID`, `WHATSAPP_ACCESS_TOKEN`, `WA_NOTIFY_NUMBER`
37
90
  - Secrets CallMeBot: `CALLMEBOT_PHONE`, `CALLMEBOT_APIKEY`
38
91
  - Contexto de erro (para alertas CallMeBot): plataforma afetada, mensagem de erro, timestamp
39
92
 
@@ -58,9 +111,9 @@ Sempre que o usuário desejar robustez na mensageria:
58
111
  "eixos": {
59
112
  "notificacoes": {
60
113
  "api": "Meta Cloud API v22.0",
61
- "endpoint": "POST /v22.0/{WA_PHONE_ID}/messages",
114
+ "endpoint": "POST /v22.0/{WHATSAPP_PHONE_NUMBER_ID}/messages",
62
115
  "tipos": ["Purchase", "Lead"],
63
- "secrets": ["WA_PHONE_ID", "WA_ACCESS_TOKEN", "WA_NOTIFY_NUMBER"]
116
+ "secrets": ["WHATSAPP_PHONE_NUMBER_ID", "WHATSAPP_ACCESS_TOKEN", "WA_NOTIFY_NUMBER"]
64
117
  },
65
118
  "alertas_sistema": {
66
119
  "api": "CallMeBot",
@@ -7,7 +7,7 @@ Sua missão: configurar o rastreamento completo de anúncios Click to WhatsApp d
7
7
 
8
8
  ## ✅ REGRAS CRÍTICAS
9
9
 
10
- 0. **CONSULTA OBRIGATÓRIA À MEMÓRIA**: Extraia o ID de Número WhatsApp, Token de API e Token de Verificação (`WA_PHONE_NUMBER_ID`, `WA_TOKEN`, `WA_WEBHOOK_VERIFY_TOKEN`) consultando ativamente o "memory-agent.json". Solicite ao Orquestrador tudo o que faltar. Execute configurações de WhatsApp exclusivamente com os dados oficiais guardados na Memória para garantir alinhamento sistêmico.
10
+ 0. **CONSULTA OBRIGATÓRIA À MEMÓRIA**: Extraia o ID de Número WhatsApp, Token de API e Token de Verificação (`WHATSAPP_PHONE_NUMBER_ID`, `WHATSAPP_ACCESS_TOKEN`, `WA_WEBHOOK_VERIFY_TOKEN`) consultando ativamente o "memory-agent.json". Solicite ao Orquestrador tudo o que faltar. Execute configurações de WhatsApp exclusivamente com os dados oficiais guardados na Memória para garantir alinhamento sistêmico.
11
11
  1. Cloudflare-Only: Sem dependências externas.
12
12
  2. Same-Domain: Worker no domínio do site (anti-adblock).
13
13
 
@@ -172,8 +172,8 @@ message_body | Olá, vi o anúncio e tenho interesse
172
172
  [ ] META_APP_ID — ID do app no Meta for Developers ← usuário fornece
173
173
  [ ] META_APP_SECRET — App Secret (developers.facebook.com → Básico) ← usuário fornece
174
174
  [ ] WA_WEBHOOK_VERIFY_TOKEN — gerado pelo agente (crypto.randomUUID) ← agente cria
175
- [ ] WA_PHONE_ID — descoberto automaticamente via API ← agente descobre
176
- [ ] WA_ACCESS_TOKEN — mesmo que META_ACCESS_TOKEN ← agente reutiliza
175
+ [ ] WHATSAPP_PHONE_NUMBER_ID — descoberto automaticamente via API ← agente descobre
176
+ [ ] WHATSAPP_ACCESS_TOKEN — mesmo que META_ACCESS_TOKEN ← agente reutiliza
177
177
  [ ] WHATSAPP_BUSINESS_ACCOUNT_ID — descoberto automaticamente via API ← agente descobre
178
178
  ```
179
179
 
@@ -498,16 +498,16 @@ Deve mostrar o app com `override_callback_uri` (se não-SMB) ou apenas o app sub
498
498
 
499
499
  ```bash
500
500
  echo "{WABA_ID}" | wrangler secret put WHATSAPP_BUSINESS_ACCOUNT_ID
501
- echo "{PHONE_ID}" | wrangler secret put WA_PHONE_ID
502
- echo "{META_ACCESS_TOKEN}" | wrangler secret put WA_ACCESS_TOKEN
501
+ echo "{PHONE_ID}" | wrangler secret put WHATSAPP_PHONE_NUMBER_ID
502
+ echo "{META_ACCESS_TOKEN}" | wrangler secret put WHATSAPP_ACCESS_TOKEN
503
503
  echo "{META_APP_SECRET}" | wrangler secret put META_APP_SECRET
504
504
  echo "{META_ACCESS_TOKEN}" | wrangler secret put META_ACCESS_TOKEN
505
505
 
506
506
  # ── Secrets para Auto-Resposta WhatsApp (enviar mensagens de saída) ─────────
507
507
  # Necessários para: worker.js → auto-resposta após Lead/Purchase
508
- # WHATSAPP_TOKEN = mesmo token Meta (Cloud API) — pode reutilizar META_ACCESS_TOKEN
508
+ # WHATSAPP_ACCESS_TOKEN = mesmo token Meta (Cloud API) — pode reutilizar META_ACCESS_TOKEN
509
509
  # WHATSAPP_PHONE_NUMBER_ID = mesmo {PHONE_ID} descoberto acima
510
- echo "{META_ACCESS_TOKEN}" | wrangler secret put WHATSAPP_TOKEN
510
+ echo "{META_ACCESS_TOKEN}" | wrangler secret put WHATSAPP_ACCESS_TOKEN
511
511
  echo "{PHONE_ID}" | wrangler secret put WHATSAPP_PHONE_NUMBER_ID
512
512
 
513
513
  # Confirmar todos
@@ -518,15 +518,15 @@ Secrets esperados no worker ao final:
518
518
  ```
519
519
  META_ACCESS_TOKEN
520
520
  META_APP_SECRET
521
- WA_ACCESS_TOKEN
522
- WA_PHONE_ID
521
+ WHATSAPP_ACCESS_TOKEN
522
+ WHATSAPP_PHONE_NUMBER_ID
523
523
  WA_WEBHOOK_VERIFY_TOKEN
524
524
  WHATSAPP_BUSINESS_ACCOUNT_ID
525
- WHATSAPP_TOKEN ← auto-resposta outbound (mesmo valor de META_ACCESS_TOKEN)
526
- WHATSAPP_PHONE_NUMBER_ID ← auto-resposta outbound (mesmo valor de WA_PHONE_ID)
525
+ WHATSAPP_ACCESS_TOKEN ← auto-resposta outbound (mesmo valor de META_ACCESS_TOKEN)
526
+ WHATSAPP_PHONE_NUMBER_ID ← auto-resposta outbound (mesmo valor de WHATSAPP_PHONE_NUMBER_ID)
527
527
  ```
528
528
 
529
- > **Nota:** `WHATSAPP_TOKEN` e `WHATSAPP_PHONE_NUMBER_ID` são usados pela função de auto-resposta no worker (envio de mensagens de saída para o lead após eventos de conversão). São o mesmo token/phone_id da CTWA — apenas referenciados por nomes distintos no código.
529
+ > **Nota:** `WHATSAPP_ACCESS_TOKEN` e `WHATSAPP_PHONE_NUMBER_ID` são usados pela função de auto-resposta no worker (envio de mensagens de saída para o lead após eventos de conversão). São o mesmo token/phone_id da CTWA — apenas referenciados por nomes distintos no código.
530
530
 
531
531
  ---
532
532
 
@@ -611,13 +611,13 @@ App: {META_APP_ID} — subscriptions: messages ✅
611
611
 
612
612
  SECRETS NO WORKER:
613
613
  ✅ META_ACCESS_TOKEN
614
- WA_ACCESS_TOKEN
615
- WA_PHONE_ID → {PHONE_ID}
614
+ WHATSAPP_ACCESS_TOKEN
615
+ WHATSAPP_PHONE_NUMBER_ID → {PHONE_ID}
616
616
  ✅ WA_WEBHOOK_VERIFY_TOKEN
617
617
  ✅ WHATSAPP_BUSINESS_ACCOUNT_ID → {WABA_ID}
618
618
  ✅ META_APP_SECRET
619
- WHATSAPP_TOKEN → mesmo valor de META_ACCESS_TOKEN (auto-resposta)
620
- ✅ WHATSAPP_PHONE_NUMBER_ID → mesmo valor de WA_PHONE_ID (auto-resposta)
619
+ WHATSAPP_ACCESS_TOKEN → mesmo valor de META_ACCESS_TOKEN (auto-resposta)
620
+ ✅ WHATSAPP_PHONE_NUMBER_ID → mesmo valor de WHATSAPP_PHONE_NUMBER_ID (auto-resposta)
621
621
 
622
622
  TESTE E2E:
623
623
  ✅ GET verificação → challenge retornado
@@ -89,39 +89,152 @@ const _gbraid = _urlParams.get('gbraid') || ''; // App campaigns (privacy)
89
89
  **ATENÇÃO wbraid/gbraid**: São os click IDs para campanhas YouTube em iOS (pós ATT).
90
90
  Nunca hashear — enviar como texto plano para Google Ads API.
91
91
 
92
- ### 2. Rastreamento de vídeo YouTube na página
92
+ ### 2. Rastreamento de vídeo YouTube na página — YouTube IFrame API
93
93
 
94
- O `behavior-engine.js` já implementa rastreamento de vídeos via YouTube IFrame API.
95
94
  Para usar, o iframe deve ter `enablejsapi=1`:
96
95
 
97
96
  ```html
98
- <!-- Embed YouTube com JS API habilitada -->
97
+ <!-- Embed YouTube com JS API habilitada (obrigatório) -->
99
98
  <iframe
100
99
  id="video-tour-imovel"
101
- src="https://www.youtube.com/embed/VIDEO_ID?enablejsapi=1"
100
+ src="https://www.youtube.com/embed/VIDEO_ID?enablejsapi=1&origin=https://seudominio.com.br"
102
101
  allow="autoplay"
103
102
  ></iframe>
104
103
  ```
105
104
 
106
- O BehaviorEngine detecta automaticamente e dispara via `cdpTrack.track()`:
107
- - `video_milestone` com `percent: 25, 50, 75, 100`
108
- - Score: +10 (25%), +15 (50%), +25 (75%/100%)
105
+ #### Implementação real do YouTube IFrame API listener
106
+
107
+ ```javascript
108
+ /**
109
+ * YouTube IFrame API Listener — injeta no behavior-engine.js ou tracking.js
110
+ * Rastreia: video_start, video_25, video_50, video_75, video_complete
111
+ * Dispara via cdpTrack.track() para o Worker → GA4 MP + demais plataformas
112
+ */
113
+
114
+ // Carregar YouTube IFrame API (uma vez por página)
115
+ (function initYouTubeTracking() {
116
+ if (window._ytTrackingInitialized) return;
117
+ window._ytTrackingInitialized = true;
118
+
119
+ // Mapa de iframes já trackeados
120
+ const trackedPlayers = new Map();
121
+
122
+ // Injetar API script do YouTube (não carrega 2x se já existe)
123
+ if (!document.getElementById('youtube-iframe-api')) {
124
+ const tag = document.createElement('script');
125
+ tag.id = 'youtube-iframe-api';
126
+ tag.src = 'https://www.youtube.com/iframe_api';
127
+ document.head.appendChild(tag);
128
+ }
129
+
130
+ // Callback global chamado pelo YouTube quando API estiver pronta
131
+ window.onYouTubeIframeAPIReady = function() {
132
+ // Auto-detectar todos os iframes com enablejsapi=1
133
+ document.querySelectorAll('iframe[src*="youtube.com/embed"]').forEach(iframe => {
134
+ if (trackedPlayers.has(iframe.id)) return;
135
+
136
+ const videoTitle = iframe.title || iframe.id || 'YouTube Video';
137
+
138
+ const player = new YT.Player(iframe.id, {
139
+ events: {
140
+ onStateChange: (event) => handlePlayerStateChange(event, player, videoTitle),
141
+ onReady: (event) => handlePlayerReady(event, player, videoTitle)
142
+ }
143
+ });
144
+
145
+ trackedPlayers.set(iframe.id, { player, milestone: new Set() });
146
+ });
147
+ };
148
+
149
+ // Se API já carregada (SPA reload), inicializar diretamente
150
+ if (typeof YT !== 'undefined' && YT.Player) {
151
+ window.onYouTubeIframeAPIReady();
152
+ }
153
+
154
+ function handlePlayerReady(event, player, videoTitle) {
155
+ // Iniciar polling de progresso
156
+ const iframeId = player.getIframe().id;
157
+ const state = trackedPlayers.get(iframeId);
158
+
159
+ const interval = setInterval(() => {
160
+ if (!player.getDuration) return;
161
+ const duration = player.getDuration();
162
+ const current = player.getCurrentTime();
163
+ if (duration <= 0) return;
164
+
165
+ const percent = Math.floor((current / duration) * 100);
166
+
167
+ // Disparar milestones: 25, 50, 75 (100% é coberto pelo estado ENDED)
168
+ const milestoneEvents = { 25: 'video_25', 50: 'video_50', 75: 'video_75' };
169
+ [25, 50, 75].forEach(milestone => {
170
+ if (percent >= milestone && !state.milestone.has(milestone)) {
171
+ state.milestone.add(milestone);
172
+
173
+ window.cdpTrack?.track(milestoneEvents[milestone], {
174
+ content_name: videoTitle,
175
+ video_percent: milestone,
176
+ video_duration: Math.round(duration),
177
+ video_provider: 'youtube',
178
+ value: 0,
179
+ currency: 'BRL'
180
+ });
181
+ }
182
+ });
183
+ }, 1000); // checar a cada 1s
184
+
185
+ state.progressInterval = interval;
186
+ }
187
+
188
+ function handlePlayerStateChange(event, player, videoTitle) {
189
+ const iframeId = player.getIframe().id;
190
+ const state = trackedPlayers.get(iframeId);
191
+
192
+ // YT.PlayerState: PLAYING=1, PAUSED=2, ENDED=0, BUFFERING=3
193
+ switch (event.data) {
194
+ case YT.PlayerState.PLAYING:
195
+ if (!state.started) {
196
+ state.started = true;
197
+ window.cdpTrack?.track('video_start', {
198
+ content_name: videoTitle,
199
+ video_duration: Math.round(player.getDuration() || 0),
200
+ video_provider: 'youtube'
201
+ });
202
+ }
203
+ break;
204
+
205
+ case YT.PlayerState.ENDED:
206
+ clearInterval(state.progressInterval);
207
+ window.cdpTrack?.track('video_complete', {
208
+ content_name: videoTitle,
209
+ video_duration: Math.round(player.getDuration() || 0),
210
+ video_provider: 'youtube'
211
+ });
212
+ break;
213
+ }
214
+ }
215
+ })();
216
+ ```
217
+
218
+ O listener dispara via `cdpTrack.track()`:
219
+ - `video_start` — primeiros 2s de play
220
+ - `video_25`, `video_50`, `video_75` — marcos de progresso (25/50/75%)
221
+ - `video_complete` — 100% assistido
109
222
 
110
223
  ### 3. Evento de Lead após assistir vídeo (imóveis)
111
224
 
112
225
  ```javascript
113
226
  // Disparar Lead qualificado quando usuário assiste 75%+ do tour
114
- document.addEventListener('pb:video_milestone', (e) => {
115
- if (e.detail.percent >= 75) {
116
- cdpTrack.track('InitiateCheckout', {
117
- content_name: 'Tour_Virtual_Empreendimento',
118
- value: 0,
119
- currency: 'BRL',
120
- // Sinaliza alta intenção para Meta + Google
121
- meta_intensity: 'high',
122
- });
123
- }
124
- });
227
+ // Adicionar dentro do callback de milestoneEvents no IFrame API listener:
228
+ // milestoneEvents[75] 'video_75' — adicionar lógica abaixo no bloco forEach
229
+ if (milestone === 75) {
230
+ window.cdpTrack?.track('InitiateCheckout', {
231
+ content_name: 'Tour_Virtual_Empreendimento',
232
+ value: 0,
233
+ currency: 'BRL',
234
+ // Sinaliza alta intenção para Meta + Google
235
+ meta_intensity: 'high',
236
+ });
237
+ }
125
238
  ```
126
239
 
127
240
  ### 4. Consent Mode v2 — OBRIGATÓRIO para YouTube/Google Ads
@@ -161,9 +274,11 @@ Para verificar persistência correta:
161
274
  ```javascript
162
275
  // No sendGA4Mp() — adicionar mapeamento de eventos YouTube
163
276
  const VIDEO_GA4_MAP = {
164
- video_start: 'video_start',
165
- video_milestone: 'video_progress', // GA4 usa video_progress com percent
166
- video_complete: 'video_complete',
277
+ video_start: 'video_start',
278
+ video_25: 'video_progress', // GA4 usa video_progress com percent
279
+ video_50: 'video_progress',
280
+ video_75: 'video_progress',
281
+ video_complete: 'video_complete',
167
282
  };
168
283
 
169
284
  // Params obrigatórios para video_progress (GA4)
@@ -228,9 +343,9 @@ if (!payload.gclid && payload.utmSource === 'youtube') {
228
343
  | Evento cdpTrack | Mapeamento GA4 | Mapeamento Google Ads | Quando Disparar |
229
344
  |---|---|---|---|
230
345
  | `video_start` | `video_start` | — | Primeiros 2s de reprodução |
231
- | `video_milestone` (25%) | `video_progress` | — | 25% assistido |
232
- | `video_milestone` (50%) | `video_progress` | `engaged_view` candidate | 50% assistido |
233
- | `video_milestone` (75%) | `video_progress` | `engaged_view` | 75% assistido — alta intenção |
346
+ | `video_25` | `video_progress` | — | 25% assistido |
347
+ | `video_50` | `video_progress` | `engaged_view` candidate | 50% assistido |
348
+ | `video_75` | `video_progress` | `engaged_view` | 75% assistido — alta intenção |
234
349
  | `video_complete` | `video_complete` | `video_view_complete` | 100% assistido |
235
350
  | `Lead` (após vídeo) | `generate_lead` | Conversão primária | Formulário submetido |
236
351
  | `InitiateCheckout` | `begin_checkout` | Conversão micro | Clique em "Quero saber mais" |
@@ -244,7 +359,7 @@ if (!payload.gclid && payload.utmSource === 'youtube') {
244
359
  ```
245
360
  FASE 1: AWARENESS (YouTube TrueView 30s)
246
361
  ↓ Tour aéreo do empreendimento / lifestyle do bairro
247
- ↓ Rastrear: video_milestone 50% + 75% → score alto no LTV
362
+ ↓ Rastrear: video_50 + video_75 → score alto no LTV
248
363
  ↓ Remarketing: quem assistiu 50%+ vira audiência no Google Ads
249
364
 
250
365
  FASE 2: CONSIDERAÇÃO (YouTube Non-skip 15s + Display)
@@ -212,10 +212,14 @@
212
212
  ]
213
213
  },
214
214
  "conversions_api": {
215
- "current": "v2",
216
- "minimum_supported": "v1",
217
- "recommended": "v2",
218
- "endpoint_pattern": "https://api.linkedin.com/rest/attributionConversions/{VERSION}",
215
+ "current": "202401",
216
+ "minimum_supported": "202401",
217
+ "recommended": "202401",
218
+ "endpoint_pattern": "https://api.linkedin.com/rest/conversionEvents",
219
+ "required_headers": {
220
+ "LinkedIn-Version": "202401",
221
+ "X-Restli-Protocol-Version": "2.0.0"
222
+ },
219
223
  "authentication": "Bearer token (LINKEDIN_ACCESS_TOKEN)",
220
224
  "rate_limits": {
221
225
  "requests_per_second": 10,
@@ -359,10 +363,10 @@
359
363
  },
360
364
 
361
365
  "last_updated_by": {
362
- "agent": "Intelligence Agent",
363
- "session_id": "CDP_2026-03-28_sync",
364
- "timestamp": "2026-03-28T00:00:00.000Z"
366
+ "agent": "Audit — CDP Edge v2.0",
367
+ "session_id": "CDP_2026-04-10_audit",
368
+ "timestamp": "2026-04-10T00:00:00.000Z"
365
369
  },
366
370
 
367
- "next_review_date": "2026-04-27T00:00:00.000Z"
371
+ "next_review_date": "2026-05-10T00:00:00.000Z"
368
372
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdp-edge",
3
- "version": "1.18.0",
3
+ "version": "1.18.2",
4
4
  "description": "CDP Edge - Quantum Tracking - Sistema multi-agente para tracking digital Cloudflare Native (Workers + D1)",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -23,7 +23,7 @@
23
23
  "build": "node build.js",
24
24
  "dev": "node build.js --watch",
25
25
  "test": "node test.js",
26
- "test:unit": "node tests/unit/*.test.js",
26
+ "test:unit": "node tests/unit/normalization.test.js && node tests/unit/hashing.test.js && node tests/unit/deduplication.test.js && node tests/unit/payload-validation.test.js && node tests/unit/new-features.test.js",
27
27
  "test:unit:normalize": "node tests/unit/normalization.test.js",
28
28
  "test:unit:hash": "node tests/unit/hashing.test.js",
29
29
  "test:unit:dedup": "node tests/unit/deduplication.test.js",