From 788ed3bada31183576384a5e67fb9ed9d664538d Mon Sep 17 00:00:00 2001 From: Aitor Date: Mon, 1 Jun 2026 22:38:34 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20exportar=20informe=20de=20tx=20en=20JSO?= =?UTF-8?q?N=20y=20Markdown=20+=20arreglo=20atajo=20Lab=E2=86=92Auditor?= =?UTF-8?q?=C3=ADa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 21 ++++++++++++ dashboard.html | 90 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 2 deletions(-) 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 (
Privacy Lab
+ +
@@ -1917,8 +2003,8 @@
{err&&
Error: {err}
} {loading&&} - {result&&result.type==="tx"&&{window.__auditQuery=q; window.__setTab&&window.__setTab("auditoria");}}/>} - {result&&result.type==="address"&&{window.__auditQuery=q; window.__setTab&&window.__setTab("auditoria");}}/>} + {result&&result.type==="tx"&&{window.__setTab&&window.__setTab("auditoria",q);}}/>} + {result&&result.type==="address"&&{window.__setTab&&window.__setTab("auditoria",q);}}/>} {result&&result.type==="block"&&}