// Búsqueda de literatura en PubMed (modal). Puerto TS de js/papers.js, apuntando al // nuevo endpoint FastAPI /api/papers. import type { Patron } from './tipos.js'; import { manejadorAsync } from './async.js'; const PROXY_URL = '/api/papers'; const POR_PAGINA = 10; interface Paper { pmid?: string; title?: string; authors?: Array<{ name: string }>; year?: string; doi?: string; journal?: string; } let todosLosPapers: Paper[] = []; let paginaActual = 0; let consultaActual = ''; // PubMed sólo recibe texto en inglés; se traducen los nombres de patrón. const TERMINOS_EN: Record = { 'Anemia': 'anemia', 'Eritrocitosis': 'erythrocytosis polycythemia', 'Leucocitosis': 'leukocytosis', 'Leucocitosis neutrofílica': 'neutrophilic leukocytosis', 'Leucocitosis linfocítica': 'lymphocytic leukocytosis', 'Leucopenia': 'leukopenia', 'Eosinofilia': 'eosinophilia', 'Neutropenia': 'neutropenia', 'Linfopenia': 'lymphopenia', 'Trombocitopenia': 'thrombocytopenia', 'Trombocitosis': 'thrombocytosis', 'Daño hepatocelular': 'hepatocellular damage liver injury', 'Elevación de ALT aislada': 'ALT elevation liver', 'Patrón colestásico': 'cholestasis', 'Hiperbilirrubinemia': 'hyperbilirubinemia jaundice', 'Azotemia': 'azotemia renal failure', 'Hiperuremia aislada (BUN)': 'elevated BUN prerenal azotemia', 'BUN disminuido': 'low BUN hepatic failure', 'Creatinina elevada (BUN normal)': 'elevated creatinine kidney', 'Hiperglucemia': 'hyperglycemia diabetes mellitus', 'Hipoglucemia': 'hypoglycemia', 'Hiperproteinemia': 'hyperproteinemia', 'Hipoproteinemia / Hipoalbuminemia': 'hypoproteinemia hypoalbuminemia', 'Hipoalbuminemia': 'hypoalbuminemia', 'Hipercalcemia': 'hypercalcemia', 'Hipocalcemia': 'hypocalcemia', 'Hipernatremia': 'hypernatremia', 'Hiponatremia': 'hyponatremia', 'Hiperpotasemia': 'hyperkalemia', 'Hipopotasemia': 'hypokalemia', 'Hiperfosforemia': 'hyperphosphatemia', 'Hipotiroidismo': 'hypothyroidism', 'Hipertiroidismo': 'hyperthyroidism', 'Hiperadrenocorticismo (Cushing)': 'hyperadrenocorticism Cushing', 'Hipoadrenocorticismo (Addison)': 'hypoadrenocorticism Addison', 'Ratio Na:K reducido — sospecha de hipoadrenocorticismo': 'hypoadrenocorticism sodium potassium ratio', 'Cortisol basal bajo — posible hipoadrenocorticismo': 'low basal cortisol hypoadrenocorticism', 'Hiposthenuria': 'hyposthenuria urine specific gravity', 'Isosthenuria': 'isosthenuria urine concentration renal', 'Posible déficit de insulina': 'insulin deficiency hyperglycemia diabetes mellitus', }; const ACENTOS: Record = { á: 'a', é: 'e', í: 'i', ó: 'o', ú: 'u', ñ: 'n' }; const traducirPatron = (nombre: string): string => { for (const [es, en] of Object.entries(TERMINOS_EN)) { if (nombre.startsWith(es)) return en; } return nombre.replace(/[áéíóúñ]/g, (c) => ACENTOS[c] || c); }; const construirQuery = (patrones: Patron[]): string => { if (!patrones || patrones.length === 0) return 'veterinary clinical laboratory diagnosis canine feline'; const terminos = [...new Set(patrones.map((p) => traducirPatron(p.nombre)))].slice(0, 3); return `${terminos.join(' ')} canine OR feline veterinary`; }; const buscarPapers = async (query: string): Promise => { const respuesta = await fetch(`${PROXY_URL}?query=${encodeURIComponent(query)}`, { credentials: 'same-origin' }); if (!respuesta.ok) { const detalle = await respuesta.json().catch(() => ({})); throw new Error(detalle.detail || detalle.error || `Error ${respuesta.status}`); } const datos = await respuesta.json(); return datos.data || []; }; const renderizarTarjetaPaper = (paper: Paper): HTMLElement => { const titulo = paper.title || 'Sin título'; const autores = paper.authors?.slice(0, 3).map((a) => a.name).join(', ') || 'Autores desconocidos'; const masAutores = (paper.authors?.length || 0) > 3 ? ' et al.' : ''; const anio = paper.year || '—'; const revista = paper.journal || ''; const urlDoi = paper.doi ? `https://doi.org/${paper.doi}` : null; const urlPubmed = paper.pmid ? `https://pubmed.ncbi.nlm.nih.gov/${paper.pmid}/` : null; const urlPrincipal = urlDoi || urlPubmed; const articulo = document.createElement('article'); articulo.className = 'paper-tarjeta'; const meta = document.createElement('div'); meta.className = 'paper-meta'; const spanAnio = document.createElement('span'); spanAnio.className = 'paper-anio'; spanAnio.textContent = anio; meta.append(spanAnio); if (revista) { const spanRevista = document.createElement('span'); spanRevista.className = 'paper-revista'; spanRevista.textContent = revista; meta.append(spanRevista); } articulo.append(meta); const h3 = document.createElement('h3'); h3.className = 'paper-titulo'; if (urlPrincipal) { const link = document.createElement('a'); link.href = urlPrincipal; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.textContent = titulo; h3.append(link); } else { h3.textContent = titulo; } articulo.append(h3); const pAutores = document.createElement('p'); pAutores.className = 'paper-autores'; pAutores.textContent = autores + masAutores; articulo.append(pAutores); return articulo; }; const renderizarPaginacion = (): void => { const totalPaginas = Math.ceil(todosLosPapers.length / POR_PAGINA); const contenedor = document.getElementById('papers-paginacion'); if (!contenedor) return; if (totalPaginas <= 1) { contenedor.innerHTML = ''; return; } const inicio = Math.max(0, paginaActual - 2); const fin = Math.min(totalPaginas, inicio + 5); let html = ``; for (let i = inicio; i < fin; i++) { html += ``; } html += ``; contenedor.innerHTML = html; }; const renderizarPaginaActual = (): void => { const lista = document.getElementById('papers-lista'); if (!lista) return; const inicio = paginaActual * POR_PAGINA; const pagina = todosLosPapers.slice(inicio, inicio + POR_PAGINA); lista.replaceChildren(); if (pagina.length === 0) { const vacio = document.createElement('p'); vacio.className = 'papers-vacio'; vacio.textContent = 'No se encontraron artículos para esta búsqueda.'; lista.append(vacio); } else { lista.append(...pagina.map(renderizarTarjetaPaper)); } renderizarPaginacion(); lista.scrollTop = 0; }; const irAPagina = (numeroPagina: number): void => { const totalPaginas = Math.ceil(todosLosPapers.length / POR_PAGINA); if (numeroPagina < 0 || numeroPagina >= totalPaginas) return; paginaActual = numeroPagina; renderizarPaginaActual(); }; const mostrarEstadoCarga = (): void => { const lista = document.getElementById('papers-lista'); if (lista) lista.innerHTML = '

