/* App router */ /* ── Rutas reales ──────────────────────────────────────────────────────── * El plan de redirecciones marca estas URLs como «200 — conservar»: no * cambian, y el sitio nuevo tiene que responderlas tal cual. El prototipo * navegaba por estado y sólo el blog usaba hash, así que ninguna existía como * ruta. Aquí vive la canónica por página; Header y Footer la consumen desde * window.RUTAS_WEB para que sus enlaces apunten a la ruta real. Las URLs que * cambian llevan 301/410 en .htaccess, no aquí. */ const RUTAS_WEB = { home: "/", catalogo: "/estudios-de-laboratorio/", sucursales: "/sucursales/", contacto: "/contactanos/", vacunacion: "/centro-de-vacunacion/", calidad: "/certificaciones/", privacidad: "/politicas-de-privacidad/", terminos: "/terminos-y-condiciones/", cookies: "/politica-de-cookies/", regulatorio: "/informacion-regulatoria/", "pcr-multiplex": "/estudios-de-laboratorio/estudios-pcr-multiplex/", sindromatico: "/estudios-de-laboratorio/estudios-pcr-multiplex/diagnostico-sindromatico/", vih: "/estudios-de-laboratorio/vih/", ejemplos: "/estudios-de-laboratorio/ejemplos-de-resultados-de-laboratorio/", "ets-box": "/prueba-ets/", "ets-instrucciones": "/prueba-ets/instrucciones/", paquenote: "/pa-quenote-arrepientas/", "perfiles-ginecologicos": "/perfiles-ginecologicos/", "vaginitis-vaginosis": "/pcr-vaginitis-vaginosis-33/", blog: "/blog/", autor: "/dr-samuel-navarro-alvarez/", /* politica-calidad: desactivada 2026-10-05 (auditoría ISO); la página * completa sigue en src/pages-legal.jsx y src/legal-data.js. */ }; window.RUTAS_WEB = RUTAS_WEB; /* El hash fue el enlace profundo del prototipo; se conserva como respaldo * para no romper enlaces ya compartidos. /iso-9001-2015/ es la única URL * vieja con destino distinto en el plan (301 → /certificaciones/). */ const RUTAS_LEGADO = { "/iso-9001-2015": "calidad" }; const sinBarra = (r) => (r || "").replace(/\/+$/, "") || "/"; /* Cada autor tiene su URL canónica en los datos del blog (profile_url). */ const rutaAutor = (id) => { const autores = (window.BLOG_DATA && window.BLOG_DATA.authors) || []; const a = autores.find((x) => x.id === id); return (a && a.profile_url) || null; }; /* Resuelve una ruta (pathname o hash) a página y parámetros. */ const leerRuta = (ruta) => { const p = sinBarra((ruta || "").split("?")[0]); let m; if (p === "/") return { page: "home" }; /* Dinámicas primero: /blog/page/2/ y /blog// comparten prefijo. */ if ((m = p.match(/^\/blog\/page\/(\d+)$/))) return { page: "blog", blogPage: +m[1] }; if ((m = p.match(/^\/blog\/([^\/]+)$/))) return { page: "blog-articulo", articleSlug: m[1] }; if ((m = p.match(/^\/category\/([^\/]+)$/))) return { page: "categoria", topicSlug: m[1] }; const autor = ((window.BLOG_DATA && window.BLOG_DATA.authors) || []).find((a) => sinBarra(a.profile_url) === p); if (autor) return { page: "autor", authorId: autor.id }; const pagina = Object.keys(RUTAS_WEB).find((k) => sinBarra(RUTAS_WEB[k]) === p); if (pagina) return { page: pagina }; const legado = Object.keys(RUTAS_LEGADO).find((k) => sinBarra(k) === p); return legado ? { page: RUTAS_LEGADO[legado] } : null; }; function App() { const [page, setPage] = React.useState("home"); const [studyId, setStudyId] = React.useState(null); const [leadOpen, setLeadOpen] = React.useState(false); /* Filtro del catálogo elevado a App: así persiste cuando se llega desde una * landing con `openCatalog({cat, q})` y sigue puesto al volver del detalle. */ const CAT_FILTRO_VACIO = { cat: "all", q: "", sample: "all" }; const [catalogFilter, setCatalogFilter] = React.useState(CAT_FILTRO_VACIO); /* Blog: slug del artículo, slug del tema, estado del listado y de la paginación * de categoría. El estado del listado se conserva al volver desde un artículo. */ const [articleSlug, setArticleSlug] = React.useState(null); const [topicSlug, setTopicSlug] = React.useState(null); const [authorId, setAuthorId] = React.useState("samuel-navarro"); const [blogState, setBlogState] = React.useState({ q: "", topic: "all", author: "all", page: 1 }); const [catPage, setCatPage] = React.useState(1); const navigate = (p) => { setPage(p); if (p !== "detalle") setStudyId(null); window.scrollTo({ top: 0, behavior: "instant" }); }; const openStudy = (id) => { setStudyId(id); setPage("detalle"); window.scrollTo({ top: 0, behavior: "instant" }); }; const onOpenLead = () => setLeadOpen(true); /* `null` limpia el filtro; un objeto lo aplica sobre el estado vacío. */ const openCatalog = (filtro) => { setCatalogFilter(filtro ? { ...CAT_FILTRO_VACIO, ...filtro } : CAT_FILTRO_VACIO); navigate("catalogo"); }; const toTop = () => window.scrollTo({ top: 0, behavior: "instant" }); const openArticle = (slug) => { setArticleSlug(slug); setPage("blog-articulo"); toTop(); }; const openTopic = (slug) => { setTopicSlug(slug); setCatPage(1); setPage("categoria"); toTop(); }; const openAuthor = (id) => { setAuthorId(id || "samuel-navarro"); setPage("autor"); toTop(); }; /* Ruta canónica de la página actual. Las páginas sin URL aprobada * (audiencias, detalle de estudio, resultados) devuelven null y no tocan la * barra de direcciones: no se inventan URLs. */ const rutaDe = (p) => { if (p === "autor") return rutaAutor(authorId) || RUTAS_WEB.autor || null; if (p === "blog-articulo") return articleSlug ? "/blog/" + articleSlug + "/" : null; if (p === "categoria") return topicSlug ? "/category/" + topicSlug + "/" : null; if (p === "blog") return blogState.page > 1 ? "/blog/page/" + blogState.page + "/" : "/blog/"; return RUTAS_WEB[p] || null; }; /* La primera pasada no toca la URL: el lector de abajo interpreta la de * entrada (ruta real o hash heredado). Después, cada navegación apila la * ruta canónica, de modo que atrás/adelante recorren el historial. */ const primeraRuta = React.useRef(true); React.useEffect(() => { if (primeraRuta.current) { primeraRuta.current = false; return; } const r = rutaDe(page); if (r && sinBarra(window.location.pathname) !== sinBarra(r)) { window.history.pushState(null, "", r); } }, [page, articleSlug, topicSlug, blogState.page, authorId]); React.useEffect(() => { const aplicar = (r) => { if (r.blogPage) setBlogState((s) => ({ ...s, page: r.blogPage })); if (r.articleSlug) setArticleSlug(r.articleSlug); if (r.topicSlug) { setCatPage(1); setTopicSlug(r.topicSlug); } if (r.page === "autor") setAuthorId(r.authorId || "samuel-navarro"); setPage(r.page); }; const leer = () => { /* El hash heredado gana si existe: es el enlace ya compartido. */ const h = window.location.hash.replace(/^#/, ""); const r = h ? leerRuta(h) : leerRuta(window.location.pathname); if (r) aplicar(r); else if (!h) setPage("home"); }; leer(); window.addEventListener("popstate", leer); window.addEventListener("hashchange", leer); return () => { window.removeEventListener("popstate", leer); window.removeEventListener("hashchange", leer); }; }, []); // Sync screen label on body for verifier/comment context React.useEffect(() => { document.body.setAttribute("data-current-page", page); }, [page]); return ( <>
{page === "home" && } {page === "pacientes" && } {page === "medicos" && } {page === "lealtad" && } {page === "clinicas" && } {page === "empresas" && } {page === "catalogo" && } {page === "detalle" && } {page === "pcr-multiplex" && } {page === "sindromatico" && } {page === "ejemplos" && } {page === "calidad" && } {page === "ets-box" && } {page === "ets-instrucciones" && } {page === "paquenote" && } {page === "vih" && } {page === "perfiles-ginecologicos" && } {page === "vaginitis-vaginosis" && } {page === "privacidad" && } {page === "terminos" && } {page === "cookies" && } {page === "regulatorio" && } {page === "vacunacion" && } {page === "sucursales" && } {page === "resultados" && } {page === "contacto" && } {page === "mobile" && } {page === "blog" && } {page === "blog-articulo" && } {page === "autor" && } {page === "categoria" && }