diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c99281..bc6f9e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,27 @@ y el versionado sigue [Versionado Semántico](https://semver.org/lang/es/): --- +## [1.2.0] — 2026-06-01 + +### Añadido + +- Exportación del informe de privacidad de una transacción en dos formatos, + desde la pestaña Auditoría. El botón "↓ JSON" descarga el análisis completo + con su estructura (score, banda, wallets detectados y todas las señales con + su detalle), pensado para procesar con scripts o archivar en formato máquina. + El botón "↓ MD" descarga el informe legible en Markdown (diagnóstico, qué + puede saber un analista, limitaciones, recomendaciones y una tabla de señales), + para leer o guardar como documento. Ambos se generan en el navegador y se + descargan en local — no sale nada del nodo. + +### Corregido + +- El botón "Analizar privacidad" del Lab ahora envía correctamente el txid a + la pestaña Auditoría y lanza el análisis. Antes la pestaña aparecía vacía + porque el identificador no se transmitía entre vistas. + +--- + ## [1.1.0] — 2026-06-01 ### Añadido diff --git a/dashboard.html b/dashboard.html index d0d3eb2..f664ae2 100644 --- a/dashboard.html +++ b/dashboard.html @@ -1069,11 +1069,97 @@ const {score,checks,summary,summaryColor,wallets,band} = analysis; const report = generateReport(analysis, tx); + // ── Exportar informe ────────────────────────────────────────────── + // Genera el archivo en el navegador y lo descarga en local. + // No pasa por ningún servidor, no sale nada del nodo. + const descargar = (contenido, nombre, tipo) => { + const blob = new Blob(["\uFEFF" + contenido], {type:tipo}); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; a.download = nombre; + document.body.appendChild(a); a.click(); document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + const nombreBase = () => { + const fecha = new Date().toISOString().slice(0,10); + const txCorto = (tx?.txid || "tx").slice(0,12); + return `txoko-informe-${txCorto}-${fecha}`; + }; + + const exportJSON = () => { + const datos = { + generado: new Date().toISOString(), + herramienta: "Txoko Node Dashboard", + txid: tx?.txid || null, + privacidad: { score, banda: band, resumen: summary }, + wallets_detectados: (wallets||[]).map(w => ({ + nombre: w.name || w, confianza: w.confidence || null, señales: w.signals || [] + })), + señales: checks.map(c => ({ + id: c.id, etiqueta: c.label, certeza: c.certainty, + pasa: c.pass, informativo: !!c.informational, + accionabilidad: c.actionability || null, + penalizacion: c.penalty || 0, detalle: c.detail, + })), + }; + descargar(JSON.stringify(datos, null, 2), nombreBase()+".json", "application/json;charset=utf-8"); + }; + + const exportMarkdown = () => { + const L = []; + L.push(`# Informe de privacidad — Txoko`); + L.push(""); + L.push(`**Transacción:** \`${tx?.txid || "—"}\` `); + L.push(`**Generado:** ${new Date().toLocaleString("es-ES")} `); + L.push(`**Nivel de privacidad:** ${band} (${score}/100) — ${summary}`); + L.push(""); + if (wallets && wallets.length > 0) { + L.push(`**Wallet posible:** ${wallets.map(w=>w.name||w).join(", ")}`); + L.push(""); + } + // Informe narrativo (reutiliza generateReport) + report.forEach(sec => { + L.push(`## ${sec.title}`); + L.push(""); + if (sec.text) { L.push(sec.text); L.push(""); } + if (sec.recs) { + sec.recs.forEach(r => { L.push(`- **${r.action}** — ${r.why} _(${r.tag})_`); }); + L.push(""); + } + }); + // Tabla de señales detectadas + L.push(`## Señales detectadas`); + L.push(""); + L.push(`| Señal | Certeza | Estado | Detalle |`); + L.push(`| --- | --- | --- | --- |`); + checks.forEach(c => { + const estado = c.informational ? "informativo" : (c.pass ? "ok" : "detectado"); + const detalle = (c.detail||"").replace(/\|/g,"\\|").replace(/\n/g," "); + L.push(`| ${c.label} | ${c.certainty} | ${estado} | ${detalle} |`); + }); + L.push(""); + L.push(`---`); + L.push(`_Generado por Txoko Node Dashboard — your node, your rules. Análisis local, ningún dato salió de tu nodo._`); + descargar(L.join("\n"), nombreBase()+".md", "text/markdown;charset=utf-8"); + }; + return (