// ============================================ // SECTIONS 3 — QR Scanner, Features, Final CTA // ============================================ const { useState: useS3, useEffect: useE3, useRef: useR3 } = React; // ---------- QR SCANNER ---------- function QRScanner() { const [active, setActive] = useS3(false); const [result, setResult] = useS3(null); const [error, setError] = useS3(null); const videoRef = useR3(null); const streamRef = useR3(null); const rafRef = useR3(null); const stop = () => { if (rafRef.current) cancelAnimationFrame(rafRef.current); if (streamRef.current) { streamRef.current.getTracks().forEach(t => t.stop()); streamRef.current = null; } setActive(false); }; const start = async () => { setError(null); setResult(null); try { const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }); streamRef.current = stream; if (videoRef.current) { videoRef.current.srcObject = stream; await videoRef.current.play(); } setActive(true); scanLoop(); } catch (e) { setError('No se pudo acceder a la cámara. Permití el acceso y probá de nuevo.'); } }; const scanLoop = () => { const tick = () => { if (!videoRef.current || !streamRef.current) return; if (videoRef.current.readyState === videoRef.current.HAVE_ENOUGH_DATA && window.jsQR) { const canvas = document.createElement('canvas'); canvas.width = videoRef.current.videoWidth; canvas.height = videoRef.current.videoHeight; const ctx = canvas.getContext('2d'); ctx.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height); const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); const code = window.jsQR(imgData.data, canvas.width, canvas.height); if (code) { setResult(code.data); stop(); return; } } rafRef.current = requestAnimationFrame(tick); }; rafRef.current = requestAnimationFrame(tick); }; useE3(() => () => stop(), []); return (
ESCÁNER QR · LIVE

Escaneá un QR real

Imprimí una etiqueta con el código único de cada activo. El operador lo escanea desde su celular y accede al instante a la ficha: hoja de vida, mantenimientos, responsable y ubicación.

{!active ? ( ) : ( )} Ver todos los módulos →
{error && (
{error}
)} {result && (
ACTIVO DETECTADO
{result}
)}
); } // ---------- FEATURES ---------- function Features() { const feats = [ { i: '☁', t: '100% en la nube', d: 'Accedé desde cualquier dispositivo. Sin instalaciones ni mantenimiento de servidores.' }, { i: '🔒', t: 'Seguridad nivel bancario', d: 'Cifrado SSL, backups automáticos y control de acceso granular por rol.' }, { i: '📱', t: 'Optimizado para móvil', d: 'Escaneá QR, aprobá solicitudes y registrá novedades desde el campo.' }, { i: '🤝', t: 'Soporte humano', d: 'Asesores por WhatsApp, videollamada y capacitación incluida.' }, ]; return (
POR QUÉ ELEGIRNOS

Tecnología que trabaja para ti

Sin servidores. Sin instalaciones. Sin Excel. Todo centralizado, seguro y en tiempo real.

{feats.map(f => (
{f.i}
{f.t}
{f.d}
))}
COP en activos gestionados este mes
2 min
activación promedio
-68%
activos perdidos
🌎
12
países LATAM
4.9/5
satisfacción
); } function CountUp3({ value }) { const [d, setD] = useS3(0); const ref = useR3(null); useE3(() => { const el = ref.current; let done = false; const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting && !done) { done = true; const t0 = performance.now(); const dur = 1200; const tick = t => { const p = Math.min(1, (t - t0) / dur); const eased = 1 - Math.pow(1-p, 3); setD(Math.round(value * eased)); if (p < 1) requestAnimationFrame(tick); }; requestAnimationFrame(tick); } }); if (el) obs.observe(el); return () => obs.disconnect(); }, [value]); return {d.toLocaleString('es-CO')}; } // ---------- FINAL CTA ---------- function FinalCTA() { return (
EMPEZÁ EN 2 MINUTOS

¿Listo para tomar el control
de tus activos?

Contactar por WhatsApp Calcular mi ROI →
); } // ---------- FOOTER ---------- function Footer() { return ( ); } window.QRScanner = QRScanner; window.Features = Features; window.FinalCTA = FinalCTA; window.Footer = Footer;