/* ===================================================================== DIAGNÓSTICO DE TU FORMULARIO (v7) - Solo observa. No cambia nada de tu landing, tu CRM ni tus etiquetas. - Se borra solo al recargar la página. - Antes de usarlo: en la consola, engranaje → marca "Preserve log". - Uso: pega todo este bloque en la consola, presiona Enter y envía un formulario de prueba (usa "PRUEBA" como nombre). - Para volver a ver el resumen, escribe: diagnostico() ===================================================================== */ (function () { if (window.__diagLQC) { console.log('El diagnóstico ya está activo. Envía tu formulario de prueba.'); return; } window.__diagLQC = true; var envio = null; // momento del primer envío var destinos = []; // { via, url, t, tr, estado, texto, tipo } var disparos = []; // { plataforma, clave, t } var temporizador = null; // Servicios de medición: no son destinos del formulario var MEDICION = /google-analytics\.com|googletagmanager\.com|googleadservices\.com|doubleclick\.net|google\.[a-z.]+\/(pagead|ccm|ads|measurement)|facebook\.com\/tr|facebook\.net|tiktok\.com|bing\.com|clarity\.ms|hotjar|linkedin\.com|licdn\.com|snapchat\.com|criteo|script\.google\.com|sentry|newrelic|cloudflareinsights/i; function param(url, nombre) { try { return new URL(url, location.href).searchParams.get(nombre) || ''; } catch (e) { return ''; } } function dominio(url) { try { return new URL(url, location.href).hostname; } catch (e) { return String(url).slice(0, 80); } } // Identifica conversiones de plataformas publicitarias function conversionDe(url) { var m = url.match(/(?:googleadservices\.com\/pagead\/conversion|google\.[a-z.]+\/pagead\/1p-conversion)\/(\d+)/i); if (m) return { plataforma: 'Google Ads', clave: m[1] + '|' + param(url, 'label') }; if (/facebook\.com\/tr/i.test(url)) { var ev = param(url, 'ev'); if (ev && ev !== 'PageView') return { plataforma: 'Meta', clave: ev }; return null; } if (/analytics\.tiktok\.com\/api\/v2\/pixel/i.test(url)) return { plataforma: 'TikTok', clave: 'tiktok' }; if (/px\.ads\.linkedin\.com\/collect/i.test(url) && param(url, 'conversionId')) return { plataforma: 'LinkedIn', clave: param(url, 'conversionId') }; return null; } // Valores que la persona escribió en la página (nombre, correo, celular…) // Solo valores únicos: correos y números de 7+ dígitos. // Se descarta todo lo que ya aparece en la dirección de la página. function valoresEscritos() { var vals = []; var pagina = location.href.toLowerCase(); var campos = document.querySelectorAll('input, textarea'); for (var i = 0; i < campos.length; i++) { var c = campos[i], tipo = (c.type || 'text').toLowerCase(); if (['text', 'email', 'tel', 'number', 'search', 'textarea'].indexOf(tipo) === -1) continue; var v = String(c.value || '').trim().toLowerCase(); if (!v || pagina.indexOf(v) !== -1) continue; if (v.indexOf('@') > 0) { vals.push(v); continue; } var digitos = v.replace(/\D/g, ''); if (digitos.length >= 7 && digitos.length === v.replace(/[\s+()-]/g, '').length) vals.push(digitos); } return vals; } function cuerpoComoTexto(body) { try { if (!body) return ''; if (typeof body === 'string') return body; if (body instanceof URLSearchParams) return body.toString(); if (typeof FormData !== 'undefined' && body instanceof FormData) { var partes = []; body.forEach(function (v, k) { partes.push(k + '=' + (typeof v === 'string' ? v : '')); }); return partes.join('&'); } } catch (e) {} return ''; } // Un envío es del formulario solo si lleva lo que la persona escribió function llevaDatos(body) { var crudo = cuerpoComoTexto(body); if (!crudo) return false; var versiones = [crudo]; try { versiones.push(decodeURIComponent(crudo)); } catch (e) {} try { versiones.push(decodeURIComponent(crudo.replace(/\+/g, ' '))); } catch (e) {} var s = versiones.join(' ').toLowerCase(); return valoresEscritos().some(function (v) { return s.indexOf(v) !== -1; }); } function esCandidato(url, metodo, body) { if (!url) return false; if (MEDICION.test(url)) return false; if (!/^(POST|PUT|PATCH)$/i.test(metodo || '')) return false; return llevaDatos(body); } function nuevoDestino(via, url, tipo) { if (!envio) envio = Date.now(); var d = { via: via, url: url, t: Date.now(), tr: null, estado: null, texto: '', tipo: tipo }; destinos.push(d); console.log('• Envío detectado → ' + dominio(url) + ' (' + via + ')'); return d; } function cerrarDestino(d, estado, texto, tipo) { d.estado = estado; d.texto = String(texto || ''); d.tipo = tipo; d.tr = Date.now(); console.log('• ' + dominio(d.url) + ': ' + descRespuesta(d)); // Se imprime al instante: muchas landings cambian de página apenas llega la respuesta imprimirResumen(); clearTimeout(temporizador); temporizador = setTimeout(imprimirResumen, 1500); } function descRespuesta(d) { var vista = d.texto.replace(/\s+/g, ' ').slice(0, 160); switch (d.tipo) { case 'visible': if (d.estado >= 200 && d.estado < 300) return 'recibido (código ' + d.estado + '). Ojo: algunos sistemas responden OK aunque rechacen el lead. Detalle: ' + (vista || '(vacío)'); return 'rechazado o error (código ' + d.estado + '). Detalle: ' + (vista || '(vacío)'); case 'opaca': return 'respuesta no visible: ese servicio no deja que la página lea lo que contestó.'; case 'error': return 'sin respuesta: falló la conexión o se bloqueó.'; case 'sin': return 'este tipo de envío no espera respuesta.'; case 'clasico': return 'la respuesta se ve en la página siguiente. Si muestra un error, el lead fue rechazado.'; default: return 'esperando respuesta…'; } } // 1) Envío clásico del formulario (la página cambia) // Los píxeles a veces envían con formularios ocultos hacia un recuadro: no cuentan function esFormularioReal(f) { var accion = f.action || location.href; if (MEDICION.test(accion)) return false; var destino = (f.target || '').toLowerCase(); if (destino && ['_self', '_top', '_parent'].indexOf(destino) === -1) return false; return true; } document.addEventListener('submit', function () { if (!envio) envio = Date.now(); }, true); window.addEventListener('submit', function (ev) { var f = ev.target; if (!ev.defaultPrevented && f && !f.__diagLQC && esFormularioReal(f)) { f.__diagLQC = true; nuevoDestino('envío clásico: la página cambia', f.action || location.href, 'clasico'); imprimirResumen(); setTimeout(function () { f.__diagLQC = false; }, 1500); } }, false); var oSubmit = HTMLFormElement.prototype.submit; HTMLFormElement.prototype.submit = function () { if (!this.__diagLQC && esFormularioReal(this)) { nuevoDestino('envío clásico: la página cambia', this.action || location.href, 'clasico'); imprimirResumen(); } return oSubmit.apply(this, arguments); }; // 2) fetch if (window.fetch) { var oFetch = window.fetch; window.fetch = function (input, init) { var url = typeof input === 'string' ? input : (input && input.url) || ''; var metodo = (init && init.method) || (input && input.method) || 'GET'; var cuerpo = init && init.body; var p = oFetch.apply(this, arguments); if (esCandidato(url, metodo, cuerpo)) { var d = nuevoDestino('envío en segundo plano (fetch)', url, 'pendiente'); p.then(function (r) { if (r.type === 'opaque') { cerrarDestino(d, null, '', 'opaca'); return; } try { r.clone().text().then(function (t) { cerrarDestino(d, r.status, t, 'visible'); }, function () { cerrarDestino(d, r.status, '', 'visible'); }); } catch (e) { cerrarDestino(d, r.status, '', 'visible'); } }, function () { cerrarDestino(d, 0, '', 'error'); }); } return p; }; } // 3) XMLHttpRequest (incluye jQuery) var xOpen = XMLHttpRequest.prototype.open; var xSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (m, u) { this.__diagLQC = { m: String(m || 'GET').toUpperCase(), u: String(u || '') }; return xOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function (cuerpo) { var x = this, info = x.__diagLQC; if (info && esCandidato(info.u, info.m, cuerpo)) { var d = nuevoDestino('envío en segundo plano (XHR / jQuery)', info.u, 'pendiente'); x.addEventListener('loadend', function () { if (x.status === 0) { cerrarDestino(d, 0, '', 'error'); return; } var t = ''; try { t = (x.responseType === '' || x.responseType === 'text') ? x.responseText : '[respuesta no textual]'; } catch (e) {} cerrarDestino(d, x.status, t, 'visible'); }); } return xSend.apply(this, arguments); }; // 4) sendBeacon if (navigator.sendBeacon) { var oBeacon = navigator.sendBeacon.bind(navigator); navigator.sendBeacon = function (url, cuerpo) { if (esCandidato(url, 'POST', cuerpo)) { var d = nuevoDestino('sendBeacon', url, 'pendiente'); cerrarDestino(d, null, '', 'sin'); } return oBeacon.apply(null, arguments); }; } // 5) Conversiones (observa las solicitudes de red de los píxeles) try { new PerformanceObserver(function (lista) { lista.getEntries().forEach(function (e) { var c = conversionDe(e.name); if (!c) return; var t = Math.round(performance.timeOrigin + e.startTime); // Una misma conversión manda varias señales seguidas: se agrupan var repetida = disparos.some(function (x) { return x.plataforma === c.plataforma && x.clave === c.clave && Math.abs(x.t - t) < 2000; }); if (repetida) return; disparos.push({ plataforma: c.plataforma, clave: c.clave, t: t }); console.log('• Conversión detectada: ' + c.plataforma + (c.plataforma === 'Meta' ? ' (' + c.clave + ')' : '')); }); }).observe({ type: 'resource', buffered: false }); } catch (e) {} // ---------- Resumen ---------- // Se compara contra el ÚLTIMO destino: es el que decide si el lead entró function referencia() { for (var i = destinos.length - 1; i >= 0; i--) { var d = destinos[i]; if (d.tipo === 'clasico') return { t: d.t, nombre: 'el envío final del formulario' }; if (d.tipo === 'visible') return { t: d.tr, nombre: 'la respuesta final del destino' }; } return null; } function imprimirResumen() { var ref = referencia(); var tras = disparos.filter(function (c) { return envio && c.t >= envio - 500; }); var algunaAntes = false; var lineaVias = destinos.length ? destinos.map(function (d) { return d.via; }).filter(function (v, i, a) { return a.indexOf(v) === i; }).join(' + ') : 'no detectado'; // Agrupa los envíos al mismo lugar var grupos = []; destinos.forEach(function (d) { var dom = dominio(d.url), g = null; for (var i = 0; i < grupos.length; i++) if (grupos[i].dom === dom) g = grupos[i]; if (!g) { g = { dom: dom, veces: 0, ultimo: d }; grupos.push(g); } g.veces++; g.ultimo = d; }); var lineasDestino = grupos.length ? (grupos.length > 1 ? [' Tus datos pasan por ' + grupos.length + ' lugares, en este orden:'] : []) .concat(grupos.map(function (g, i) { return ' ' + (i + 1) + ') ' + g.dom + (g.veces > 1 ? ' (' + g.veces + ' veces)' : '') + (i === grupos.length - 1 && grupos.length > 1 ? ' ← envío final' : ''); })) : [' no detectado']; var lineasRespuesta = grupos.length ? grupos.map(function (g, i) { return ' ' + (i + 1) + ') ' + descRespuesta(g.ultimo); }) : [' sin respuesta detectada']; var lineasConv = []; if (!tras.length) { lineasConv.push(' No se detectó en esta página.'); } else { // Agrupa por plataforma: cuántas conversiones distintas y cuándo se dispararon var porPlataforma = []; tras.forEach(function (c) { var cuando = 'al enviar'; if (ref) { var antes = c.t < ref.t; if (antes) algunaAntes = true; cuando = ((antes ? 'ANTES de ' : 'DESPUÉS de ') + ref.nombre).replace('de el ', 'del '); } else { algunaAntes = true; } var p = null; for (var i = 0; i < porPlataforma.length; i++) if (porPlataforma[i].nombre === c.plataforma) p = porPlataforma[i]; if (!p) { p = { nombre: c.plataforma, claves: [], cuandos: [] }; porPlataforma.push(p); } if (p.claves.indexOf(c.clave) === -1) p.claves.push(c.clave); if (p.cuandos.indexOf(cuando) === -1) p.cuandos.push(cuando); }); porPlataforma.forEach(function (p) { var detalle = ''; if (p.nombre === 'Meta') detalle = ' (' + p.claves.join(', ') + ')'; else if (p.claves.length > 1 && p.nombre !== 'TikTok') detalle = ' (' + p.claves.length + ' conversiones distintas)'; lineasConv.push(' ' + p.nombre + detalle + ': ' + p.cuandos.join(' y ')); }); } var significa = []; if (!destinos.length) { significa.push('No se detectó a dónde van los datos. El formulario puede estar dentro de un recuadro incrustado de otro servicio, o no se envió. Revisa que el envío haya funcionado y repite.'); } else { if (tras.length && algunaAntes) { significa.push('Tu conversión se cuenta antes de saber si el sistema aceptó el lead. Si lo rechaza o falla la conexión, la plataforma igual lo suma. Esto puede explicar parte de tu diferencia.'); } else if (tras.length) { significa.push('Tu conversión se cuenta después de la respuesta. Falta saber si se dispara solo cuando la respuesta es exitosa o siempre: si también se dispara con rechazos, la plataforma suma leads que tu CRM no tiene.'); } else { significa.push('No viste la conversión aquí. Lo más común es que se cuente al cargar la página de gracias; también puede contarse desde un servidor o no estar configurada.'); } if (destinos.some(function (d) { return d.tipo === 'clasico'; })) { significa.push(tras.length && algunaAntes ? 'El envío final es clásico: la respuesta la ves en la página siguiente. Si esa página muestra un error, ese lead fue rechazado, aunque las conversiones ya se hayan contado.' : 'El envío final es clásico: la respuesta la ves en la página siguiente. Si esa página muestra un error, ese lead fue rechazado.'); } var cuenta = {}; tras.forEach(function (c) { if (c.plataforma !== 'TikTok') { var k = c.plataforma + '|' + c.clave; cuenta[k] = (cuenta[k] || 0) + 1; } }); Object.keys(cuenta).forEach(function (k) { if (cuenta[k] > 1) significa.push('La misma conversión de ' + k.split('|')[0] + ' se disparó ' + cuenta[k] + ' veces en un solo envío: la plataforma podría estar contando doble.'); }); } var lineas = ['', 'DIAGNÓSTICO DE TU FORMULARIO', '1. Cómo envía tu formulario: ' + lineaVias, '2. A dónde van los datos:'].concat(lineasDestino) .concat(['3. Qué respondió cada destino:']).concat(lineasRespuesta) .concat(['4. Conversiones de la plataforma:']).concat(lineasConv) .concat(['', 'QUÉ SIGNIFICA']).concat(significa) .concat(['', 'LO QUE ESTE DIAGNÓSTICO NO TE DICE', 'Esto revisa un solo envío. Para saber qué pasa con TODOS tus leads,', 'necesitas registrarlos uno por uno.', '']); console.log('%c' + lineas.join('\n'), 'font-size:13px;line-height:1.5'); } window.diagnostico = imprimirResumen; var yaSalio = false; function alSalir() { if (envio && !yaSalio) { yaSalio = true; imprimirResumen(); } } window.addEventListener('beforeunload', alSalir); window.addEventListener('pagehide', alSalir); console.log('%cDiagnóstico activo. Ahora envía tu formulario de prueba.', 'background:#FFE94D;color:#111;padding:4px 8px;font-weight:bold'); })();