// nexa-sections-2.jsx — Sistemas, Proceso, CTA, Footer
// ---------- Sistemas ----------
const Sistemas = () => {
const [active, setActive] = React.useState(0);
const items = [
{
tag: "WhatsApp",
t: "WhatsApp centralizado",
d: "Todas las consultas en un solo lugar. Nada se pierde entre teléfonos.",
mock: ,
},
{
tag: "Pipeline",
t: "Pipeline industrial",
d: "Qué trabajos están cotizados, aprobados o pendientes. En una pantalla.",
mock: ,
},
{
tag: "Panel",
t: "Panel comercial",
d: "Consultas, cierres y facturación en tiempo real.",
mock: ,
},
{
tag: "Clientes",
t: "Clientes y presupuestos",
d: "Historial de cada cliente, cada trabajo, cada presupuesto.",
mock: ,
},
];
return (
04 · El sistema
Así se ve funcionando.
{/* List */}
{items.map((it, i) => (
setActive(i)} style={{
textAlign: 'left', padding: '24px 0',
borderBottom: '1px solid var(--border)',
display: 'flex', alignItems: 'flex-start', gap: 20,
opacity: active === i ? 1 : 0.5,
transition: 'opacity .3s',
flexWrap: 'wrap',
}}>
0{i+1}
))}
{/* Mock viewer */}
{items.map((it, i) => (
{it.mock}
))}
);
};
// --- Animated counter hook ---
const useCounter = (target, { duration = 1500, decimals = 0, suffix = '', prefix = '' } = {}) => {
const [val, setVal] = React.useState(0);
const ref = React.useRef(null);
React.useEffect(() => {
const el = ref.current; if (!el) return;
let started = false;
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting && !started) {
started = true;
const start = performance.now();
const tick = (now) => {
const t = Math.min(1, (now - start) / duration);
const eased = 1 - Math.pow(1 - t, 3);
setVal(target * eased);
if (t < 1) requestAnimationFrame(tick);
else setVal(target);
};
requestAnimationFrame(tick);
io.unobserve(el);
}
});
}, { threshold: 0.4 });
io.observe(el);
return () => io.disconnect();
}, [target, duration]);
const formatted = decimals === 0
? Math.floor(val).toLocaleString('es-AR')
: val.toFixed(decimals);
return { ref, text: prefix + formatted + suffix };
};
// --- MockChat: animated typing conversation ---
const MockChat = () => {
const [step, setStep] = React.useState(0);
const visibleRef = React.useRef(null);
const [active, setActive] = React.useState(false);
React.useEffect(() => {
const el = visibleRef.current; if (!el) return;
const io = new IntersectionObserver((es) => {
es.forEach(e => { if (e.isIntersecting) setActive(true); });
}, { threshold: 0.3 });
io.observe(el);
return () => io.disconnect();
}, []);
React.useEffect(() => {
if (!active) return;
// pasos 0..6 normales, 7 = pausa de 3.5s antes de reset
const seq = [800, 1400, 1100, 1600, 1300, 1500, 3500];
let i = 0;
let id;
const tick = () => {
setStep(s => (s + 1) % 8);
i++;
id = setTimeout(tick, seq[i % seq.length]);
};
id = setTimeout(tick, 600);
return () => clearTimeout(id);
}, [active]);
const showMsg1 = step >= 2 && step <= 7;
const showMsg2 = step >= 4 && step <= 7;
const showMsg3 = step >= 5 && step <= 7;
const showMsg4 = step >= 6 && step <= 7;
const typingThem = step === 1;
const typingMe = step === 3;
return (
{showMsg1 &&
Hola, quería un presupuesto para una estructura metálica. }
{typingMe &&
}
{showMsg2 &&
¡Hola! Perfecto. ¿Podés enviarnos medidas o foto de referencia? }
{showMsg3 &&
Sí, te paso ahora. }
{showMsg4 &&
Genial. Registramos tu consulta y un asesor te envía el presupuesto. }
{typingThem &&
}
IA respondiendo · derivá si necesita
);
};
const Msg = ({ side, children }) => (
{children}
);
const TypingDots = ({ side }) => (
{[0, 1, 2].map(i => (
))}
);
// --- MockPipeline: lead moves from Nuevos → Contactados → Cerrados ---
const MockPipeline = () => {
const [phase, setPhase] = React.useState(0); // 0=Nuevos, 1=Contactados, 2=Cerrados
const [counts, setCounts] = React.useState([12, 8, 5]);
const [activeRef, setActiveRef] = React.useState(false);
const rootRef = React.useRef(null);
React.useEffect(() => {
const el = rootRef.current; if (!el) return;
const io = new IntersectionObserver(es => es.forEach(e => { if (e.isIntersecting) setActiveRef(true); }), { threshold: 0.3 });
io.observe(el); return () => io.disconnect();
}, []);
React.useEffect(() => {
if (!activeRef) return;
let i = 0;
let id;
const tick = () => {
setPhase(p => {
const next = (p + 1) % 3;
if (next === 1) setCounts(c => [c[0] - 1, c[1] + 1, c[2]]);
else if (next === 2) setCounts(c => [c[0], c[1] - 1, c[2] + 1]);
else setCounts([12, 8, 5]);
return next;
});
i++;
// después de completar el ciclo (3 fases) deja 3s antes de seguir
const delay = (i % 3 === 0) ? 5200 : 2200;
id = setTimeout(tick, delay);
};
id = setTimeout(tick, 2200);
return () => clearTimeout(id);
}, [activeRef]);
const cols = [
{ t: "Consulta", c: "#7AA0FF" },
{ t: "Cotizado", c: "#F5C26B" },
{ t: "Aprobado", c: "#5BD9A6" },
];
return (
{cols.map((col, i) => (
{col.t}
{counts[i]}
{Array.from({ length: 3 }).map((_, j) => {
const isActive = phase === i && j === 0;
return (
);
})}
))}
);
};
// --- MockDashboard: counters + drawing chart + bars ---
const MockDashboard = () => {
const sales = useCounter(2890000, { duration: 2000, prefix: '$ ' });
const orders = useCounter(10, { duration: 1400 });
const contacts = useCounter(78, { duration: 1600 });
const closed = useCounter(10, { duration: 1500 });
const today = useCounter(327, { duration: 1700 });
return (
{/* Hero stat: ventas hoy big */}
Facturado hoy
{sales.text}
{orders.text}
ventas
↑ +24% vs ayer
EN VIVO
{/* Secondary stats row */}
Contactados
{contacts.text}
+18 hoy
Cerrados
{closed.text}
+9% sem.
Msj contestados
{today.text}
último: 2m
{/* Chart with growing line + bars */}
Ventas / día
últimos 30d
{[
{ x: 10, h: 30 }, { x: 30, h: 38 }, { x: 50, h: 28 }, { x: 70, h: 50 },
{ x: 90, h: 42 }, { x: 110, h: 60 }, { x: 130, h: 52 }, { x: 150, h: 70 },
{ x: 170, h: 64 }, { x: 190, h: 78 }, { x: 210, h: 70 }, { x: 230, h: 86 },
{ x: 250, h: 80 }, { x: 270, h: 92 }, { x: 290, h: 100 },
].map((b, i) => (
))}
);
};
// --- MockCRM: animated rows with live updates ---
const MockCRM = () => {
const [tick, setTick] = React.useState(0);
const ref = React.useRef(null);
React.useEffect(() => {
const el = ref.current; if (!el) return;
let id;
const io = new IntersectionObserver(es => es.forEach(e => {
if (e.isIntersecting) {
let count = 0;
const next = () => {
setTick(t => t + 1);
count++;
const delay = (count % 5 === 0) ? 5800 : 2400;
id = setTimeout(next, delay);
};
id = setTimeout(next, 2400);
} else if (id) { clearTimeout(id); id = null; }
}), { threshold: 0.3 });
io.observe(el);
return () => { if (id) clearTimeout(id); io.disconnect(); };
}, []);
const rowsData = [
{ name: "Estructura Galpón", baseAmount: 8400, status: ["En curso", "Activo", "Activo"], c: "#5BD9A6" },
{ name: "Corte Láser CNC", baseAmount: 1850, status: ["Activo", "Activo", "Activo"], c: "#5BD9A6" },
{ name: "Reja Industrial 12m", baseAmount: 3200, status: ["Activo", "En curso", "Activo"], c: "#F5C26B" },
{ name: "Plegado Chapa 4mm", baseAmount: 920, status: ["Cotizado", "En curso", "Activo"], c: "#7AA0FF" },
{ name: "Soldadura Pampa SA", baseAmount: 14500, status: ["Activo", "Activo", "Activo"], c: "#5BD9A6" },
];
const fmt = (n) => n === 0 ? '—' : `$ ${n.toLocaleString('es-AR')}`;
const statusColor = (s) => s === 'Activo' ? '#5BD9A6' : s === 'En curso' ? '#F5C26B' : '#7AA0FF';
const statusBg = (s) => s === 'Activo' ? 'rgba(91,217,166,0.12)' : s === 'En curso' ? 'rgba(245,194,107,0.12)' : 'rgba(122,160,255,0.12)';
const statusBorder = (s) => s === 'Activo' ? 'rgba(91,217,166,0.25)' : s === 'En curso' ? 'rgba(245,194,107,0.25)' : 'rgba(122,160,255,0.25)';
// Live "new lead" indicator that pulses each tick
const liveIdx = tick % rowsData.length;
return (
nexa.crm / presupuestos
EN VIVO
Presupuesto
Estado
Monto
Últ. acción
{rowsData.map((r, i) => {
const status = r.status[tick % r.status.length];
const amount = r.baseAmount + (i + tick) * 37 * (i % 2 === 0 ? 1 : -1) + tick * 12;
const isLive = i === liveIdx;
return (
{r.name.split(' ').map(s => s[0]).join('').slice(0, 2)}
{r.name}
{isLive && }
{status}
{fmt(Math.max(0, amount))}
{isLive ? 'ahora' : `${(i + 1) * 4 + (tick % 5)}m`}
);
})}
);
};
// ---------- Proceso ----------
const Proceso = () => {
const steps = [
{ n: "01", t: "Diagnóstico", d: "Detectamos dónde se pierden oportunidades." },
{ n: "02", t: "Captura y Organización", d: "Ordenamos consultas y presupuestos." },
{ n: "03", t: "Seguimiento Inteligente", d: "Ningún presupuesto queda olvidado." },
{ n: "04", t: "Conversión", d: "Más presupuestos convertidos en trabajos." },
{ n: "05", t: "Control y Mejora", d: "Métricas claras para mejorar decisiones." },
];
const [reached, setReached] = React.useState(-1);
const rootRef = React.useRef(null);
React.useEffect(() => {
const el = rootRef.current; if (!el) return;
const io = new IntersectionObserver(es => es.forEach(e => {
if (e.isIntersecting) {
steps.forEach((_, i) => setTimeout(() => setReached(r => Math.max(r, i)), 400 + i * 700));
io.unobserve(el);
}
}), { threshold: 0.25 });
io.observe(el);
return () => io.disconnect();
}, []);
return (
02 · Método
Método FCI™
Flujo Comercial Inteligente. 5 fases.
{steps.map((s, i) => {
const on = reached >= i;
return (
);
})}
);
};
// ---------- CTA ----------
const CTA = () => (
Cada presupuesto sin seguimiento es una oportunidad perdida.
Analizamos tu operación comercial en 30 minutos. Cupos limitados por mes.
);
// ---------- Footer ----------
const Footer = () => (
);
const FooterCol = ({ t, links }) => (
);
const FabWsp = () => {
const onClick = (e) => {
e.preventDefault();
window.open(WSP_URL, '_blank', 'noopener');
};
return (
);
};
Object.assign(window, { Sistemas, Proceso, CTA, Footer, FabWsp });