/* Reusable components: Header, Footer, Buttons, Cards, Modal */
const NAV_ITEMS = [
{ key: "pacientes", label: "Pacientes" },
{ key: "medicos", label: "Médicos" },
{ key: "clinicas", label: "Clínicas" },
{ key: "empresas", label: "Empresas" },
{ key: "catalogo", label: "Estudios" },
{ key: "sucursales", label: "Sucursales" },
{ key: "vacunacion", label: "Vacunación" },
{ key: "resultados", label: "Resultados" },
{ key: "contacto", label: "Contacto" },
];
function Header({ page, navigate, onOpenLead }) {
const [drawer, setDrawer] = React.useState(false);
const [openAcc, setOpenAcc] = React.useState(null);
const APP = "https://app.infectolab.mx/";
const ORDEN = "https://app.infectolab.mx/solicitud";
const FACT = "https://facturacion.infectolab.mx/";
const go = (k) => { navigate(k); setDrawer(false); };
// Mega-menu data. Items use either nav (internal page) or href (external).
const CATS = [
{ key: "pacientes", label: "Pacientes", nav: "pacientes", width: 480, align: "left",
cols: [
{ label: "Servicios", items: [
{ icon: "microscope", name: "Estudios y diagnóstico", desc: "Más de 200 pruebas disponibles", nav: "catalogo" },
{ icon: "drop", name: "Vacunación", desc: "Vacunas para adultos con orientación profesional", nav: "vacunacion" },
{ icon: "home", name: "Toma domiciliaria", desc: "Muestras en la comodidad de tu hogar", nav: "pacientes" },
]},
{ label: "Acceso", items: [
{ icon: "docCheck", name: "Mis resultados", desc: "Portal seguro 24/7", nav: "resultados" },
{ icon: "route", name: "Cómo funciona", desc: "Proceso de 3 pasos para tu prueba", nav: "pacientes" },
]},
],
foot: { variant: "light", lead: "¿Primera vez?", linkText: "Conoce InfectoLab →", nav: "pacientes" } },
{ key: "medicos", label: "Médicos", nav: "medicos", width: 480, align: "left",
cols: [
{ label: "Plataforma", items: [
{ icon: "monitor", name: "InfectoApp", desc: "Gestión de órdenes y resultados en línea", href: APP },
{ icon: "clipboard", name: "Orden de laboratorio", desc: "Genera y envía órdenes en segundos", href: ORDEN },
]},
{ label: "Catálogo", items: [
{ icon: "dna", name: "Catálogo de estudios", desc: "Búsqueda por nombre, clave o patología", nav: "catalogo" },
{ icon: "star", name: "Programa Médico Lealtad", desc: "Descuentos y línea directa con el especialista", nav: "lealtad" },
]},
],
foot: { variant: "light", lead: "¿Eres médico con convenio?", linkText: "Ingresar a InfectoApp →", href: APP } },
{ key: "clinicas", label: "Clínicas", nav: "clinicas", width: 480, align: "left",
cols: [
{ label: "Operaciones", items: [
{ icon: "hospital", name: "Convenios institucionales", desc: "Condiciones especiales para tu clínica", nav: "clinicas" },
{ icon: "home", name: "Toma domiciliaria", desc: "Servicio para pacientes hospitalizados", nav: "clinicas" },
]},
{ label: "Digital", items: [
{ icon: "monitor", name: "InfectoApp para clínicas", desc: "Panel de gestión multi-usuario", href: APP },
{ icon: "trending", name: "Reportes y seguimiento", desc: "Dashboard de resultados en tiempo real", nav: "clinicas" },
]},
],
foot: { variant: "light", linkText: "Agendar cita con área de ventas →", nav: "contacto" } },
{ key: "empresas", label: "Empresas", nav: "empresas", width: 420, align: "right",
cols: [
{ items: [
{ icon: "building", name: "Convenios corporativos", desc: "Pruebas para tu equipo con factura", nav: "empresas" },
{ icon: "stethoscope", name: "Pruebas ocupacionales", desc: "Salud laboral y medicina del trabajo", nav: "empresas" },
{ icon: "briefcase", name: "Cotización corporativa", desc: "Precios por volumen personalizados", action: onOpenLead },
{ icon: "receipt", name: "Facturación", desc: "Portal de facturación electrónica", href: FACT },
]},
],
foot: { variant: "naranja", linkText: "¿Más de 10 colaboradores? Escríbenos →", nav: "contacto" } },
{ key: "infectolab", label: "InfectoLab", nav: null, width: 520, align: "right",
cols: [
{ label: "Encuéntranos", items: [
{ icon: "pin", name: "Sucursales", desc: "6 ubicaciones en Tijuana · una 24/7", nav: "sucursales" },
{ icon: "phone", name: "Contacto", desc: "WhatsApp · teléfono · email", nav: "contacto" },
{ icon: "receipt", name: "Facturación", desc: "Genera tu CFDI en línea", href: FACT },
]},
{ label: "Nosotros", items: [
{ icon: "award", name: "Acerca de InfectoLab", desc: "15+ años de diagnóstico clínico", nav: "home" },
{ icon: "user", name: "Dr. Samuel Navarro", desc: "Director Médico e Infectólogo", nav: "home" },
{ icon: "shield", name: "Certificaciones", desc: "ISO 9001:2015 · PACAL", nav: "home" },
]},
],
foot: { variant: "dark" } },
];
const itemHandler = (it) => {
if (it.href) return null; // anchor handles it
return (e) => { e.preventDefault(); if (it.action) { it.action(); setDrawer(false); } else { go(it.nav); } };
};
const PanelLink = ({ it }) => {
const inner = (
<>
{it.name}{it.desc}
>
);
if (it.href) return {inner};
return {inner};
};
const PanelFoot = ({ foot }) => {
if (foot.variant === "dark") {
return (
© 2026 InfectoLab · Tijuana, B.C.
);
}
return (
);
};
const isActive = (cat) =>
cat.nav === page || (cat.key === "infectolab" && page === "sucursales");
return (
<>
{/* Mobile drawer */}
setDrawer(false)}>
>
);
}
const HEADER_CSS = `
.il-nav { position: sticky; top: 0; z-index: 100; background: #fff; border-bottom: 1px solid var(--ink-200); }
.il-nav-inner { display: flex; align-items: center; gap: 20px; height: 72px; }
.il-logo { flex: 0 0 auto; display: inline-flex; align-items: center; }
.il-logo img { height: 34px; width: auto; }
.il-menu { display: flex; align-items: center; gap: 2px; list-style: none; margin: 0; padding: 0; flex: 1; justify-content: center; }
.il-item { position: relative; }
.il-trigger {
display: inline-flex; align-items: center; gap: 6px;
background: none; border: none; font: inherit; font-size: 15px; font-weight: 600; color: var(--ink-700);
padding: 10px 14px; border-radius: 8px; position: relative; cursor: pointer;
}
.il-trigger .chev { transition: transform .2s; }
.il-trigger::after {
content: ""; position: absolute; left: 50%; bottom: 4px; height: 2px; width: 0;
background: var(--brand-yellow); border-radius: 2px; transform: translateX(-50%); transition: width .22s ease;
}
.il-item:hover > .il-trigger, .il-item:focus-within > .il-trigger, .il-item.is-active > .il-trigger { color: var(--brand-primary); }
.il-item:hover > .il-trigger::after, .il-item:focus-within > .il-trigger::after, .il-item.is-active > .il-trigger::after { width: calc(100% - 28px); }
.il-item:hover > .il-trigger .chev, .il-item:focus-within > .il-trigger .chev { transform: rotate(180deg); }
.il-result {
display: inline-flex; align-items: center; gap: 7px;
border: 1.5px solid var(--brand-primary); color: var(--brand-primary);
font-size: 14px; font-weight: 700; border-radius: 8px; padding: 8px 16px; margin-left: 6px;
transition: background .15s, color .15s;
}
.il-result svg { transition: stroke .15s; }
.il-result:hover, .il-result.is-on { background: var(--brand-primary); color: #fff; }
.il-result:hover svg, .il-result.is-on svg { stroke: #fff; }
.il-panel {
position: absolute; top: 100%; left: 0; padding-top: 12px;
opacity: 0; visibility: hidden; transform: translateY(8px);
transition: opacity .18s ease, transform .18s ease, visibility .18s; z-index: 60;
}
.il-panel.right { left: auto; right: 0; }
.il-item:hover > .il-panel, .il-item:focus-within > .il-panel { opacity: 1; visibility: visible; transform: translateY(0); }
.il-panel-card { background: #fff; border: 1px solid var(--ink-200); border-radius: 16px; box-shadow: 0 8px 40px rgba(57,83,164,.12); overflow: hidden; }
.il-panel-body { display: grid; gap: 4px; padding: 16px; }
.il-col { display: flex; flex-direction: column; }
.il-col-label { font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-500); padding: 6px 12px 8px; }
.il-link { display: flex; gap: 12px; align-items: flex-start; padding: 10px 12px; border-radius: 10px; transition: background .14s; text-decoration: none; }
.il-link:hover { background: var(--ink-50); }
.il-link-ic { width: 36px; height: 36px; border-radius: 9px; background: rgba(57,83,164,.08); color: var(--brand-primary); display: flex; align-items: center; justify-content: center; flex: 0 0 36px; }
.il-link-tx b { display: block; font-size: 14px; font-weight: 700; color: var(--ink-900); line-height: 1.25; }
.il-link:hover .il-link-tx b { color: var(--brand-primary); }
.il-link-tx small { display: block; font-size: 12px; color: var(--ink-500); margin-top: 3px; line-height: 1.4; }
.il-panel-foot { padding: 12px 20px; border-top: 1px solid var(--ink-200); background: var(--ink-50); font-size: 13px; color: var(--ink-500); }
.il-panel-foot a { color: var(--brand-primary); font-weight: 700; }
.il-panel-foot a:hover { text-decoration: underline; }
.il-panel-foot.naranja a { color: #F07B35; }
.il-panel-foot.dark { background: linear-gradient(120deg, #1A2A5E, #30477F); color: rgba(255,255,255,.78); border-top: none; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.il-panel-foot.dark img { height: 20px; }
.il-actions { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.il-ghost {
display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
border: 1px solid var(--ink-200); background: #fff; color: var(--ink-900);
font: inherit; font-size: 14px; font-weight: 600; border-radius: 8px; padding: 9px 16px; transition: .15s;
}
.il-ghost:hover { border-color: var(--brand-primary); color: var(--brand-primary); }
.il-primary {
display: inline-flex; align-items: center; gap: 8px; cursor: pointer; border: none;
background: var(--brand-yellow); color: var(--brand-deep);
font: inherit; font-size: 14px; font-weight: 700; border-radius: 8px; padding: 10px 18px;
box-shadow: 0 6px 16px -8px rgba(253,195,69,.9); transition: .15s;
}
.il-primary:hover { background: var(--brand-yellow-600); transform: translateY(-1px); }
.il-burger { display: none; width: 42px; height: 42px; border-radius: 10px; border: 1px solid var(--ink-200); background: #fff; align-items: center; justify-content: center; color: var(--ink-900); }
.il-overlay { position: fixed; inset: 0; background: rgba(26,42,94,.45); backdrop-filter: blur(2px); opacity: 0; visibility: hidden; transition: opacity .25s, visibility .25s; z-index: 200; }
.il-overlay.open { opacity: 1; visibility: visible; }
.il-drawer { position: fixed; top: 0; right: 0; height: 100%; width: 85%; max-width: 380px; background: #fff; transform: translateX(100%); transition: transform .28s cubic-bezier(.4,0,.2,1); z-index: 201; display: flex; flex-direction: column; }
.il-drawer.open { transform: translateX(0); }
.il-drawer-head { display: flex; align-items: center; justify-content: space-between; padding: 0 20px; height: 72px; border-bottom: 1px solid var(--ink-200); flex: 0 0 auto; }
.il-drawer-head img { height: 30px; }
.il-x { width: 40px; height: 40px; border-radius: 10px; border: 1px solid var(--ink-200); background: #fff; display: flex; align-items: center; justify-content: center; color: var(--ink-900); cursor: pointer; }
.il-drawer-body { flex: 1; overflow-y: auto; }
.il-acc-trigger { width: 100%; display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border: none; border-bottom: 1px solid var(--ink-200); background: none; font: inherit; font-size: 15.5px; font-weight: 700; color: var(--ink-900); text-align: left; cursor: pointer; }
.il-acc-trigger .chev { transition: transform .2s; color: var(--ink-500); }
.il-acc-trigger.open .chev { transform: rotate(180deg); }
.il-acc-content { display: none; background: var(--ink-50); }
.il-acc-content.open { display: block; }
.il-acc-link { display: flex; gap: 12px; align-items: flex-start; padding: 14px 20px; border-bottom: 1px solid var(--ink-200); text-decoration: none; }
.il-acc-link:last-child { border-bottom: none; }
.il-acc-link .il-link-ic { width: 32px; height: 32px; flex: 0 0 32px; }
.il-acc-link b { font-size: 14px; font-weight: 700; color: var(--ink-900); display: block; }
.il-acc-link small { font-size: 12px; color: var(--ink-500); display: block; margin-top: 2px; }
.il-drawer-foot { flex: 0 0 auto; padding: 16px 20px; border-top: 1px solid var(--ink-200); display: flex; flex-direction: column; gap: 10px; }
.il-drawer-foot .il-ghost, .il-drawer-foot .il-primary { width: 100%; justify-content: center; }
@media (max-width: 960px) {
.il-menu, .il-actions .il-ghost, .il-actions .il-primary { display: none; }
.il-burger { display: inline-flex; }
.il-nav-inner { justify-content: space-between; gap: 0; }
}
`;
function Footer({ navigate }) {
return (
);
}
/* ─────────── Section heading helper ─────────── */
function SectionHead({ eyebrow, title, sub, aside, light = false }) {
return (
{eyebrow &&
{eyebrow}
}
{title}
{sub &&
{sub}
}
{aside}
);
}
/* ─────────── Segment card ─────────── */
function SegmentCard({ icon, title, desc, cta, featured, onClick, sub }) {
return (
{title}
{desc}
{sub &&
{sub}
}
{cta}
);
}
/* ─────────── Service card (grid) ─────────── */
function ServiceCard({ icon, title, count, tag }) {
return (
{title}
{count} estudios
{tag && {tag}}
);
}
/* ─────────── FAQ accordion ─────────── */
function FaqItem({ q, a, defaultOpen = false }) {
const [open, setOpen] = React.useState(defaultOpen);
return (
);
}
/* ─────────── Lead modal ─────────── */
function LeadModal({ onClose, defaultSegment = "" }) {
const [step, setStep] = React.useState(0);
const [data, setData] = React.useState({ tipo: defaultSegment, nombre: "", email: "", tel: "", mensaje: "" });
const update = (k, v) => setData({ ...data, [k]: v });
return (
e.stopPropagation()}>
{step === 0 && (
Agendar prueba
Cuéntanos quién eres
Te conectamos con el equipo de atención correcto para tu necesidad.
{[
["paciente", "Paciente", "user"],
["medico", "Médico", "stethoscope"],
["clinica", "Clínica", "hospital"],
["empresa", "Empresa", "building"],
].map(([key, label, ic]) => (
))}
)}
{step === 1 && (
Paso 2 de 2
Tus datos
Un asesor te contactará en menos de 24 horas.
update("nombre", e.target.value)} placeholder="Ej. Ana Ramírez" />
)}
{step === 2 && (
Solicitud recibida
Gracias, {data.nombre || "te contactaremos"}. Un asesor de InfectoLab se pondrá en contacto a la brevedad.
)}
);
}
Object.assign(window, {
Header, Footer, SectionHead, SegmentCard, ServiceCard, FaqItem, LeadModal, NAV_ITEMS,
});