/* Catálogo y detalle de estudio — alimentados por la API de InfectoApp * * Los datos ya no viven aquí. Antes había un arreglo STUDIES con 13 estudios * escritos a mano y un mapa DEPT_COLORS con 7 departamentos; ninguno de los dos * coincidía con el catálogo real, que son 801 estudios en 15 categorías. * * De dónde sale cada cosa: * src/api.js → el catálogo y el detalle, con caché y freno de peticiones * src/normalize.js → limpieza de los campos crudos y color por categoría * src/enrich.js → contenido editorial (nombre comercial, descripción, * audiencias, resistencias, artículo clínico) por id de API * * Forma del estudio que reciben estos componentes: * id, code, name → tal cual de la API * titulo → nombre presentable (comercial si hay overlay) * categorias[], color → categoría y su color de diseño * muestra {etiqueta, icono, tags[]} * contenedor {etiqueta, colores[]} * entrega {texto, horas} * precio {texto, valor, consultar} * preparacion, convenio, incluye[{texto, sub}] * destacado → true si tiene contenido curado * descripcion_corta, audiencias, resistencias, articulo_clinico, * observaciones, incluye_intro, disponibilidad → solo en los que tienen overlay */ /* Datos del prototipo, ya no alimentan el catálogo. Siguen exportándose * como window.STUDIES / CATEGORIES / DEPT_COLORS porque el blog, las * landings y la página de utilidades resuelven con ellos los ids que * mencionan ("QS-001", "PCR-301"). El catálogo y la ficha se pintan desde * la API. */ const DEPT_COLORS = { "Molecular": "#3953A4", "Infectología": "#30477F", "Hematología": "#7EB9C7", "Bioquímica": "#4D4D4D", "Endocrinología": "#7EB9C7", "Inmunología": "#FDC345", "Microbiología": "#F04F4C", "Genética": "#3953A4", }; const STUDIES = [ /* ──────── Q SEPSIS — ejemplo molecular completo ──────── */ { id: "QS-001", code: "QS-001", name: "Q Sepsis", departamentos: ["Molecular", "Infectología"], color_carta: "#3953A4", descripcion_corta: "Panel molecular de identificación rápida de patógenos en sangre y detección de genes de resistencia antimicrobiana en pacientes con sospecha de sepsis.", keywords: ["sepsis", "molecular", "pcr", "amr", "resistencia"], tipo_muestra: "Sangre total", tipo_muestra_icon: "drop", tipo_contenedor: "Tubo morado EDTA 4 mL", tipo_contenedor_color: "#7C53A8", preparacion_previa: "No requiere ayuno", tiempo_entrega: "4–6 horas", disponibilidad: "Disponible los 7 días de la semana", precio_particular: 4997, precio_hospital: "Solicitar convenio", publico_objetivo: "Pacientes con sospecha clínica de sepsis", aplica_empresa: false, servicio_domicilio: true, default_audience: "medico", audiencias: { paciente: { descripcion: "Q Sepsis es un análisis de sangre avanzado que usa tecnología molecular para detectar si tienes una infección grave en sangre (sepsis o bacteriemia) y qué bacteria la causa — todo sin necesidad de hemocultivo y en solo 4 a 6 horas.", que_esperar: [ "Una sola toma de sangre de 4 mL", "No necesitas ayuno ni preparación especial", "Resultado listo en 4–6 horas, el mismo día", "Recibes tu resultado digital con firma del QF certificado", "No necesitas hemocultivo ni hospitalización para la prueba", ], }, medico: { descripcion: "Q Sepsis utiliza PCR molecular multiplex para la detección directa en sangre total de los 20 microorganismos más frecuentes en bacteriemia y sepsis, con identificación simultánea de 16 blancos genéticos de resistencia antimicrobiana. Resultado en 4 a 6 horas con solo 4 mL de muestra.", indicaciones: [ "Paciente con sospecha clínica de sepsis o bacteriemia", "Fiebre de origen desconocido con deterioro hemodinámico", "Inmunocomprometidos: oncológicos, trasplantados, VIH+", "Pacientes con hemocultivos negativos y cuadro clínico persistente", "Necesidad de desescalamiento terapéutico basado en evidencia molecular", "UCI y urgencias donde cada hora cuenta", ], comparativo: { esta_nombre: "Q Sepsis", alt_nombre: "Hemocultivo convencional", rows: [ { feature: "Tiempo de resultado", esta: "4–6 horas", alt: "3–5 días" }, { feature: "Volumen de muestra", esta: "4 mL sangre total", alt: "20–40 mL" }, { feature: "Detección de gérmenes", esta: "20 patógenos directos", alt: "Solo los que crecen en cultivo" }, { feature: "Resistencias", esta: "16 blancos moleculares", alt: "Antibiograma fenotípico posterior" }, { feature: "Requiere crecimiento", esta: "No", alt: "Sí — depende de viabilidad bacteriana" }, { feature: "Disponibilidad", esta: "7 días a la semana", alt: "Sujeto a turno de laboratorio" }, ], }, desempeno: { sensibilidad: "98.4%", especificidad: "96%", vpp: "94.1%", vpn: "97.6%", n: "115 pacientes", }, }, clinica: { descripcion_extra: "Con convenio InfectoLab su clínica accede a Q Sepsis los 7 días del año con tiempo de respuesta garantizado de 4 a 6 horas. Recolección en instalaciones disponible. Soporte técnico de Dr. Samuel Navarro Álvarez para interpretación de resultados complejos.", logistica: { estabilidad: "6 h a temperatura ambiente / 24 h refrigerada (2–8 °C)", contenedor: "Tubo morado EDTA 4 mL", recoleccion: "Diaria, en instalaciones de la clínica con convenio activo", formato_reporte: "PDF digital con firma QF certificado", dias_disponibles: "7 días a la semana, 365 días al año", }, }, }, incluye_intro: "Q Sepsis identifica simultáneamente más de 25 patógenos bacterianos y fúngicos asociados a sepsis directamente desde sangre, junto con genes de resistencia antimicrobiana clínicamente relevantes. Reduce el tiempo de orientación etiológica de 48–72 horas a 6–8 horas.", incluye: [ "Detección de 18 bacterias Gram negativas y positivas", "Detección de 6 especies de Candida spp.", "Identificación de mecanismos de resistencia (BLEE, carbapenemasas, mecA, vanA/B)", "Reporte con interpretación clínica orientada", "Notificación inmediata ante resultado crítico", "Validación por especialista en biología molecular", ], resistencias: [ { titulo: "Carbapenémicos", color: "#F04F4C", descripcion: "Pérdida de sensibilidad a último recurso β-lactámico.", genes: ["KPC", "NDM", "OXA-48", "VIM", "IMP"], }, { titulo: "BLEE", color: "#FDC345", descripcion: "β-lactamasas de espectro extendido.", genes: ["CTX-M", "SHV", "TEM"], }, { titulo: "Meticilino-resistencia", color: "#F04F4C", descripcion: "Marca SARM (MRSA) en Staphylococcus aureus.", genes: ["mecA", "mecC"], }, { titulo: "Glicopéptidos", color: "#7EB9C7", descripcion: "Resistencia a vancomicina en enterococos.", genes: ["vanA", "vanB"], }, { titulo: "Colistín", color: "#4D4D4D", descripcion: "Resistencia mediada por plásmidos.", genes: ["mcr-1"], }, { titulo: "Quinolonas", color: "#30477F", descripcion: "Marcadores plasmídicos de resistencia.", genes: ["qnrA", "qnrB", "qnrS"], }, ], observaciones: [ "Tomar la muestra antes del inicio o cambio de antibioticoterapia siempre que sea clínicamente posible.", "Idealmente acompañar con hemocultivo convencional para correlación fenotípica.", "El estudio no sustituye al hemocultivo; lo complementa y acelera la orientación terapéutica inicial.", "Para neonatos solicitar contenedor pediátrico (EDTA 2 mL).", ], articulo_clinico: { autor: "Dr. Samuel Navarro Álvarez", rol: "Infectólogo e Internista · InfectoLab", avatar_initials: "SN", badge: "Artículo clínico", titulo: "Sepsis y bacteriemia: diagnóstico molecular como ventaja clínica", secciones: [ { label: "Epidemiología", heading: "La sepsis: una emergencia médica con mortalidad del 10 al 50%", parrafos: [ "La sepsis severa y el choque séptico continúan representando uno de los mayores desafíos en medicina crítica. La mortalidad oscila entre el 10 y el 50% según múltiples estudios, y cada hora sin tratamiento antimicrobiano dirigido incrementa el riesgo de muerte.", "El ABC de la sepsis exige estabilizar la vía aérea, mantener hemodinamia óptima e iniciar de forma inmediata el manejo antibiótico empírico, mientras se investiga simultáneamente la posible fuente y etiología infecciosa.", ], imagen: "https://assets.cdn.filesafe.space/nzcTy7e8QYhzh5745VIV/media/6a04ce1a51d5d92ddcac0a33.png", referencia: "Intensive Care Med. 2025;51(12):2318 · JAMA. 2025 Apr 9;333(19):1724–1726 · Crit Care Med. 2026 Apr 1;54(4):725-812", }, { label: "Reto Diagnóstico", heading: "El hemocultivo: 3 a 5 días de espera en la era molecular", parrafos: [ "Los hemocultivos han sido la principal herramienta para el diagnóstico etiológico de bacteriemia y sepsis. Sin embargo, el tiempo de espera —entre 3 y 5 días— resulta inapropiado en el contexto de la medicina molecular actual.", "Adicionalmente, los hemocultivos requieren hasta 20 mL de sangre y presentan limitaciones en pacientes con cobertura antibiótica.", ], callout: "Resultado completo en 3 a 6 horas con tan solo 4 mL de muestra, vs. 20 mL de los hemocultivos clásicos con resultados entre 3 y 5 días.", imagen: "https://assets.cdn.filesafe.space/nzcTy7e8QYhzh5745VIV/media/6a04ce1a21edbf4eefd085d0.png", }, { label: "Q Sepsis · InfectoLab", heading: "Detección directa en sangre: 20 gérmenes y resistencias en tiempo real", parrafos: [ "Q Sepsis detecta los 20 gérmenes más comunes causantes de bacteriemia y sepsis directamente en sangre, sin hemocultivo. Resultados en 4 a 6 horas con solo 4 mL de muestra.", "La detección de mutaciones de resistencia facilita el desescalamiento terapéutico con base en evidencia molecular en tiempo real.", ], bullets: [ "No requiere hemocultivo", "Resultados en 4–6 h vs. 3–5 días", "Solo 4 mL de muestra vs. 20–40 mL", "Disponible los 7 días de la semana", ], imagen: "https://assets.cdn.filesafe.space/nzcTy7e8QYhzh5745VIV/media/6a04cf9621edbf4eefd0d907.png", }, { label: "Resistencia Antimicrobiana", heading: "16 blancos genéticos de resistencia detectados en la misma muestra", parrafos: [ "Q Sepsis incluye detección simultánea de 16 blancos genéticos que predicen resistencia a los principales antimicrobianos:", ], lista_resistencias: [ { familia: "Carbapenémicos", genes: "VIM · OXA-48 · OXA-23 · IMP · NDM · KPC" }, { familia: "Glucopéptidos (VRE)", genes: "VanA · VanB" }, { familia: "Meticilina (SARM)", genes: "mecA" }, { familia: "Colistimetato", genes: "MCR-1" }, { familia: "Cefalosporinas / BLEE", genes: "TEM · CTX-M · SHV" }, { familia: "AmpC", genes: "citM" }, { familia: "Trimetoprim / Sulfas", genes: "dfrA · dfrA5 · sul2" }, { familia: "Quinolonas", genes: "aac(6')-Ib-cr" }, ], }, ], stats: [ { num: "98.4%", label: "Sensibilidad" }, { num: "96%", label: "Especificidad" }, { num: "115", label: "Pacientes validados" }, { num: "4–6 h", label: "Tiempo de resultado" }, ], referencias: [ "Intensive Care Med. 2025;51(12):2318", "JAMA. 2025 Apr 9;333(19):1724–1726", "Crit Care Med. 2026 Apr 1;54(4):725-812", ], }, estudios_relacionados: ["PCR-301", "PCR-302", "BH-001"], tags: ["Molecular", "Urgencia"], }, /* ──────── Estudios básicos / regulares ──────── */ { id: "BH-001", code: "BH-001", name: "Biometría hemática completa", departamentos: ["Hematología"], color_carta: "#7EB9C7", descripcion_corta: "Evaluación cuantitativa y morfológica de las tres series sanguíneas: eritroide, leucoide y plaquetaria.", tipo_muestra: "Sangre total", tipo_muestra_icon: "drop", tipo_contenedor: "Tubo morado EDTA 4 mL", tipo_contenedor_color: "#7C53A8", preparacion_previa: "No requiere ayuno", tiempo_entrega: "Mismo día", disponibilidad: "Lunes a sábado", precio_particular: 249.9, precio_hospital: "Solicitar convenio", publico_objetivo: "General", aplica_empresa: true, servicio_domicilio: true, default_audience: "paciente", incluye_intro: "Análisis completo de las células sanguíneas: serie roja, serie blanca y plaquetas. El estudio de rutina más solicitado en medicina general y punto de partida diagnóstico para la mayoría de las condiciones clínicas.", incluye: [ "Eritrocitos", "Hemoglobina", "Hematocrito", "Volumen globular medio (VGM)", "Conc. media de Hb corpuscular (CMHC)", "Índice de distribución de eritrocito (RDW)", "Plaquetas", "Volumen plaquetario medio (VPM)", "Leucocitos totales", "Neutrófilos totales", "Monocitos", "Linfocitos", ], observaciones: [ "No requiere ayuno.", ], audiencias: { paciente: { descripcion: "La biometría hemática completa es el análisis de sangre más común y es el punto de partida para entender tu estado de salud. En un solo estudio tu médico puede detectar si tienes anemia, una infección, problemas con tus defensas o alteraciones en las plaquetas que afectan la coagulación.", que_esperar: [ "Una sola toma de sangre en tubo morado EDTA", "No necesitas ayuno ni preparación especial", "Resultado listo el mismo día", "Recibes tu resultado digital con firma del QF certificado", ], }, medico: { descripcion: "La BHC es el estudio hematológico de referencia para el diagnóstico y seguimiento de anemias, procesos infecciosos, trastornos hematológicos y enfermedades sistémicas. Incluye conteo automatizado con diferencial de 5 partes y morfología eritrocitaria.", indicaciones: [ "Diagnóstico y clasificación de anemias (microcítica, normocítica, macrocítica)", "Evaluación de procesos infecciosos bacterianos y virales", "Seguimiento de pacientes en quimioterapia o radioterapia", "Tamizaje pre-operatorio", "Control de enfermedades crónicas (DM, HAS, autoinmunes)", "Evaluación de trombocitopenia o trombocitosis", ], }, clinica: { descripcion_extra: "La BHC es el estudio de mayor volumen en cualquier laboratorio. Con convenio InfectoLab accedes a precio preferencial por volumen, recolección programada y resultados el mismo día.", logistica: { estabilidad: "6 h temperatura ambiente / 24 h refrigerada (2–8 °C)", contenedor: "Tubo morado EDTA", recoleccion: "Programada en instalaciones con convenio activo", formato_reporte: "PDF digital con firma QF certificado", dias_disponibles: "Lunes a sábado", }, }, empresa: { caso_uso: "La biometría hemática es el pilar de los exámenes médicos de ingreso y los chequeos periódicos de salud en empresas. Permite descartar anemias, infecciones activas y alteraciones plaquetarias que pueden afectar el desempeño y seguridad del trabajador.", procesos: [ "Examen médico de ingreso", "Control periódico de salud ocupacional", "Certificación médica para puestos de riesgo", "Programas de bienestar para empleados", ], }, }, estudios_relacionados: ["QS-002", "PL-118"], tags: ["General"], }, { id: "QS-002", code: "QS-002", name: "Química sanguínea de 6 elementos", departamentos: ["Bioquímica"], color_carta: "#4D4D4D", descripcion_corta: "Glucosa, urea, creatinina, ácido úrico, colesterol total y triglicéridos.", tipo_muestra: "Suero", tipo_muestra_icon: "tube", tipo_contenedor: "Tubo rojo o amarillo (SST) 5 mL", tipo_contenedor_color: "#E54141", preparacion_previa: "Ayuno 8 h", tiempo_entrega: "24 h", disponibilidad: "Lunes a sábado", precio_particular: 320, precio_hospital: "Consultar convenio", publico_objetivo: "General", incluye_intro: "Perfil bioquímico básico para tamizaje metabólico y renal.", incluye: ["Glucosa en ayuno", "Urea (BUN)", "Creatinina", "Ácido úrico", "Colesterol total", "Triglicéridos"], observaciones: ["Ayuno estricto de 8 horas, agua permitida.", "No suspender medicamentos sin indicación médica."], estudios_relacionados: ["PL-118", "PT-104"], tags: ["Metabólico"], }, { id: "PT-104", code: "PT-104", name: "Perfil tiroideo (TSH, T3, T4 libre)", departamentos: ["Endocrinología"], color_carta: "#7EB9C7", descripcion_corta: "Tres marcadores clave para evaluación de función tiroidea.", tipo_muestra: "Suero", tipo_muestra_icon: "tube", tipo_contenedor: "Tubo rojo (SST) 5 mL", tipo_contenedor_color: "#E54141", preparacion_previa: "Sin preparación específica", tiempo_entrega: "24 h", disponibilidad: "Lunes a viernes", precio_particular: 690, precio_hospital: "Consultar convenio", publico_objetivo: "Adulto", incluye_intro: "Evaluación de función tiroidea para tamizaje, seguimiento y diagnóstico diferencial.", incluye: ["TSH (hormona estimulante tiroidea)", "T3 total", "T4 libre"], observaciones: ["Reportar uso de levotiroxina y biotina (suplementos).", "Tomar la muestra antes de la dosis matutina de levotiroxina."], estudios_relacionados: ["VIT-611", "QS-002"], tags: ["Endocrino"], }, { id: "PL-118", code: "PL-118", name: "Perfil lipídico extendido", departamentos: ["Bioquímica"], color_carta: "#4D4D4D", descripcion_corta: "Colesterol total, LDL, HDL, VLDL, triglicéridos y relación aterogénica.", tipo_muestra: "Suero", tipo_muestra_icon: "tube", tipo_contenedor: "Tubo rojo (SST) 5 mL", tipo_contenedor_color: "#E54141", preparacion_previa: "Ayuno 12 h", tiempo_entrega: "24 h", disponibilidad: "Lunes a sábado", precio_particular: 540, precio_hospital: "Consultar convenio", publico_objetivo: "Adulto", incluye_intro: "Evaluación del riesgo cardiovascular metabólico.", incluye: ["Colesterol total", "Colesterol HDL", "Colesterol LDL (calculado)", "Colesterol VLDL", "Triglicéridos", "Relación aterogénica"], observaciones: ["Ayuno estricto de 12 horas.", "Evitar consumo de alcohol 24 h antes."], estudios_relacionados: ["QS-002", "VIT-611"], tags: ["Cardiovascular"], }, { id: "CV-220", code: "CV-220", name: "Carga viral VIH-1 cuantitativa", departamentos: ["Molecular", "Infectología"], color_carta: "#3953A4", descripcion_corta: "Cuantificación de ARN del VIH-1 por PCR en tiempo real. Límite de detección: 20 copias/mL.", tipo_muestra: "Plasma EDTA", tipo_muestra_icon: "drop", tipo_contenedor: "Tubo morado EDTA 6 mL", tipo_contenedor_color: "#7C53A8", preparacion_previa: "Sin preparación", tiempo_entrega: "3–5 días hábiles", disponibilidad: "Lunes a viernes", precio_particular: 2100, precio_hospital: "Consultar convenio", publico_objetivo: "Adulto", incluye_intro: "Seguimiento cuantitativo de carga viral en pacientes con VIH-1.", incluye: ["Cuantificación de ARN viral", "Reporte en copias/mL y log10", "Interpretación clínica orientada"], observaciones: ["Procesar plasma dentro de las 6 horas posteriores a la toma.", "Mantener refrigeración 2–8 °C durante transporte."], estudios_relacionados: ["CV-230", "CV-240", "ITS-520"], tags: ["Molecular"], }, { id: "CV-230", code: "CV-230", name: "Carga viral hepatitis C", departamentos: ["Molecular", "Infectología"], color_carta: "#3953A4", descripcion_corta: "Cuantificación de ARN del virus de la hepatitis C por PCR en tiempo real.", tipo_muestra: "Plasma EDTA", tipo_muestra_icon: "drop", tipo_contenedor: "Tubo morado EDTA 6 mL", tipo_contenedor_color: "#7C53A8", preparacion_previa: "Sin preparación", tiempo_entrega: "3–5 días hábiles", disponibilidad: "Lunes a viernes", precio_particular: 2380, precio_hospital: "Consultar convenio", publico_objetivo: "Adulto", incluye_intro: "Cuantificación viral para diagnóstico, evaluación pretratamiento y seguimiento.", incluye: ["Cuantificación de ARN VHC", "Reporte en UI/mL y log10"], observaciones: ["Mantener plasma a 2–8 °C hasta procesamiento."], estudios_relacionados: ["CV-240", "CV-220"], tags: ["Molecular"], }, { id: "CV-240", code: "CV-240", name: "Carga viral hepatitis B", departamentos: ["Molecular", "Infectología"], color_carta: "#3953A4", descripcion_corta: "Cuantificación de ADN del virus de la hepatitis B por PCR en tiempo real.", tipo_muestra: "Plasma EDTA", tipo_muestra_icon: "drop", tipo_contenedor: "Tubo morado EDTA 6 mL", tipo_contenedor_color: "#7C53A8", preparacion_previa: "Sin preparación", tiempo_entrega: "3–5 días hábiles", disponibilidad: "Lunes a viernes", precio_particular: 2280, precio_hospital: "Consultar convenio", publico_objetivo: "Adulto", incluye_intro: "Cuantificación de ADN viral para diagnóstico y seguimiento de hepatitis B crónica.", incluye: ["Cuantificación de ADN VHB", "Reporte en UI/mL y log10"], observaciones: ["Mantener plasma a 2–8 °C hasta procesamiento."], estudios_relacionados: ["CV-220", "CV-230"], tags: ["Molecular"], }, { id: "PCR-301", code: "PCR-301", name: "PCR múltiplex respiratorio (22 patógenos)", departamentos: ["Molecular", "Infectología"], color_carta: "#3953A4", descripcion_corta: "Detección simultánea de 22 patógenos respiratorios virales y bacterianos.", tipo_muestra: "Hisopado nasofaríngeo", tipo_muestra_icon: "scan", tipo_contenedor: "Hisopo flocado + medio viral (UTM/VTM)", tipo_contenedor_color: "#7EB9C7", preparacion_previa: "Sin preparación", tiempo_entrega: "24 h", disponibilidad: "Lunes a sábado", precio_particular: 1850, precio_hospital: "Consultar convenio", publico_objetivo: "General", incluye_intro: "Panel sindrómico respiratorio para diagnóstico diferencial en infecciones agudas.", incluye: [ "Influenza A/B, VRS A/B", "SARS-CoV-2", "Parainfluenza 1–4", "Adenovirus, Metaneumovirus, Rinovirus", "Bordetella pertussis y parapertussis", "Mycoplasma pneumoniae, Chlamydia pneumoniae", "Otros virus respiratorios estacionales", ], observaciones: ["Toma idealmente dentro de las primeras 72 h del cuadro clínico.", "Conservar la muestra a 2–8 °C hasta su transporte."], estudios_relacionados: ["QS-001", "PCR-302"], tags: ["Molecular"], }, { id: "PCR-302", code: "PCR-302", name: "PCR múltiplex gastrointestinal", departamentos: ["Molecular"], color_carta: "#3953A4", descripcion_corta: "Panel ampliado de patógenos gastrointestinales: bacterias, virus y parásitos.", tipo_muestra: "Muestra fecal", tipo_muestra_icon: "tube", tipo_contenedor: "Frasco estéril con medio Cary-Blair", tipo_contenedor_color: "#7EB9C7", preparacion_previa: "Sin preparación", tiempo_entrega: "48 h", disponibilidad: "Lunes a sábado", precio_particular: 1980, precio_hospital: "Consultar convenio", publico_objetivo: "General", incluye_intro: "Diagnóstico molecular rápido en cuadros de gastroenteritis aguda.", incluye: ["Bacterias: Salmonella, Shigella, Campylobacter, E. coli patógenas", "Virus: Norovirus, Rotavirus, Adenovirus 40/41", "Parásitos: Giardia, Cryptosporidium, Entamoeba histolytica"], observaciones: ["Recolectar la muestra dentro de las primeras 48 h del cuadro."], estudios_relacionados: ["PCR-301", "QS-001"], tags: ["Molecular"], }, { id: "TB-410", code: "TB-410", name: "QuantiFERON-TB Gold Plus", departamentos: ["Inmunología", "Infectología"], color_carta: "#FDC345", descripcion_corta: "Detección de infección por Mycobacterium tuberculosis basada en interferón gamma.", tipo_muestra: "Sangre total", tipo_muestra_icon: "drop", tipo_contenedor: "Tubos QuantiFERON (4 colores)", tipo_contenedor_color: "#4D4D4D", preparacion_previa: "Sin preparación", tiempo_entrega: "3 días", disponibilidad: "Lunes a viernes", precio_particular: 1640, precio_hospital: "Consultar convenio", publico_objetivo: "Adulto", incluye_intro: "Prueba IGRA para tamizaje de tuberculosis latente.", incluye: ["Análisis cuantitativo de IFN-γ", "Reporte categorizado: positivo/negativo/indeterminado"], observaciones: ["Incubar tubos a 37 °C dentro de 16 h posteriores a la toma."], estudios_relacionados: ["QS-001", "PCR-301"], tags: ["Tamizaje"], }, { id: "ITS-520", code: "ITS-520", name: "Panel ITS ampliado", departamentos: ["Microbiología", "Infectología"], color_carta: "#F04F4C", descripcion_corta: "VIH, sífilis, hepatitis B y C, clamidia y gonorrea.", tipo_muestra: "Sangre + orina", tipo_muestra_icon: "drop", tipo_contenedor: "Tubo SST + frasco estéril", tipo_contenedor_color: "#E54141", preparacion_previa: "Sin preparación", tiempo_entrega: "48 h", disponibilidad: "Lunes a sábado", precio_particular: 1750, precio_hospital: "Consultar convenio", publico_objetivo: "Adulto", incluye_intro: "Tamizaje completo de infecciones de transmisión sexual.", incluye: ["Anticuerpos VIH 1/2", "Anti-HCV", "HBsAg", "VDRL / RPR para sífilis", "PCR Chlamydia trachomatis", "PCR Neisseria gonorrhoeae"], observaciones: ["Muestra de orina del primer chorro, 1 h sin orinar previamente."], estudios_relacionados: ["CV-220", "CV-230"], tags: ["Tamizaje"], }, { id: "VIT-611", code: "VIT-611", name: "Perfil de vitaminas D, B12 y ácido fólico", departamentos: ["Bioquímica"], color_carta: "#4D4D4D", descripcion_corta: "Evaluación nutricional con tres marcadores fundamentales.", tipo_muestra: "Suero", tipo_muestra_icon: "tube", tipo_contenedor: "Tubo rojo (SST) 5 mL", tipo_contenedor_color: "#E54141", preparacion_previa: "Ayuno 8 h", tiempo_entrega: "48 h", disponibilidad: "Lunes a sábado", precio_particular: 1180, precio_hospital: "Consultar convenio", publico_objetivo: "General", incluye_intro: "Tamizaje de deficiencias nutricionales más prevalentes.", incluye: ["25-OH Vitamina D", "Vitamina B12", "Ácido fólico (folato sérico)"], observaciones: ["Suspender suplementos vitamínicos 48 h antes si es posible.", "Reportar uso de biotina."], estudios_relacionados: ["PT-104", "PL-118"], tags: ["Nutricional"], }, ]; const CATEGORIES = [ { key: "all", label: "Todos los estudios" }, { key: "Molecular", label: "Molecular" }, { key: "Hematología", label: "Hematología" }, { key: "Bioquímica", label: "Bioquímica" }, { key: "Endocrinología", label: "Endocrinología" }, { key: "Infectología", label: "Infectología" }, { key: "Inmunología", label: "Inmunología" }, { key: "Microbiología", label: "Microbiología" }, ]; /* ────────────────────────── CATÁLOGO ────────────────────────── */ function CatalogPage({ navigate, openStudy, onOpenLead, filtro, setFiltro }) { const API = window.InfectoAPI, N = window.InfectoNorm, E = window.InfectoEnrich; const POR_PAGINA = 24; const [cats, setCats] = React.useState([]); const [cat, setCat] = React.useState("all"); const [q, setQ] = React.useState(""); const [qServidor, setQServidor] = React.useState(""); const [muestra, setMuestra] = React.useState("all"); const [pagina, setPagina] = React.useState(1); const [datos, setDatos] = React.useState(null); // página actual del servidor const [cargando, setCargando] = React.useState(true); const [error, setError] = React.useState(null); /* El filtro vive en App: persiste al volver de la ficha y llega preaplicado * cuando se entra desde una landing. Comparando identidad se evita pisar * lo que el usuario ya estaba haciendo dentro de la página. */ const filtroLocal = React.useRef(null); React.useEffect(() => { if (!filtro || filtro === filtroLocal.current) return; filtroLocal.current = filtro; setCat(filtro.cat || "all"); if (filtro.q) { setQ(filtro.q); setQServidor(filtro.q); } if (filtro.sample && filtro.sample !== "all") setMuestra(filtro.sample); }, [filtro]); React.useEffect(() => { if (!setFiltro) return; if (filtro && filtro.cat === cat && filtro.q === q && (filtro.sample || "all") === muestra) return; setFiltro({ cat, q, sample: muestra }); }, [cat, q, muestra, filtro]); /* El catálogo completo son 9 peticiones y ~220 KB. No se pide al entrar: la * página se arma con una sola petición de 24. Solo se baja entero cuando hace * falta de verdad, que es al usar el filtro por tipo de muestra — ese filtro * la API no lo soporta y no hay forma de resolverlo del lado del servidor. */ const [catalogo, setCatalogo] = React.useState(null); const [bajandoCatalogo, setBajandoCatalogo] = React.useState(false); /* El buscador de la API hace coincidencia literal sobre nombre, clave y * keywords del laboratorio. No conoce los nombres comerciales, que viven en * src/enrich.js: buscar "Q Sepsis" contra el servidor devuelve cero, porque * ahí ese estudio se llama "PCR MÚLTIPLEX E HIBRIDACIÓN REVERSA PARA SEPSIS…". * Cuando el servidor no encuentra nada, se baja el catálogo y se reintenta * contra el índice local, que sí incluye lo del overlay. */ const [respaldoLocal, setRespaldoLocal] = React.useState(false); const quiereLocal = muestra !== "all" || respaldoLocal; const enLocal = quiereLocal && !!catalogo; React.useEffect(() => { let vivo = true; API.getCategorias().then((c) => { if (vivo) setCats(c); }).catch(() => {}); return () => { vivo = false; }; }, []); // una petición por página / filtro / búsqueda, con caché por combinación React.useEffect(() => { if (muestra !== "all") return; // en ese caso manda el catálogo local let vivo = true; setCargando(true); API.getEstudios({ q: qServidor || null, departamento: cat === "all" ? null : cat, page: pagina, per_page: POR_PAGINA, }) .then((r) => { if (!vivo) return; setDatos(r); setError(null); // el servidor no encontró nada y hay algo escrito: puede ser un nombre // comercial, que solo existe del lado del overlay setRespaldoLocal(!!qServidor && r.meta.total === 0); }) .catch((e) => { if (vivo) setError(e); }) .finally(() => { if (vivo) setCargando(false); }); return () => { vivo = false; }; }, [qServidor, cat, pagina, muestra]); // el catálogo completo, solo cuando de verdad hace falta React.useEffect(() => { if (!quiereLocal || catalogo || bajandoCatalogo) return; let vivo = true; setBajandoCatalogo(true); API.getTodosLosEstudios() .then((lista) => { if (vivo) setCatalogo(lista.map(E.enriquecer)); }) .catch((e) => { if (vivo) setError(e); }) .finally(() => { if (vivo) setBajandoCatalogo(false); }); return () => { vivo = false; }; }, [quiereLocal]); // la búsqueda va al servidor: sin debounce, cada tecla sería una petición const aplicarBusqueda = React.useMemo(() => API.debounce(setQServidor, 400), []); const onBuscar = (e) => { setQ(e.target.value); aplicarBusqueda(e.target.value); }; React.useEffect(() => { setPagina(1); }, [qServidor, cat, muestra]); const filtradoLocal = React.useMemo( () => (enLocal ? API.filtrarEstudios(catalogo, { q: qServidor, categoria: cat, muestra: muestra }) : null), [enLocal, catalogo, qServidor, cat, muestra] ); /* Con filtro de muestra activo y el catálogo todavía en camino, no se puede * mostrar la página del servidor: son los resultados SIN filtrar, y aparentarían * estar filtrados. Mejor vaciar la lista y que se vea que está cargando. */ const listo = quiereLocal ? !!catalogo : !!datos; const lista = !listo ? [] : enLocal ? filtradoLocal.slice((pagina - 1) * POR_PAGINA, pagina * POR_PAGINA) : datos.estudios.map(E.enriquecer); const totalResultados = !listo ? 0 : (enLocal ? filtradoLocal.length : datos.meta.total); const ultimaPagina = !listo ? 1 : enLocal ? Math.max(1, Math.ceil(filtradoLocal.length / POR_PAGINA)) : (datos.meta.last_page || 1); const ocupado = quiereLocal ? (bajandoCatalogo || !catalogo) : cargando; const tiposMuestra = React.useMemo(() => { const vistos = {}; (N.REGLAS_MUESTRA || []).forEach((r) => { vistos[r.tag] = r.label; }); return Object.keys(vistos).map((tag) => [tag, vistos[tag]]); }, []); const irA = (p) => { setPagina(Math.min(Math.max(1, p), ultimaPagina)); const tope = document.querySelector(".catalog-shell"); if (tope) tope.scrollIntoView({ behavior: "smooth", block: "start" }); }; if (error && !datos) { return (

No pudimos cargar el catálogo

{error.message}

); } return (
Catálogo clínico{totalResultados ? ` · ${totalResultados.toLocaleString("es-MX")} estudios` : ""}

Estudios y perfiles, organizados para encontrar lo que buscas.

Filtra por departamento, busca por nombre o código y consulta preparación, tipo de muestra y tiempo de respuesta de cada estudio.

{ocupado && !lista.length ? "Cargando…" : <>Mostrando {lista.length} de {totalResultados.toLocaleString("es-MX")} · página {pagina} de {ultimaPagina}}
{!ocupado && lista.length === 0 && (

No encontramos estudios con esos filtros

Prueba con otra categoría o solicítanos el estudio directamente.

)} {lista.map(s => (
{s.code && {s.code}} {s.categorias.map(d => ( {d} ))} {s.resistencias && Detección AMR}

{s.titulo}

{s.descripcion_corta &&

{s.descripcion_corta}

}
Muestra: {s.muestra.etiqueta} Entrega: {s.entrega.texto} {s.contenedor.texto && Contenedor: {s.contenedor.etiqueta}}
{s.precio.consultar ? Consultar precio : <>{s.precio.texto} MXN }
))}
{ultimaPagina > 1 && }
); } /* Paginador con ventana deslizante: primera, última y las vecinas de la actual. * Con 34 páginas no caben todas, y saltar de la 1 a la 34 tiene que ser posible. */ function Paginador({ pagina, ultima, irA }) { const numeros = []; const cerca = (p) => p === 1 || p === ultima || Math.abs(p - pagina) <= 1; for (let p = 1; p <= ultima; p++) { if (cerca(p)) numeros.push(p); else if (numeros[numeros.length - 1] !== "…") numeros.push("…"); } const btn = { minWidth: 38, height: 38, padding: "0 10px", borderRadius: 10, border: "1px solid var(--ink-200)", background: "#fff", fontSize: 14, fontWeight: 500, cursor: "pointer", color: "var(--ink-700)", }; const activo = Object.assign({}, btn, { background: "var(--brand-primary)", borderColor: "var(--brand-primary)", color: "#fff", fontWeight: 700, }); const inerte = Object.assign({}, btn, { border: "none", background: "none", cursor: "default", color: "var(--ink-400)" }); return ( ); } /* ────────────────────────── STUDY DETAIL — DYNAMIC TEMPLATE ────────────────────────── */ function StudyDetailPage({ studyId, navigate, openStudy, onOpenLead }) { const API = window.InfectoAPI, N = window.InfectoNorm, E = window.InfectoEnrich; const [s, setS] = React.useState(null); const [catalogo, setCatalogo] = React.useState([]); const [error, setError] = React.useState(null); const [audience, setAudience] = React.useState("paciente"); /* La home, el blog y las landings todavía apuntan a los ids del prototipo * ("QS-001", "PCR-301") y la API responde por id numérico. Si el id no * existe, se localiza el estudio del prototipo por nombre y se abre el * real. En cuanto esos contenidos se migren, esto se elimina. */ const [aliasId, setAliasId] = React.useState(null); React.useEffect(() => { setAliasId(null); }, [studyId]); React.useEffect(() => { let vivo = true; setS(null); setError(null); // El detalle se pide SOLO aquí, al abrir la ficha — nunca al pintar el listado. API.getEstudio(aliasId || studyId) .then((bruto) => { if (!vivo) return; const est = E.enriquecer(bruto); setS(est); setAudience(est.default_audience || "paciente"); }) .catch((e) => { if (!vivo) return; if (!e.noEncontrado || !window.STUDIES || aliasId) { setError(e); return; } const legado = window.STUDIES.find((x) => x.id === studyId); if (!legado) { setError(e); return; } const nombre = N.plano(legado.name); API.getTodosLosEstudios() .then((lista) => { const hit = lista.map(E.enriquecer).find((x) => N.plano(x.titulo) === nombre); if (vivo && hit) setAliasId(hit.id); else if (vivo) setError(e); }) .catch(() => { if (vivo) setError(e); }); }); // Ya está en caché desde el catálogo; sirve para los estudios relacionados. API.getTodosLosEstudios() .then((lista) => { if (vivo) setCatalogo(lista.map(E.enriquecer)); }) .catch(() => { /* sin catálogo simplemente no hay relacionados */ }); return () => { vivo = false; }; }, [studyId, aliasId]); const related = React.useMemo(() => { if (!s || !catalogo.length) return []; const porId = (id) => catalogo.filter((x) => x.id === id)[0]; const explicitos = (s.relacionados || []).map(porId).filter(Boolean); if (explicitos.length) return explicitos.slice(0, 3); // si el overlay no los declara, se infieren por categoría return catalogo .filter((x) => x.id !== s.id && x.categorias.some((d) => s.categorias.indexOf(d) !== -1)) .slice(0, 3); }, [s, catalogo]); /* Mientras se resuelve un id del prototipo, la ficha sigue en carga. */ if (error && !aliasId) { const noExiste = error.noEncontrado; return (

{noExiste ? "Este estudio ya no está disponible" : "No pudimos cargar el estudio"}

{noExiste ? "Es posible que el laboratorio lo haya dado de baja. Búscalo en el catálogo o solicítanos información." : error.message}

); } if (!s) { return (
Cargando estudio…
); } const dept = s.categorias[0] || null; const deptColor = s.color; /* Las pestañas por audiencia solo tienen sentido donde hay contenido curado. * De los 801 estudios del catálogo, 12 lo tienen; el resto se pinta con la * ficha técnica del laboratorio, que es lo único que existe de ellos. */ const tieneAudiencias = !!s.audiencias; const aplicaEmpresa = s.aplica_empresa === true; const audTabs = [ ["paciente", "Paciente"], ["medico", "Médico"], ["clinica", "Clínica / Hospital"], ...(aplicaEmpresa ? [["empresa", "Empresa"]] : []), ]; const aud = (s.audiencias && s.audiencias[audience]) || {}; // Clínica hereda Médico para indicaciones/comparativo/desempeño const medAud = (s.audiencias && s.audiencias.medico) || {}; const clinAud = (s.audiencias && s.audiencias.clinica) || {}; const showIndicaciones = (audience === "medico" || audience === "clinica") && medAud.indicaciones && medAud.indicaciones.length > 0; const showComparativo = (audience === "medico" || audience === "clinica") && medAud.comparativo; const showDesempeno = (audience === "medico" || audience === "clinica") && medAud.desempeno; const showLogistica = audience === "clinica" && clinAud.logistica; const showEmpresaCaso = audience === "empresa" && aud.caso_uso; const audDesc = aud.descripcion || s.descripcion_corta; return (
{/* Breadcrumb */}
{/* Color accent bar */}
{/* Audience tabs — solo donde hay contenido curado por audiencia */} {tieneAudiencias && (
{audTabs.map(([k, l]) => ( ))}
Cambia el contenido y los precios según quién consulta
)}
{/* MAIN */}
{/* 1. Identificación */}
{s.categorias.map(d => ( {d} ))} Código {s.code} {s.resistencias && Detección AMR} {tieneAudiencias && audience !== "paciente" && Vista {audience}}

{s.titulo}

{audDesc &&

{audDesc}

} {/* Domicilio banner — copy varies by audience */} {s.servicio_domicilio && } {/* 2. Info grid 2x2 */}
{/* 3. ¿Qué incluye? */} {s.incluye && s.incluye.length > 0 && (

¿Qué incluye este estudio?

{s.incluye_intro &&

{s.incluye_intro}

}
{s.incluye.map((it, i) => (
{it.texto}
))}
)} {/* 4. Resistencias antimicrobianas — solo si aplica */} {s.resistencias && audience !== "paciente" && (

Detección de resistencia antimicrobiana

Solo estudios moleculares

Identificación de genes asociados a los principales mecanismos de resistencia clínicamente relevantes.

{s.resistencias.map(r => (

{r.titulo}

{r.descripcion}

{r.genes.map(g => {g})}
))}
)} {s.resistencias && audience === "paciente" && (

Este estudio detecta resistencia a antibióticos

Q Sepsis no solo identifica al patógeno: también detecta genes de resistencia que ayudan a tu médico a elegir el antibiótico correcto desde el primer momento. La vista clínica detallada está disponible para personal médico.

)} {/* 5. Observaciones */} {s.observaciones && s.observaciones.length > 0 && (

Observaciones generales

    {s.observaciones.map((o, i) => (
  1. {i+1} {o}
  2. ))}
)} {/* ─────── BLOQUE DE AUDIENCIA ─────── */} {/* Paciente · ¿Qué esperar? */} {audience === "paciente" && aud.que_esperar && aud.que_esperar.length > 0 && (
Vista paciente

¿Qué esperar?

El paso a paso desde que llegas al laboratorio hasta que recibes tu resultado digital.

    {aud.que_esperar.map((p, i) => (
  1. {i+1} {p}
  2. ))}
)} {/* Médico / Clínica · Indicaciones clínicas */} {showIndicaciones && (
Vista clínica

Indicaciones clínicas

Escenarios en los que esta prueba aporta mayor valor diagnóstico.

{medAud.indicaciones.map((it, i) => (
{it}
))}
)} {/* Médico / Clínica · Comparativo vs alternativa convencional */} {showComparativo && (

Comparativo vs. {medAud.comparativo.alt_nombre}

Diferencias operativas y clínicas frente al método convencional.

{medAud.comparativo.esta_nombre}
{medAud.comparativo.alt_nombre}
{medAud.comparativo.rows.map((r, i) => (
{r.feature}
{r.esta}
{r.alt}
))}
)} {/* Médico / Clínica · Datos de desempeño */} {showDesempeno && (

Datos de desempeño diagnóstico

Resultados de validación clínica interna de InfectoLab.

{[ ["Sensibilidad", medAud.desempeno.sensibilidad], ["Especificidad", medAud.desempeno.especificidad], ["VPP", medAud.desempeno.vpp], ["VPN", medAud.desempeno.vpn], ["n", medAud.desempeno.n], ].filter(([,v]) => v).map(([k, v]) => (
{v}
{k}
))}
)} {/* Clínica · Descripción adicional + Logística */} {audience === "clinica" && clinAud.descripcion_extra && (
Vista clínica / hospital

Para tu institución

{clinAud.descripcion_extra}

)} {showLogistica && (

Logística operativa

Parámetros que tu equipo necesita para integrar la prueba a su flujo.

{[ ["Estabilidad de muestra", clinAud.logistica.estabilidad, "clock"], ["Tipo de contenedor", clinAud.logistica.contenedor, "drop"], ["Frecuencia de recolección", clinAud.logistica.recoleccion, "calendar"], ["Formato de reporte", clinAud.logistica.formato_reporte, "shield"], ["Días disponibles", clinAud.logistica.dias_disponibles, "check"], ].filter(([,v]) => v).map(([k, v, ic]) => (
{k}
{v}
))}
)} {/* Empresa · Caso de uso + procesos */} {showEmpresaCaso && (
Vista empresa

Caso de uso corporativo

{aud.caso_uso}

{aud.procesos && aud.procesos.length > 0 && ( <>

En qué procesos aplica

{aud.procesos.map((p, i) => (
{p}
))}
)}
)} {/* 6. Relacionados */} {related.length > 0 && (

Estudios relacionados

{related.map(r => ( ))}
)} {/* 7. Artículo clínico — siempre al final */} {s.articulo_clinico && audience !== "paciente" && (
)} {/* 8. Recursos del estudio — ejemplos de resultado y landings */}
{/* SIDEBAR */}
); } /* ─────────── helpers ─────────── */ /* Acceso a los ejemplos de resultado del estudio y a las landings de la * especialidad. Lee de window.LANDINGS_DATA: la misma fuente que la * biblioteca, para que ficha y biblioteca nunca se desincronicen. */ /* Acceso a los ejemplos de resultado del estudio y a las landings de la * especialidad. Lee de window.LANDINGS_DATA: la misma fuente que la * biblioteca, para que ficha y biblioteca nunca se desincronicen. */ function EstudioRecursos({ s, navigate }) { const LD = window.LANDINGS_DATA; if (!LD) return null; const ejemplos = LD.recursosDeEstudio(s.code); const esMolecular = (s.categorias || []).some((c) => /molecular/i.test(c)); if (ejemplos.length === 0 && !esMolecular) return null; return (

Recursos del estudio

{ejemplos.length > 0 ? (
{ejemplos.map(r => ( {r.formato ? r.formato.toUpperCase() : ""}

{r.nombre}

Ejemplo de resultado Ver → (abre en una pestaña nueva)
))}
) : (

Consulta el formato de reporte de nuestros paneles en la biblioteca de ejemplos de resultados.

)} {esMolecular && (
)}
); } function InfoCard({ icon, containerColor, label, value, sub, accent }) { return (
{containerColor ? : }
{label}
{value}
{sub &&
{sub}
}
); } function TubeIcon({ color }) { return ( ); } function ClinicalArticle({ a }) { // Backward-compatible: si viene con `parrafos` plano, lo envuelve en una sección const secciones = a.secciones || (a.parrafos ? [{ parrafos: a.parrafos }] : []); return (
{a.avatar_initials || "MD"}
{a.autor}
{a.rol}
{a.badge || "Artículo clínico"}
{a.titulo &&

{a.titulo}

} {secciones.map((sec, i) => (
{sec.label &&
{sec.label}
} {sec.heading &&

{sec.heading}

} {(sec.parrafos || []).map((p, j) =>

{p}

)} {sec.callout && (
{sec.callout}
)} {sec.imagen && (
{sec.heading
)} {sec.bullets && sec.bullets.length > 0 && (
    {sec.bullets.map((b, j) => (
  • {b}
  • ))}
)} {sec.lista_resistencias && sec.lista_resistencias.length > 0 && (
{sec.lista_resistencias.map((r, j) => (
{r.familia} {r.genes}
))}
)} {sec.referencia && (
{sec.referencia}
)}
))} {a.stats && a.stats.length > 0 && (
{a.stats.map((st, i) => (
{st.num}{st.label}
))}
)} {a.referencias && a.referencias.length > 0 && (
Referencias
    {a.referencias.map((r, i) =>
  1. {r}
  2. )}
)}
); } /* ─────── Audience-specific helpers ─────── */ function DomicilioBanner({ audience, accent, onOpenLead }) { const copy = { paciente: { title: "¿No puedes venir? Te vamos a tu domicilio.", sub: "Solo agéndalo. Sin convenio, sin trámite — directo a tu casa.", cta: "Agendar toma a domicilio", }, medico: { title: "Servicio de toma en consultorio disponible con convenio.", sub: "¿Aún no tienes convenio? Habla con nuestra área de ventas para gestionarlo.", cta: "Solicitar convenio", }, clinica: { title: "Recolección en tus instalaciones con convenio activo.", sub: "Consulta ruta y frecuencia con nuestra área de ventas.", cta: "Hablar con ventas", }, empresa: { title: "Toma masiva en tus instalaciones — sin que tus empleados salgan.", sub: "Disponible mediante orden de compra o contrato de servicio corporativo.", cta: "Solicitar cotización", }, }[audience] || {}; if (!copy.title) return null; return (
Servicio a domicilio · {audience}
{copy.title}
{copy.sub}
); } function AudienceActions({ audience, onOpenLead, navigate }) { const set = { paciente: { primary: { label: "Encuentra tu sucursal más cercana", onClick: () => navigate("contacto") }, secondary: { label: "Contáctanos por WhatsApp", onClick: onOpenLead, icon: "whatsapp" }, }, medico: { primary: { label: "Generar orden de laboratorio", onClick: onOpenLead, icon: "arrow", note: "Acceso InfectoApp" }, secondary: { label: "Solicitar convenio", onClick: onOpenLead }, }, clinica: { primary: { label: "Solicitar convenio", onClick: onOpenLead, icon: "arrow" }, secondary: { label: "Agendar cita con área de ventas", onClick: () => navigate("contacto") }, }, empresa: { primary: { label: "Solicitar cotización corporativa", onClick: onOpenLead, icon: "arrow" }, secondary: { label: "Contáctanos por WhatsApp", onClick: onOpenLead, icon: "whatsapp" }, }, }[audience] || {}; return (
{set.primary && ( )} {set.primary?.note &&
{set.primary.note}
} {set.secondary && ( )}
); } Object.assign(window, { CatalogPage, StudyDetailPage, EstudioRecursos, STUDIES, CATEGORIES, DEPT_COLORS, });