Inicio dice lo que ya se hizo: el plan se olvida al mover y se vuelve a pensar solo

Auditoría de «mover y reorganizar». El motor movía bien, pero el plan en memoria solo se olvidaba al aplicar
desde «Qué va a pasar»: tras «Hacer las N de una vez», apartar stock, juntar repetidos, reemplazar o deshacer,
Inicio seguía ofreciendo lo ya hecho («5 cambios · Ver y aplicar», «Plan listo: 7 de 9») o, tras deshacer,
daba por hecho lo que volvía a estar en desorden. Ahora cualquier movimiento olvida el plan y lo vuelve a
pensar en segundo plano; un plan sin nada marcado es «hecho», cada ficha dice cuándo se hizo y cuántos
movimientos, y el diagnóstico no cuenta como basura lo que ya está en _Para borrar.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Este commit está contenido en:
Labstream
2026-10-14 05:15:22 -05:00
padre 0c8caa0116
commit 91618474a0
Se han modificado 5 ficheros con 189 adiciones y 25 borrados

Ver fichero

@@ -1137,8 +1137,8 @@ function desconectar(clave){
$('#btnDesconectar').onclick = () => desconectar(raizActual());
// ---------------- carpeta ----------------
const COLOR = {camara_raw:'#6b2f2a', stock_viejo:'#b8923a', stock_reciente:'#d9c58c', camara_video:'#3d5a73', exportados:'#7f8a94', proyectos:'#a9b0b8', basura:'#d3d6da', proxies:'#c4c9ce', audio:'#4f6b5a', celular:'#7a5c8a', fotos:'#8a6f4f', video_otro:'#94a3ad', imagenes:'#b5bec6', documentos:'#cfd5da', otros:'#e1e5e8'};
const NOMBRE_CORTO = {camara_raw:'BRAW', stock_viejo:'stock viejo', stock_reciente:'stock reciente', camara_video:'cámara MP4', exportados:'exportados', proyectos:'proyectos', basura:'basura', proxies:'proxies', audio:'audio', celular:'celular', fotos:'fotos', video_otro:'otros videos', imagenes:'imágenes', documentos:'documentos', otros:'otros'};
const COLOR = {camara_raw:'#6b2f2a', stock_viejo:'#b8923a', stock_reciente:'#d9c58c', camara_video:'#3d5a73', exportados:'#7f8a94', proyectos:'#a9b0b8', basura:'#d3d6da', proxies:'#c4c9ce', audio:'#4f6b5a', celular:'#7a5c8a', fotos:'#8a6f4f', video_otro:'#94a3ad', imagenes:'#b5bec6', documentos:'#cfd5da', otros:'#e1e5e8', apartado:'#cfd4d8'};
const NOMBRE_CORTO = {camara_raw:'BRAW', stock_viejo:'stock viejo', stock_reciente:'stock reciente', camara_video:'cámara MP4', exportados:'exportados', proyectos:'proyectos', basura:'basura', proxies:'proxies', audio:'audio', celular:'celular', fotos:'fotos', video_otro:'otros videos', imagenes:'imágenes', documentos:'documentos', otros:'otros', apartado:'ya apartado'};
function pintarPortada(){
// las carpetas que el programa ya conoce, para entrar en una con un clic; con la última vez que se
// escaneó cada una. Es el «historial» de la portada: nada se abre solo.
@@ -1207,10 +1207,12 @@ async function pintarAcciones(){
const corriendo = asis && asis.activo;
const cab = corriendo
? `<div class="cabsec">Trabajando en esta carpeta<span class="sep"></span><span class="mut" style="text-transform:none;letter-spacing:0">${esc(NOMBRE_PASO[asis.paso] || asis.paso || '')} · ${asis.hechas || 0} de ${asis.total || 0}</span></div>`
: `<div class="cabsec">${quedan ? `Lo que se puede hacer ahora · ${quedan}` : 'Esta carpeta está al día'}${auto.length > 1 ? '<span class="sep"></span><button class="p" id="btnHacerTodo" style="text-transform:none;letter-spacing:0">Hacer las ' + auto.length + ' de una vez</button>' : ''}</div>`;
: `<div class="cabsec">${quedan ? `Lo que se puede hacer ahora · ${quedan}` : 'Esta carpeta está al día' + (d.ultimo ? ` · último cambio ${esc(fechaCorta(d.ultimo.ts))}` : '')}${auto.length > 1 ? '<span class="sep"></span><button class="p" id="btnHacerTodo" style="text-transform:none;letter-spacing:0">Hacer las ' + auto.length + ' de una vez</button>' : ''}</div>`;
$('#acciones').innerHTML = cab +
ACCIONES.map((a, i) => `<div class="ac ${a.estado}"><span class="ico">${a.estado === 'hecho' ? '✓' : a.estado === 'espera' ? '·' : i + 1}</span><div class="tx"><b>${esc(a.titulo)}</b><div class="q">${esc(a.que)}</div>${a.detalle ? `<div class="d">${esc(a.detalle)}</div>` : ''}</div><div class="der">${a.cifra ? `<span class="cifra">${esc(a.cifra)}</span>` : ''}${a.boton ? `<button class="${a.estado === 'espera' ? 'lnk' : 'p'}" data-a="${i}">${esc(a.boton)}</button>` : ''}</div></div>`).join('');
ACCIONES.map((a, i) => `<div class="ac ${a.estado}"><span class="ico">${a.calculando ? '<span class="spin"></span>' : a.estado === 'hecho' ? '✓' : a.estado === 'espera' ? '·' : i + 1}</span><div class="tx"><b>${esc(a.titulo)}</b><div class="q">${esc(a.que)}</div>${a.detalle ? `<div class="d">${esc(a.detalle)}</div>` : ''}</div><div class="der">${a.cifra ? `<span class="cifra">${esc(a.cifra)}</span>` : ''}${a.boton ? `<button class="${a.estado === 'espera' ? 'lnk' : 'p'}" data-a="${i}">${esc(a.boton)}</button>` : ''}</div></div>`).join('');
$$('#acciones [data-a]').forEach(b => b.onclick = () => { const a = ACCIONES[+b.dataset.a]; (IR_ACCION[a.ir] || (() => {}))(a.paso); });
// se acaba de mover algo y el programa está comprobando qué queda: se vuelve a preguntar en un momento
if (d.calculando) setTimeout(() => { if (raizActual() === raiz && vistaActual() === 'carpeta') cargarCarpeta().catch(() => {}); }, 1500);
const bt = $('#btnHacerTodo');
if (bt) bt.onclick = () => modal('Hacer todo lo seguro de una vez',
`<p>La app va a hacer seguidas las <b>${auto.length}</b> cosas de la lista que no necesitan que elijas nada, en orden y sin preguntar.</p>
@@ -1250,7 +1252,9 @@ function pasosDe(C, r){
const lotes = (C.lotes || []).filter(l => !l.lote.startsWith('stock-') && !l.lote.startsWith('optimizar') && !l.lote.startsWith('descartados-') && !l.lote.startsWith('tanda-'));
const asis = C.asistente && C.asistente.activo;
P.push(!esc ? {e:'bloq', t:'Ordenar', d:'Después de escanear'} : asis ? {e:'curso', t:'Ordenar', d: (NOMBRE_PASO[C.asistente.paso] || '') + '…', corto: 'en marcha'}
: C.plan ? {e:'listo', t:'Ordenar', d:`Plan listo: ${C.plan.marcadas} de ${C.plan.filas} movimientos marcados`, corto: `${C.plan.marcadas} mov.`}
: C.plan && C.plan.marcadas ? {e:'listo', t:'Ordenar', d:`Plan listo: ${C.plan.marcadas} de ${C.plan.filas} movimientos marcados`, corto: `${C.plan.marcadas} mov.`}
// un plan sin nada marcado es una carpeta en orden (quizá con dudosos): hecho, no «listo para aplicar»
: C.plan ? {e:'hecho', t:'Ordenar', d: (lotes.length ? `Último cambio ${fecha(lotes[0].ts)} · ${lotes[0].movimientos} mov.` : 'Nada que mover') + (C.plan.filas ? ` · ${C.plan.filas} dudoso${C.plan.filas > 1 ? 's' : ''} sin marcar` : ''), corto: lotes.length ? fechaCorta(lotes[0].ts) : 'ok'}
: lotes.length ? {e:'hecho', t:'Ordenar', d:`Último cambio ${fecha(lotes[0].ts)} · ${lotes[0].movimientos} mov.`, corto: fechaCorta(lotes[0].ts)} : {e:'pend', t:'Ordenar', d:'Calcula qué va a pasar y aplica', corto: ''});
const cv = C.convertir || {}; const tr = cv.trabajos || {}; const n = k => (tr[k] || {}).n || 0;
const activos = n('pendiente') + n('corriendo'), listos = n('listo'), verif = n('verificado'), reemp = n('reemplazado');
@@ -1281,7 +1285,7 @@ function pintarSiguiente(P){
else if (P[0].e === 'pend') { s.t = 'Empieza por escanear'; s.p = 'La app lee los nombres, tamaños y fechas de todos los archivos. No mueve nada. En una carpeta grande del NAS tarda unos minutos.'; b('Escanear ahora', () => $('#btnEscanear').click(), true); }
else if (P[2].e === 'curso') { s.t = 'El asistente está trabajando'; s.p = 'Puedes seguir el avance en la barra de abajo.'; }
else if (P[1].e === 'listo' && !C.plan && P[2].e !== 'hecho') { const n = C.sin_nombre_n; s.t = `Antes de ordenar: nombra ${n} carpeta${n > 1 ? 's' : ''}`; s.p = `La app no sabe qué son ${C.sin_nombre.slice(0, 4).map(x => '«' + x.ejemplo + '»').join(', ')}${n > 4 ? ' y otras' : ''}. Si le dices qué es cada una, las ordena bien y se lo aprende para todos los discos. Si prefieres, sáltalo: quedarán como «dudoso» y no se moverán.`; b('Nombrar las carpetas', () => { irA('revisar'); subRev('nombrar'); }, true); b('Saltar y calcular', () => { irA('revisar'); subRev('plan'); $('#btnPlan').click(); }); }
else if (C.plan) { s.t = `Plan listo: ${C.plan.marcadas} de ${C.plan.filas} movimientos marcados`; s.p = `Todo se mueve dentro de la misma carpeta y nada se borra. ${C.plan.baja ? C.plan.baja + ' son dudosos y están sin marcar. ' : ''}Revisa la lista y aplica; se puede deshacer desde Historial.`; b('Revisar y aplicar', () => { irA('revisar'); subRev('plan'); }, true); }
else if (C.plan && C.plan.marcadas) { s.t = `Plan listo: ${C.plan.marcadas} de ${C.plan.filas} movimientos marcados`; s.p = `Todo se mueve dentro de la misma carpeta y nada se borra. ${C.plan.baja ? C.plan.baja + ' son dudosos y están sin marcar. ' : ''}Revisa la lista y aplica; se puede deshacer desde Historial.`; b('Revisar y aplicar', () => { irA('revisar'); subRev('plan'); }, true); }
else if (P[2].e === 'pend') { s.t = 'Siguiente: ordenar la carpeta'; s.p = 'Calcula qué va a pasar (basura y proxies a _Para borrar, grabaciones por mes y cámara, piezas numeradas) y aplícalo con un clic. O deja que el asistente te pregunte paso a paso.'; b('Calcular qué va a pasar', () => { irA('revisar'); subRev('plan'); $('#btnPlan').click(); }, true); }
else if (P[3].e === 'listo') { const d = P[3].d; s.t = 'Siguiente: ' + (d.includes('verificar') ? 'verificar los clips convertidos' : d.includes('reemplazar') ? 'reemplazar los originales' : 'retomar la conversión'); s.p = d.includes('verificar') ? 'Verificar comprueba códec, 10 bits y duración. Después puedes reemplazar los originales (quedan en _Para borrar/originales).' : d.includes('reemplazar') ? 'Los originales se apartan a _Para borrar/originales y el MP4 10 bits queda en su lugar. Nada se borra.' : 'Hay clips en cola con la conversión parada.'; b('Ir a Convertir', () => irA('convertir'), true); }
else if (P[3].e === 'curso') { s.t = 'Convirtiendo clips'; s.p = 'Sigue en segundo plano. El avance está en la barra de abajo; cuando termine, toca reemplazar.'; b('Ver la cola', () => irA('convertir')); }
@@ -1593,7 +1597,13 @@ function aplicarFilas(m, btn, titulo){
// mientras el servidor mueve, se le pregunta por dónde va (archivo a archivo); lo de una tanda anterior no vale
const reloj = setInterval(async () => { try { const a = await api('/api/aplicando'); if (a.inicio && a.inicio * 1000 >= t0 - 2000 && !a.fin) pintarAvance({activo: true, ...a, total: m.length}); } catch (e) {} }, 400);
let res;
try { res = await api('/api/aplicar', {raiz, ids: m.map(r => r.id), destinos}); } catch (e) { clearInterval(reloj); $('#planAvance').innerHTML = ''; throw e; }
try { res = await api('/api/aplicar', {raiz, ids: m.map(r => r.id), destinos}); }
catch (e) {
clearInterval(reloj); $('#planAvance').innerHTML = '';
// la carpeta cambió desde que se calculó (se apartó stock, se deshizo algo…): se calcula otra vez y ya
if (e.datos && e.datos.recalcular) { aviso('La carpeta cambió desde que se calculó el plan. Se vuelve a calcular; revísalo y aplica.', 'bad'); PLAN = null; await calcularPlan(); return; }
throw e;
}
clearInterval(reloj);
PLAN = null; MAPA = null;
pintarAvance({activo: false, res, seg: (Date.now() - t0) / 1000, pedidas: m.length, bytes});