cdp-edge 1.2.0

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 (128) hide show
  1. package/README.md +367 -0
  2. package/bin/cdp-edge.js +61 -0
  3. package/contracts/api-versions.json +368 -0
  4. package/dist/commands/analyze.js +52 -0
  5. package/dist/commands/infra.js +54 -0
  6. package/dist/commands/install.js +168 -0
  7. package/dist/commands/server.js +174 -0
  8. package/dist/commands/setup.js +123 -0
  9. package/dist/commands/validate.js +84 -0
  10. package/dist/index.js +12 -0
  11. package/docs/CI-CD-SETUP.md +217 -0
  12. package/docs/PixelBuilder-Documentacao-Completa (2).docx +0 -0
  13. package/docs/events-reference.md +359 -0
  14. package/docs/installation.md +155 -0
  15. package/docs/quick-start.md +185 -0
  16. package/docs/sdk-reference.md +371 -0
  17. package/docs/whatsapp-ctwa.md +209 -0
  18. package/extracted-skill/tracking-events-generator/INDEX.md +94 -0
  19. package/extracted-skill/tracking-events-generator/INSTALACAO-CDPEDGE.md +58 -0
  20. package/extracted-skill/tracking-events-generator/INTEGRACAO-COMPLETA.md +594 -0
  21. package/extracted-skill/tracking-events-generator/MELHORIAS-IMPLEMENTADAS.md +412 -0
  22. package/extracted-skill/tracking-events-generator/Premium-Tracking-Intelligence-Resumo.md +333 -0
  23. package/extracted-skill/tracking-events-generator/SKILL.md +257 -0
  24. package/extracted-skill/tracking-events-generator/advanced-matching.js +364 -0
  25. package/extracted-skill/tracking-events-generator/agents/ab-testing-agent.md +54 -0
  26. package/extracted-skill/tracking-events-generator/agents/attribution-agent.md +1304 -0
  27. package/extracted-skill/tracking-events-generator/agents/bing-agent.md +76 -0
  28. package/extracted-skill/tracking-events-generator/agents/browser-tracking.md +264 -0
  29. package/extracted-skill/tracking-events-generator/agents/code-guardian-agent.md +149 -0
  30. package/extracted-skill/tracking-events-generator/agents/compliance-agent.md +2077 -0
  31. package/extracted-skill/tracking-events-generator/agents/crm-integration-agent.md +1419 -0
  32. package/extracted-skill/tracking-events-generator/agents/dashboard-agent.md +456 -0
  33. package/extracted-skill/tracking-events-generator/agents/database-agent.md +667 -0
  34. package/extracted-skill/tracking-events-generator/agents/debug-agent.md +1455 -0
  35. package/extracted-skill/tracking-events-generator/agents/domain-setup-agent.md +224 -0
  36. package/extracted-skill/tracking-events-generator/agents/email-agent.md +61 -0
  37. package/extracted-skill/tracking-events-generator/agents/fingerprint-agent.md +52 -0
  38. package/extracted-skill/tracking-events-generator/agents/google-agent.md +109 -0
  39. package/extracted-skill/tracking-events-generator/agents/intelligence-agent.md +365 -0
  40. package/extracted-skill/tracking-events-generator/agents/intelligence-scheduling.md +643 -0
  41. package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +62 -0
  42. package/extracted-skill/tracking-events-generator/agents/localization-agent.md +55 -0
  43. package/extracted-skill/tracking-events-generator/agents/ltv-predictor-agent.md +59 -0
  44. package/extracted-skill/tracking-events-generator/agents/master-feedback-loop.md +900 -0
  45. package/extracted-skill/tracking-events-generator/agents/master-orchestrator.md +1922 -0
  46. package/extracted-skill/tracking-events-generator/agents/memory-agent.json +109 -0
  47. package/extracted-skill/tracking-events-generator/agents/memory-agent.md +703 -0
  48. package/extracted-skill/tracking-events-generator/agents/meta-agent.md +110 -0
  49. package/extracted-skill/tracking-events-generator/agents/page-analyzer.md +255 -0
  50. package/extracted-skill/tracking-events-generator/agents/performance-agent.md +1157 -0
  51. package/extracted-skill/tracking-events-generator/agents/performance-optimization-agent.md +1432 -0
  52. package/extracted-skill/tracking-events-generator/agents/pinterest-agent.md +310 -0
  53. package/extracted-skill/tracking-events-generator/agents/premium-tracking-intelligence-agent.md +849 -0
  54. package/extracted-skill/tracking-events-generator/agents/r2-setup-agent.md +250 -0
  55. package/extracted-skill/tracking-events-generator/agents/reddit-agent.md +313 -0
  56. package/extracted-skill/tracking-events-generator/agents/security-enterprise-agent.md +1752 -0
  57. package/extracted-skill/tracking-events-generator/agents/server-tracking.md +1188 -0
  58. package/extracted-skill/tracking-events-generator/agents/spotify-agent.md +383 -0
  59. package/extracted-skill/tracking-events-generator/agents/tiktok-agent.md +111 -0
  60. package/extracted-skill/tracking-events-generator/agents/tracking-plan-agent.md +364 -0
  61. package/extracted-skill/tracking-events-generator/agents/validator-agent.md +267 -0
  62. package/extracted-skill/tracking-events-generator/agents/webhook-agent.md +69 -0
  63. package/extracted-skill/tracking-events-generator/agents/whatsapp-agent.md +76 -0
  64. package/extracted-skill/tracking-events-generator/agents/whatsapp-ctwa-setup-agent.md +699 -0
  65. package/extracted-skill/tracking-events-generator/agents/youtube-agent.md +422 -0
  66. package/extracted-skill/tracking-events-generator/anti-blocking.js +285 -0
  67. package/extracted-skill/tracking-events-generator/cdpTrack.js +641 -0
  68. package/extracted-skill/tracking-events-generator/contracts/api-versions.json +368 -0
  69. package/extracted-skill/tracking-events-generator/docs/guia-cloudflare-iniciante.md +107 -0
  70. package/extracted-skill/tracking-events-generator/engagement-scoring.js +226 -0
  71. package/extracted-skill/tracking-events-generator/evals/evals.json +235 -0
  72. package/extracted-skill/tracking-events-generator/integration-test.js +497 -0
  73. package/extracted-skill/tracking-events-generator/knowledge-base.md +2894 -0
  74. package/extracted-skill/tracking-events-generator/micro-events.js +992 -0
  75. package/extracted-skill/tracking-events-generator/models/captura-de-lead.md +78 -0
  76. package/extracted-skill/tracking-events-generator/models/captura-lead-evento-externo.md +99 -0
  77. package/extracted-skill/tracking-events-generator/models/checkout-proprio.md +111 -0
  78. package/extracted-skill/tracking-events-generator/models/multi-step-checkout.md +672 -0
  79. package/extracted-skill/tracking-events-generator/models/pagina-obrigado.md +55 -0
  80. package/extracted-skill/tracking-events-generator/models/pinterest/conversions-api-template.js +144 -0
  81. package/extracted-skill/tracking-events-generator/models/pinterest/event-mappings.json +48 -0
  82. package/extracted-skill/tracking-events-generator/models/pinterest/tag-template.js +28 -0
  83. package/extracted-skill/tracking-events-generator/models/quiz-funnel.md +68 -0
  84. package/extracted-skill/tracking-events-generator/models/reddit/conversions-api-template.js +205 -0
  85. package/extracted-skill/tracking-events-generator/models/reddit/event-mappings.json +56 -0
  86. package/extracted-skill/tracking-events-generator/models/reddit/pixel-template.js +19 -0
  87. package/extracted-skill/tracking-events-generator/models/scenarios/behavior-engine.js +425 -0
  88. package/extracted-skill/tracking-events-generator/models/scenarios/real-estate-logic.md +50 -0
  89. package/extracted-skill/tracking-events-generator/models/scenarios/sales-page-logic.md +50 -0
  90. package/extracted-skill/tracking-events-generator/models/trafego-direto.md +582 -0
  91. package/extracted-skill/tracking-events-generator/models/webinar-registration.md +63 -0
  92. package/extracted-skill/tracking-events-generator/tracking.config.js +46 -0
  93. package/extracted-skill/tracking-events-generator/walkthrough.md +26 -0
  94. package/package.json +75 -0
  95. package/server-edge-tracker/INSTALAR.md +328 -0
  96. package/server-edge-tracker/migrate-new-db.sql +137 -0
  97. package/server-edge-tracker/migrate-v2.sql +16 -0
  98. package/server-edge-tracker/migrate-v3.sql +6 -0
  99. package/server-edge-tracker/migrate-v4.sql +18 -0
  100. package/server-edge-tracker/migrate-v5.sql +17 -0
  101. package/server-edge-tracker/migrate-v6.sql +24 -0
  102. package/server-edge-tracker/migrate.sql +111 -0
  103. package/server-edge-tracker/schema.sql +265 -0
  104. package/server-edge-tracker/worker.js +2574 -0
  105. package/server-edge-tracker/wrangler.toml +85 -0
  106. package/templates/afiliado-sem-landing.md +312 -0
  107. package/templates/captura-de-lead.md +78 -0
  108. package/templates/captura-lead-evento-externo.md +99 -0
  109. package/templates/checkout-proprio.md +111 -0
  110. package/templates/install/.claude/commands/cdp.md +1 -0
  111. package/templates/install/CLAUDE.md +65 -0
  112. package/templates/linkedin/tag-template.js +46 -0
  113. package/templates/multi-step-checkout.md +673 -0
  114. package/templates/pagina-obrigado.md +55 -0
  115. package/templates/pinterest/conversions-api-template.js +144 -0
  116. package/templates/pinterest/event-mappings.json +48 -0
  117. package/templates/pinterest/tag-template.js +28 -0
  118. package/templates/quiz-funnel.md +68 -0
  119. package/templates/reddit/conversions-api-template.js +205 -0
  120. package/templates/reddit/event-mappings.json +56 -0
  121. package/templates/reddit/pixel-template.js +46 -0
  122. package/templates/scenarios/behavior-engine.js +402 -0
  123. package/templates/scenarios/real-estate-logic.md +50 -0
  124. package/templates/scenarios/sales-page-logic.md +50 -0
  125. package/templates/spotify/pixel-template.js +46 -0
  126. package/templates/trafego-direto.md +582 -0
  127. package/templates/vsl-page.md +292 -0
  128. package/templates/webinar-registration.md +63 -0
