cdp-edge 1.23.2 → 1.24.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.
- package/README.md +82 -21
- package/bin/cdp-edge.js +10 -1
- package/contracts/agent-versions.json +42 -41
- package/contracts/types.ts +81 -0
- package/dist/commands/install.js +6 -1
- package/dist/commands/server.js +4 -4
- package/docs/whatsapp-ctwa.md +3 -2
- package/extracted-skill/tracking-events-generator/agents/database-agent.md +5 -4
- package/extracted-skill/tracking-events-generator/agents/fraud-detection-agent.md +0 -1
- package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +1 -1
- package/extracted-skill/tracking-events-generator/agents/ltv-predictor-agent.md +4 -4
- package/extracted-skill/tracking-events-generator/agents/ml-clustering-agent.md +81 -70
- package/extracted-skill/tracking-events-generator/agents/page-analyzer.md +6 -2
- package/extracted-skill/tracking-events-generator/cdpTrack.js +7 -0
- package/extracted-skill/tracking-events-generator/models/lancamento-imobiliario.md +344 -0
- package/extracted-skill/tracking-events-generator/route-intent-capture.js +222 -0
- package/package.json +9 -5
- package/server-edge-tracker/INSTALAR.md +5 -5
- package/server-edge-tracker/{index.js → index.ts} +186 -72
- package/server-edge-tracker/modules/{db.js → db.ts} +180 -69
- package/server-edge-tracker/modules/dispatch/{ga4.js → ga4.ts} +12 -10
- package/server-edge-tracker/modules/dispatch/meta.ts +138 -0
- package/server-edge-tracker/modules/dispatch/{platforms.js → platforms.ts} +58 -56
- package/server-edge-tracker/modules/dispatch/{tiktok.js → tiktok.ts} +22 -20
- package/server-edge-tracker/modules/dispatch/{whatsapp.js → whatsapp.ts} +59 -25
- package/server-edge-tracker/modules/{intelligence.js → intelligence.ts} +175 -60
- package/server-edge-tracker/modules/ml/{bidding.js → bidding.ts} +37 -35
- package/server-edge-tracker/modules/ml/{fraud.js → fraud.ts} +49 -56
- package/server-edge-tracker/modules/ml/{logistic.js → logistic.ts} +44 -19
- package/server-edge-tracker/modules/ml/{ltv.js → ltv.ts} +179 -83
- package/server-edge-tracker/modules/ml/{matchquality.js → matchquality.ts} +70 -26
- package/server-edge-tracker/modules/ml/segmentation.ts +407 -0
- package/server-edge-tracker/modules/utils.ts +186 -0
- package/server-edge-tracker/schema-ltv-feedback.sql +11 -0
- package/server-edge-tracker/types.ts +251 -0
- package/server-edge-tracker/wrangler.toml +24 -6
- package/templates/lancamento-imobiliario.md +344 -0
- package/docs/PixelBuilder-Documentacao-Completa (2).docx +0 -0
- package/server-edge-tracker/modules/dispatch/meta.js +0 -119
- package/server-edge-tracker/modules/ml/segmentation.js +0 -316
- package/server-edge-tracker/modules/utils.js +0 -89
- package/server-edge-tracker/worker.js +0 -4577
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDP Edge — Route Intent Capture
|
|
3
|
+
* @version 2.0.0
|
|
4
|
+
*
|
|
5
|
+
* Fluxo:
|
|
6
|
+
* 1. Usuário clica em "Ver rota" → Google Maps abre normalmente
|
|
7
|
+
* 2. Widget aparece com um botão de WhatsApp
|
|
8
|
+
* 3. Usuário clica → WhatsApp abre com mensagem pré-escrita
|
|
9
|
+
* 4. Usuário envia → corretor recebe e responde
|
|
10
|
+
*
|
|
11
|
+
* Uso:
|
|
12
|
+
* initRouteIntentCapture({
|
|
13
|
+
* whatsappNumber: '5511999999999', // número do plantão/corretor
|
|
14
|
+
* propertyName: 'Reserva do Jardim',
|
|
15
|
+
* propertyId: 'rj-001',
|
|
16
|
+
* propertyLat: -23.6519,
|
|
17
|
+
* propertyLng: -46.5330,
|
|
18
|
+
* });
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const isBrowser = typeof window !== 'undefined';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {object} options
|
|
25
|
+
* @param {string} options.whatsappNumber Número do plantão (ex: '5511999999999')
|
|
26
|
+
* @param {string} [options.propertyName] Nome do empreendimento
|
|
27
|
+
* @param {string} [options.propertyId] ID interno do imóvel
|
|
28
|
+
* @param {number} [options.propertyLat] Latitude
|
|
29
|
+
* @param {number} [options.propertyLng] Longitude
|
|
30
|
+
* @param {string} [options.routeSelector] CSS selector dos botões de rota (default auto)
|
|
31
|
+
* @param {string} [options.distanceBucket] 'very_close'|'close'|'nearby'|'moderate'|'far'
|
|
32
|
+
* @param {number} [options.distanceKm] Distância em km — exibe tempo estimado
|
|
33
|
+
* @param {string} [options.metaSignalBucket] 'hot'|'warm'|'cold'
|
|
34
|
+
* @param {string} [options.brokerName] Nome do corretor de plantão (ex: 'Ramon')
|
|
35
|
+
*/
|
|
36
|
+
export function initRouteIntentCapture(options = {}) {
|
|
37
|
+
if (!isBrowser) return;
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
whatsappNumber,
|
|
41
|
+
propertyName = 'o imóvel',
|
|
42
|
+
propertyId = null,
|
|
43
|
+
propertyLat = null,
|
|
44
|
+
propertyLng = null,
|
|
45
|
+
routeSelector = '[data-route-intent], a[href*="maps/dir"], a[href*="maps?q="]',
|
|
46
|
+
distanceBucket = null,
|
|
47
|
+
distanceKm = null,
|
|
48
|
+
metaSignalBucket = null,
|
|
49
|
+
brokerName = null,
|
|
50
|
+
} = options;
|
|
51
|
+
|
|
52
|
+
if (!whatsappNumber) {
|
|
53
|
+
console.warn('[RouteIntent] whatsappNumber é obrigatório.');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_injectStyles();
|
|
58
|
+
|
|
59
|
+
document.addEventListener('click', (e) => {
|
|
60
|
+
const btn = e.target.closest(routeSelector);
|
|
61
|
+
if (!btn) return;
|
|
62
|
+
_showWidget(btn, { whatsappNumber, propertyName, propertyId, propertyLat, propertyLng, distanceBucket, distanceKm, metaSignalBucket, brokerName });
|
|
63
|
+
}, true);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Widget ────────────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
function _showWidget(anchorEl, opts) {
|
|
69
|
+
document.getElementById('cdp-ri-widget')?.remove();
|
|
70
|
+
|
|
71
|
+
const travelMinutes = _estimateTravelMinutes(opts.distanceKm);
|
|
72
|
+
const travelText = travelMinutes
|
|
73
|
+
? `<p class="cdp-ri-travel">📍 Você está a cerca de <strong>${travelMinutes} min</strong> daqui</p>`
|
|
74
|
+
: '';
|
|
75
|
+
|
|
76
|
+
const brokerLine = opts.brokerName
|
|
77
|
+
? `<p class="cdp-ri-broker">Ao chegar, pergunte pelo <strong>${opts.brokerName}</strong> — ele vai estar te aguardando com tudo pronto 🤝</p>`
|
|
78
|
+
: '';
|
|
79
|
+
|
|
80
|
+
const widget = document.createElement('div');
|
|
81
|
+
widget.id = 'cdp-ri-widget';
|
|
82
|
+
widget.innerHTML = `
|
|
83
|
+
<div class="cdp-ri-inner">
|
|
84
|
+
${travelText}
|
|
85
|
+
<p class="cdp-ri-headline">Vi que você quer visitar o local! Confirme sua vinda enviando a mensagem abaixo — nossa equipe já fica de prontidão pra te receber 👇</p>
|
|
86
|
+
${brokerLine}
|
|
87
|
+
<button id="cdp-ri-btn" type="button">
|
|
88
|
+
${_waIcon()} Confirmar minha visita
|
|
89
|
+
</button>
|
|
90
|
+
<button id="cdp-ri-dismiss" type="button" class="cdp-ri-dismiss">Agora não</button>
|
|
91
|
+
</div>
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
anchorEl.insertAdjacentElement('afterend', widget);
|
|
95
|
+
setTimeout(() => widget.scrollIntoView({ behavior: 'smooth', block: 'nearest' }), 150);
|
|
96
|
+
|
|
97
|
+
document.getElementById('cdp-ri-btn').addEventListener('click', () => _handleClick(widget, opts));
|
|
98
|
+
document.getElementById('cdp-ri-dismiss').addEventListener('click', () => {
|
|
99
|
+
window.cdpTrack?.track?.('ViewContent', {
|
|
100
|
+
content_name: 'rota_dispensada',
|
|
101
|
+
property_id: opts.propertyId,
|
|
102
|
+
funnel_stage: 'route_dismiss',
|
|
103
|
+
intent_score: 'medium',
|
|
104
|
+
distance_bucket: opts.distanceBucket || undefined,
|
|
105
|
+
meta_signal_bucket: opts.metaSignalBucket || undefined,
|
|
106
|
+
});
|
|
107
|
+
widget.remove();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── Click — dispara evento e abre WhatsApp ────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
async function _handleClick(widget, opts) {
|
|
114
|
+
const btn = document.getElementById('cdp-ri-btn');
|
|
115
|
+
if (btn) btn.disabled = true;
|
|
116
|
+
|
|
117
|
+
// Evento Contact → Worker: LTV + hot lead trigger + CAPI
|
|
118
|
+
// Phone capturado depois via webhook quando a mensagem chegar
|
|
119
|
+
window.cdpTrack?.track?.('Contact', {
|
|
120
|
+
content_name: 'aviso_chegada_whatsapp',
|
|
121
|
+
property_id: opts.propertyId,
|
|
122
|
+
property_lat: opts.propertyLat,
|
|
123
|
+
property_lng: opts.propertyLng,
|
|
124
|
+
funnel_stage: 'route_click',
|
|
125
|
+
intent_score: 'high',
|
|
126
|
+
distance_bucket: opts.distanceBucket || undefined,
|
|
127
|
+
meta_signal_bucket: opts.metaSignalBucket || undefined,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Monta mensagem — acolhedora, foco em confirmar chegada
|
|
131
|
+
const travelMinutes = _estimateTravelMinutes(opts.distanceKm);
|
|
132
|
+
const travelLine = travelMinutes ? `Estou a cerca de ${travelMinutes} minutos daí.` : '';
|
|
133
|
+
const brokerLine = opts.brokerName
|
|
134
|
+
? `Vou procurar pelo ${opts.brokerName} ao chegar.`
|
|
135
|
+
: '';
|
|
136
|
+
|
|
137
|
+
const msg = [
|
|
138
|
+
`Oi! Estou interessado(a) em visitar o ${opts.propertyName} e gostaria de confirmar minha visita.`,
|
|
139
|
+
travelLine,
|
|
140
|
+
brokerLine || `Vocês conseguem me receber agora ou preciso marcar horário?`,
|
|
141
|
+
].filter(Boolean).join(' ');
|
|
142
|
+
|
|
143
|
+
_showSuccess(widget, opts.whatsappNumber, msg);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── Tela de confirmação ───────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
function _showSuccess(widget, whatsappNumber, msg) {
|
|
149
|
+
widget.innerHTML = `
|
|
150
|
+
<div class="cdp-ri-inner cdp-ri-ok">
|
|
151
|
+
<span class="cdp-ri-check">✅</span>
|
|
152
|
+
<p><strong>Abrindo WhatsApp...</strong><br><span>A equipe já foi avisada!</span></p>
|
|
153
|
+
</div>
|
|
154
|
+
`;
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
window.open(`https://wa.me/${whatsappNumber}?text=${encodeURIComponent(msg)}`, '_blank');
|
|
157
|
+
setTimeout(() => widget?.remove(), 3500);
|
|
158
|
+
}, 600);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
function _estimateTravelMinutes(distanceKm) {
|
|
164
|
+
if (!distanceKm || distanceKm <= 0) return null;
|
|
165
|
+
return Math.max(5, Math.round((distanceKm * 60 / 25) / 5) * 5);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function _waIcon() {
|
|
169
|
+
return `<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
170
|
+
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347z"/>
|
|
171
|
+
<path d="M12 0C5.373 0 0 5.373 0 12c0 2.122.554 4.118 1.528 5.852L0 24l6.335-1.513A11.933 11.933 0 0012 24c6.627 0 12-5.373 12-12S18.627 0 12 0zm0 21.818a9.818 9.818 0 01-5.002-1.368l-.359-.213-3.722.888.924-3.617-.234-.372A9.818 9.818 0 012.182 12C2.182 6.57 6.57 2.182 12 2.182S21.818 6.57 21.818 12 17.43 21.818 12 21.818z"/>
|
|
172
|
+
</svg>`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── Estilos ───────────────────────────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
function _injectStyles() {
|
|
178
|
+
if (document.getElementById('cdp-ri-styles')) return;
|
|
179
|
+
const s = document.createElement('style');
|
|
180
|
+
s.id = 'cdp-ri-styles';
|
|
181
|
+
s.textContent = `
|
|
182
|
+
#cdp-ri-widget {
|
|
183
|
+
margin-top: 12px;
|
|
184
|
+
padding: 16px 18px;
|
|
185
|
+
background: #f0fdf4;
|
|
186
|
+
border: 1.5px solid #22c55e;
|
|
187
|
+
border-radius: 12px;
|
|
188
|
+
font-family: Arial, sans-serif;
|
|
189
|
+
animation: cdp-ri-in .25s ease;
|
|
190
|
+
}
|
|
191
|
+
@keyframes cdp-ri-in {
|
|
192
|
+
from { opacity: 0; transform: translateY(-8px); }
|
|
193
|
+
to { opacity: 1; transform: translateY(0); }
|
|
194
|
+
}
|
|
195
|
+
.cdp-ri-inner { display: flex; flex-direction: column; gap: 10px; }
|
|
196
|
+
.cdp-ri-travel { margin: 0; font-size: 13px; color: #555; }
|
|
197
|
+
.cdp-ri-travel strong { color: #0f766e; }
|
|
198
|
+
.cdp-ri-headline { margin: 0; font-size: 14px; color: #15803d; font-weight: bold; line-height: 1.4; }
|
|
199
|
+
.cdp-ri-broker { margin: 0; font-size: 13px; color: #555; line-height: 1.4; }
|
|
200
|
+
.cdp-ri-broker strong { color: #0f766e; }
|
|
201
|
+
#cdp-ri-btn {
|
|
202
|
+
display: flex; align-items: center; justify-content: center; gap: 8px;
|
|
203
|
+
width: 100%; padding: 13px 16px;
|
|
204
|
+
background: #25D366; color: #fff;
|
|
205
|
+
border: none; border-radius: 10px;
|
|
206
|
+
font-size: 15px; font-weight: bold; cursor: pointer;
|
|
207
|
+
}
|
|
208
|
+
#cdp-ri-btn:hover { background: #1ebe5a; }
|
|
209
|
+
#cdp-ri-btn:disabled { background: #86efac; cursor: not-allowed; }
|
|
210
|
+
.cdp-ri-dismiss {
|
|
211
|
+
background: none; border: none;
|
|
212
|
+
color: #aaa; font-size: 12px;
|
|
213
|
+
cursor: pointer; padding: 2px 0; text-align: center;
|
|
214
|
+
}
|
|
215
|
+
.cdp-ri-dismiss:hover { color: #666; }
|
|
216
|
+
.cdp-ri-ok { align-items: center; text-align: center; gap: 8px; }
|
|
217
|
+
.cdp-ri-check { font-size: 28px; }
|
|
218
|
+
.cdp-ri-ok p { margin: 0; font-size: 14px; color: #15803d; line-height: 1.5; }
|
|
219
|
+
.cdp-ri-ok span { font-size: 13px; color: #555; }
|
|
220
|
+
`;
|
|
221
|
+
document.head.appendChild(s);
|
|
222
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdp-edge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
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,17 +23,19 @@
|
|
|
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/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",
|
|
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 && node tests/unit/utils.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",
|
|
30
30
|
"test:unit:payload": "node tests/unit/payload-validation.test.js",
|
|
31
|
+
"test:unit:utils": "node tests/unit/utils.test.js",
|
|
31
32
|
"test:all": "npm run test:unit",
|
|
32
33
|
"test:integration": "cd tests/integration && npx vitest run",
|
|
33
34
|
"agents:check": "node scripts/validate-agents.js",
|
|
34
35
|
"agents:sync": "node scripts/sync-agents.js",
|
|
35
36
|
"agents:sync:list": "node scripts/sync-agents.js --list",
|
|
36
|
-
"agents:sync:all": "node scripts/sync-agents.js --apply-all"
|
|
37
|
+
"agents:sync:all": "node scripts/sync-agents.js --apply-all",
|
|
38
|
+
"typecheck": "tsc --noEmit"
|
|
37
39
|
},
|
|
38
40
|
"keywords": [
|
|
39
41
|
"pixel",
|
|
@@ -70,12 +72,14 @@
|
|
|
70
72
|
"ora": "^8.0.0"
|
|
71
73
|
},
|
|
72
74
|
"devDependencies": {
|
|
75
|
+
"@cloudflare/workers-types": "^4.20260412.1",
|
|
73
76
|
"@semantic-release/changelog": "^6.0.3",
|
|
74
77
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
75
78
|
"@semantic-release/github": "^12.0.6",
|
|
76
79
|
"@semantic-release/npm": "^13.1.5",
|
|
77
80
|
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
78
|
-
"@types/node": "^20.
|
|
79
|
-
"semantic-release": "^25.0.3"
|
|
81
|
+
"@types/node": "^20.19.39",
|
|
82
|
+
"semantic-release": "^25.0.3",
|
|
83
|
+
"typescript": "^6.0.2"
|
|
80
84
|
}
|
|
81
85
|
}
|
|
@@ -298,7 +298,7 @@ wrangler d1 execute cdp-edge-db --remote --command="SELECT event_name, email, ci
|
|
|
298
298
|
```
|
|
299
299
|
ERROR: Can't deploy routes that are assigned to another worker.
|
|
300
300
|
"server-edge-tracker" is already assigned to routes:
|
|
301
|
-
-
|
|
301
|
+
- SEU_DOMINIO/track*
|
|
302
302
|
```
|
|
303
303
|
|
|
304
304
|
### SOLUÇÃO 1 — Via Painel Cloudflare (RECOMENDADO):
|
|
@@ -306,7 +306,7 @@ ERROR: Can't deploy routes that are assigned to another worker.
|
|
|
306
306
|
1. Acesse: https://dash.cloudflare.com/[ID_DA_CONTA]/workers/overview
|
|
307
307
|
2. Clique no worker que está usando as rotas do seu domínio
|
|
308
308
|
3. Vá em Settings → Triggers → Routes
|
|
309
|
-
4. Clique "Delete" nas rotas do domínio `
|
|
309
|
+
4. Clique "Delete" nas rotas do domínio `SEU_DOMINIO`
|
|
310
310
|
5. Repita o `wrangler deploy`
|
|
311
311
|
|
|
312
312
|
### SOLUÇÃO 2 — Via Wrangler CLI:
|
|
@@ -325,11 +325,11 @@ Se não quiser remover rotas existentes, use sufixo:
|
|
|
325
325
|
|
|
326
326
|
```toml
|
|
327
327
|
[[routes]]
|
|
328
|
-
pattern = "
|
|
329
|
-
zone_name = "
|
|
328
|
+
pattern = "SEU_DOMINIO/track-worker-novo*"
|
|
329
|
+
zone_name = "SEU_DOMINIO"
|
|
330
330
|
```
|
|
331
331
|
|
|
332
|
-
**URL do tracking:** `https://
|
|
332
|
+
**URL do tracking:** `https://SEU_DOMINIO/track-worker-novo`
|
|
333
333
|
|
|
334
334
|
---
|
|
335
335
|
|