diff --git a/public/_4v_attr.js b/public/_4v_attr.js new file mode 100644 index 0000000..910155c --- /dev/null +++ b/public/_4v_attr.js @@ -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 + }; +})();