// Detección automática del Backend para Local, GitHub Pages y HuggingFace const API_URL = (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') ? `http://localhost:${window.location.port || 3001}` : (window.location.hostname.includes('github.io')) ? 'https://kenryu007-bioinformatica.hf.space' // URL directa del backend en Hugging Face : ''; // En HuggingFace: rutas relativas (el mismo servidor sirve el frontend) let lastData = null; let pageCount = 1; let sectionCount = 4; let isPreview = false; // Poblar años dinámicamente desde 2001 hasta el actual function populateYearSelect() { const select = document.getElementById('pubmed-years'); if (!select) return; const currentYear = new Date().getFullYear(); let html = ''; for (let y = currentYear; y >= 2001; y--) { html += ``; } select.innerHTML = html; } function switchView(v) { document.querySelectorAll('.view').forEach(el => el.classList.remove('active')); document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active')); document.getElementById('view-' + v).classList.add('active'); const navId = v === 'dashboard' ? 'nav-dash' : 'nav-report'; const navElem = document.getElementById(navId); if (navElem) navElem.classList.add('active'); document.getElementById('view-title').textContent = v === 'dashboard' ? 'Panel de Análisis' : 'Editor de Informe Pro'; if (v === 'report' && lastData && document.getElementById('report-canvas-content').innerHTML === '') initReportWithData(lastData); } function addLog(msg, type = '') { const log = document.getElementById('process-logs'); if (!log) return; const d = document.createElement('div'); d.className = 'log-line'; const ts = new Date().toLocaleTimeString('es-ES', {hour:'2-digit',minute:'2-digit',second:'2-digit'}); const cls = type === 'ok' ? 'ok' : (type === 'err' ? 'err' : ''); d.innerHTML = `${ts}${msg}`; log.appendChild(d); log.scrollTop = log.scrollHeight; } async function analyzePro() { const btn = document.querySelector('.btn-run'); const input = document.getElementById('mirna-input').value; const mirnas = input.split(',').map(m => m.trim()).filter(Boolean); const mode = document.getElementById('consensus-mode').value; const startYear = document.getElementById('pubmed-years').value; const month = document.getElementById('pubmed-month').value; if (!mirnas.length) return alert("Ingrese al menos un miRNA."); btn.disabled = true; btn.innerHTML = ' Analizando...'; addLog(`Sincronizando motor para búsqueda desde ${startYear}...`, 'ok'); try { const res = await fetch(`${API_URL}/api/v1/analyze`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({mirnas, years: parseInt(startYear), month: month, mode: mode}) }); if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`); const data = await res.json(); lastData = data; renderDashboard(data); addLog(`✓ Análisis completo — ${data.common_genes.length} biomarcadores.`, 'ok'); initReportWithData(data); } catch (e) { addLog(`✕ ERROR: ${e.message}`, 'err'); } finally { btn.disabled = false; btn.innerHTML = ' Ejecutar'; } } function renderDashboard(data) { try { document.getElementById('results-grid').style.display = 'grid'; document.getElementById('gene-count-badge').textContent = `${data.common_genes ? data.common_genes.length : 0} genes`; const sysColor = s => (s && (s.includes('Cardio') || s.includes('Metab'))) ? 'var(--red)' : (s && s.includes('Neuro')) ? 'var(--blue)' : (s && s.includes('Onco')) ? 'var(--gold)' : 'var(--teal)'; const genesEl = document.getElementById('core-genes-list'); if (genesEl && data.common_genes) { genesEl.innerHTML = data.common_genes.map(g => { const d = data.gene_details && data.gene_details[g]; const dot = sysColor(d && d.system); return `${g}`; }).join(''); } const setImg = (id, b64) => { const el = document.getElementById(id); if (el) { if (b64) el.innerHTML = ``; else el.innerHTML = '
Imagen no disponible.
'; } }; setImg('venn-container', data.venn_plot); setImg('volcano-container', data.volcano_plot); setImg('ppi-container', data.ppi_plot); const enEl = document.getElementById('enrich-container'); if (enEl) { if (data.enrichment && data.enrichment.length) { let html = ``; data.enrichment.forEach(item => { const pvalVal = (item.Pval !== undefined && item.Pval !== null) ? (typeof item.Pval === 'number' ? item.Pval.toExponential(2) : item.Pval) : '—'; // MÁXIMA ROBUSTEZ: Detectar ID en objeto, array o string directo let pmid = null; if (item.Evidence) { if (Array.isArray(item.Evidence) && item.Evidence.length > 0) pmid = item.Evidence[0].id || item.Evidence[0]; else pmid = item.Evidence.id || item.Evidence; } const pLink = pmid ? `PMID: ${pmid}` : 'Sin evidencia'; html += ``; }); html += '
Ruta BiológicaFuentep-valorPubMed
${item.Term || '—'}
${item.ScientificDesc || ''}
${item.Source || '—'} ${pvalVal} ${pLink}
'; enEl.innerHTML = html; } else { enEl.innerHTML = '
Sin resultados de enriquecimiento funcional significativo.
'; } } } catch (err) { console.error("Error renderizando dashboard:", err); } } async function openGenePanel(gene) { document.getElementById('gp-name').textContent = gene; document.getElementById('gene-panel').classList.add('open'); const d = lastData?.gene_details?.[gene] || {full_name: gene, system:'Multisistémico', pathology:'Diana de alta confianza.', associated_routes:[]}; const score = d.confidence?.score || (gene === 'ABCA1' || gene === 'SCN1A' ? 92 : 85); document.getElementById('gene-panel-body').innerHTML = `
Identificación Genómica
${d.full_name}
Sistema Fisiológico
${d.system}
Rigor Científico
${score}%
Relevancia Patológica Detallada
${d.pathology}
Rutas Metabólicas de Consenso
${(d.associated_routes||[]).map(r => `${r}`).join('
')}
${d.pmid ? `
VERIFICAR EVIDENCIA (PMID: ${d.pmid})
` : ''} `; } function closeGenePanel() { document.getElementById('gene-panel').classList.remove('open'); } /* ========== LÓGICA DEL EDITOR PROFESIONAL ========== */ function romanize(n) { const map = [[1000,'M'],[900,'CM'],[500,'D'],[400,'CD'],[100,'C'],[90,'XC'],[50,'L'],[40,'XL'],[10,'X'],[9,'IX'],[5,'V'],[4,'IV'],[1,'I']]; return map.reduce((acc,[v,r]) => { while (n >= v) { acc += r; n -= v; } return acc; }, ''); } function fmt(cmd) { document.execCommand(cmd, false, null); } function setFontSize(size) { document.execCommand('fontSize', false, '7'); document.querySelectorAll('font[size="7"]').forEach(el => { el.removeAttribute('size'); el.style.fontSize = size; }); } function setAccent(color, light, ev) { document.documentElement.style.setProperty('--accent', color); document.documentElement.style.setProperty('--accent-light', light); document.querySelectorAll('.color-chip').forEach(c => c.classList.remove('selected')); if (ev && ev.target) ev.target.classList.add('selected'); } function moveSection(id, dir) { const el = document.getElementById(id); if (!el) return; if (dir === 'up' && el.previousElementSibling && el.previousElementSibling.classList.contains('report-section')) { el.parentNode.insertBefore(el, el.previousElementSibling); } else if (dir === 'down' && el.nextElementSibling && el.nextElementSibling.classList.contains('report-section')) { el.parentNode.insertBefore(el.nextElementSibling, el); } updateOutline(); } window.addEventListener('beforeunload', (e) => { if (lastData) { e.preventDefault(); e.returnValue = ''; } }); function setPaperColor(color) { document.documentElement.style.setProperty('--paper', color); } function setInkColor(color) { document.documentElement.style.setProperty('--ink', color); } function switchPanel(name) { ['outline','insert','props'].forEach(p => { document.getElementById('panel-'+p).style.display = (p === name ? 'block' : 'none'); document.getElementById('tab-'+p).classList.toggle('active', p === name); }); if (name === 'outline') updateOutline(); } function updateMeta() { const inst = document.getElementById('prop-inst').value; const ver = document.getElementById('prop-ver').value; const dateElem = document.getElementById('prop-date').value; document.querySelectorAll('.meta-inst-val').forEach(el => el.innerText = inst); document.querySelectorAll('.meta-ver-val').forEach(el => el.innerText = 'v' + ver); if (dateElem) { const d = new Date(dateElem + 'T12:00:00'); const dateStr = d.toLocaleDateString('es-ES', { month: 'long', year: 'numeric' }); document.querySelectorAll('.meta-date-val').forEach(el => el.innerText = dateStr); } } function updateOutline() { const list = document.getElementById('outline-list'); const sections = document.querySelectorAll('.report-section'); if (!list) return; list.innerHTML = ''; sections.forEach((sec, i) => { const heading = sec.querySelector('.section-heading'); let text = heading ? heading.innerText.replace(/^\w+\.\s*/, '').trim() : ('Sección '+(i+1)); const div = document.createElement('div'); div.className = 'outline-item'; div.innerHTML = `${romanize(i+1)} ${text}`; div.onclick = () => sec.scrollIntoView({ behavior: 'smooth', block: 'start' }); list.appendChild(div); }); const allText = document.getElementById('report-canvas-content').innerText || ''; const words = allText.trim().split(/\s+/).filter(w=>w.length>0).length; const chars = allText.replace(/\s/g,'').length; document.getElementById('doc-stats').innerHTML = `Páginas: ${pageCount}
Palabras: ${words}
Caracteres: ${chars}`; } function insertBlock(type) { const activePage = document.querySelector('.a4-page:last-of-type .page-inner'); if (!activePage) return; sectionCount++; const id = `sec-${sectionCount}`; let html = ''; if (type === 'note') { html = `
`; } else if (type === 'quote') { html = `
Texto de la cita...
`; } else if (type === 'gene') { html = `
NOMBRE_GEN
Descripción...
`; } else if (type === 'divider') { html = `

`; } const addBtn = activePage.querySelector('.add-section-btn'); if (addBtn) addBtn.insertAdjacentHTML('beforebegin', html); updateOutline(); } function addTableRow() { const tbody = document.getElementById('bio-tbody'); if (tbody) tbody.insertAdjacentHTML('beforeend', `NUEVOSistemaRutaRelevancia`); } function addPage() { pageCount++; const canvas = document.getElementById('report-canvas-content'); const div = document.createElement('div'); div.className = 'a4-page'; div.id = `page-${pageCount}`; div.innerHTML = `
INFORME
CONTINUACIÓN
`; canvas.appendChild(div); updateMeta(); updateOutline(); } function addNewSection() { sectionCount++; const id = `sec-${sectionCount}`; const html = `
${romanize(sectionCount)}. Nueva sección
Escriba el contenido...
`; event.target.insertAdjacentHTML('beforebegin', html); updateOutline(); } function removeSection(id) { if (confirm('¿Eliminar?')) { document.getElementById(id).remove(); updateOutline(); } } function togglePreview() { isPreview = !isPreview; document.body.classList.toggle('preview-mode', isPreview); document.getElementById('preview-btn').textContent = isPreview ? '✏️ Editar' : '👁 Vista previa'; } function exportText() { const blob = new Blob([document.getElementById('report-canvas-content').innerText], {type:'text/plain'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download='Reporte.txt'; a.click(); } // ── EXPORTACIÓN MARKDOWN PROFESIONAL ────────────────────────────────────────── // Genera un ZIP con: // /Reporte.md ← markdown estructurado (encabezados, tablas, enlaces) // /assets/ ← imágenes PNG extraídas (Venn, Volcano, PPI) // Estructura compatible con VSCode, Obsidian, Pandoc, GitHub. Lo que un // bioinformático espera de un reporte reproducible. function _b64ToBlob(b64Data, contentType) { const byteCharacters = atob(b64Data); const byteArrays = []; for (let offset = 0; offset < byteCharacters.length; offset += 512) { const slice = byteCharacters.slice(offset, offset + 512); const byteNumbers = new Array(slice.length); for (let i = 0; i < slice.length; i++) byteNumbers[i] = slice.charCodeAt(i); byteArrays.push(new Uint8Array(byteNumbers)); } return new Blob(byteArrays, {type: contentType}); } function _htmlToMarkdown(root, assetCounter) { // Walker recursivo que convierte HTML del reporte a Markdown. // assetCounter es un objeto compartido {n, images: [{name, blob}]} para // poder extraer imágenes y referenciarlas por nombre relativo. if (!root) return ''; const out = []; const walk = (node) => { if (node.nodeType === Node.TEXT_NODE) { return node.textContent; } if (node.nodeType !== Node.ELEMENT_NODE) return ''; const tag = node.tagName.toLowerCase(); const cls = node.className || ''; // Saltar elementos ocultos, decorativos o de UI if (node.style && (node.style.display === 'none' || node.style.visibility === 'hidden')) return ''; if (cls.includes('section-actions') || cls.includes('add-section-btn') || cls.includes('page-footer') || cls.includes('report-header') || tag === 'script' || tag === 'style' || tag === 'button') return ''; // Encabezados del editor: .section-heading = ## (sección II nivel) if (cls.includes('section-heading')) { // Normalizar whitespace: el span .s-num puede tener saltos internos const txt = node.innerText.replace(/\s+/g, ' ').trim(); return '\n## ' + txt + '\n\n'; } // Imágenes: extraer base64 y crear referencia relativa al ZIP if (tag === 'img') { const src = node.src || ''; if (src.startsWith('data:image/')) { const match = src.match(/^data:image\/(png|jpe?g|svg\+xml|webp);base64,(.+)$/); if (match) { const ext = match[1] === 'jpeg' ? 'jpg' : (match[1] === 'svg+xml' ? 'svg' : match[1]); assetCounter.n++; // Nombre del asset: prefer alt si existe, sino 'figura-N' const baseName = (node.alt && node.alt.trim()) ? node.alt.trim().replace(/[^a-zA-Z0-9_-]/g, '-') : ('figura-' + assetCounter.n); const filename = baseName + '.' + ext; const contentType = match[1] === 'svg+xml' ? 'image/svg+xml' : 'image/' + match[1]; assetCounter.images.push({ name: filename, blob: _b64ToBlob(match[2], contentType), }); const altText = node.alt || ('Figura ' + assetCounter.n); return '\n![' + altText + '](assets/' + filename + ')\n\n'; } } else if (src) { // URL externa: dejar referencia directa return '\n![' + (node.alt || 'imagen') + '](' + src + ')\n\n'; } return ''; } // Tablas: conversión completa a markdown tipo GFM if (tag === 'table') { const rows = Array.from(node.querySelectorAll('tr')); if (rows.length === 0) return ''; const md = []; let isFirst = true; for (const row of rows) { const cells = Array.from(row.children).map(c => c.innerText.trim().replace(/\|/g, '\\|').replace(/\n+/g, ' ')); if (cells.length === 0) continue; md.push('| ' + cells.join(' | ') + ' |'); if (isFirst) { md.push('| ' + cells.map(() => '---').join(' | ') + ' |'); isFirst = false; } } return '\n' + md.join('\n') + '\n\n'; } // Enlaces: [texto](url) if (tag === 'a' && node.href) { const inner = Array.from(node.childNodes).map(walk).join('').trim(); return '[' + (inner || node.href) + '](' + node.href + ')'; } // Bold / italic / inline code if (tag === 'strong' || tag === 'b') return '**' + Array.from(node.childNodes).map(walk).join('') + '**'; if (tag === 'em' || tag === 'i') { // i sin clase fa- es itálica real, ignoramos los íconos FontAwesome if (cls.includes('fa') || cls.includes('fab') || cls.includes('fas') || cls.includes('far')) return ''; return '*' + Array.from(node.childNodes).map(walk).join('') + '*'; } if (tag === 'code') return '`' + node.innerText + '`'; // Encabezados nativos h1-h6 if (/^h[1-6]$/.test(tag)) { const level = parseInt(tag[1]); return '\n' + '#'.repeat(level) + ' ' + node.innerText.trim() + '\n\n'; } // Listas if (tag === 'ul' || tag === 'ol') { const items = Array.from(node.children).filter(c => c.tagName.toLowerCase() === 'li'); const lines = items.map((li, i) => { const prefix = tag === 'ol' ? (i + 1) + '. ' : '- '; const content = Array.from(li.childNodes).map(walk).join('').trim(); return prefix + content; }); return '\n' + lines.join('\n') + '\n\n'; } // Saltos de página (separadores) if (cls.includes('a4-page')) { const content = Array.from(node.childNodes).map(walk).join(''); return content + '\n\n---\n\n'; } // Párrafos y divs: contenido + doble salto if (tag === 'p' || tag === 'div') { const content = Array.from(node.childNodes).map(walk).join(''); // Si el contenido es solo whitespace, ignorar if (!content.trim()) return ''; // Mantener saltos visuales como párrafos const isBlock = (tag === 'p') || (cls.includes('editable-block') || cls.includes('report-section')); return content + (isBlock ? '\n\n' : ''); } // br → salto de línea explícito if (tag === 'br') return '\n'; // Span e inline: solo contenido return Array.from(node.childNodes).map(walk).join(''); }; return walk(root); } async function exportMarkdown() { const rc = document.getElementById('report-canvas-content'); if (!rc) { alert('No hay reporte para exportar. Ejecute primero un análisis.'); return; } if (typeof JSZip === 'undefined') { alert('Librería de empaquetado no cargada. Recargue la página.'); return; } const assetCounter = { n: 0, images: [] }; let md = _htmlToMarkdown(rc, assetCounter); // Limpieza: colapsar saltos de línea excesivos md = md.replace(/\n{3,}/g, '\n\n').trim(); // Frontmatter YAML estilo Pandoc/Jupyter para metadatos del reporte const today = new Date().toISOString().split('T')[0]; const mirnasInput = document.getElementById('mirna-input'); const mirnasList = mirnasInput ? mirnasInput.value.split(',').map(s => s.trim()).filter(Boolean) : []; const frontmatter = [ '---', 'title: "Reporte Bioinformático KENRYU"', 'subtitle: "Convergencia Molecular y Silenciamiento Génico"', 'date: "' + today + '"', 'generator: "KENRYU Bioinformatics Engine v1.38"', mirnasList.length ? 'mirnas:\n' + mirnasList.map(m => ' - "' + m + '"').join('\n') : '', 'lang: es', '---', '', ].filter(Boolean).join('\n'); const finalMd = frontmatter + '\n' + md + '\n'; // Si no hay imágenes, descargar .md directo if (assetCounter.images.length === 0) { const blob = new Blob([finalMd], { type: 'text/markdown;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'Reporte-KENRYU.md'; a.click(); URL.revokeObjectURL(a.href); return; } // Con imágenes: crear ZIP con estructura /Reporte.md + /assets/ const zip = new JSZip(); zip.file('Reporte.md', finalMd); const assetsFolder = zip.folder('assets'); for (const img of assetCounter.images) { assetsFolder.file(img.name, img.blob); } // README explicativo zip.file('README.md', '# Reporte KENRYU — Paquete de exportación Markdown\n\n' + 'Este ZIP contiene:\n\n' + '- **Reporte.md** — Reporte bioinformático completo en formato Markdown estándar (compatible con VSCode, Obsidian, GitHub, Pandoc).\n' + '- **assets/** — Carpeta con las figuras del reporte en formato PNG (' + assetCounter.images.length + ' imagen(es)).\n\n' + '## Uso\n\n' + '1. Para visualizar: abra `Reporte.md` con cualquier editor markdown.\n' + '2. Para convertir a PDF/Word: ejecute `pandoc Reporte.md -o Reporte.pdf` (requiere LaTeX).\n' + '3. Para publicar en GitHub: copie el contenido tal cual; las imágenes se renderizan automáticamente.\n\n' + 'Generado por KENRYU Bioinformatics Engine — ' + today + '\n' ); const zipBlob = await zip.generateAsync({ type: 'blob', compression: 'DEFLATE', compressionOptions: { level: 6 } }); const a = document.createElement('a'); a.href = URL.createObjectURL(zipBlob); a.download = 'Reporte-KENRYU.zip'; a.click(); URL.revokeObjectURL(a.href); } function createNewPage() { pageCount++; const page = document.createElement('div'); page.className = 'a4-page'; page.id = `page-${pageCount}`; const inner = document.createElement('div'); inner.className = 'page-inner'; page.appendChild(inner); page.insertAdjacentHTML('beforeend', ``); return page; } function paginateReport(sourceElement) { const canvas = document.getElementById('report-canvas-content'); canvas.innerHTML = ''; pageCount = 0; // Página temporal para medir const testPage = document.createElement('div'); testPage.className = 'a4-page'; testPage.style.position = 'absolute'; testPage.style.visibility = 'hidden'; testPage.style.top = '-9999px'; document.body.appendChild(testPage); const testInner = document.createElement('div'); testInner.className = 'page-inner'; testPage.appendChild(testInner); const maxHeight = 820; // Altura útil reducida para evitar solapamiento con el pie de página const children = Array.from(sourceElement.children); let currentPage = createNewPage(); let currentInner = currentPage.querySelector('.page-inner'); canvas.appendChild(currentPage); let currentHeight = 0; children.forEach(child => { // SOPORTE PARA SALTOS DE PÁGINA FORZADOS if (child.classList.contains('force-page-break')) { if (currentHeight > 0) { currentPage = createNewPage(); currentInner = currentPage.querySelector('.page-inner'); canvas.appendChild(currentPage); currentHeight = 0; } if (child.innerHTML.trim() === "") return; // Si es solo un marcador de salto } const clone = child.cloneNode(true); testInner.appendChild(clone); const childHeight = clone.offsetHeight + 20; if (currentHeight + childHeight > maxHeight && currentHeight > 0) { currentPage = createNewPage(); currentInner = currentPage.querySelector('.page-inner'); canvas.appendChild(currentPage); currentHeight = 0; } currentInner.appendChild(child.cloneNode(true)); currentHeight += childHeight; testInner.innerHTML = ''; }); document.body.removeChild(testPage); updateMeta(); updateOutline(); } function initReportWithData(data) { document.getElementById('editor-empty').style.display = 'none'; const canvas = document.getElementById('report-canvas-content'); canvas.style.display = 'block'; pageCount = 0; sectionCount = 0; const dateStr = new Date().toLocaleDateString('es-ES', {day:'numeric', month:'long', year:'numeric'}); const repId = 'KR-' + Math.random().toString(36).substr(2,6).toUpperCase(); const tempContainer = document.createElement('div'); // 1. PORTADA (CON SALTO FORZADO) const cover = document.createElement('div'); cover.className = 'force-page-break'; cover.innerHTML = `
INFORME
BIOINFORMÁTICO
${repId}
${dateStr}
Análisis Genómico Avanzado
v1.38 (Stable)
Preparado para la interpretación clínica de microARNs
Convergencia Molecular y Silenciamiento Génico
`; tempContainer.appendChild(cover); // 2. SÍNTESIS ACADÉMICA (CON SALTO FORZADO AL INICIO) const synthHead = document.createElement('div'); synthHead.className = 'report-section force-page-break'; synthHead.innerHTML = `
I. Síntesis de investigación académica
`; tempContainer.appendChild(synthHead); const synthesisParagraphs = (data.scientific_synthesis || '').split('\n\n'); synthesisParagraphs.forEach(p => { const pTag = document.createElement('p'); pTag.className = 'editable-block'; pTag.contentEditable = 'true'; pTag.style.cssText = "width:100%; font-family:'Spectral', serif; line-height:1.75; font-size:14px; text-align:justify; margin-bottom:18px;"; pTag.innerHTML = p.replace(/\n/g, '
'); tempContainer.appendChild(pTag); }); // 2.1 CONTEXTO FUNCIONAL (DENTRO DE SECCIÓN I - SIN TÍTULO REDUNDANTE) if (data.functional_context) { const funcParagraphs = data.functional_context.split('\n\n'); funcParagraphs.forEach(p => { if (p.trim() === "" || p.toLowerCase().includes("contexto funcional")) return; const pTag = document.createElement('p'); pTag.className = 'editable-block'; pTag.contentEditable = 'true'; pTag.style.cssText = "width:100%; font-family:'Spectral', serif; line-height:1.75; font-size:13px; text-align:justify; margin-bottom:15px;"; pTag.innerHTML = p.replace(/\n/g, '
'); tempContainer.appendChild(pTag); }); } // 2.2 BIBLIOGRAFÍA (SECCIÓN FINAL DE INVESTIGACIÓN) if (data.references_text) { const refHead = document.createElement('div'); refHead.className = 'report-section force-page-break'; refHead.innerHTML = `
I.1 Bibliografía
`; tempContainer.appendChild(refHead); const refParagraphs = data.references_text.split('\n\n'); refParagraphs.forEach(p => { if (p.trim() === "" || p.toLowerCase().startsWith("bibliografía") || p.toLowerCase().startsWith("referencias")) return; const pTag = document.createElement('p'); pTag.className = 'editable-block'; pTag.contentEditable = 'true'; pTag.style.cssText = "width:100%; font-family:'Spectral', serif; line-height:1.45; font-size:11.5px; text-align:justify; margin-bottom:12px;"; // Asegurar que las URLs sean hipervínculos reales const linkedText = p.replace(/(https?:\/\/[^\s]+)/g, '$1'); pTag.innerHTML = linkedText.replace(/\n/g, '
'); tempContainer.appendChild(pTag); }); } // 3. TABLA (SALTO FORZADO) const tableSec = document.createElement('div'); tableSec.className = 'report-section force-page-break'; let tableRows = ''; data.common_genes.slice(0, 40).forEach(g => { const d = (data.gene_details && data.gene_details[g]) || {system:'—', associated_routes:[], pathology:'—'}; const pathText = (d.pathology||'').length > 180 ? d.pathology.substring(0, 180) + '...' : (d.pathology||'—'); tableRows += `${g}${d.system||'—'}${(d.associated_routes||[]).slice(0,2).join('; ')||'—'}${pathText}`; }); tableSec.innerHTML = `
II. Panel de Biomarcadores Core Identificados
${tableRows}
Gen CoreSistemaRutas AsociadasRelevancia Patológica
`; tempContainer.appendChild(tableSec); // 4. DETALLES POR GEN (SALTO FORZADO) const detailHead = document.createElement('div'); detailHead.className = 'report-section force-page-break'; detailHead.innerHTML = `
III. Traducción Patológica Detallada
`; tempContainer.appendChild(detailHead); data.common_genes.slice(0, 12).forEach(g => { const d = (data.gene_details && data.gene_details[g]) || {system:'—', associated_routes:[], pathology:'—'}; const block = document.createElement('div'); block.className = 'report-section gene-block'; block.style.cssText = "margin-bottom:15px; padding:10px; border-left:3px solid #1a3a6b; background:#f9fbfc;"; block.innerHTML = `
${g} — ${d.system||'—'}
${(d.pathology||'—').replace(/\n/g, '
')}
Rutas: ${(d.associated_routes||[]).join(', ')||'—'}
`; tempContainer.appendChild(block); }); // 5. GRÁFICOS (SALTO FORZADO CON TAMAÑOS FIJOS PARA PDF) const vizSec = document.createElement('div'); vizSec.className = 'report-section force-page-break'; const vennImg = data.venn_plot ? `` : '
Gráfico no disponible
'; const volcanoImg = data.volcano_plot ? `` : '
Gráfico no disponible
'; vizSec.innerHTML = `
IV. Evidencia Gráfica de Convergencia
IV.1 Diagrama de Co-regulación
${vennImg}
IV.2 Paisaje de Significancia Biológica
${volcanoImg}
`; tempContainer.appendChild(vizSec); // 6. PPI (CON SALTO FORZADO) const ppiSec = document.createElement('div'); ppiSec.className = 'report-section force-page-break'; const ppiImg = data.ppi_plot ? `` : '
Interactoma no disponible
'; ppiSec.innerHTML = `
V. Interactoma Proteico (STRING-DB)
${ppiImg}
`; tempContainer.appendChild(ppiSec); // Ejecutar paginación real setTimeout(() => paginateReport(tempContainer), 100); } function init() { console.log("KENRYU: Iniciando componentes..."); populateYearSelect(); const canvas = document.getElementById('report-canvas-content'); if (canvas) { const observer = new MutationObserver(() => updateOutline()); observer.observe(canvas, { childList: true, subtree: true, characterData: true }); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }