// Ejecutora — Design System Components
const EJ = {
cyan: '#00C2CB', navy: '#1A1F6E', violet: '#7B5CF5',
cyanLight: '#E8FFFE', bgApp: '#F5F9FF', pink: '#FF6B8A',
white: '#FFFFFF', body: '#2A2A3A', secondary: '#5A5A72',
muted: '#9898B0', border: '#E0E8FF', cyanDark: '#00A8B0',
violetLight: '#EDE8FF', pinkLight: '#FFE8EE',
green: '#22C55E', greenLight: '#F0FFF4',
};
// ── Badge ──────────────────────────────────────────────────────
function Badge({ children, variant = 'cyan', style }) {
const variants = {
cyan: { background: EJ.cyanLight, color: EJ.cyanDark },
violet: { background: EJ.violetLight, color: EJ.violet },
pink: { background: EJ.pinkLight, color: EJ.pink },
navy: { background: EJ.navy, color: '#fff' },
soft: { background: EJ.bgApp, color: EJ.navy },
green: { background: EJ.greenLight, color: EJ.green },
};
return (
{children}
);
}
// ── Button ─────────────────────────────────────────────────────
function Btn({ children, variant = 'primary', size = 'md', onClick, style, disabled }) {
const sizes = {
xs: { fontSize: 11, padding: '6px 14px' },
sm: { fontSize: 13, padding: '9px 20px' },
md: { fontSize: 15, padding: '13px 28px' },
lg: { fontSize: 17, padding: '17px 40px' },
};
const variants = {
primary: { background: EJ.cyan, color: '#fff', boxShadow: '0 4px 20px rgba(0,194,203,0.28)' },
secondary: { background: EJ.pink, color: '#fff', boxShadow: '0 4px 20px rgba(255,107,138,0.28)' },
navy: { background: EJ.navy, color: '#fff', boxShadow: '0 4px 20px rgba(26,31,110,0.18)' },
violet: { background: EJ.violet, color: '#fff', boxShadow: '0 4px 20px rgba(123,92,245,0.28)' },
outline: { background: 'transparent', color: EJ.cyan, border: `2px solid ${EJ.cyan}` },
ghost: { background: 'transparent', color: EJ.navy, border: 'none' },
danger: { background: 'transparent', color: EJ.pink, border: `2px solid ${EJ.pink}` },
};
const [hover, setHover] = React.useState(false);
const hoverMap = { primary: EJ.cyanDark, secondary: '#e8567a', navy: '#2D3399', violet: '#6a4ee0' };
return (
);
}
// ── Card ───────────────────────────────────────────────────────
function Card({ children, soft, style, onClick }) {
return (
{children}
);
}
// ── Input ──────────────────────────────────────────────────────
function Input({ label, placeholder, type = 'text', error, value, onChange, style }) {
const [focus, setFocus] = React.useState(false);
return (
{label && }
setFocus(true)} onBlur={() => setFocus(false)}
style={{
fontFamily: 'Nunito Sans, sans-serif', fontSize: 14, color: EJ.body,
background: EJ.white, borderRadius: 16, padding: '11px 16px',
border: `1.5px solid ${error ? '#EF4444' : focus ? EJ.cyan : EJ.border}`,
boxShadow: focus ? '0 0 0 3px rgba(0,194,203,0.18)' : 'none',
outline: 'none', transition: 'all 0.2s', width: '100%', boxSizing: 'border-box',
}}
/>
{error && {error}}
);
}
// ── Select ─────────────────────────────────────────────────────
function Select({ label, value, onChange, options, style }) {
return (
{label && }
);
}
// ── Textarea ───────────────────────────────────────────────────
function Textarea({ label, placeholder, value, onChange, rows = 3 }) {
const [focus, setFocus] = React.useState(false);
return (
{label && }
);
}
// ── Stat Card ──────────────────────────────────────────────────
function StatCard({ label, value, change, positive = true, icon, color }) {
const c = color || EJ.cyan;
return (
{value}
{change && {positive ? '↑' : '↓'} {change}
}
);
}
// ── Progress Bar ───────────────────────────────────────────────
function ProgressBar({ value, label, color, sublabel }) {
const c = color || EJ.cyan;
return (
{label}
{sublabel && {sublabel}}
{value}%
);
}
// ── Avatar ─────────────────────────────────────────────────────
function Avatar({ src, name, size = 36 }) {
const initials = name ? name.split(' ').map(w => w[0]).join('').substring(0, 2).toUpperCase() : '?';
return src
?
: {initials}
;
}
// ── Sidebar Item ───────────────────────────────────────────────
function SidebarItem({ icon, label, active, onClick, badge }) {
const [hover, setHover] = React.useState(false);
return (
setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
display: 'flex', alignItems: 'center', gap: 10, padding: '9px 12px',
borderRadius: 12, cursor: 'pointer', transition: 'all 0.15s',
background: active ? EJ.cyanLight : hover ? EJ.bgApp : 'transparent',
color: active ? EJ.cyanDark : EJ.secondary,
fontFamily: 'Nunito Sans, sans-serif', fontSize: 13, fontWeight: 600,
}}
>
{icon}
{label}
{badge &&
{badge}}
{active && !badge &&
}
);
}
// ── Checkbox ───────────────────────────────────────────────────
function Checkbox({ checked, onChange, label, style }) {
return (
);
}
// ── Modal ──────────────────────────────────────────────────────
function Modal({ open, onClose, title, children, width = 520 }) {
if (!open) return null;
return (
e.stopPropagation()}>
{title}
{children}
);
}
// ── Pill Tabs ──────────────────────────────────────────────────
function PillTabs({ tabs, active, onChange }) {
return (
{tabs.map(t => (
))}
);
}
// ── Section Header ─────────────────────────────────────────────
function SectionHeader({ title, subtitle, action }) {
return (
{title}
{subtitle &&
{subtitle}
}
{action}
);
}
// ── Semáforo ───────────────────────────────────────────────────
function Semaforo({ value }) {
const colors = { rojo: '#EF4444', amarillo: '#F59E0B', verde: EJ.green };
const labels = { rojo: '🔴 Atención', amarillo: '🟡 En proceso', verde: '🟢 Al día' };
return (
{labels[value] || value}
);
}
Object.assign(window, {
EJ, Badge, Btn, Card, Input, Select, Textarea,
StatCard, ProgressBar, Avatar, SidebarItem,
Checkbox, Modal, PillTabs, SectionHeader, Semaforo,
});