// 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 = `| Ruta Biológica | Fuente | p-valor | PubMed |
`;
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 += `${item.Term || '—'} ${item.ScientificDesc || ''} |
${item.Source || '—'} |
${pvalVal} |
${pLink} |
`;
});
html += '
';
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}
Relevancia Patológica Detallada
${d.pathology}
Rutas Metabólicas de Consenso
${(d.associated_routes||[]).map(r => `${r}`).join('
')}
${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 = ``;
} 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', `| NUEVO | Sistema | Ruta | Relevancia |
`);
}
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 = ``;
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\n\n';
}
} else if (src) {
// URL externa: dejar referencia directa
return '\n\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 = `
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
| Gen Core | Sistema | Rutas Asociadas | Relevancia Patológica |
${tableRows}
`;
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();
}