// ConvierteAudit — Componentes compartidos (íconos SVG, gauge, barras, badges) /* ---- Íconos estilo Lucide (stroke 1.5, currentColor) ---- */ const ICON_PATHS = { message: <>, cursor: <>, shield: <>, form: <>, gauge: <>, mobile: <>, search: <>, layers: <>, // UI lock: <>, check: <>, arrow: <>, download: <>, bolt: <>, desktop: <>, spark: <>, trending: <>, declining:<>, link: <>, globe: <>, key: <>, network: <>, alert: <>, }; function Icon({ name, size = 24, stroke = 1.5, className, style, ...rest }) { return ( ); } /* ---- Mapa tono → color (puntaje / severidad) ---- */ const TONE = { danger: { color: "var(--danger-500)", fg: "var(--danger-700)", bg: "var(--danger-100)" }, warning: { color: "var(--warning-500)", fg: "var(--warning-700)", bg: "var(--warning-100)" }, info: { color: "var(--blue-700)", fg: "var(--blue-700)", bg: "var(--blue-100)" }, success: { color: "var(--success-500)", fg: "var(--success-700)", bg: "var(--success-100)" }, }; const SEV_TONE = { alta: "danger", media: "warning", baja: "info" }; const SEV_LABEL = { alta: "Alta", media: "Media", baja: "Baja" }; /* ---- Gauge de anillo (0–100) con conteo animado ---- */ function RingGauge({ score, tone, label, animate = true, size = 200 }) { const [shown, setShown] = React.useState(animate ? 0 : score); const reduce = React.useRef(typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches).current; React.useEffect(() => { if (!animate || reduce) { setShown(score); return; } let raf, start; const dur = 1100; const step = (t) => { if (!start) start = t; const p = Math.min(1, (t - start) / dur); const eased = 1 - Math.pow(1 - p, 3); setShown(Math.round(score * eased)); if (p < 1) raf = requestAnimationFrame(step); }; raf = requestAnimationFrame(step); // Garantía: si rAF no avanza (tab en segundo plano, entornos sin rAF), // aterriza siempre en el valor real. const fallback = setTimeout(() => setShown(score), dur + 120); return () => { cancelAnimationFrame(raf); clearTimeout(fallback); }; }, [score, animate, reduce]); const stroke = 14; const r = (size - stroke) / 2; const c = 2 * Math.PI * r; const pct = shown / 100; const color = TONE[tone].color; return (
{shown}
de 100
{label}
); } /* ---- Barra de progreso de categoría ---- */ function CatBar({ score, tone, animate = true, delay = 0 }) { const [w, setW] = React.useState(animate ? 0 : score); const reduce = React.useRef(typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches).current; React.useEffect(() => { if (!animate || reduce) { setW(score); return; } const t = setTimeout(() => setW(score), 80 + delay); return () => clearTimeout(t); }, [score, animate, reduce, delay]); return (
); } function SeverityBadge({ severity }) { const tone = SEV_TONE[severity]; return ( {SEV_LABEL[severity]} ); } Object.assign(window, { Icon, RingGauge, CatBar, SeverityBadge, TONE, SEV_TONE, SEV_LABEL });