// 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 (
);
}
/* ---- 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 });