feat(attribution): add universal client-side attribution script _4v_attr.js
This commit is contained in:
182
public/_4v_attr.js
Normal file
182
public/_4v_attr.js
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
/**
|
||||||
|
* 4Ventures — Universal Attribution Script v1.0
|
||||||
|
* Almacena parámetros UTM y Click IDs en cookie '_4v_attr' (90 días)
|
||||||
|
* e inyecta automáticamente en formularios y enlaces de WhatsApp.
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var COOKIE_NAME = '_4v_attr';
|
||||||
|
var COOKIE_DAYS = 90;
|
||||||
|
|
||||||
|
function getQueryParam(name) {
|
||||||
|
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
|
||||||
|
return results !== null ? decodeURIComponent(results[1].replace(/\+/g, ' ')) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCookie(name, value, days) {
|
||||||
|
var expires = '';
|
||||||
|
if (days) {
|
||||||
|
var date = new Date();
|
||||||
|
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||||
|
expires = '; expires=' + date.toUTCString();
|
||||||
|
}
|
||||||
|
var secure = location.protocol === 'https:' ? '; Secure' : '';
|
||||||
|
document.cookie = name + '=' + encodeURIComponent(JSON.stringify(value)) + expires + '; path=/; SameSite=Lax' + secure;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCookie(name) {
|
||||||
|
var nameEQ = name + '=';
|
||||||
|
var ca = document.cookie.split(';');
|
||||||
|
for (var i = 0; i < ca.length; i++) {
|
||||||
|
var c = ca[i];
|
||||||
|
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
||||||
|
if (c.indexOf(nameEQ) === 0) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(decodeURIComponent(c.substring(nameEQ.length, c.length)));
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectReferrerSource() {
|
||||||
|
var ref = document.referrer.toLowerCase();
|
||||||
|
if (!ref) return { source: 'direct', medium: 'none' };
|
||||||
|
if (ref.indexOf('google.') !== -1) return { source: 'google', medium: 'organic' };
|
||||||
|
if (ref.indexOf('bing.') !== -1) return { source: 'bing', medium: 'organic' };
|
||||||
|
if (ref.indexOf('instagram.') !== -1) return { source: 'instagram', medium: 'social' };
|
||||||
|
if (ref.indexOf('facebook.') !== -1) return { source: 'facebook', medium: 'social' };
|
||||||
|
if (ref.indexOf('linkedin.') !== -1) return { source: 'linkedin', medium: 'social' };
|
||||||
|
if (ref.indexOf('tiktok.') !== -1) return { source: 'tiktok', medium: 'social' };
|
||||||
|
if (ref.indexOf('youtube.') !== -1) return { source: 'youtube', medium: 'social' };
|
||||||
|
try {
|
||||||
|
var url = new URL(ref);
|
||||||
|
return { source: url.hostname, medium: 'referral' };
|
||||||
|
} catch(e) {
|
||||||
|
return { source: 'referral', medium: 'referral' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function captureAttribution() {
|
||||||
|
var utmSource = getQueryParam('utm_source');
|
||||||
|
var utmMedium = getQueryParam('utm_medium');
|
||||||
|
var utmCampaign = getQueryParam('utm_campaign');
|
||||||
|
var utmContent = getQueryParam('utm_content');
|
||||||
|
var utmTerm = getQueryParam('utm_term');
|
||||||
|
var gclid = getQueryParam('gclid');
|
||||||
|
var fbclid = getQueryParam('fbclid');
|
||||||
|
var ttclid = getQueryParam('ttclid');
|
||||||
|
var liFatId = getQueryParam('li_fat_id');
|
||||||
|
|
||||||
|
var clickId = gclid || fbclid || ttclid || liFatId || null;
|
||||||
|
var clickType = gclid ? 'gclid' : fbclid ? 'fbclid' : ttclid ? 'ttclid' : liFatId ? 'li_fat_id' : null;
|
||||||
|
|
||||||
|
var existingAttr = getCookie(COOKIE_NAME);
|
||||||
|
|
||||||
|
// Si hay nuevos parámetros en URL, actualizamos o creamos
|
||||||
|
if (utmSource || clickId) {
|
||||||
|
var newAttr = {
|
||||||
|
source: utmSource || (gclid ? 'google' : fbclid ? 'meta' : ttclid ? 'tiktok' : liFatId ? 'linkedin' : 'unknown'),
|
||||||
|
medium: utmMedium || (clickId ? 'cpc' : 'unknown'),
|
||||||
|
campaign: utmCampaign || '',
|
||||||
|
content: utmContent || '',
|
||||||
|
term: utmTerm || '',
|
||||||
|
click_id: clickId,
|
||||||
|
click_type: clickType,
|
||||||
|
landing_page: window.location.href,
|
||||||
|
referrer: document.referrer || '',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
setCookie(COOKIE_NAME, newAttr, COOKIE_DAYS);
|
||||||
|
return newAttr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si no hay params y ya hay cookie previa, la mantenemos
|
||||||
|
if (existingAttr) {
|
||||||
|
return existingAttr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si es primera visita sin parámetros, deducimos del referrer
|
||||||
|
var refData = detectReferrerSource();
|
||||||
|
var defaultAttr = {
|
||||||
|
source: refData.source,
|
||||||
|
medium: refData.medium,
|
||||||
|
campaign: '',
|
||||||
|
content: '',
|
||||||
|
term: '',
|
||||||
|
click_id: null,
|
||||||
|
click_type: null,
|
||||||
|
landing_page: window.location.href,
|
||||||
|
referrer: document.referrer || '',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
setCookie(COOKIE_NAME, defaultAttr, COOKIE_DAYS);
|
||||||
|
return defaultAttr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectHiddenFields(attr) {
|
||||||
|
var forms = document.querySelectorAll('form');
|
||||||
|
var fieldKeys = ['source', 'medium', 'campaign', 'content', 'term', 'click_id', 'click_type', 'landing_page', 'referrer'];
|
||||||
|
|
||||||
|
forms.forEach(function(form) {
|
||||||
|
fieldKeys.forEach(function(key) {
|
||||||
|
var inputName = 'utm_' + key;
|
||||||
|
if (key === 'click_id' || key === 'click_type' || key === 'landing_page' || key === 'referrer') {
|
||||||
|
inputName = key;
|
||||||
|
}
|
||||||
|
var val = attr[key] || '';
|
||||||
|
var existing = form.querySelector('input[name="' + inputName + '"]');
|
||||||
|
if (existing) {
|
||||||
|
existing.value = val;
|
||||||
|
} else {
|
||||||
|
var input = document.createElement('input');
|
||||||
|
input.type = 'hidden';
|
||||||
|
input.name = inputName;
|
||||||
|
input.value = val;
|
||||||
|
form.appendChild(input);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectWhatsAppLinks(attr) {
|
||||||
|
if (!attr.click_id && !attr.source) return;
|
||||||
|
var links = document.querySelectorAll('a[href*="wa.me"], a[href*="api.whatsapp.com"]');
|
||||||
|
var tag = ' [ref:' + (attr.source || 'web') + (attr.click_id ? '-' + attr.click_id.substring(0, 8) : '') + ']';
|
||||||
|
links.forEach(function(link) {
|
||||||
|
var href = link.getAttribute('href');
|
||||||
|
if (href && href.indexOf('[ref:') === -1) {
|
||||||
|
if (href.indexOf('text=') !== -1) {
|
||||||
|
link.setAttribute('href', href + encodeURIComponent(tag));
|
||||||
|
} else {
|
||||||
|
var sep = href.indexOf('?') !== -1 ? '&' : '?';
|
||||||
|
link.setAttribute('href', href + sep + 'text=' + encodeURIComponent('Hola,' + tag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicialización inmediata y en DOMContentLoaded
|
||||||
|
var currentAttr = captureAttribution();
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
injectHiddenFields(currentAttr);
|
||||||
|
injectWhatsAppLinks(currentAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exponer API global
|
||||||
|
window._4v = {
|
||||||
|
getAttribution: function() { return currentAttr; },
|
||||||
|
refresh: captureAttribution,
|
||||||
|
inject: init
|
||||||
|
};
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user