// Medidor de Rendimiento Sell-U — App principal (Hero · Analizando · Resultados · Captura lead) // Llama al backend Laravel para datos reales de PageSpeed + HTML scraping. const { useState, useEffect, useRef } = React; const ANALYZE_STEPS = window.ConvierteEngine.CATEGORIES.map((c) => c.label); function scrollToId(id) { const el = document.getElementById(id); if (!el) return; const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; const y = el.getBoundingClientRect().top + window.scrollY - 24; window.scrollTo({ top: y, behavior: reduce ? "auto" : "smooth" }); } function normalizeUrl(raw) { let u = (raw || "").trim(); if (!u) return ""; if (!/^https?:\/\//i.test(u)) u = "https://" + u; return u; } function isValidUrl(raw) { const u = normalizeUrl(raw); return /^https?:\/\/([\w-]+\.)+[a-z]{2,}(\/\S*)?$/i.test(u); } function prettyHost(url) { try { return new URL(normalizeUrl(url)).host.replace(/^www\./, ""); } catch { return url; } } /* Nav y Footer del convertidor se eliminaron — ahora la vista Blade envuelve el #root con y de Sell-U para que el convertidor se integre visualmente con el resto del sitio. */ /* ===================== HERO ===================== */ function Hero({ url, setUrl, device, setDevice, onAnalyze, error }) { const inputRef = useRef(null); return (
); } /* ===================== ANALIZANDO ===================== */ function Analyzing({ url, device, done, error, onRetry }) { const reduce = useRef(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches).current; const total = ANALYZE_STEPS.length; // 'done' viene del padre cuando termina el fetch — pero animamos visualmente // un progreso suave mientras esperamos la respuesta del backend. const [step, setStep] = useState(0); useEffect(() => { if (done) { setStep(total); return; } const perStep = reduce ? 280 : 1100; // analisis backend tarda ~10–20s const id = setInterval(() => { setStep((s) => Math.min(total - 1, s + 1)); }, perStep); return () => clearInterval(id); }, [done]); const pct = Math.round((step / total) * 100); if (error) { return (

No pudimos completar el análisis

{error}

); } return (

Revisando {prettyHost(url)}…

Estamos midiendo tu página en {device === "mobile" ? "celular" : "computador"} usando la misma tecnología que Google. Esto toma 10 a 20 segundos.

{pct}%
    {ANALYZE_STEPS.map((label, i) => { const state = i < step ? "done" : i === step ? "active" : "wait"; return (
  • ); })}
); } /* ===================== RESULTADOS ===================== */ function PotentialCard({ pct }) { return (
Mejora potencial de conversión estimada
+{pct}%

Resolviendo los problemas detectados, basado en tu puntaje actual.

); } /* ===================== VELOCIDAD REAL (Core Web Vitals con nombres humanos) ===================== */ function CWVCard({ label, sublabel, explain, value, unit, threshold, lowerBetter = true }) { if (value === null || value === undefined) { return (
{label}

No pudimos medirlo en este momento.

); } // Determinar tono según threshold ([bueno, necesita-mejora]) let tone = "success"; if (lowerBetter) { if (value > threshold[1]) tone = "danger"; else if (value > threshold[0]) tone = "warning"; } else { if (value < threshold[0]) tone = "danger"; else if (value < threshold[1]) tone = "warning"; } const display = unit === "ms" && value > 1000 ? (value / 1000).toFixed(1) + "s" : unit === "ms" ? Math.round(value) + "ms" : unit === "cls" ? value.toFixed(2) : value; const statusText = tone === "success" ? "✓ Muy bien" : tone === "warning" ? "Mejorable" : "✗ Problema"; const statusFull = tone === "success" ? "Tu sitio cumple bien con esta medida." : tone === "warning" ? "Está aceptable, pero podría mejorar." : "Está afectando la experiencia de tus visitantes."; return (
{display}
{label}
{sublabel &&
{sublabel}
}

{statusText}

{statusFull}

{explain &&

{explain}

}
); } function CoreWebVitalsSection({ cwv }) { if (!cwv) return null; return (
DATOS REALES Medido en vivo por Google

¿Qué tan rápida y estable es tu página?

Estas 4 mediciones son las que Google usa para decidir cuánto destaca tu sitio en los resultados de búsqueda y cuán cómoda es tu página para tus visitantes.

); } /* ===================== AUDITORÍA TÉCNICA (datos REALES) ===================== */ function AuditoriaTecnicaSection({ a }) { if (!a || !a.ok) return null; const { fmtNum, fmtCompact } = window.ConvierteEngine; const w = a.whois || {}; const c = a.crawl || {}; const s = a.seo || {}; const idx = a.indexed || {}; const t = a.tech || {}; const ssl = a.ssl || {}; const sec = a.security || {}; const ct = a.content || {}; // KPIs reales con lenguaje natural const kpis = []; // 1. Páginas indexadas if (idx.indexed_pages !== null && idx.indexed_pages !== undefined) { kpis.push({ icon:"globe", label:"Páginas que aparecen en Google", value:fmtCompact(idx.indexed_pages), hint: idx.indexed_pages > 10 ? "Suficientes páginas en los resultados de búsqueda." : "Pocas páginas. Google podría no estar viendo todo tu sitio.", tone: idx.indexed_pages > 10 ? "success" : "warning" }); } // 2. Edad del dominio if (w.domain_age_years) { kpis.push({ icon:"network", label:"Tu sitio existe hace", value:w.domain_age_years, suffix:" años", hint: w.domain_age_years >= 3 ? "Google confía más en dominios viejos." : (w.domain_age_years >= 1 ? "Aún relativamente nuevo." : "Muy nuevo. Google tarda en confiar."), tone: w.domain_age_years >= 3 ? "success" : (w.domain_age_years >= 1 ? "info" : "warning") }); } // 3. Páginas crawleadas con OK kpis.push({ icon:"check", label:"Páginas revisadas", value:c.pages_crawled || 0, hint: (c.broken_count || 0) === 0 ? "Todas funcionando bien." : `${c.broken_count} con problemas (links rotos).`, tone: (c.broken_count || 0) === 0 ? "success" : "warning" }); // 4. Links rotos if ((c.broken_count || 0) > 0) { kpis.push({ icon:"alert", label:"Enlaces rotos en tu sitio", value:c.broken_count, hint:"Cuando alguien hace click y la página no existe. Frustra al visitante y a Google.", tone: c.broken_count > 5 ? "danger" : "warning" }); } // 5. SSL grade (candado de seguridad) if (ssl.grade) { kpis.push({ icon:"shield", label:"Seguridad del candado (HTTPS)", value:ssl.grade, suffix:"/A+", hint: ssl.grade.startsWith('A') ? "Excelente. Tu sitio es seguro para el visitante." : (ssl.grade === 'B' ? "Aceptable, podría mejorar." : "Tu certificado de seguridad tiene problemas."), tone: ssl.grade.startsWith('A') ? "success" : (ssl.grade === 'B' ? "info" : "warning") }); } else if (ssl.has_https === false) { kpis.push({ icon:"alert", label:"Candado de seguridad", value:"NO TIENE", hint:"Tu sitio no usa HTTPS — Chrome lo marca como 'No seguro' y la gente se va.", tone:"danger" }); } // 6. Security headers (configuración de seguridad) kpis.push({ icon:"shield", label:"Configuración de seguridad", value:sec.grade || "F", hint: (sec.missing_count || 0) > 0 ? `Le faltan ${sec.missing_count} protecciones de seguridad estándar.` : "Bien configurado.", tone: (sec.grade || "F") === "A" ? "success" : ((sec.grade || "F") === "B" ? "info" : "warning") }); // 7. CMS detectado (qué plataforma usa) if (t.cms) { kpis.push({ icon:"layers", label:"Hecho con", value:t.cms, hint:"Plataforma con la que está construido tu sitio.", tone:"info" }); } else if (t.frameworks?.length) { kpis.push({ icon:"layers", label:"Hecho con", value:t.frameworks[0], hint:"Tecnología detectada en tu sitio.", tone:"info" }); } // 8. E-commerce if (t.ecommerce) { kpis.push({ icon:"cursor", label:"Tienda online", value:t.ecommerce, hint:"Detectamos que vendes online con esta plataforma.", tone:"info" }); } // 9. Schema.org (Datos para Google) if ((s.meta?.schema_count || 0) > 0) { kpis.push({ icon:"check", label:"Datos extra para Google", value:"Sí", hint:"Le das información estructurada a Google (precios, reseñas, etc.) para que destaque mejor tu sitio.", tone:"success" }); } else { kpis.push({ icon:"alert", label:"Datos extra para Google", value:"No", hint:"Pierdes la oportunidad de que Google muestre tu producto con estrellas, precios, etc.", tone:"warning" }); } // 10. Open Graph (cómo se ve al compartir) const ogOk = s.meta?.has_og_image && s.meta?.has_og_title && s.meta?.has_og_desc; kpis.push({ icon:"globe", label:"Vista previa al compartir", value: ogOk ? "Bien" : "Incompleta", hint: ogOk ? "Cuando comparten tu link en WhatsApp/Facebook, se ve con foto y descripción." : "Al compartir tu link en redes, no aparece foto/título correctamente.", tone: ogOk ? "success" : "warning" }); // 11. Sitemap (mapa de tu sitio) kpis.push({ icon:"layers", label:"Mapa del sitio (sitemap)", value: s.sitemap?.exists ? `${s.sitemap.urls_count} páginas` : "No tiene", hint: s.sitemap?.exists ? "Le facilitas a Google encontrar todas tus páginas." : "Sin sitemap, Google puede olvidar algunas páginas.", tone: s.sitemap?.exists ? "success" : "warning" }); // 12. Contenido kpis.push({ icon:"message", label:"Palabras en tu página principal", value: fmtCompact(ct.word_count || 0), hint: ct.has_thin_content ? "Muy poco texto (menos de 250 palabras). Google prefiere páginas con más contenido." : "Cantidad de texto adecuada para Google.", tone: ct.has_thin_content ? "warning" : "success" }); return (
DATOS REALES Revisamos {c.pages_crawled || 0} páginas de tu sitio en vivo

¿Cómo está la salud técnica de tu sitio?

Estos son aspectos técnicos importantes que afectan cuán bien se ve tu página en Google, qué tan rápida es y qué tan segura. Todo medido en vivo, nada de estimaciones.

{kpis.map((m, i) => (
{m.value}{m.suffix ? {m.suffix} : null}
{m.label}

{m.hint}

))}
{/* Tecnologías y analytics si hay */} {(t.analytics?.length > 0 || t.frameworks?.length > 0) && (
{t.frameworks?.length > 0 && (
Tecnologías que detectamos
{t.frameworks.map((f) => ( {f} ))}
)} {t.analytics?.length > 0 && (
Herramientas de seguimiento
{t.analytics.map((a) => ( {a} ))}
)} {t.hosting && (
Dónde está alojado
{t.hosting}
)}
)}

Todo lo de arriba es real — lo medimos directamente entrando a tu sitio, revisando hasta {c.pages_crawled || 0} páginas, el certificado de seguridad, las tecnologías que usas y cuánto contenido tienes.

); } function CategoryGrid({ categories }) { return (
{categories.map((c, i) => (
{c.score}
{c.label}
))}
); } /* ════════════════════════════════════════════════════════════════════════════ REDISEÑO 2026 — Paleta del handoff. Helpers compartidos para Google notes, métricas técnicas, áreas y problemas. Tres tonos según score: >=90 → success (verde) 50-89 → warn (ámbar) <50 → danger (rojo) Los colores y border-radius vienen tal cual del mock HTML/CSS del handoff. ════════════════════════════════════════════════════════════════════════════ */ const NEW_META = (s) => { if (s == null || isNaN(s)) return { ring:'#8a90a3', track:'#eef0f4', bg:'#eef0f6', text:'#5b607d', statusLabel:'Sin dato' }; if (s >= 90) return { ring:'#22c55e', track:'#d4f1de', bg:'#e7f8ee', text:'#15803d', statusLabel:'Bien' }; if (s >= 50) return { ring:'#eab308', track:'#f3e9c6', bg:'#fdf6e0', text:'#a16207', statusLabel:'Mejorable' }; return { ring:'#ef4444', track:'#f6d8d8', bg:'#fdecec', text:'#c0271d', statusLabel:'Crítico' }; }; const donutDash = (score, r) => { const c = 2 * Math.PI * r; const filled = c * (score == null ? 0 : score) / 100; return filled.toFixed(1) + ' ' + (c - filled).toFixed(1); }; /* ─── Tooltip wrapper ─── click/tap en móvil, hover en desktop, esc para cerrar. Antes solo CSS :hover, no funcionaba en touch. Ahora usa state + outside-click. */ function HelpTip({ children, position = 'bottom' }) { const [open, setOpen] = useState(false); const wrapRef = useRef(null); const isTop = position === 'top'; // Click fuera → cerrar. Esc → cerrar. useEffect(() => { if (!open) return; const onDocClick = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); }; const onKey = (e) => { if (e.key === 'Escape') setOpen(false); }; document.addEventListener('click', onDocClick); document.addEventListener('keydown', onKey); return () => { document.removeEventListener('click', onDocClick); document.removeEventListener('keydown', onKey); }; }, [open]); return ( {open && ( e.stopPropagation()}>{children} )} ); } /* ─── 1. Google verification band (verde, con check) ─── */ function GoogleVerificationBand({ dx, onReanalizar }) { const [busy, setBusy] = useState(false); const fetched = dx.meta?.fetched_at ? new Date(dx.meta.fetched_at) : null; const ageMin = fetched ? Math.round((Date.now() - fetched.getTime()) / 60000) : null; const psWebUrl = `https://pagespeed.web.dev/analysis?url=${encodeURIComponent(dx.url)}&form_factor=${dx.device}`; const handleRe = async () => { setBusy(true); await onReanalizar(); setBusy(false); }; return (
DATOS DIRECTOS DE GOOGLE
Estos no son inventados. Son los mismos números de la herramienta oficial de Google {fetched && <> · análisis hecho {ageMin < 1 ? 'hace un momento' : `hace ${ageMin} min`}}
Comprobar con Google →
); } /* ─── 2. Google 4 notes (donuts con tooltip) ─── */ const GOOGLE_TOOLTIPS = [ { what:'Mide cuánto tarda tu página en cargar y volverse usable en un celular.', ideal:'90 o más', why:'Una página lenta pierde ventas: muchos se van antes de ver tu producto.' }, { what:'Revisa si cualquier persona puede leer, tocar y navegar tu página sin problemas.', ideal:'90 o más', why:'Si cuesta usarla, cuesta comprar.' }, { what:'Comprueba que el sitio esté bien construido: HTTPS, código sano y sin errores.', ideal:'90 o más', why:'Un sitio sólido da confianza y evita fallas durante la compra.' }, { what:'Mide qué tan bien Google entiende y muestra tu página en los resultados.', ideal:'90 o más', why:'Más visibilidad significa más visitas y más ventas.' }, ]; function GoogleNotesSection({ psiScores, psiScoresSource }) { const labels = ['Velocidad', 'Fácil de usar', 'Calidad técnica', 'Visible en Google']; const subs = ['Qué tan rápido carga tu página.', 'Qué tan fácil es navegarla.', 'Si está bien hecha por dentro.', 'Qué tan bien la encuentra Google.']; const keys = ['performance', 'accessibility', 'best_practices', 'seo']; const src = psiScoresSource || ['unavailable','unavailable','unavailable','unavailable']; const algunaFalta = src.some(o => o !== 'google'); return (
Las 4 notas de Google

Notas que Google le da a tu página

Las mismas 4 notas que verías en la herramienta oficial de Google. Van de 0 a 100 — mientras más alto, mejor.

{keys.map((k, i) => { const score = psiScores?.[k]; const unavailable = src[i] === 'unavailable' || score == null; const m = NEW_META(score); const tip = GOOGLE_TOOLTIPS[i]; return (
{labels[i]} {tip.what} Ideal: {tip.ideal} {tip.why}
{unavailable ? '—' : score}
{unavailable ? 'Sin dato' : m.statusLabel}

{subs[i]}

); })}
{algunaFalta && (
Google no respondió todas las notas esta vez. Toca "↻ Volver a medir" arriba.
)}
); } /* ─── 3. Métricas técnicas (LCP / CLS / FCP / TBT) ─── */ function TechnicalMetricsSection({ cwv }) { if (!cwv) return null; const formatMs = (v) => v == null ? '—' : v >= 1000 ? (v/1000).toFixed(1) + ' s' : Math.round(v) + ' ms'; const scoreFromCwv = { LCP: cwv.lcp_ms == null ? null : (cwv.lcp_ms <= 2500 ? 95 : cwv.lcp_ms <= 4000 ? 60 : 20), CLS: cwv.cls == null ? null : (cwv.cls <= 0.1 ? 95 : cwv.cls <= 0.25 ? 60 : 20), FCP: cwv.fcp_ms == null ? null : (cwv.fcp_ms <= 1800 ? 95 : cwv.fcp_ms <= 3000 ? 60 : 20), TBT: cwv.tbt_ms == null ? null : (cwv.tbt_ms <= 200 ? 95 : cwv.tbt_ms <= 600 ? 60 : 20), }; const items = [ { code:'LCP', name:'Tiempo de carga visual', value: formatMs(cwv.lcp_ms), score: scoreFromCwv.LCP, what:'Cuánto tarda en aparecer lo más grande de la pantalla (foto o banner principal).', ideal:'Menos de 2.5 s', why:'Si tarda, la gente se va antes de ver tu producto.' }, { code:'CLS', name:'Estabilidad visual', value: cwv.cls == null ? '—' : Number(cwv.cls).toFixed(2), score: scoreFromCwv.CLS, what:'Si los elementos saltan o se mueven solos mientras la página carga.', ideal:'Menos de 0.1', why:'Los saltos hacen que toquen el botón equivocado y se frustren.' }, { code:'FCP', name:'Primer contenido', value: formatMs(cwv.fcp_ms), score: scoreFromCwv.FCP, what:'Cuándo aparece el primer texto o imagen de tu página.', ideal:'Menos de 1.8 s', why:'Es la primera señal de que "esto sí funciona".' }, { code:'TBT', name:'Tiempo sin responder', value: formatMs(cwv.tbt_ms), score: scoreFromCwv.TBT, what:'Cuánto tiempo la página no reacciona a los toques mientras carga.', ideal:'Menos de 200 ms', why:'Si no responde, parece rota y pierdes la venta.' }, ]; return (
Métricas técnicas de Google

Cómo se siente tu página al cargar

Pasa el cursor sobre el ? para entender cada una
{items.map((t) => { const m = NEW_META(t.score); return (
{t.code} {t.what} Ideal: {t.ideal} {t.why}
{t.value}
{t.name}
{m.statusLabel}
); })}
); } /* ─── 4. Acordeón con 8 áreas Sell-U (barras de progreso) ─── */ function AreasAccordion({ categories }) { const [open, setOpen] = useState(true); return (
{open && (
{categories.map((c) => { const m = NEW_META(c.score); const firstSignal = c.signals?.[0] || 'Cómo Sell-U lo mide.'; return (
{c.label} Cómo se mide {firstSignal} {c.score} /100
); })}
)}
); } /* ─── 5. Lista de problemas con prioridad + solución bloqueada ─── */ const PRIORITY_META = { alta: { bg:'#fdecec', text:'#c0271d', dot:'#ef4444', label:'ALTA' }, media: { bg:'#fdf6e0', text:'#a16207', dot:'#eab308', label:'MEDIA' }, baja: { bg:'#eef0f6', text:'#4a5169', dot:'#8a90a3', label:'BAJA' }, }; function NewProblemCard({ p, locked, onUnlock }) { const m = PRIORITY_META[p.severity] || PRIORITY_META.media; return (
{m.label} {p.catLabel}

{p.problema}

Impacto en tus ventas
{p.impacto}
Cómo se soluciona {locked && ( BLOQUEADO )}
{p.solucion}
{locked && (
)}
); } /* Banner de verificación: muestra cuándo se hizo el análisis y permite comparar con pagespeed.web.dev en una pestaña nueva. */ function VerificacionBanner({ dx, onReanalizar }) { const [reanalizando, setReanalizando] = useState(false); const fetched = dx.meta?.fetched_at ? new Date(dx.meta.fetched_at) : null; const ageMin = fetched ? Math.round((Date.now() - fetched.getTime()) / 60000) : null; const psWebUrl = `https://pagespeed.web.dev/analysis?url=${encodeURIComponent(dx.url)}&form_factor=${dx.device}`; const handleRe = async () => { setReanalizando(true); await onReanalizar(); setReanalizando(false); }; return (
DATOS DE GOOGLE
Estos no son inventados. Los obtenemos directo de Google — los mismos números que verías en su herramienta oficial. {fetched && Análisis hecho {ageMin < 1 ? "hace un momento" : `hace ${ageMin} min`}} ¿Quieres comprobarlo? Toca el botón verde →
Comprobar con Google
); } /* ─── 6. Hero de resultados (gradient navy + donut global + verdict + URL chip) ─── */ function ResultsHero({ dx }) { const score = dx.overall; const m = NEW_META(score); const verdict = score >= 90 ? { label:'Buen estado', title:'Tu página va muy bien. Afinemos los últimos detalles.', badgeBg:'rgba(34,197,94,.16)', badgeText:'#7be0a5' } : score >= 50 ? { label:'Necesita atención', title:'Tu página tiene buena base, pero está perdiendo ventas por velocidad.', badgeBg:'rgba(245,166,35,.16)', badgeText:'#f5a623' } : { label:'Estado crítico', title:'Tu página necesita arreglos urgentes para dejar de perder ventas.', badgeBg:'rgba(239,68,68,.16)', badgeText:'#fca5a5' }; const ringColor = '#f5a623'; // tono cálido de marca en el hero, independiente del score return (
{score} de 100
Puntaje Sell-U
{verdict.label}

{verdict.title}

Combinamos las notas oficiales de Google con nuestra propia revisión de 8 áreas. Abajo te mostramos exactamente qué arreglar y en qué orden.

); } /* ─── 7. CTA "no quieres lidiar con todo esto" ─── */ function MidPageCTA() { return (

¿No quieres lidiar con todo esto?

Nuestro equipo arregla cada uno de estos puntos por ti y te entrega tu página vendiendo más, en menos de lo que crees.

{ e.preventDefault(); scrollToId('arreglar'); }} style={{display:'inline-flex', alignItems:'center', gap:9, background:'#f5a623', color:'#1a2340', fontSize:16, fontWeight:700, padding:'15px 28px', borderRadius:12, textDecoration:'none', boxShadow:'0 10px 22px -8px rgba(245,166,35,.55)', whiteSpace:'nowrap'}}> Quiero que lo arreglen →
); } /* ─── 8. Sticky "Volver a medir" abajo a la derecha ─── */ function StickyVolver({ onClick }) { return ( ); } function Results({ dx, onReset, onReanalizar }) { const free = dx.problems.slice(0, dx.freeCount); const locked = dx.problems.slice(dx.freeCount); const lockedCount = locked.length; return (
{/* Breadcrumb */}
{/* 1 · Hero navy con donut grande, verdict y URL chip */} {/* 2 · Banda verde "DATOS DIRECTOS DE GOOGLE" + comprobar/re-medir */} {/* 3 · Las 4 notas oficiales de Google (donuts con tooltip) */} {/* 4 · Métricas técnicas (LCP, CLS, FCP, TBT) */} {dx.meta?.psi_ok && ( )} {/* 5 · Acordeón con las 8 áreas Sell-U */} {/* 6 · Lista de problemas con prioridad y solución bloqueada */}
Lo que hay que arreglar

Problemas detectados, ordenados por impacto

Empieza por los de prioridad alta — son los que más te están costando ventas hoy. {lockedCount > 0 && <> Te mostramos {dx.freeCount} gratis; los {lockedCount} restantes los recibes en el reporte completo.}

{free.map((p) => ( ))} {locked.map((p) => ( scrollToId('arreglar')} /> ))}
{/* CTA mid page */}
{/* 7 · Análisis técnico detallado (colapsable, mantiene Auditoría + Methodology del análisis previo para no perder profundidad técnica). */}
Análisis técnico avanzado Auditoría completa: indexación, SEO, SSL, seguridad, tecnología detectada y metodología Ver detalle ▼
{dx.auditoria?.ok && }
{/* 8 · Sticky "Volver a medir" abajo a la derecha */}
); } function Methodology({ categories }) { return (
¿De dónde salen estos números?

Cómo medimos tu página

Usamos la misma tecnología de Google para medir la velocidad y el SEO técnico, además revisamos cada parte de tu página (titulares, botones, formularios, orden visual). Cada área pesa distinto: lo que más afecta tus ventas, pesa más.

{categories.map((c) => (

{c.label}

Impacto {c.weight >= 1.3 ? "alto" : c.weight >= 1.1 ? "medio" : "base"}
    {c.signals.map((s, i) => (
  • {s}
  • ))}
))}

Sobre los datos: la velocidad, accesibilidad y SEO técnico son datos reales medidos por Google. Las visitas estimadas y enlaces externos son cálculos aproximados basados en lo que vemos en tu sitio.

); } /* ===================== CAPTURA ===================== */ function Capture({ url, dx }) { const [data, setData] = useState({ nombre: "", correo: "", sitio: url || "", objetivo: "", mensaje: "" }); const [touched, setTouched] = useState({}); const [sent, setSent] = useState(false); const [submitting, setSubmitting] = useState(false); const [serverErr, setServerErr] = useState(""); useEffect(() => { setData((d) => ({ ...d, sitio: d.sitio || url || "" })); }, [url]); const errors = { nombre: !data.nombre.trim() ? "Ingresa tu nombre." : "", correo: !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.correo) ? "Ingresa un correo válido." : "", objetivo: !data.objetivo ? "Elige una opción." : "", }; const valid = !errors.nombre && !errors.correo && !errors.objetivo; const set = (k) => (e) => setData((d) => ({ ...d, [k]: e.target.value })); const blur = (k) => () => setTouched((t) => ({ ...t, [k]: true })); const submit = async () => { setTouched({ nombre: true, correo: true, objetivo: true }); if (!valid) return; setSubmitting(true); setServerErr(""); try { // Snapshot del diagnóstico actual para guardarlo con el lead const payload = { nombre: data.nombre.trim(), correo: data.correo.trim().toLowerCase(), sitio: data.sitio.trim() || null, objetivo: data.objetivo, mensaje: data.mensaje.trim() || null, }; if (dx) { payload.url_analizada = dx.url; payload.dispositivo = dx.device; payload.score_general = dx.overall; payload.scores_categorias = Object.fromEntries(dx.categories.map(c => [c.key, c.score])); payload.core_web_vitals = dx.core_web_vitals || dx.meta?.core_web_vitals || null; payload.problemas_count = dx.problems.length; payload.snapshot_diagnostico = { overall: dx.overall, potentialPct: dx.potentialPct, problems: dx.problems, categories: dx.categories.map(c => ({ key: c.key, label: c.label, score: c.score })), }; } await window.ConvierteEngine.submitLead(payload); setSent(true); } catch (e) { setServerErr(e.message || "No pudimos enviar tu solicitud. Intenta de nuevo."); } finally { setSubmitting(false); } }; // ── Helper para inputs estilo handoff (border #d6dae3, radius 10, etc.) ── const inpStyle = (hasErr) => ({ width:'100%', border: hasErr ? '1px solid #c0271d' : '1px solid #d6dae3', borderRadius:10, padding:'11px 13px', fontSize:14, fontFamily:'inherit', color:'#1a2340', outline:'none', background:'#fff', }); const monoInpStyle = (hasErr) => ({ ...inpStyle(hasErr), fontFamily:"'JetBrains Mono',monospace", fontSize:13 }); const labelStyle = { display:'block', fontSize:13, fontWeight:600, color:'#1a2340', marginBottom:6 }; const errStyle = { display:'block', fontSize:12, color:'#c0271d', marginTop:4 }; return (
{/* Columna izquierda — copy */}
Hacemos los arreglos por ti

¿Prefieres que nosotros lo arreglemos?

En Sell-U LATAM tenemos un equipo que arregla las cosas que detectó este medidor para que tu página venda más. Déjanos tus datos y te enviamos el reporte completo + una propuesta a tu medida.

{[ 'Reporte completo con todo lo que hay que arreglar', 'Plan ordenado por lo que más impacta tus ventas', 'Te contactamos en menos de 24h, sin compromiso', ].map((perk) => (
{perk}
))}
{/* Columna derecha — form (o success state) */}
{sent ? (

¡Listo! Te contactaremos en menos de 24h

Recibimos tu solicitud para {prettyHost(data.sitio)}. Nuestro equipo te enviará el reporte completo y propuesta a {data.correo}.

) : ( <> {/* Honeypot anti-bot */} {}} />