From b5f0c81b8806582523efc55fdd661b28eb6e0117 Mon Sep 17 00:00:00 2001 From: Aitor Date: Wed, 3 Jun 2026 09:23:23 +0200 Subject: [PATCH] feat: mejoras de legibilidad del rastro - veredicto, jerarquia visual, barras y tooltip de detalle --- CHANGELOG.md | 22 ++++++++++ dashboard.html | 110 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 117 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 945210c..2f712c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,28 @@ y el versionado sigue [Versionado Semántico](https://semver.org/lang/es/): --- +## [1.5.0] — 2026-06-03 + +### Cambiado + +- Mejoras de legibilidad del rastro de procedencia, para interpretar los datos + de un vistazo: + - Veredicto arriba del rastro: "Camino limpio" (en verde) si nada de lo + explorado toca entidades marcadas, o "Atención" (en ámbar) indicando qué + toca y a cuántos saltos. Responde de un vistazo si hay algo que revisar. + - Jerarquía visual: los eslabones con buena privacidad y sin marcas se + muestran discretos; los que merecen atención (marca o banda no alta) se + destacan con color. El ojo va directo a lo relevante. + - Barra visual de cantidad en cada eslabón, para captar la proporción sin + leer la cifra; el número queda al lado como apoyo. + - Indicador de estructura (entradas → salidas) siempre visible junto a cada + eslabón, útil para detectar consolidaciones grandes de un vistazo. + - Detalle ampliado al pasar el ratón (o tocar en móvil) sobre el indicador + de estructura: número exacto de entradas y salidas, valor en sat y BTC, + bloque, comisión y tasa. Mantiene la vista limpia y el detalle a mano. + +--- + ## [1.4.0] — 2026-06-02 ### Añadido diff --git a/dashboard.html b/dashboard.html index a27e311..5790d38 100644 --- a/dashboard.html +++ b/dashboard.html @@ -2420,6 +2420,24 @@ const {marcas, esCoinbase} = useMemo(()=>marcasDeTx(txData,ana),[txData,ana]); const mempoolUrl = base ? `${base}/tx/${txData.txid}` : `https://mempool.space/tx/${txData.txid}`; const inputs = txData.vin || []; + const [hover,setHover]=useState(false); + + // Detalle para el tooltip: lo que un noderunner querría ver de un eslabón + const detalle = useMemo(()=>{ + const nIn = (txData.vin||[]).length; + const nOut = (txData.vout||[]).length; + const totalOut = (txData.vout||[]).reduce((s,o)=>s+(o.value||0),0); + const totalIn = (txData.vin||[]).reduce((s,v)=>s+(v.prevout?.value||0),0); + const fee = txData.fee || (totalIn>totalOut ? totalIn-totalOut : null); + const vsize = txData.weight ? Math.ceil(txData.weight/4) : null; + const feeRate = (fee && vsize) ? (fee/vsize) : (txData.feerate ? Number(txData.feerate) : null); + return { + nIn, nOut, + btc: (fluyo/1e8).toFixed(8), + bloque: txData.status?.confirmed ? txData.status.block_height : null, + fee, feeRate, + }; + },[txData,fluyo]); const seguirInput = async (i) => { const vin=inputs[i]; @@ -2433,28 +2451,79 @@ }catch(e){ setHijos(s=>({...s,[i]:{err:e.message}})); } }; + // ¿Este eslabón merece atención? (tiene marca, o banda no es ALTA) + // Lo relevante se destaca; lo limpio se silencia. + const relevante = marcas.length>0 || (ana?.band && ana.band!=="ALTA"); + const acento = marcas.length>0 ? marcas[0].color : bandColorRastro(ana?.band); + // Barra visual de cantidad: proporción respecto al flujo de entrada (vout origen). + // Da una idea de tamaño sin leer la cifra. Si no hay referencia, escala suave. + const pctBarra = (() => { + const ref = fluyo || 1; + const outTotal = (txData.vout||[]).reduce((s,o)=>s+(o.value||0),0) || ref; + const p = Math.min(100, Math.round((fluyo/outTotal)*100)); + return isFinite(p) && p>0 ? p : 5; + })(); + return ( -
+
- - {abrevTxid(txData.txid)} ↗ - +
+ + {abrevTxid(txData.txid)} ↗ + + + + {/* Tooltip de detalle: aparece al pasar el ratón o tocar la pastilla i→o */} + {hover&&( +
+
+
Estructura {detalle.nIn} in → {detalle.nOut} out
+
Fluyó {fmt.num(fluyo)} sat · {detalle.btc} BTC
+ {detalle.bloque!=null + ?
Bloque #{fmt.num(detalle.bloque)}
+ :
Estado sin confirmar (mempool)
} + {detalle.fee!=null&&
Fee {fmt.num(detalle.fee)} sat{detalle.feeRate&& · {detalle.feeRate.toFixed(1)} sat/vB}
} + {marcas.length>0&&
{marcas.map((m,j)=>({m.txt}))}
} +
+
+ )} +
{ana?.band&&( - {ana.band} + {ana.band} )}
-
- Fluyó {fmt.num(fluyo)} sat hacia el eslabón anterior - {txData.status?.block_time&& · {fmt.date(txData.status.block_time)}} + {/* Barra visual de cantidad: el ojo capta proporción sin leer cifras */} +
+
+
+
+ + {fmt.num(fluyo)} sat{txData.status?.block_time&&` · ${fmt.date(txData.status.block_time)}`} +
{marcas.length>0&&(
- {marcas.map((m,j)=>({m.txt}))} + {marcas.map((m,j)=>({m.txt}))}
)} {esCoinbase&&( -
Fin de la rama: estas monedas se crearon aquí (recompensa de minado).
+
⛏️ Origen: estas monedas se crearon aquí (recompensa de minado).
)} {/* Seguir más atrás desde este eslabón (recursivo, bajo demanda) */} @@ -2563,9 +2632,11 @@ Sigue cada input hacia atrás, un salto a la vez. Cada consulta la pides tú, para no sobrecargar el nodo. Desde cada eslabón puedes seguir bajando.
- {resumenDistancia.length>0&&( -
-
De lo explorado, lo más cercano
+ {resumenDistancia.length>0 ? ( +
+
+ Atención: el rastro toca {resumenDistancia.map(r=>r.cat.replace(/^.. /,"")).join(", ")} +
{resumenDistancia.map((r,j)=>( @@ -2577,7 +2648,16 @@ Solo cuenta las ramas que has seguido. Puede haber más sin explorar.
- )} + ) : explorados>0 ? ( +
+
+ Camino limpio +
+
+ De lo explorado, nada toca entidades marcadas. Puede haber más ramas sin seguir. +
+
+ ) : null} {/* ¿La propia tx analizada es coinbase? Entonces es el origen: no hay nada hacia atrás, estas monedas se crearon aquí. */}