// ============================================================
// Componentes compartidos
// ============================================================
const { useState, useEffect, useMemo, useRef } = React;
// -------- Iconos SVG (minimalistas, line-icon style) --------
const Icon = ({ name, size = 16, style = {} }) => {
const icons = {
dashboard: <>>,
book: <>>,
camera: <>>,
edit: <>>,
check_shield: <>>,
convert: <>>,
fifo: <>>,
stats: <>>,
plus: <>>,
x: <>>,
logout: <>>,
calendar: <>>,
clock: <>>,
users: <>>,
filter: <>>,
search: <>>,
alert: <>>,
check: <>>,
arrow_right: <>>,
trending: <>>,
target: <>>,
library: <>>,
plus_circle: <>>,
settings: <>>,
};
const path = icons[name] || ;
return (
);
};
// -------- Avatar --------
const Avatar = ({ user, size = 'md' }) => {
if (!user) return null;
return (
{user.iniciales}
);
};
// -------- Status pill --------
const StatusPill = ({ estatus }) => {
const s = APP_DATA.ESTATUS[estatus];
if (!s) return null;
return (
{s.label}
);
};
// -------- Progress bar --------
const ProgressBar = ({ value, max, color }) => {
const pct = Math.min(100, (value / max) * 100);
return (
);
};
// -------- KPI Card --------
const KPI = ({ label, value, sub, progress, tone = 'primary' }) => {
return (
{label}
{value}
{sub &&
{sub}
}
{progress !== undefined && (
)}
);
};
// -------- Modal --------
const Modal = ({ open, onClose, title, children, footer, maxWidth = 640 }) => {
useEffect(() => {
if (!open) return;
const h = (e) => e.key === 'Escape' && onClose();
window.addEventListener('keydown', h);
return () => window.removeEventListener('keydown', h);
}, [open, onClose]);
if (!open) return null;
return (
e.stopPropagation()}>
{children}
{footer &&
{footer}
}
);
};
// -------- Empty state --------
const Empty = ({ icon = 'book', title, subtitle }) => (
{title}
{subtitle &&
{subtitle}
}
);
// -------- Helpers --------
const fmtDate = (d) => {
if (!d) return '—';
const dt = new Date(d + 'T00:00:00');
return dt.toLocaleDateString('es-MX', { day: '2-digit', month: 'short', year: 'numeric' });
};
const fmtNum = (n) => (n == null ? '—' : n.toLocaleString('es-MX'));
const daysBetween = (a, b) => {
const da = new Date(a); const db = new Date(b);
return Math.round((db - da) / 86400000);
};
const getUser = (id) => APP_DATA.USUARIOS.find(u => u.id === id);
// Expose globally
Object.assign(window, { Icon, Avatar, StatusPill, ProgressBar, KPI, Modal, Empty, fmtDate, fmtNum, daysBetween, getUser });