Buscando artículos científicos…

'; const paginacion = document.getElementById('papers-paginacion'); if (paginacion) paginacion.innerHTML = ''; }; const mostrarError = (mensaje: string): void => { const lista = document.getElementById('papers-lista'); if (!lista) return; const p = document.createElement('p'); p.className = 'papers-error'; p.textContent = mensaje; lista.replaceChildren(p); }; export const abrirModalPapers = async (patrones: Patron[]): Promise => { const modal = document.getElementById('modal-papers'); const overlay = document.getElementById('modal-papers-overlay'); if (!modal || !overlay) return; const nuevaConsulta = construirQuery(patrones); modal.removeAttribute('hidden'); overlay.classList.add('activo'); document.body.style.overflow = 'hidden'; requestAnimationFrame(() => modal.classList.add('visible')); const etiquetaConsulta = document.getElementById('papers-consulta'); if (etiquetaConsulta) etiquetaConsulta.textContent = `"${nuevaConsulta}"`; if (nuevaConsulta === consultaActual && todosLosPapers.length > 0) { renderizarPaginaActual(); return; } consultaActual = nuevaConsulta; todosLosPapers = []; paginaActual = 0; mostrarEstadoCarga(); try { todosLosPapers = await buscarPapers(nuevaConsulta); renderizarPaginaActual(); } catch (error) { mostrarError((error as Error).message || 'No se pudo conectar con PubMed. Intenta de nuevo más tarde.'); console.error('Error buscando papers:', error); } }; const cerrarModalPapers = (): void => { const modal = document.getElementById('modal-papers'); const overlay = document.getElementById('modal-papers-overlay'); if (!modal || !overlay) return; modal.classList.remove('visible'); overlay.classList.remove('activo'); document.body.style.overflow = ''; setTimeout(() => modal.setAttribute('hidden', ''), 250); }; export const inicializarModalPapers = (): void => { document.getElementById('modal-papers-cerrar')?.addEventListener('click', cerrarModalPapers); document.getElementById('modal-papers-overlay')?.addEventListener('click', cerrarModalPapers); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { const modal = document.getElementById('modal-papers'); if (modal && !modal.hidden) cerrarModalPapers(); } }); document.getElementById('papers-paginacion')?.addEventListener('click', (e) => { const btn = (e.target as Element).closest('.papers-pag-btn'); if (!btn || btn.disabled) return; irAPagina(parseInt(btn.dataset.pagina ?? '0', 10)); }); document.getElementById('papers-busqueda-form')?.addEventListener('submit', manejadorAsync('Búsqueda en PubMed', async (e: Event) => { e.preventDefault(); const input = document.getElementById('papers-busqueda-input') as HTMLInputElement | null; const termino = input?.value.trim(); if (!termino) return; const etiquetaConsulta = document.getElementById('papers-consulta'); if (etiquetaConsulta) etiquetaConsulta.textContent = `"${termino}"`; consultaActual = termino; todosLosPapers = []; paginaActual = 0; mostrarEstadoCarga(); try { todosLosPapers = await buscarPapers(termino); renderizarPaginaActual(); } catch (error) { mostrarError((error as Error).message || 'No se pudo conectar con PubMed. Intenta de nuevo más tarde.'); console.error('Error buscando papers:', error); } })); };