// ============================================================ // 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 ( {path} ); }; // -------- 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()}>
{title}
{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 });