1.6.2: manifiesto firmado, tiempo estimado en tareas, pruebas automáticas

- firma.py: RSA-2048 PKCS#1 v1.5 SHA-256 sin dependencias; empaquetar.py firma
  el manifiesto y actualizador.py exige la firma (CLAVE_PUBLICA incrustada).
- Barra de tareas: tiempo restante estimado a partir del avance.
- Corregido: APLICANDO nunca llegaba al total y «Moviendo» quedaba fijo,
  bloqueando desconectar la carpeta.
- probar.py: reglas, firma y recorrido completo sobre una copia temporal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Este commit está contenido en:
Labstream
2026-09-15 14:48:47 -05:00
padre 8c5153e8ed
commit 8b482af652
Se han modificado 9 ficheros con 414 adiciones y 14 borrados

Ver fichero

@@ -228,6 +228,7 @@
.tarea .b i{display:block;height:100%;background:var(--acc);min-width:2px;transition:width .3s}
.tarea .b.ind i{width:30%!important;animation:ind 1.2s infinite linear}
.tarea .pct{font-variant-numeric:tabular-nums;color:var(--mut);white-space:nowrap}
.tarea .pct small{display:block;font-size:11px;color:var(--mut);font-weight:500;text-align:right}
.tarea button{padding:3px 8px;font-size:11.5px}
/* avisos */
#avisos{position:fixed;right:18px;bottom:60px;display:flex;flex-direction:column;gap:8px;z-index:30;max-width:420px}
@@ -558,10 +559,22 @@ function tareasDe(E){
const act = E.actualizacion || {}; if (act.estado === 'descargando' || act.estado === 'verificando') T.push({q: 'Descargando versión nueva', d: `${(act.bytes / 1e6).toFixed(0)} de ${(act.total / 1e6).toFixed(0)} MB`, pct: act.pct});
return T;
}
// tiempo restante estimado: se recuerda cuándo se vio cada tarea por primera vez y a qué % iba; con eso se extrapola
const ETA = {};
function etaDe(t){
if (t.pct == null) return '';
const k = t.q + '|' + (t.raiz || ''); const ahora = Date.now();
const h = ETA[k] && ETA[k].pct <= t.pct ? ETA[k] : (ETA[k] = {t0: ahora, pct: t.pct});
const avance = t.pct - h.pct, seg = (ahora - h.t0) / 1000;
if (avance < 2 || seg < 8) return '';
const resta = seg * (100 - t.pct) / avance;
return resta < 60 ? '≈ ' + Math.max(5, Math.round(resta / 5) * 5) + ' s' : resta < 3600 ? '≈ ' + Math.round(resta / 60) + ' min' : '≈ ' + (resta / 3600).toFixed(1) + ' h';
}
function pintarTareas(){
const T = tareasDe(ESTADO);
const vivas = new Set(T.map(t => t.q + '|' + (t.raiz || ''))); Object.keys(ETA).forEach(k => { if (!vivas.has(k)) delete ETA[k]; });
const caja = $('#tareas'); caja.classList.toggle('on', T.length > 0);
caja.innerHTML = '<span class="tit">En curso</span>' + T.map((t, i) => `<div class="tarea run"><div class="w"><div class="q">${esc(t.q)}</div><div class="d" title="${esc(t.d)}">${esc(t.d)}</div><div class="b ${t.pct == null ? 'ind' : ''}"><i style="width:${t.pct == null ? 30 : t.pct}%"></i></div></div>${t.pct != null ? `<span class="pct">${t.pct} %</span>` : ''}${t.cancelar ? `<button data-i="${i}">Detener</button>` : ''}</div>`).join('');
caja.innerHTML = '<span class="tit">En curso</span>' + T.map((t, i) => `<div class="tarea run"><div class="w"><div class="q">${esc(t.q)}</div><div class="d" title="${esc(t.d)}">${esc(t.d)}</div><div class="b ${t.pct == null ? 'ind' : ''}"><i style="width:${t.pct == null ? 30 : t.pct}%"></i></div></div>${t.pct != null ? `<span class="pct">${t.pct} %<small>${etaDe(t)}</small></span>` : ''}${t.cancelar ? `<button data-i="${i}">Detener</button>` : ''}</div>`).join('');
$$('#tareas button').forEach(b => b.onclick = () => accion(b, async () => { await T[+b.dataset.i].cancelar(); await cargarEstado(); }));
}