// Navegación-Pestañas const tabs = document.querySelectorAll('.tab-nav'); const paneles = document.querySelectorAll('main > .panel, .col3-wrapper > .panel'); const examenesSubtabsBar = document.getElementById('examenes-subtabs-bar'); const EXAMENES_SUBTAB_PANELS = new Set(['panel-hema', 'panel-bioquim', 'panel-uri', 'panel-endo', 'panel-coag', 'panel-gas']); let panelExamenActivo = 'panel-hema'; let panelActivo = 'panel-flujo'; const SWIPE_ORDER = ['panel-flujo', 'panel-paciente', 'panel-hema', 'panel-bioquim', 'panel-uri', 'panel-endo', 'panel-coag', 'panel-gas', 'panel-imagenes', 'panel-resultados']; export function activarTab(targetId) { const esSubpanelExamenes = EXAMENES_SUBTAB_PANELS.has(targetId); const esTabExamenes = targetId === 'examenes'; const mostrarExamenes = esTabExamenes || esSubpanelExamenes; // Si se clickea la tab generica "Examenes", muestra el ultimo subtab activo; si es un subtab, lo guarda let idPanelActual; if (esTabExamenes) { idPanelActual = panelExamenActivo; } else if (esSubpanelExamenes) { panelExamenActivo = targetId; idPanelActual = targetId; } else { idPanelActual = targetId; } tabs.forEach(tab => { const estaActivo = mostrarExamenes ? tab.dataset.target === 'examenes' : tab.dataset.target === targetId; tab.classList.toggle('activo', estaActivo); tab.setAttribute('aria-current', estaActivo ? 'true' : 'false'); }); if (examenesSubtabsBar) examenesSubtabsBar.hidden = !mostrarExamenes; if (mostrarExamenes) { document.querySelectorAll('.tab-examenes').forEach(btn => { btn.classList.toggle('activo', btn.dataset.subtabTarget === panelExamenActivo); }); } paneles.forEach(panel => { panel.classList.toggle('activo', panel.id === idPanelActual); }); panelActivo = idPanelActual; if (targetId === 'panel-paciente') sincronizarPacienteMob(); } tabs.forEach(tab => { tab.addEventListener('click', () => activarTab(tab.dataset.target)); }); document.querySelectorAll('.tab-examenes').forEach(btn => { btn.addEventListener('click', () => activarTab(btn.dataset.subtabTarget)); }); // Swipe para navegar entre secciones let inicioSwipeX = 0; let inicioSwipeY = 0; document.querySelector('main').addEventListener('touchstart', e => { inicioSwipeX = e.touches[0].clientX; inicioSwipeY = e.touches[0].clientY; }, { passive: true }); document.querySelector('main').addEventListener('touchend', e => { const dx = e.changedTouches[0].clientX - inicioSwipeX; const dy = e.changedTouches[0].clientY - inicioSwipeY; // Ignora gestos cortos o verticales para no interferir con scroll if (Math.abs(dx) < 50 || Math.abs(dx) < Math.abs(dy)) return; const indice = SWIPE_ORDER.indexOf(panelActivo); const siguiente = dx < 0 ? SWIPE_ORDER[indice + 1] : SWIPE_ORDER[indice - 1]; if (siguiente) activarTab(siguiente); }, { passive: true }); // Sincronizacón de datos de pacientes en mobile const MAPA_MOB_CANON = { 'mob-pt-especie': 'pt-especie', 'mob-pt-raza': 'pt-raza', 'mob-pt-edad': 'pt-edad', 'mob-pt-edad-unidad': 'pt-edad-unidad', 'mob-pt-sexo': 'pt-sexo' }; function sincronizarPacienteMob() { Object.entries(MAPA_MOB_CANON).forEach(([mobId, canonId]) => { const mobEl = document.getElementById(mobId); const canonEl = document.getElementById(canonId); if (mobEl && canonEl) mobEl.value = canonEl.value; }); } export function inicializarSincMob(evaluar) { Object.entries(MAPA_MOB_CANON).forEach(([mobId, canonId]) => { const mobEl = document.getElementById(mobId); if (!mobEl) return; const tipoEvento = mobEl.tagName === 'SELECT' ? 'change' : 'input'; mobEl.addEventListener(tipoEvento, () => { const canonEl = document.getElementById(canonId); if (canonEl) canonEl.value = mobEl.value; evaluar(); }); }); } // Filas de grid const panelFlujo = document.getElementById('panel-flujo'); const btnColapsar = document.getElementById('btn-colapsar-flujo'); const mainEl = document.querySelector('main'); let filaColapsada = ''; let filaExpandida = ''; const esGridEscritorio = () => window.innerWidth > 1100; function inicializarFilasGrid() { if (!esGridEscritorio()) return; mainEl.style.gridTemplateRows = '1fr auto auto auto'; // Mide el panel de flujo expandido y colapsado para animar grid-template-rows con precision const alturaPanel = panelFlujo.getBoundingClientRect().height; const alturaEncabezado = panelFlujo.querySelector('.panel-cabecera').getBoundingClientRect().height; if (alturaPanel > 0) filaExpandida = `${alturaPanel}px`; if (alturaEncabezado > 0) filaColapsada = `${alturaEncabezado}px`; mainEl.style.gridTemplateRows = `1fr auto auto ${filaExpandida || 'auto'}`; } function establecerFilasGrid(colapsado, animar) { if (!esGridEscritorio()) return; if (!animar) mainEl.style.transition = 'none'; mainEl.style.gridTemplateRows = colapsado ? `1fr auto auto ${filaColapsada}` : `1fr auto auto ${filaExpandida}`; if (!animar) { mainEl.offsetHeight; mainEl.style.transition = ''; } } inicializarFilasGrid(); const inicioColapsado = localStorage.getItem('mx-flujo-collapsed') === '1'; if (inicioColapsado) { panelFlujo.classList.add('collapsed'); btnColapsar.setAttribute('aria-expanded', 'false'); establecerFilasGrid(true, false); } btnColapsar.addEventListener('click', () => { const colapsado = panelFlujo.classList.toggle('collapsed'); btnColapsar.setAttribute('aria-expanded', String(!colapsado)); establecerFilasGrid(colapsado, true); localStorage.setItem('mx-flujo-collapsed', colapsado ? '1' : '0'); if (!colapsado) { ['panel-endo', 'panel-uri', 'panel-coag', 'panel-gas'].forEach(id => { const sp = document.getElementById(id); if (sp) establecerSubpanelColapsado(sp, true); }); } }); window.addEventListener('resize', () => { if (esGridEscritorio()) { if (!panelFlujo.classList.contains('collapsed')) inicializarFilasGrid(); } else { document.querySelectorAll('.subpanel-anim').forEach(animEl => { animEl.style.height = ''; animEl.style.transition = ''; }); document.getElementById('subpanel-citologia') ?.querySelector('.subpanel-anim') ?.style.setProperty('height', ''); } }); // Paneles colapsables function establecerSubpanelColapsado(subpanel, debeColapsar) { if (!esGridEscritorio()) return; const animEl = subpanel.querySelector('.subpanel-anim'); const btn = subpanel.querySelector('.btn-colapsar-subpanel'); const esRelleno = subpanel.id === 'subpanel-citologia'; if (debeColapsar === subpanel.classList.contains('collapsed')) return; subpanel.classList.toggle('collapsed', debeColapsar); if (btn) btn.setAttribute('aria-expanded', String(!debeColapsar)); // Forzar reflujo antes de cambiar height permite que CSS transition anime correctamente if (debeColapsar) { animEl.style.height = `${animEl.offsetHeight}px`; animEl.offsetHeight; animEl.style.height = '0px'; } else { animEl.style.height = `${animEl.scrollHeight}px`; if (esRelleno) { animEl.addEventListener('transitionend', () => { animEl.style.height = ''; }, { once: true }); } } localStorage.setItem(`mx-${subpanel.id}-collapsed`, debeColapsar ? '1' : '0'); } const GRUPOS_VINCULADOS = [ ['panel-endo', 'panel-uri'], ['panel-coag', 'panel-gas'], ]; const PANELES_COLAPSADOS_POR_DEFECTO = new Set(['panel-uri', 'panel-endo', 'panel-coag', 'panel-gas']); document.querySelectorAll('.btn-colapsar-subpanel').forEach(btn => { const subpanel = btn.closest('.subpanel'); const animEl = subpanel.querySelector('.subpanel-anim'); const claveAlmacenamiento = `mx-${subpanel.id}-collapsed`; const esRelleno = subpanel.id === 'subpanel-citologia'; if (esGridEscritorio()) { if (!esRelleno) { animEl.style.transition = 'none'; animEl.style.height = `${animEl.scrollHeight}px`; } const valorGuardado = localStorage.getItem(claveAlmacenamiento); const debeColapsar = valorGuardado !== null ? valorGuardado === '1' : PANELES_COLAPSADOS_POR_DEFECTO.has(subpanel.id); if (debeColapsar) { subpanel.classList.add('collapsed'); btn.setAttribute('aria-expanded', 'false'); if (esRelleno) animEl.style.transition = 'none'; animEl.style.height = '0px'; if (esRelleno) { animEl.offsetHeight; animEl.style.transition = ''; } } if (!esRelleno) { animEl.offsetHeight; animEl.style.transition = ''; } } btn.addEventListener('click', () => { if (!esGridEscritorio()) return; const colapsado = subpanel.classList.toggle('collapsed'); btn.setAttribute('aria-expanded', String(!colapsado)); if (colapsado) { animEl.style.height = `${animEl.offsetHeight}px`; animEl.offsetHeight; animEl.style.height = '0px'; } else { animEl.style.height = `${animEl.scrollHeight}px`; if (esRelleno) { animEl.addEventListener('transitionend', () => { animEl.style.height = ''; }, { once: true }); } } localStorage.setItem(claveAlmacenamiento, colapsado ? '1' : '0'); const grupo = GRUPOS_VINCULADOS.find(g => g.includes(subpanel.id)); if (grupo) { grupo.forEach(id => { if (id !== subpanel.id) { const asociado = document.getElementById(id); if (asociado) establecerSubpanelColapsado(asociado, colapsado); } }); } }); }); // Patrones de paneles colapsables const btnColapsarPatrones = document.getElementById('btn-colapsar-patrones'); const patronesAnim = document.getElementById('patrones-anim'); export function colapsarPatrones(debeColapsar) { const estaExpandido = btnColapsarPatrones.getAttribute('aria-expanded') === 'true'; const colapsado = debeColapsar ?? estaExpandido; if (colapsado && estaExpandido) { patronesAnim.style.height = `${patronesAnim.scrollHeight}px`; patronesAnim.offsetHeight; patronesAnim.style.height = '0px'; btnColapsarPatrones.setAttribute('aria-expanded', 'false'); } else if (!colapsado && !estaExpandido) { patronesAnim.style.height = `${patronesAnim.scrollHeight}px`; patronesAnim.addEventListener('transitionend', () => { if (btnColapsarPatrones.getAttribute('aria-expanded') === 'true') { patronesAnim.style.height = ''; } }, { once: true }); btnColapsarPatrones.setAttribute('aria-expanded', 'true'); } } btnColapsarPatrones.addEventListener('click', () => colapsarPatrones()); // Imágenes export const imagenesDataUrl = [null, null]; export const capturasMicroscopio = []; const MAX_CAPTURAS_MICRO = 4; document.querySelectorAll('.zona-imagen').forEach(zona => { const indice = parseInt(zona.dataset.zona); const input = zona.querySelector('.input-zona'); const vacia = zona.querySelector('.zona-vacia'); const btnQuitar = zona.querySelector('.btn-quitar-zona'); const vistaPrevia = document.createElement('img'); vistaPrevia.className = 'zona-img-preview'; vistaPrevia.alt = `Citología ${indice + 1}`; vistaPrevia.hidden = true; btnQuitar.before(vistaPrevia); zona.addEventListener('click', e => { if (btnQuitar.contains(e.target)) return; input.click(); }); input.addEventListener('change', () => { const file = input.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = ev => { const img = new Image(); img.onload = () => { // Reduce la imagen a max 1024px en su lado mayor para no saturar la memoria ni la API const MAX_PIXELES = 1024; const scale = Math.min(MAX_PIXELES / img.width, MAX_PIXELES / img.height, 1); const canvas = document.createElement('canvas'); canvas.width = Math.round(img.width * scale); canvas.height = Math.round(img.height * scale); canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height); const dataUrl = canvas.toDataURL('image/jpeg', 0.85); imagenesDataUrl[indice] = dataUrl; vistaPrevia.src = dataUrl; vistaPrevia.hidden = false; btnQuitar.hidden = false; vacia.hidden = true; zona.classList.add('con-imagen'); }; img.src = ev.target.result; }; reader.readAsDataURL(file); }); btnQuitar.addEventListener('click', e => { e.stopPropagation(); imagenesDataUrl[indice] = null; vistaPrevia.src = ''; vistaPrevia.hidden = true; btnQuitar.hidden = true; vacia.hidden = false; zona.classList.remove('con-imagen'); input.value = ''; }); }); // Captura de microscopio (function () { const zona = document.querySelector('.zona-microscopio'); if (!zona) return; const micVacia = zona.querySelector('.micro-vacia'); const video = zona.querySelector('.micro-video'); const controles = zona.querySelector('.micro-controles'); const btnGaleria = zona.querySelector('.micro-btn-galeria'); const badge = zona.querySelector('.micro-badge'); const btnCapturar = zona.querySelector('.micro-btn-capturar'); const btnCerrar = zona.querySelector('.micro-btn-cerrar'); const galeriaEl = zona.querySelector('.micro-galeria'); let stream = null; let galeriaEsVisible = false; function detenerStream() { if (stream) { stream.getTracks().forEach(t => t.stop()); stream = null; } } function actualizarInsignia() { const n = capturasMicroscopio.length; badge.textContent = n; badge.hidden = n === 0; btnCapturar.disabled = n >= MAX_CAPTURAS_MICRO; } function renderizarGaleria() { if (capturasMicroscopio.length === 0) { galeriaEl.innerHTML = 'Sin capturas'; return; } galeriaEl.innerHTML = capturasMicroscopio.map((src, i) => `
Captura ${i + 1}
`).join(''); galeriaEl.querySelectorAll('.micro-thumb-quitar').forEach(btn => { btn.addEventListener('click', e => { e.stopPropagation(); const i = parseInt(btn.dataset.captureIdx); capturasMicroscopio.splice(i, 1); actualizarInsignia(); renderizarGaleria(); if (capturasMicroscopio.length === 0 && galeriaEsVisible) alternarGaleria(); }); }); } function alternarGaleria() { galeriaEsVisible = !galeriaEsVisible; galeriaEl.hidden = !galeriaEsVisible; if (galeriaEsVisible) renderizarGaleria(); btnGaleria.style.color = galeriaEsVisible ? 'var(--accent)' : ''; } async function abrirCamara() { try { // Preferencia por camara trasera (microscopio o movil apuntando a la muestra) stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: 'environment' }, width: { ideal: 1920 } } }); video.srcObject = stream; video.hidden = false; micVacia.hidden = true; controles.hidden = false; actualizarInsignia(); } catch { // Permiso denegado o camara no disponible; no se requiere fallback } } zona.addEventListener('click', e => { if (controles.contains(e.target) || galeriaEl.contains(e.target)) return; if (!stream) abrirCamara(); }); btnGaleria.addEventListener('click', e => { e.stopPropagation(); alternarGaleria(); }); btnCapturar.addEventListener('click', e => { e.stopPropagation(); if (capturasMicroscopio.length >= MAX_CAPTURAS_MICRO) return; const canvas = document.createElement('canvas'); // Escala el fotograma de video para mantener un tamano razonable antes de enviarlo al modelo const MAX_PIXELES = 1024; const scale = Math.min(MAX_PIXELES / video.videoWidth, MAX_PIXELES / video.videoHeight, 1); canvas.width = Math.round(video.videoWidth * scale); canvas.height = Math.round(video.videoHeight * scale); canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height); capturasMicroscopio.push(canvas.toDataURL('image/jpeg', 0.85)); actualizarInsignia(); if (galeriaEsVisible) renderizarGaleria(); }); btnCerrar.addEventListener('click', e => { e.stopPropagation(); detenerStream(); video.hidden = true; video.srcObject = null; controles.hidden = true; galeriaEl.hidden = true; galeriaEsVisible = false; micVacia.hidden = false; }); })();