@@ -0,0 +1,641 @@
1
+ /**
2
+ * CDP EDGE TRACKING SDK (Quantum Tier)
3
+ * @version 1.0.0
4
+ *
5
+ * SDK principal de tracking do CDP Edge.
6
+ * Comunica diretamente com o Cloudflare Worker.
7
+ * Suporta micro-events, engagement scoring e tracking premium.
8
+ */
9
+
10
+ // ── Imports ────────────────────────────────────────────────
11
+ import CONFIG from './tracking.config.js';
12
+ import { initMicroEvents, trackScroll, trackTimeOnPage, trackVideo, trackCTAHover } from './micro-events.js';
13
+ import {
14
+ calculateEngagementScore,
15
+ updateEngagementState,
16
+ engagementState
17
+ } from './engagement-scoring.js';
18
+ import {
19
+ extractFormPII,
20
+ capturePII,
21
+ isValidPII
22
+ } from './advanced-matching.js';
23
+ import {
24
+ sendWithRetry,
25
+ initAntiBlocking,
26
+ ANTI_BLOCKING_CONFIG
27
+ } from './anti-blocking.js';
28
+
29
+ // ── Guards — segurança em SSR e SDK não carregado ──
30
+ const isBrowser = typeof window !== 'undefined';
31
+ const has = (fn) => isBrowser && typeof window[fn] === 'function';
32
+
33
+ // ── Google Consent Mode v2 ────────────────────────────────────────────────────
34
+ // Inicializa com todos os sinais negados por padrão (LGPD/GDPR compliance).
35
+ // Sites devem chamar cdpTrack.updateConsent({ analytics: true, ads: true })
36
+ // após o usuário aceitar o banner de cookies.
37
+ const CONSENT_KEY = '_cdp_consent';
38
+
39
+ const _defaultConsent = {
40
+ ad_storage: 'denied',
41
+ analytics_storage: 'denied',
42
+ ad_user_data: 'denied',
43
+ ad_personalization: 'denied',
44
+ };
45
+
46
+ // Lê consentimento salvo (boolean → objeto de sinais)
47
+ function _readStoredConsent() {
48
+ if (!isBrowser) return null;
49
+ try {
50
+ const raw = localStorage.getItem(CONSENT_KEY);
51
+ return raw ? JSON.parse(raw) : null;
52
+ } catch { return null; }
53
+ }
54
+
55
+ /**
56
+ * Inicializa o Google Consent Mode v2 com defaults negados.
57
+ * Chamado automaticamente em init() ANTES de qualquer gtag disparo.
58
+ * Também aplica consentimento salvo previamente (sessões recorrentes).
59
+ */
60
+ function initConsentMode() {
61
+ if (!isBrowser) return;
62
+
63
+ // Se consent mode desabilitado na config, não inicializa
64
+ if (CONFIG.consent?.defaultDenied === false) return;
65
+
66
+ // gtag helper — funciona mesmo que gtag não tenha sido carregado ainda
67
+ window.dataLayer = window.dataLayer || [];
68
+ function gtag() { window.dataLayer.push(arguments); }
69
+
70
+ const waitForUpdate = CONFIG.consent?.waitForUpdate ?? 500;
71
+
72
+ // Consent padrão: tudo negado + url_passthrough para preservar atribuição
73
+ gtag('consent', 'default', {
74
+ ..._defaultConsent,
75
+ wait_for_update: waitForUpdate,
76
+ });
77
+
78
+ // url_passthrough: preserva gclid/fbclid na URL mesmo sem consent
79
+ if (CONFIG.consent?.urlPassthrough !== false) {
80
+ gtag('set', 'url_passthrough', true);
81
+ }
82
+
83
+ // Aplicar consentimento salvo (usuário que já aceitou anteriormente)
84
+ const stored = _readStoredConsent();
85
+ if (stored) {
86
+ gtag('consent', 'update', stored);
87
+ if (CONFIG.debug) console.log('✅ Consent Mode: consentimento anterior restaurado');
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Atualiza o Consent Mode quando o usuário aceita/rejeita o banner.
93
+ *
94
+ * @param {object} params - { analytics?: boolean, ads?: boolean }
95
+ * @example
96
+ * // Usuário aceitou tudo
97
+ * cdpTrack.updateConsent({ analytics: true, ads: true });
98
+ *
99
+ * // Usuário aceitou só analytics, rejeitou anúncios
100
+ * cdpTrack.updateConsent({ analytics: true, ads: false });
101
+ */
102
+ export function updateConsent(params = {}) {
103
+ if (!isBrowser) return;
104
+
105
+ window.dataLayer = window.dataLayer || [];
106
+ function gtag() { window.dataLayer.push(arguments); }
107
+
108
+ const analyticsGranted = params.analytics === true ? 'granted' : 'denied';
109
+ const adsGranted = params.ads === true ? 'granted' : 'denied';
110
+
111
+ const consentUpdate = {
112
+ analytics_storage: analyticsGranted,
113
+ ad_storage: adsGranted,
114
+ ad_user_data: adsGranted,
115
+ ad_personalization: adsGranted,
116
+ };
117
+
118
+ gtag('consent', 'update', consentUpdate);
119
+
120
+ // Persiste para sessões futuras
121
+ try {
122
+ localStorage.setItem(CONSENT_KEY, JSON.stringify(consentUpdate));
123
+ } catch { /* storage bloqueado (incognito, etc.) */ }
124
+
125
+ console.log('✅ Consent Mode atualizado:', consentUpdate);
126
+ }
127
+
128
+ // ── Captura de parâmetros de URL no carregamento da página ──
129
+ // Cada plataforma injeta seu próprio Click ID na URL quando o usuário clica em um anúncio.
130
+ // Guardamos todos em memória para enviar ao servidor e garantir atribuição correta.
131
+ const _urlParams = isBrowser ? new URLSearchParams(window.location.search) : new URLSearchParams();
132
+
133
+ // Meta
134
+ const _fbclid = _urlParams.get('fbclid') || ''; // Meta Ads click ID → gera cookie _fbc
135
+
136
+ // Google Ads (três variantes por tipo de match/privacy)
137
+ const _gclid = _urlParams.get('gclid') || ''; // Google Ads standard click ID → gera cookie _gcl_aw
138
+ const _wbraid = _urlParams.get('wbraid') || ''; // Google Ads (iOS, web-to-app, privacy preserving)
139
+ const _gbraid = _urlParams.get('gbraid') || ''; // Google Ads (app campaigns, privacy preserving)
140
+
141
+ // TikTok
142
+ const _ttclid = _urlParams.get('ttclid') || ''; // TikTok Ads click ID → complementa cookie _ttp
143
+
144
+ // UTMs — rastreamento interno de origem de tráfego (independente da atribuição das plataformas)
145
+ const _utms = {
146
+ utm_source: _urlParams.get('utm_source') || '',
147
+ utm_medium: _urlParams.get('utm_medium') || '',
148
+ utm_campaign: _urlParams.get('utm_campaign') || '',
149
+ utm_content: _urlParams.get('utm_content') || '',
150
+ utm_term: _urlParams.get('utm_term') || '',
151
+ };
152
+
153
+ // ── User ID persistente (first-party cookie) ──────────────────
154
+ // Identifica o mesmo usuário entre sessões diferentes, mesmo sem login.
155
+ // Resolve o problema de cookies de terceiros bloqueados por ad-blockers.
156
+ const _getUserId = () => {
157
+ if (!isBrowser) return '';
158
+ const KEY = '_cdp_uid';
159
+ let uid = document.cookie.match(new RegExp(`${KEY}=([^;]+)`))?.[1];
160
+ if (!uid) {
161
+ uid = `${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
162
+ // Cookie first-party de 365 dias — não bloqueado por navegadores
163
+ document.cookie = `${KEY}=${uid}; max-age=${60*60*24*365}; path=/; SameSite=Lax`;
164
+ }
165
+ return uid;
166
+ };
167
+ const _userId = isBrowser ? _getUserId() : '';
168
+
169
+ // ── Session ID (cross-tab) ─────────────────────────────────────
170
+ const _getSessionId = () => {
171
+ if (!isBrowser) return '';
172
+ const sessionId = 'cdp_session';
173
+ let sid = sessionStorage.getItem(sessionId);
174
+ if (!sid) {
175
+ sid = `${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
176
+ sessionStorage.setItem(sessionId, sid);
177
+ }
178
+ return sid;
179
+ };
180
+ const _sessionId = isBrowser ? _getSessionId() : '';
181
+
182
+ /**
183
+ * getUTMs() — retorna os UTMs capturados na chegada do usuário
184
+ * Útil para: salvar no CRM, incluir no payload do servidor, rastreamento interno.
185
+ * Atribuição própria independente da janela das plataformas (último clique registrado).
186
+ */
187
+ export const getUTMs = () => ({ ..._utms });
188
+
189
+ /**
190
+ * getUserId() — retorna o User ID persistente gerado como cookie first-party.
191
+ * Identificador de usuário persistente nativo.
192
+ */
193
+ export const getUserId = () => _userId;
194
+
195
+ /**
196
+ * getSessionId() — retorna o Session ID (cross-tab).
197
+ */
198
+ export const getSessionId = () => _sessionId;
199
+
200
+ /**
201
+ * generateId() — gera um ID único de evento
202
+ * Baseado em timestamp + random
203
+ */
204
+ export const generateId = () => {
205
+ return `${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
206
+ };
207
+
208
+ // ── Captura de Click IDs das plataformas ──────────────────────
209
+ const _getClickIDs = () => {
210
+ return {
211
+ fbp: document.cookie.match(/_fbp=([^;]+)/)?.[1],
212
+ fbc: document.cookie.match(/_fbc=([^;]+)/)?.[1],
213
+ gclid: _gclid,
214
+ gbraid: _gbraid,
215
+ wbraid: _wbraid,
216
+ ttclid: _ttclid,
217
+ rclid: _urlParams.get('rclid')
218
+ };
219
+ };
220
+
221
+ /**
222
+ * passCheckoutParams(options?) — adiciona UTMs + User ID nos links de checkout externo.
223
+ *
224
+ * Problema: quando o usuário clica em um link de Hotmart, Kiwify, Eduzz etc., os UTMs e
225
+ * o user ID são perdidos — a plataforma não sabe de onde veio o comprador.
226
+ *
227
+ * Solução: interceptar os links de checkout e adicionar os parâmetros de rastreamento
228
+ * na URL antes do clique, para que a plataforma receba e registre a origem.
229
+ *
230
+ * Plataformas suportadas e seus parâmetros:
231
+ * hotmart → xcod (user ID) + sck (UTMs pipe-separados: src|med|camp|con|term)
232
+ * kiwify → src (utm_source)
233
+ * eduzz → src (utm_source)
234
+ * monetizze → src (utm_source)
235
+ * cartpanda → utm_* passthrough direto
236
+ * custom → qualquer domínio via opção `domains`
237
+ *
238
+ * Uso:
239
+ * passCheckoutParams() // detecta automaticamente
240
+ * passCheckoutParams({ platforms: ['hotmart'] }) // só Hotmart
241
+ * passCheckoutParams({ domains: ['meusite.com/checkout'] }) // domínio customizado
242
+ */
243
+ export const passCheckoutParams = (options = {}) => {
244
+ if (!isBrowser) return;
245
+
246
+ const {
247
+ platforms = ['hotmart', 'kiwify', 'eduzz', 'monetizze', 'cartpanda', 'ticto'],
248
+ domains = []
249
+ } = options;
250
+
251
+ const utms = getUTMs();
252
+ const userId = getUserId();
253
+
254
+ // Mapa de domínios por plataforma
255
+ const PLATFORM_DOMAINS = {
256
+ hotmart: ['hotmart.com', 'pay.hotmart.com', 'payment.hotmart.com'],
257
+ kiwify: ['kiwify.com.br', 'checkout.kiwify.com.br'],
258
+ eduzz: ['eduzz.com', 'sun.eduzz.com'],
259
+ monetizze: ['monetizze.com.br'],
260
+ cartpanda: ['cartpanda.com', 'pay.cartpanda.com'],
261
+ ticto: ['ticto.app', 'pay.ticto.app', 'checkout.ticto.app']
262
+ };
263
+
264
+ // Constrói o `sck` do Hotmart: utm_source|utm_medium|utm_campaign|utm_content|utm_term
265
+ const buildSck = () =>
266
+ [utms.utm_source, utms.utm_medium, utms.utm_campaign, utms.utm_content, utms.utm_term]
267
+ .map(v => v || 'direto')
268
+ .join('|');
269
+
270
+ // Parâmetros por plataforma
271
+ const getParamsForUrl = (href) => {
272
+ const params = { ...domains };
273
+ const url = href.toLowerCase();
274
+
275
+ const isMatch = (list) => list.some(d => url.includes(d));
276
+
277
+ if (platforms.includes('hotmart') && isMatch(PLATFORM_DOMAINS.hotmart)) {
278
+ if (userId) params.xcod = userId;
279
+ if (utms.utm_source) params.sck = buildSck();
280
+ } else if (platforms.includes('kiwify') && isMatch(PLATFORM_DOMAINS.kiwify)) {
281
+ if (utms.utm_source) params.src = utms.utm_source;
282
+ if (utms.utm_medium) params.utm_medium = utms.utm_medium;
283
+ if (utms.utm_campaign) params.utm_campaign = utms.utm_campaign;
284
+ } else if (platforms.includes('eduzz') && isMatch(PLATFORM_DOMAINS.eduzz)) {
285
+ if (utms.utm_source) params.src = utms.utm_source;
286
+ } else if (platforms.includes('monetizze') && isMatch(PLATFORM_DOMAINS.monetizze)) {
287
+ if (utms.utm_source) params.src = utms.utm_source;
288
+ } else if (platforms.includes('cartpanda') && isMatch(PLATFORM_DOMAINS.cartpanda)) {
289
+ Object.entries(utms).forEach(([k, v]) => { if (v) params[k] = v; });
290
+ } else if (platforms.includes('ticto') && isMatch(PLATFORM_DOMAINS.ticto)) {
291
+ // Ticto captura UTMs em wh.tracking e parâmetros extras em wh.url_params
292
+ Object.entries(utms).forEach(([k, v]) => { if (v) params[k] = v; });
293
+ if (userId) params.user_id = userId; // recuperado no webhook via D1 cross-check
294
+ } else {
295
+ // Domínios customizados: repassa utm_* e userId
296
+ const allDomains = Object.values(PLATFORM_DOMAINS).flat().concat(domains);
297
+ if (!isMatch(allDomains)) return null;
298
+ Object.entries(utms).forEach(([k, v]) => { if (v) params[k] = v; });
299
+ if (userId) params.user_id = userId;
300
+ }
301
+
302
+ return Object.keys(params).length ? params : null;
303
+ };
304
+
305
+ const applyParams = (link) => {
306
+ if (!link.href || link.href.startsWith('javascript')) return;
307
+ const p = getParamsForUrl(link);
308
+ if (!p) return;
309
+
310
+ try {
311
+ const url = new URL(link.href);
312
+ Object.entries(p).forEach(([k, v]) => url.searchParams.set(k, v));
313
+ link.href = url.toString();
314
+ } catch { /* URL inválida — ignora */ }
315
+ };
316
+
317
+ // Aplica em todos os links existentes
318
+ document.querySelectorAll('a[href]').forEach(applyParams);
319
+
320
+ // Aplica em links adicionados dinamicamente (ex: botões de checkout lazy loaded)
321
+ // BUG CORRIGIDO: .observe() estava faltando — links dinâmicos nunca eram rastreados
322
+ new MutationObserver((mutations) => {
323
+ mutations.forEach(m => m.addedNodes.forEach(node => {
324
+ if (node.nodeType !== 1) return;
325
+ if (node.tagName === 'A') applyParams(node);
326
+ // Também verifica <a> aninhados dentro do nó adicionado
327
+ node.querySelectorAll?.('a[href]').forEach(applyParams);
328
+ }));
329
+ }).observe(document.body, { childList: true, subtree: true });
330
+
331
+ console.log('✅ PassCheckoutParams inicializado');
332
+ };
333
+
334
+ // ── Fingerprint Fallback para Links de Afiliado ───────────────────────────────
335
+ // Problema: quando o cookie _cdp_uid ou os UTMs são perdidos antes do clique
336
+ // (ex: usuário abre em nova aba, troca de navegador), o checkout chega sem cdp_uid.
337
+ //
338
+ // Solução: salvar uid + UTMs no localStorage ao carregar. passCheckoutParams()
339
+ // usa este backup quando os parâmetros de URL estiverem vazios.
340
+
341
+ const _AFFILIATE_STORAGE_KEY = '_cdp_aff';
342
+
343
+ function _saveAffiliateContext() {
344
+ if (!isBrowser) return;
345
+ try {
346
+ const hasUtms = Object.values(_utms).some(v => !!v);
347
+ const existing = _loadAffiliateContext();
348
+ // Só sobrescreve se esta visita tem UTMs — não apaga atribuição melhor anterior
349
+ if (hasUtms || !existing) {
350
+ localStorage.setItem(_AFFILIATE_STORAGE_KEY, JSON.stringify({
351
+ uid: _userId,
352
+ utms: _utms,
353
+ ts: Date.now(),
354
+ }));
355
+ }
356
+ } catch { /* localStorage bloqueado */ }
357
+ }
358
+
359
+ function _loadAffiliateContext() {
360
+ if (!isBrowser) return null;
361
+ try {
362
+ const raw = localStorage.getItem(_AFFILIATE_STORAGE_KEY);
363
+ if (!raw) return null;
364
+ const ctx = JSON.parse(raw);
365
+ if (Date.now() - ctx.ts > 30 * 24 * 60 * 60 * 1000) {
366
+ localStorage.removeItem(_AFFILIATE_STORAGE_KEY);
367
+ return null;
368
+ }
369
+ return ctx;
370
+ } catch { return null; }
371
+ }
372
+
373
+ /**
374
+ * Retorna UTMs ativos: da URL (prioridade) ou do localStorage (fallback de 30 dias).
375
+ * Uso principal: links de afiliado onde o usuário pode chegar sem parâmetros na URL.
376
+ */
377
+ export function getUTMsWithFallback() {
378
+ const live = getUTMs();
379
+ if (Object.values(live).some(v => !!v)) return live;
380
+ const stored = _loadAffiliateContext();
381
+ if (stored?.utms && Object.values(stored.utms).some(v => !!v)) {
382
+ if (CONFIG.debug) console.log('ℹ️ UTMs restaurados do localStorage (affiliate fallback)');
383
+ return stored.utms;
384
+ }
385
+ return live;
386
+ }
387
+
388
+ /**
389
+ * Retorna o userId com fallback para o armazenado no localStorage.
390
+ */
391
+ export function getUserIdWithFallback() {
392
+ return _userId || _loadAffiliateContext()?.uid || '';
393
+ }
394
+
395
+ // ── Tracking Events (Principal) ─────────────────────────────────
396
+
397
+ /**
398
+ * track() — Função principal de tracking
399
+ *
400
+ * @param {string} eventName - Nome do evento
401
+ * @param {object} data - Dados do evento
402
+ * @param {object} options - Opções adicionais
403
+ * @returns {Promise} - Promise com resultado
404
+ *
405
+ * Dispara evento para:
406
+ * - Worker (server-side)
407
+ * - Meta CAPI v22.0 (se configurado)
408
+ * - Google GA4 Measurement Protocol (se configurado)
409
+ * - TikTok Events API v1.3 (se configurado)
410
+ *
411
+ * Processo:
412
+ * 1. Gera event_id único
413
+ * 2. Coleta UTMs e Click IDs
414
+ * 3. Envia para Worker via fetch (same-domain)
415
+ * 4. Worker processa e despacha para plataformas
416
+ */
417
+ export async function track(eventName, data = {}, options = {}) {
418
+ if (!isBrowser) {
419
+ console.warn('⚠️ cdpTrack.track() deve ser chamado no browser');
420
+ return { success: false, error: 'Not in browser' };
421
+ }
422
+
423
+ const event_id = generateId();
424
+ const utms = getUTMs();
425
+ const clickIds = _getClickIDs();
426
+ const userId = getUserId();
427
+ const sessionId = getSessionId();
428
+ const timestamp = Date.now();
429
+
430
+ // Calcular engajamento preliminar (browser-side)
431
+ const engagementScore = calculateEngagementScore();
432
+ const { totalScore, timeLevel, scrollScore, clickScore, videoScore, hoverScore, intentionLevel } = engagementScore;
433
+
434
+ // Payload completo
435
+ const payload = {
436
+ event_id,
437
+ event_name: eventName,
438
+ user_id: userId,
439
+ session_id: sessionId,
440
+ utms,
441
+ click_ids: clickIds,
442
+ timestamp,
443
+ page_url: window.location.href,
444
+ referrer: document.referrer,
445
+ user_agent: navigator.userAgent,
446
+ behavioral_data: {
447
+ ...data,
448
+ engagement_score: totalScore,
449
+ time_level: timeLevel,
450
+ scroll_score: scrollScore,
451
+ click_score: clickScore,
452
+ video_score: videoScore,
453
+ hover_score: hoverScore,
454
+ intention_level: intentionLevel
455
+ },
456
+ ...options
457
+ };
458
+
459
+ try {
460
+ // Enviar para Worker com retry automático (anti-blocking)
461
+ const result = await sendWithRetry(payload, '/api/tracking');
462
+
463
+ if (result.success) {
464
+ console.log(`✅ Evento ${eventName} enviado com sucesso:`, event_id, result);
465
+
466
+ // Atualizar estado de engajamento
467
+ updateEngagementState('timeOnPage', Math.round((Date.now() - timestamp) / 1000));
468
+ updateEngagementState('totalScore', totalScore);
469
+
470
+ return { success: true, event_id, result };
471
+ } else {
472
+ throw new Error(result.error || 'Envio falhou após todas as tentativas');
473
+ }
474
+ } catch (error) {
475
+ console.error(`❌ Erro ao enviar evento ${eventName}:`, error);
476
+ return { success: false, error: error.message, event_id, attempts: ANTI_BLOCKING_CONFIG.maxRetries };
477
+ }
478
+ }
479
+
480
+ // ── Conveniência: Track Direct ──────────────────────────────────────
481
+
482
+ /**
483
+ * trackLead() — Captura de lead com dados PII crus
484
+ *
485
+ * @param {object} userData - Dados do usuário (email, phone, name, etc.)
486
+ * @param {HTMLFormElement} form - Formulário HTML (opcional, para captura automática)
487
+ * @returns {Promise} - Promise com resultado
488
+ */
489
+ export async function trackLead(userData, form = null) {
490
+ // Se formulário fornecido, extrair dados PII automaticamente
491
+ let piiData = { ...userData };
492
+
493
+ if (form) {
494
+ const formPII = extractFormPII(form);
495
+ piiData = { ...piiData, ...formPII };
496
+ }
497
+
498
+ // Passa dados crus para Worker fazer hashing
499
+ // Worker fará SHA256 para máximo performance e segurança
500
+ return track('Lead', piiData);
501
+ }
502
+
503
+ /**
504
+ * trackPurchase() — Captura de compra
505
+ *
506
+ * @param {object} orderData - Dados da compra (order_id, value, currency, etc.)
507
+ * @param {HTMLFormElement} form - Formulário HTML (opcional, para captura automática)
508
+ * @returns {Promise} - Promise com resultado
509
+ */
510
+ export async function trackPurchase(orderData, form = null) {
511
+ let purchaseData = { ...orderData };
512
+
513
+ if (form) {
514
+ const formPII = extractFormPII(form);
515
+ purchaseData = { ...purchaseData, ...formPII };
516
+ }
517
+
518
+ return track('Purchase', purchaseData);
519
+ }
520
+
521
+ /**
522
+ * Auto-captura formulários de lead na página
523
+ * Intercepta submit de formulários e captura PII automaticamente
524
+ */
525
+ export function setupAutoFormCapture() {
526
+ if (!isBrowser) return;
527
+
528
+ // Interceptar todos os formulários
529
+ document.addEventListener('submit', async (e) => {
530
+ const form = e.target;
531
+
532
+ // Verificar se é um formulário de lead (baseado em campos)
533
+ const hasEmailField = form.querySelector('[name*="email"]');
534
+ const hasPhoneField = form.querySelector('[name*="phone"], [name*="telefone"], [name*="celular"]');
535
+
536
+ if (hasEmailField || hasPhoneField) {
537
+ e.preventDefault();
538
+
539
+ const piiData = extractFormPII(form);
540
+
541
+ if (isValidPII(piiData)) {
542
+ await trackLead(piiData, form);
543
+ console.log('✅ Lead capturado automaticamente com Advanced Matching:', piiData);
544
+ }
545
+
546
+ // Continuar com submit do formulário
547
+ setTimeout(() => form.submit(), 100);
548
+ }
549
+ }, true); // Capture para formulários dinâmicos
550
+ }
551
+
552
+ /**
553
+ * trackPurchase() — Captura de compra
554
+ *
555
+ * @param {object} orderData - Dados da compra (order_id, value, currency, etc.)
556
+ * @returns {Promise} - Promise com resultado
557
+ */
558
+ export async function trackPurchase(orderData) {
559
+ return track('Purchase', orderData);
560
+ }
561
+
562
+ /**
563
+ * trackViewContent() — Captura de visualização de conteúdo
564
+ *
565
+ * @param {object} contentData - Dados do conteúdo (content_id, content_name, value, currency)
566
+ * @returns {Promise} - Promise com resultado
567
+ */
568
+ export async function trackViewContent(contentData) {
569
+ return track('ViewContent', contentData);
570
+ }
571
+
572
+ /**
573
+ * trackAddToCart() — Captura de adição ao carrinho
574
+ *
575
+ * @param {object} cartData - Dados do carrinho (content_id, value, currency)
576
+ * @returns {Promise} - Promise com resultado
577
+ */
578
+ export async function trackAddToCart(cartData) {
579
+ return track('AddToCart', cartData);
580
+ }
581
+
582
+ // ── Inicialização ──────────────────────────────────────────────────────
583
+
584
+ /**
585
+ * Inicializa o cdpTrack SDK
586
+ * Deve ser chamado após carregar tracking.config.js
587
+ */
588
+ export async function init() {
589
+ if (!isBrowser) return;
590
+
591
+ if (CONFIG.debug) console.log('🚀 Inicializando cdpTrack SDK...');
592
+
593
+ // 0. Google Consent Mode v2 — DEVE ser o primeiro passo, antes de qualquer gtag
594
+ initConsentMode();
595
+
596
+ // 1. Inicializar anti-blocking
597
+ initAntiBlocking();
598
+
599
+ // 1-B. Salvar contexto de afiliado no localStorage (fallback para UTM resurrection)
600
+ _saveAffiliateContext();
601
+
602
+ // 2. Inicializar micro-events
603
+ initMicroEvents();
604
+
605
+ // 3. Inicializar auto-captura de formulários (respeitando CONFIG)
606
+ if (CONFIG.autoCaptureForms !== false) {
607
+ setupAutoFormCapture();
608
+ }
609
+
610
+ // 4. Expõe cdpTrack globalmente ANTES do BehaviorEngine (ele referencia window.cdpTrack)
611
+ window.cdpTrack = { track, trackLead, trackPurchase, trackViewContent, trackAddToCart, getUserId, getUserIdWithFallback, getSessionId, getUTMs, getUTMsWithFallback, updateConsent };
612
+
613
+ // 5. Carregar BehaviorEngine (respeitando CONFIG)
614
+ if (CONFIG.initBehaviorEngine !== false) {
615
+ try {
616
+ const behaviorModule = await import('./models/scenarios/behavior-engine.js');
617
+ const BehaviorEngine = behaviorModule.default;
618
+
619
+ if (BehaviorEngine && typeof BehaviorEngine.init === 'function') {
620
+ BehaviorEngine.init();
621
+ if (CONFIG.debug) console.log('✅ Behavior Engine inicializado');
622
+ }
623
+ } catch (error) {
624
+ // Não bloqueia o SDK — continua sem funcionalidades avançadas
625
+ if (CONFIG.debug) console.info('ℹ️ Behavior Engine não disponível:', error.message);
626
+ }
627
+ }
628
+
629
+ // 6. passCheckoutParams — controlado por CONFIG.passCheckoutParams + CONFIG.platforms
630
+ if (CONFIG.passCheckoutParams !== false && CONFIG.platforms?.length > 0) {
631
+ passCheckoutParams({ platforms: CONFIG.platforms });
632
+ }
633
+
634
+ if (CONFIG.debug) console.log('✅ cdpTrack SDK inicializado (Quantum Tier)');
635
+ }
636
+
637
+ // Auto-inicialização
638
+ if (isBrowser) {
639
+ // Inicializa após carregamento da página
640
+ window.addEventListener('DOMContentLoaded', init);
641
+ }