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:
28
README.md
28
README.md
@@ -880,6 +880,34 @@ Pruebas: `prueba_orden.py` 24 casos (5 nuevos: pieza, bandeja, tarjeta con una c
|
|||||||
que rechazar, versiones con ffprobe simulado, y la pasada automática con sus frenos); `prueba_funcional.py`
|
que rechazar, versiones con ffprobe simulado, y la pasada automática con sus frenos); `prueba_funcional.py`
|
||||||
29 de 29 (3 nuevas, por la interfaz de verdad del servidor); `probar.py` 19 y `prueba_ventana.py` 18.
|
29 de 29 (3 nuevas, por la interfaz de verdad del servidor); `probar.py` 19 y `prueba_ventana.py` 18.
|
||||||
|
|
||||||
|
3.6.0, Inicio dice lo que ya se hizo. Munka pidió auditar «mover y reorganizar»: «si hago el cambio debería
|
||||||
|
decir que el cambio se hizo y no mandarme para hacerlo y mostrarme lo que pasaría o lo que ahorraría cuando ya
|
||||||
|
lo había movido». Reproducido el 14-oct-2026 sobre una carpeta de ensayo: el motor mueve bien (plan → aplicar →
|
||||||
|
deshacer → volver a aplicar deja el árbol exacto y el inventario al día), pero Inicio no se enteraba:
|
||||||
|
|
||||||
|
- **El plan en memoria no se olvidaba al mover.** Solo lo olvidaba «Aplicar» desde «Qué va a pasar». Tras «Hacer
|
||||||
|
las N de una vez», apartar stock, juntar repetidos, reemplazar originales o «Deshacer», Inicio seguía con el
|
||||||
|
plan de antes: «Ordenar por año y mes · 5 cambios · Ver y aplicar» y «Plan listo: 7 de 9 movimientos marcados»
|
||||||
|
con todo ya movido; y tras deshacer, al revés, «lo seguro ya está hecho» con la carpeta otra vez en desorden.
|
||||||
|
Ahora cualquier movimiento (`actualizar_inventario`, y borrar carpetas vacías) olvida el plan y lo vuelve a
|
||||||
|
pensar en segundo plano (`olvidar_plan`); mientras tanto Inicio dice «comprobando qué queda…» y se refresca
|
||||||
|
solo. «Calcular» y la pasada automática esperan ese recálculo en vez de recorrer la carpeta dos veces, y la
|
||||||
|
pasada automática deja su plan en memoria para que por la mañana Inicio diga qué quedó. Si se intenta aplicar
|
||||||
|
un plan ya olvidado, el servidor contesta 409 y la interfaz lo recalcula en vez de mover filas que ya no
|
||||||
|
existen.
|
||||||
|
- **Un plan sin nada marcado contaba como pendiente.** Inicio decía «Plan listo: 0 de 2 movimientos marcados» y
|
||||||
|
mandaba a «Revisar y aplicar». Ahora es «hecho»: «Último cambio hoy 05:12 · 9 mov. · 2 dudosos sin marcar», y
|
||||||
|
«Siguiente» pasa a convertir o a respaldar.
|
||||||
|
- **Cada ficha dice cuándo se hizo.** «Ordenar lo demás · Hecho hoy a las 05:12: 9 movimientos»; «Basura y
|
||||||
|
proxies · 2 archivos ya en _Para borrar (0 MB): se borran cuando tú quieras»; y arriba, «Esta carpeta está al
|
||||||
|
día · último cambio 14 oct».
|
||||||
|
- **El diagnóstico no cuenta como basura lo ya apartado.** Después de apartar decía «2 archivos de basura de
|
||||||
|
sistema» (más que antes, porque los proxies y el stock movidos también se marcan como basura). Lo que está en
|
||||||
|
_Para borrar es un grupo aparte, «ya apartado», con su frase: «ahí se quedan hasta que tú los borres».
|
||||||
|
|
||||||
|
Pruebas: `prueba_funcional.py` comprueba, tras aplicar, deshacer y «hacer todo», lo que dice Inicio (hecho y
|
||||||
|
cuándo, nada vuelto a ofrecer, el diagnóstico sin contar lo apartado); 32 de 32.
|
||||||
|
|
||||||
3.5.0, «Qué va a pasar» se explica con una carpeta. Munka, con el plan de Diana Méndez delante: «se lee
|
3.5.0, «Qué va a pasar» se explica con una carpeta. Munka, con el plan de Diana Méndez delante: «se lee
|
||||||
mucho pero se entiende poco», «lo de ordenar por año y por mes, ¿me toca seleccionar uno a uno o lo hace
|
mucho pero se entiende poco», «lo de ordenar por año y por mes, ¿me toca seleccionar uno a uno o lo hace
|
||||||
todo?», y quería una barra para saber por dónde va y si hace falta algo. Lo que había: tres cajas con un
|
todo?», y quería una barra para saber por dónde va y si hace falta algo. Lo que había: tres cajas con un
|
||||||
|
|||||||
15
asistente.py
15
asistente.py
@@ -26,6 +26,7 @@ GRUPOS = [
|
|||||||
("stock_reciente", "Stock de Envato reciente", "tomas de Envato de piezas en curso"),
|
("stock_reciente", "Stock de Envato reciente", "tomas de Envato de piezas en curso"),
|
||||||
("proxies", "Proxies", "copias ligeras que Resolve/Premiere regeneran solas"),
|
("proxies", "Proxies", "copias ligeras que Resolve/Premiere regeneran solas"),
|
||||||
("basura", "Basura de sistema y autoguardados", ".DS_Store, Thumbs.db, cachés, autosaves de After Effects"),
|
("basura", "Basura de sistema y autoguardados", ".DS_Store, Thumbs.db, cachés, autosaves de After Effects"),
|
||||||
|
("apartado", "Ya apartado en _Para borrar", "basura, proxies, stock viejo y originales reemplazados que la app ya apartó; se borran cuando tú quieras"),
|
||||||
("exportados", "Exportados / masters", "videos finales renderizados"),
|
("exportados", "Exportados / masters", "videos finales renderizados"),
|
||||||
("video_otro", "Otros videos", "videos que no son de cámara ni exportados (descargas, referencias, IA)"),
|
("video_otro", "Otros videos", "videos que no son de cámara ni exportados (descargas, referencias, IA)"),
|
||||||
("proyectos", "Proyectos de edición y diseño", ".drp, .prproj, .aep, .psd, .ai…"),
|
("proyectos", "Proyectos de edición y diseño", ".drp, .prproj, .aep, .psd, .ai…"),
|
||||||
@@ -37,7 +38,12 @@ TITULO = {k: t for k, t, _ in GRUPOS}
|
|||||||
QUE_ES = {k: q for k, _, q in GRUPOS}
|
QUE_ES = {k: q for k, _, q in GRUPOS}
|
||||||
|
|
||||||
|
|
||||||
def _grupo(f, limite_stock):
|
def _grupo(f, limite_stock, papeleras=()):
|
||||||
|
# lo que ya está en _Para borrar no cuenta como basura, proxies o stock «por apartar»: ya se apartó.
|
||||||
|
# Antes, después de apartar, Inicio decía «2 archivos de basura» (más que antes de apartar, porque
|
||||||
|
# los proxies y el stock movidos también se marcan como basura) como si estuviera por hacer.
|
||||||
|
if papeleras and any(f["ruta"].lower().startswith(p) for p in papeleras):
|
||||||
|
return "apartado"
|
||||||
if f["es_basura"] or f["rol"] == "basura":
|
if f["es_basura"] or f["rol"] == "basura":
|
||||||
return "basura"
|
return "basura"
|
||||||
if f["es_proxy"] or f["rol"] == "proxy":
|
if f["es_proxy"] or f["rol"] == "proxy":
|
||||||
@@ -70,8 +76,9 @@ def diagnostico(raiz):
|
|||||||
"""Resumen corto y sencillo de lo que hay en la carpeta, por tipo de archivo."""
|
"""Resumen corto y sencillo de lo que hay en la carpeta, por tipo de archivo."""
|
||||||
raiz = os.path.normpath(raiz)
|
raiz = os.path.normpath(raiz)
|
||||||
con = O.db()
|
con = O.db()
|
||||||
filas = con.execute("SELECT rol, bytes, mtime, es_basura, es_proxy, es_stock, contexto, fecha FROM archivos WHERE raiz = ?",
|
filas = con.execute("SELECT rol, bytes, mtime, es_basura, es_proxy, es_stock, contexto, fecha, ruta FROM archivos WHERE raiz = ?",
|
||||||
(raiz,)).fetchall()
|
(raiz,)).fetchall()
|
||||||
|
papeleras = tuple(os.path.join(raiz, p).lower() + os.sep for p in O.R.PAPELERAS)
|
||||||
esc = con.execute("SELECT * FROM escaneos WHERE raiz = ?", (raiz,)).fetchone()
|
esc = con.execute("SELECT * FROM escaneos WHERE raiz = ?", (raiz,)).fetchone()
|
||||||
con.close()
|
con.close()
|
||||||
meses = int(O.CONFIG["optimizar"].get("meses_minimos", 3))
|
meses = int(O.CONFIG["optimizar"].get("meses_minimos", 3))
|
||||||
@@ -79,7 +86,7 @@ def diagnostico(raiz):
|
|||||||
acc = {k: {"clave": k, "titulo": TITULO[k], "que_es": QUE_ES[k], "n": 0, "bytes": 0, "desde": None, "hasta": None} for k, _, _ in GRUPOS}
|
acc = {k: {"clave": k, "titulo": TITULO[k], "que_es": QUE_ES[k], "n": 0, "bytes": 0, "desde": None, "hasta": None} for k, _, _ in GRUPOS}
|
||||||
total_b = 0
|
total_b = 0
|
||||||
for f in filas:
|
for f in filas:
|
||||||
g = acc[_grupo(f, limite)]
|
g = acc[_grupo(f, limite, papeleras)]
|
||||||
g["n"] += 1
|
g["n"] += 1
|
||||||
g["bytes"] += f["bytes"] or 0
|
g["bytes"] += f["bytes"] or 0
|
||||||
total_b += f["bytes"] or 0
|
total_b += f["bytes"] or 0
|
||||||
@@ -103,6 +110,8 @@ def diagnostico(raiz):
|
|||||||
frases.append(f"Los proxies suman {O._gb(b('proxies'))}: se regeneran solos, no hace falta guardarlos.")
|
frases.append(f"Los proxies suman {O._gb(b('proxies'))}: se regeneran solos, no hace falta guardarlos.")
|
||||||
if acc["basura"]["n"]:
|
if acc["basura"]["n"]:
|
||||||
frases.append(f"{acc['basura']['n']:,} archivos de basura de sistema o autoguardado ({O._gb(b('basura'))}).")
|
frases.append(f"{acc['basura']['n']:,} archivos de basura de sistema o autoguardado ({O._gb(b('basura'))}).")
|
||||||
|
if acc["apartado"]["n"]:
|
||||||
|
frases.append(f"{acc['apartado']['n']:,} archivos ya apartados en «{O.R.PAPELERA}» ({O._gb(b('apartado'))}): ahí se quedan hasta que tú los borres.")
|
||||||
dup = O.duplicados(raiz)
|
dup = O.duplicados(raiz)
|
||||||
if dup["grupos"]:
|
if dup["grupos"]:
|
||||||
frases.append(f"Hay {dup['n']} archivos repetidos ({O._gb(dup['bytes'])}) en {len(dup['grupos'])} grupos: se pueden juntar en subcarpetas Duplicados para revisarlos a mano.")
|
frases.append(f"Hay {dup['n']} archivos repetidos ({O._gb(dup['bytes'])}) en {len(dup['grupos'])} grupos: se pueden juntar en subcarpetas Duplicados para revisarlos a mano.")
|
||||||
|
|||||||
117
organizador.py
117
organizador.py
@@ -675,7 +675,7 @@ def escanear(ruta):
|
|||||||
prog["estado"] = "listo"
|
prog["estado"] = "listo"
|
||||||
if fallos:
|
if fallos:
|
||||||
prog["error"] = f"{len(fallos)} sitios no se pudieron leer (el primero: {fallos[0]})"
|
prog["error"] = f"{len(fallos)} sitios no se pudieron leer (el primero: {fallos[0]})"
|
||||||
PLANES.pop(raiz, None) # el plan anterior ya no vale: se calcula de nuevo sobre el inventario fresco
|
olvidar_plan(raiz, recalcular=False) # el plan anterior ya no vale: se calcula de nuevo sobre el inventario fresco
|
||||||
except Exception as ex: # noqa
|
except Exception as ex: # noqa
|
||||||
prog["estado"] = "error"
|
prog["estado"] = "error"
|
||||||
prog["error"] = repr(ex)
|
prog["error"] = repr(ex)
|
||||||
@@ -1975,6 +1975,9 @@ def actualizar_inventario(movidos):
|
|||||||
fila["es_proxy"], fila["es_basura"]))
|
fila["es_proxy"], fila["es_basura"]))
|
||||||
_recontar_carpetas(con, movidos)
|
_recontar_carpetas(con, movidos)
|
||||||
con.close()
|
con.close()
|
||||||
|
# el plan que hubiera en memoria ya no describe la carpeta: se olvida y se vuelve a pensar solo
|
||||||
|
for k in {r for de, a in movidos for r in (_raiz_de(de), _raiz_de(a)) if r}:
|
||||||
|
olvidar_plan(k)
|
||||||
|
|
||||||
|
|
||||||
def _recontar_carpetas(con, movidos):
|
def _recontar_carpetas(con, movidos):
|
||||||
@@ -2155,6 +2158,8 @@ def borrar_vacias(raiz, rutas):
|
|||||||
if e.get("lote") == lote and e.get("op") == "rmdir":
|
if e.get("lote") == lote and e.get("op") == "rmdir":
|
||||||
con.execute("DELETE FROM carpetas WHERE ruta = ?", (e["de"],))
|
con.execute("DELETE FROM carpetas WHERE ruta = ?", (e["de"],))
|
||||||
con.close()
|
con.close()
|
||||||
|
if borradas:
|
||||||
|
olvidar_plan(raiz)
|
||||||
bitacora({"lote": lote, "op": "fin", "raiz": raiz, "filas": borradas, "movimientos": borradas, "errores": errores})
|
bitacora({"lote": lote, "op": "fin", "raiz": raiz, "filas": borradas, "movimientos": borradas, "errores": errores})
|
||||||
return {"lote": lote, "borradas": borradas, "errores": errores}
|
return {"lote": lote, "borradas": borradas, "errores": errores}
|
||||||
|
|
||||||
@@ -4428,9 +4433,53 @@ def copiar_respaldo(raiz, plan):
|
|||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
|
|
||||||
PLANES = {} # raiz -> plan
|
PLANES = {} # raiz -> plan
|
||||||
|
PLAN_GEN = defaultdict(int) # raiz -> cuántas veces se ha olvidado su plan: un recálculo viejo no pisa uno nuevo
|
||||||
|
REPLAN = {} # raiz -> el hilo que está volviendo a pensar el plan después de mover algo
|
||||||
PLANES_RESPALDO = {}
|
PLANES_RESPALDO = {}
|
||||||
|
|
||||||
|
|
||||||
|
def olvidar_plan(raiz, recalcular=True):
|
||||||
|
"""Después de mover algo, el plan calculado ya no describe la carpeta: se olvida y, salvo que se diga lo
|
||||||
|
contrario, se vuelve a pensar en segundo plano. Así Inicio dice lo que de verdad queda (o que no queda
|
||||||
|
nada) en vez de ofrecer otra vez lo que se acaba de hacer, o de pedir que se calcule. Medido el
|
||||||
|
14-oct-2026 en el laboratorio: tras «Hacer las 5 de una vez», Inicio seguía diciendo «Ordenar por año y
|
||||||
|
mes · 5 cambios · Ver y aplicar» y «Plan listo: 7 de 9 movimientos marcados» con todo ya movido, porque
|
||||||
|
el plan solo se olvidaba al aplicar desde la pestaña «Qué va a pasar»; y tras «Deshacer», al revés:
|
||||||
|
«Lo seguro ya está hecho» con la carpeta otra vez en desorden."""
|
||||||
|
k = _clave(raiz)
|
||||||
|
PLANES.pop(k, None)
|
||||||
|
PLAN_GEN[k] += 1
|
||||||
|
if not recalcular or not raiz_cfg(k):
|
||||||
|
return
|
||||||
|
gen = PLAN_GEN[k]
|
||||||
|
|
||||||
|
def _pensar():
|
||||||
|
try:
|
||||||
|
plan = Plan(raiz_cfg(k)).correr()
|
||||||
|
if PLAN_GEN[k] == gen: # nadie movió nada más mientras tanto
|
||||||
|
PLANES[k] = plan
|
||||||
|
except Exception as ex: # noqa
|
||||||
|
print(f"no se pudo volver a pensar el plan de {k}: {ex}", file=sys.stderr)
|
||||||
|
finally:
|
||||||
|
if REPLAN.get(k) is threading.current_thread():
|
||||||
|
REPLAN.pop(k, None)
|
||||||
|
|
||||||
|
t = threading.Thread(target=_pensar, daemon=True)
|
||||||
|
REPLAN[k] = t
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
|
||||||
|
def plan_en_marcha(raiz):
|
||||||
|
"""Si el plan de esta carpeta se está volviendo a pensar, espera a que acabe y lo devuelve; si no, None.
|
||||||
|
Lo usan «Calcular» y la pasada automática para no recorrer la carpeta dos veces seguidas."""
|
||||||
|
k = _clave(raiz)
|
||||||
|
t = REPLAN.get(k)
|
||||||
|
if t is None or t is threading.current_thread():
|
||||||
|
return None
|
||||||
|
t.join()
|
||||||
|
return PLANES.get(k)
|
||||||
|
|
||||||
|
|
||||||
def _clave(ruta):
|
def _clave(ruta):
|
||||||
return os.path.normpath(ruta)
|
return os.path.normpath(ruta)
|
||||||
|
|
||||||
@@ -5188,7 +5237,7 @@ def nueva_pieza(raiz, titulo, anio=None, mes=None, crear=True):
|
|||||||
con.execute("INSERT OR IGNORE INTO carpetas VALUES (?,?,?,0,0,NULL,NULL,0,0,0,0)", (d, raiz, os.path.basename(d)))
|
con.execute("INSERT OR IGNORE INTO carpetas VALUES (?,?,?,0,0,NULL,NULL,0,0,0,0)", (d, raiz, os.path.basename(d)))
|
||||||
con.close()
|
con.close()
|
||||||
bitacora({"lote": lote, "op": "fin", "raiz": raiz, "filas": 1, "movimientos": len(creadas), "errores": []})
|
bitacora({"lote": lote, "op": "fin", "raiz": raiz, "filas": 1, "movimientos": len(creadas), "errores": []})
|
||||||
PLANES.pop(raiz, None)
|
olvidar_plan(raiz)
|
||||||
return {**out, "ok": True, "lote": lote, "creadas": len(creadas)}
|
return {**out, "ok": True, "lote": lote, "creadas": len(creadas)}
|
||||||
|
|
||||||
|
|
||||||
@@ -5522,11 +5571,11 @@ def automatico_una(raiz, limite):
|
|||||||
out["errores"] = (res.get("errores") or [])[:20]
|
out["errores"] = (res.get("errores") or [])[:20]
|
||||||
if asistente.ESTADO.get("error"):
|
if asistente.ESTADO.get("error"):
|
||||||
out["errores"].append(asistente.ESTADO["error"])
|
out["errores"].append(asistente.ESTADO["error"])
|
||||||
despues = Plan(r).correr()
|
despues = plan_en_marcha(raiz) or Plan(r).correr()
|
||||||
out["dudosos"] = sum(1 for f in despues["filas"] if not f["marcado"])
|
out["dudosos"] = sum(1 for f in despues["filas"] if not f["marcado"])
|
||||||
out["avisos"] = despues["avisos"][:20]
|
out["avisos"] = despues["avisos"][:20]
|
||||||
out["estado"] = "con errores" if out["errores"] else "con dudas" if out["dudosos"] else "al dia"
|
out["estado"] = "con errores" if out["errores"] else "con dudas" if out["dudosos"] else "al dia"
|
||||||
PLANES.pop(raiz, None)
|
PLANES[despues["raiz"]] = despues # se queda: así Inicio dice por la mañana qué quedó, sin pedir que se calcule
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
@@ -5668,6 +5717,34 @@ def automatico_por_comando():
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
_LOTES_OTROS = ("stock-", "dup-", "vacias-", "entrada-", "optimizar", "tanda-", "pieza-", "descartados-", "colores-", "respaldo")
|
||||||
|
|
||||||
|
|
||||||
|
def _ultimo_orden(raiz):
|
||||||
|
"""El último lote de orden (mover y reorganizar) aplicado sobre esta carpeta y no deshecho: cuándo y cuánto."""
|
||||||
|
ent = leer_bitacora(3000)
|
||||||
|
deshechos = {e.get("de_lote") for e in ent if e.get("op") == "deshacer"}
|
||||||
|
for e in reversed(ent):
|
||||||
|
l = e.get("lote") or ""
|
||||||
|
if e.get("op") == "fin" and (e.get("raiz") or "").lower() == raiz.lower() and l and not l.startswith(_LOTES_OTROS) and l not in deshechos:
|
||||||
|
return {"lote": l, "ts": e.get("ts"), "movimientos": e.get("movimientos", 0), "errores": len(e.get("errores") or [])}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _cuando(ts):
|
||||||
|
"""«hoy a las 05:02», «ayer a las 23:10», «el 3 de octubre a las 11:40»."""
|
||||||
|
if not ts:
|
||||||
|
return ""
|
||||||
|
t = time.localtime(ts)
|
||||||
|
hoy = time.localtime()
|
||||||
|
hora = time.strftime("%H:%M", t)
|
||||||
|
if t[:3] == hoy[:3]:
|
||||||
|
return f"hoy a las {hora}"
|
||||||
|
if t[:3] == time.localtime(time.time() - 86400)[:3]:
|
||||||
|
return f"ayer a las {hora}"
|
||||||
|
return f"el {t.tm_mday} de {R.MESES[t.tm_mon - 1]}" + (f" de {t.tm_year}" if t.tm_year != hoy.tm_year else "") + f" a las {hora}"
|
||||||
|
|
||||||
|
|
||||||
def acciones(raiz):
|
def acciones(raiz):
|
||||||
"""Todo lo que se puede hacer AHORA MISMO en esta carpeta, en orden, cada cosa con su cifra.
|
"""Todo lo que se puede hacer AHORA MISMO en esta carpeta, en orden, cada cosa con su cifra.
|
||||||
|
|
||||||
@@ -5730,8 +5807,12 @@ def acciones(raiz):
|
|||||||
detalle=f"Todo pasa a «{R.PAPELERA}». No se borra nada.",
|
detalle=f"Todo pasa a «{R.PAPELERA}». No se borra nada.",
|
||||||
ir="plan", paso="limpiar")
|
ir="plan", paso="limpiar")
|
||||||
else:
|
else:
|
||||||
|
# lo ya apartado se dice, para que se vea que el cambio se hizo (antes la ficha quedaba muda)
|
||||||
|
apartado = con.execute("SELECT COUNT(*) n, COALESCE(SUM(bytes),0) b FROM archivos WHERE raiz = ? AND ("
|
||||||
|
+ " OR ".join(["ruta LIKE ? ESCAPE '!'"] * len(R.PAPELERAS)) + ")", [raiz] + args_pap).fetchone()
|
||||||
añadir(clave="limpiar", titulo="Basura y proxies", estado="hecho",
|
añadir(clave="limpiar", titulo="Basura y proxies", estado="hecho",
|
||||||
que="No queda nada suelto que apartar.", cifra="", detalle="")
|
que="No queda nada suelto que apartar.", cifra="",
|
||||||
|
detalle=f"{_plural(apartado['n'], 'archivo', 'archivos')} ya en «{R.PAPELERA}» ({_gb(apartado['b'])}): se borran cuando tú quieras." if apartado["n"] else "")
|
||||||
|
|
||||||
# --- 2. carpetas que la app no reconoce ---
|
# --- 2. carpetas que la app no reconoce ---
|
||||||
voc = vocabulario(raiz)
|
voc = vocabulario(raiz)
|
||||||
@@ -5754,6 +5835,8 @@ def acciones(raiz):
|
|||||||
|
|
||||||
# --- 3 y 4. ordenar (usa el plan solo si ya estaba calculado) ---
|
# --- 3 y 4. ordenar (usa el plan solo si ya estaba calculado) ---
|
||||||
plan = PLANES.get(raiz)
|
plan = PLANES.get(raiz)
|
||||||
|
ultimo = _ultimo_orden(raiz)
|
||||||
|
hecho_det = f"Hecho {_cuando(ultimo['ts'])}: {_plural(ultimo['movimientos'], 'movimiento', 'movimientos')}." if ultimo else ""
|
||||||
for clave, titulo, que in (("fechas", "Ordenar por año y mes",
|
for clave, titulo, que in (("fechas", "Ordenar por año y mes",
|
||||||
"Meses con nombre completo, grabaciones repartidas por mes y cámara, piezas numeradas."),
|
"Meses con nombre completo, grabaciones repartidas por mes y cámara, piezas numeradas."),
|
||||||
("resto", "Ordenar lo demás",
|
("resto", "Ordenar lo demás",
|
||||||
@@ -5766,8 +5849,8 @@ def acciones(raiz):
|
|||||||
# Si contara como pendiente, la carpeta no llegaría nunca a estar «al día».
|
# Si contara como pendiente, la carpeta no llegaría nunca a estar «al día».
|
||||||
añadir(clave=clave, titulo=titulo, boton="Ver", que=que, estado="espera",
|
añadir(clave=clave, titulo=titulo, boton="Ver", que=que, estado="espera",
|
||||||
cifra=_plural(len(fs), "dudoso", "dudosos"),
|
cifra=_plural(len(fs), "dudoso", "dudosos"),
|
||||||
detalle="Lo seguro ya está hecho. Queda lo que la app no se atreve a mover sola: "
|
detalle=(hecho_det + " " if hecho_det else "Lo seguro ya está hecho. ")
|
||||||
"míralo y marca lo que quieras.", ir="plan", paso=clave)
|
+ "Queda lo que la app no se atreve a mover sola: míralo y marca lo que quieras.", ir="plan", paso=clave)
|
||||||
elif fs:
|
elif fs:
|
||||||
añadir(clave=clave, titulo=titulo, boton="Ver y aplicar", que=que,
|
añadir(clave=clave, titulo=titulo, boton="Ver y aplicar", que=que,
|
||||||
cifra=f"{_plural(len(fs), 'cambio', 'cambios')} · {_gb(sum(f['bytes'] for f in fs))}",
|
cifra=f"{_plural(len(fs), 'cambio', 'cambios')} · {_gb(sum(f['bytes'] for f in fs))}",
|
||||||
@@ -5775,12 +5858,16 @@ def acciones(raiz):
|
|||||||
if len(marc) < len(fs) else "Todo se mueve dentro de la misma carpeta."),
|
if len(marc) < len(fs) else "Todo se mueve dentro de la misma carpeta."),
|
||||||
ir="plan", paso=clave)
|
ir="plan", paso=clave)
|
||||||
else:
|
else:
|
||||||
añadir(clave=clave, titulo=titulo, estado="hecho", que="Nada que mover.", cifra="", detalle="")
|
añadir(clave=clave, titulo=titulo, estado="hecho", que="Nada que mover.", cifra="", detalle=hecho_det)
|
||||||
|
elif REPLAN.get(raiz):
|
||||||
|
# se acaba de mover algo y el plan se está volviendo a pensar: en un momento se sabe qué queda
|
||||||
|
añadir(clave=clave, titulo=titulo, que=que, cifra="", estado="espera", calculando=True,
|
||||||
|
detalle=(hecho_det + " " if hecho_det else "") + "Comprobando qué queda…", ir="plan", paso=clave)
|
||||||
else:
|
else:
|
||||||
# sin el plan calculado no se sabe si queda algo: se ofrece calcularlo, pero no cuenta como
|
# sin el plan calculado no se sabe si queda algo: se ofrece calcularlo, pero no cuenta como
|
||||||
# pendiente (si no, después de ordenarlo todo la lista seguía diciendo que faltaban dos cosas)
|
# pendiente (si no, después de ordenarlo todo la lista seguía diciendo que faltaban dos cosas)
|
||||||
añadir(clave=clave, titulo=titulo, boton="Calcular", que=que, cifra="", estado="espera",
|
añadir(clave=clave, titulo=titulo, boton="Calcular", que=que, cifra="", estado="espera",
|
||||||
detalle="Hay que calcular el plan para saber si queda algo.", ir="plan", paso=clave)
|
detalle=(hecho_det + " " if hecho_det else "") + "Hay que calcular el plan para saber si queda algo.", ir="plan", paso=clave)
|
||||||
|
|
||||||
# --- 5. carpetas que quedaron vacías ---
|
# --- 5. carpetas que quedaron vacías ---
|
||||||
vac = carpetas_vacias(raiz)
|
vac = carpetas_vacias(raiz)
|
||||||
@@ -5896,7 +5983,8 @@ def acciones(raiz):
|
|||||||
auto = [a["clave"] for a in out if a["estado"] == "toca" and a["clave"] in ("limpiar", "fechas", "resto", "stock", "repetidos")]
|
auto = [a["clave"] for a in out if a["estado"] == "toca" and a["clave"] in ("limpiar", "fechas", "resto", "stock", "repetidos")]
|
||||||
if (esc["reglas"] or 0) < R.VERSION_REGLAS:
|
if (esc["reglas"] or 0) < R.VERSION_REGLAS:
|
||||||
auto = [] # con un inventario viejo no se hace nada «de una vez»: primero se relee
|
auto = [] # con un inventario viejo no se hace nada «de una vez»: primero se relee
|
||||||
return {"raiz": raiz, "cliente": r["cliente"], "acciones": out, "escaneo": dict(esc),
|
return {"raiz": raiz, "cliente": r["cliente"], "acciones": out, "escaneo": dict(esc), "ultimo": ultimo,
|
||||||
|
"calculando": REPLAN.get(raiz) is not None,
|
||||||
"automatico": auto, "asistente": asistente.ESTADO if asistente.ESTADO.get("raiz", "").lower() == raiz.lower() else None}
|
"automatico": auto, "asistente": asistente.ESTADO if asistente.ESTADO.get("raiz", "").lower() == raiz.lower() else None}
|
||||||
|
|
||||||
|
|
||||||
@@ -6060,7 +6148,7 @@ class Manejador(BaseHTTPRequestHandler):
|
|||||||
r = raiz_cfg(q["raiz"])
|
r = raiz_cfg(q["raiz"])
|
||||||
if not r:
|
if not r:
|
||||||
return self._json({"error": "raíz no configurada"}, 400)
|
return self._json({"error": "raíz no configurada"}, 400)
|
||||||
plan = Plan(r).correr()
|
plan = plan_en_marcha(r["ruta"]) or Plan(r).correr()
|
||||||
PLANES[plan["raiz"]] = plan
|
PLANES[plan["raiz"]] = plan
|
||||||
return self._json(plan)
|
return self._json(plan)
|
||||||
if ruta == "/api/bitacora":
|
if ruta == "/api/bitacora":
|
||||||
@@ -6277,6 +6365,10 @@ class Manejador(BaseHTTPRequestHandler):
|
|||||||
raiz = os.path.normpath(d["raiz"])
|
raiz = os.path.normpath(d["raiz"])
|
||||||
plan = PLANES.get(raiz)
|
plan = PLANES.get(raiz)
|
||||||
if not plan:
|
if not plan:
|
||||||
|
# el plan se olvidó porque la carpeta cambió (se movió algo, se volvió a escanear): los
|
||||||
|
# números de fila que trae la interfaz ya no dicen nada; se pide calcularlo otra vez
|
||||||
|
if PLAN_GEN.get(raiz):
|
||||||
|
return self._json({"error": "la carpeta cambió desde que se calculó el plan: hay que volver a calcular «Qué va a pasar»", "recalcular": True}, 409)
|
||||||
return self._json({"error": "primero genera el plan"}, 400)
|
return self._json({"error": "primero genera el plan"}, 400)
|
||||||
ids = set(d.get("ids", []))
|
ids = set(d.get("ids", []))
|
||||||
filas = [f for f in plan["filas"] if f["id"] in ids]
|
filas = [f for f in plan["filas"] if f["id"] in ids]
|
||||||
@@ -6289,7 +6381,8 @@ class Manejador(BaseHTTPRequestHandler):
|
|||||||
return self._json({"error": f"el destino «{ed}» queda fuera de la carpeta del cliente"}, 400)
|
return self._json({"error": f"el destino «{ed}» queda fuera de la carpeta del cliente"}, 400)
|
||||||
f["destino"] = nuevo
|
f["destino"] = nuevo
|
||||||
res = aplicar(raiz, filas)
|
res = aplicar(raiz, filas)
|
||||||
PLANES.pop(raiz, None)
|
if not res.get("movimientos"):
|
||||||
|
olvidar_plan(raiz) # si algo se movió, ya lo olvidó el inventario
|
||||||
return self._json(res)
|
return self._json(res)
|
||||||
if ruta == "/api/deshacer":
|
if ruta == "/api/deshacer":
|
||||||
return self._json(deshacer(d["lote"]))
|
return self._json(deshacer(d["lote"]))
|
||||||
|
|||||||
@@ -228,6 +228,13 @@ def p_plan():
|
|||||||
LOTES = {}
|
LOTES = {}
|
||||||
|
|
||||||
|
|
||||||
|
def acciones_quietas():
|
||||||
|
"""La lista de Inicio cuando el programa ha terminado de volver a pensar el plan tras mover algo."""
|
||||||
|
assert esperar(lambda: not get('/api/carpeta/acciones' + q(raiz=RAIZ))['calculando'], 60), 'el plan no se volvió a pensar'
|
||||||
|
a = get('/api/carpeta/acciones' + q(raiz=RAIZ))
|
||||||
|
return a, {x['clave']: x for x in a['acciones']}
|
||||||
|
|
||||||
|
|
||||||
def p_aplicar():
|
def p_aplicar():
|
||||||
antes = arbol()
|
antes = arbol()
|
||||||
c, r = post('/api/aplicar', {'raiz': RAIZ, 'ids': [f['id'] for f in PLAN['filas'] if f['marcado']]})
|
c, r = post('/api/aplicar', {'raiz': RAIZ, 'ids': [f['id'] for f in PLAN['filas'] if f['marcado']]})
|
||||||
@@ -241,7 +248,17 @@ def p_aplicar():
|
|||||||
assert sorted(os.path.basename(x) for x in antes) == sorted(os.path.basename(x) for x in despues), 'se perdió algún archivo'
|
assert sorted(os.path.basename(x) for x in antes) == sorted(os.path.basename(x) for x in despues), 'se perdió algún archivo'
|
||||||
assert '10- Octubre' in os.listdir(os.path.join(RAIZ, '2026')), 'el mes sigue en minúsculas: %s' % os.listdir(os.path.join(RAIZ, '2026'))
|
assert '10- Octubre' in os.listdir(os.path.join(RAIZ, '2026')), 'el mes sigue en minúsculas: %s' % os.listdir(os.path.join(RAIZ, '2026'))
|
||||||
assert not any('thumbs.db' in x and '_para borrar' not in x for x in despues), 'la basura sigue fuera'
|
assert not any('thumbs.db' in x and '_para borrar' not in x for x in despues), 'la basura sigue fuera'
|
||||||
return '%d movimientos, sin errores, «10- octubre» pasó a «10- Octubre»; el avance acabó en %d de %d' % (r['movimientos'], av['unidades'], av['unidades_total'])
|
# Inicio, sin que nadie vuelva a calcular: lo hecho sale como hecho (y cuándo), no como pendiente
|
||||||
|
a, por = acciones_quietas()
|
||||||
|
assert por['resto']['estado'] == 'hecho' and por['resto']['detalle'].startswith('Hecho hoy'), por['resto']
|
||||||
|
assert por['fechas']['estado'] != 'toca', por['fechas']
|
||||||
|
assert por['limpiar']['estado'] == 'hecho' and 'ya en' in por['limpiar']['detalle'], por['limpiar']
|
||||||
|
assert a['ultimo'] and a['ultimo']['lote'] == r['lote'], a['ultimo']
|
||||||
|
e = get('/api/carpeta/estado' + q(raiz=RAIZ))
|
||||||
|
assert e['plan'] and e['plan']['marcadas'] == 0, e['plan']
|
||||||
|
fr = e['diagnostico']['frases']
|
||||||
|
assert any('ya apartados' in f for f in fr) and not any('de basura de sistema' in f for f in fr), fr
|
||||||
|
return '%d movimientos, sin errores, «10- octubre» pasó a «10- Octubre»; el avance acabó en %d de %d; Inicio lo da por hecho' % (r['movimientos'], av['unidades'], av['unidades_total'])
|
||||||
|
|
||||||
|
|
||||||
def p_deshacer():
|
def p_deshacer():
|
||||||
@@ -249,7 +266,11 @@ def p_deshacer():
|
|||||||
assert c == 200 and not r['errores'], r
|
assert c == 200 and not r['errores'], r
|
||||||
assert '10- octubre' in os.listdir(os.path.join(RAIZ, '2026')), 'deshacer no devolvió las minúsculas: %s' % os.listdir(os.path.join(RAIZ, '2026'))
|
assert '10- octubre' in os.listdir(os.path.join(RAIZ, '2026')), 'deshacer no devolvió las minúsculas: %s' % os.listdir(os.path.join(RAIZ, '2026'))
|
||||||
assert os.path.isfile(os.path.join(RAIZ, '2026', '03- Marzo', 'Grabacion', 'Thumbs.db')), 'la basura no volvió'
|
assert os.path.isfile(os.path.join(RAIZ, '2026', '03- Marzo', 'Grabacion', 'Thumbs.db')), 'la basura no volvió'
|
||||||
return '%d movimientos deshechos' % r['deshechos']
|
# y en Inicio vuelve a estar todo por hacer (antes se quedaba con «lo seguro ya está hecho»)
|
||||||
|
a, por = acciones_quietas()
|
||||||
|
assert all(por[k]['estado'] == 'toca' for k in ('limpiar', 'fechas', 'resto')), {k: por[k]['estado'] for k in ('limpiar', 'fechas', 'resto')}
|
||||||
|
assert not a['ultimo'], a['ultimo']
|
||||||
|
return '%d movimientos deshechos, e Inicio vuelve a ofrecerlo todo' % r['deshechos']
|
||||||
|
|
||||||
|
|
||||||
def p_reaplicar():
|
def p_reaplicar():
|
||||||
@@ -448,7 +469,10 @@ def p_asistente():
|
|||||||
assert esperar(lambda: not get('/api/estado')['asistente'].get('activo'), 120)
|
assert esperar(lambda: not get('/api/estado')['asistente'].get('activo'), 120)
|
||||||
a = get('/api/estado')['asistente']
|
a = get('/api/estado')['asistente']
|
||||||
assert not a.get('error'), a.get('error')
|
assert not a.get('error'), a.get('error')
|
||||||
return 'pasos + «hacer las N de una vez» terminan sin error'
|
# después, Inicio no vuelve a ofrecer lo que acaba de hacer
|
||||||
|
_, por = acciones_quietas()
|
||||||
|
assert por['fechas']['estado'] != 'toca' and por['resto']['estado'] != 'toca', {k: por[k] for k in ('fechas', 'resto')}
|
||||||
|
return 'pasos + «hacer las N de una vez» terminan sin error, e Inicio no lo vuelve a ofrecer'
|
||||||
|
|
||||||
|
|
||||||
def p_pieza():
|
def p_pieza():
|
||||||
|
|||||||
@@ -1137,8 +1137,8 @@ function desconectar(clave){
|
|||||||
$('#btnDesconectar').onclick = () => desconectar(raizActual());
|
$('#btnDesconectar').onclick = () => desconectar(raizActual());
|
||||||
|
|
||||||
// ---------------- carpeta ----------------
|
// ---------------- 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 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'};
|
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(){
|
function pintarPortada(){
|
||||||
// las carpetas que el programa ya conoce, para entrar en una con un clic; con la última vez que se
|
// 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.
|
// 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 corriendo = asis && asis.activo;
|
||||||
const cab = corriendo
|
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">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').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); });
|
$$('#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');
|
const bt = $('#btnHacerTodo');
|
||||||
if (bt) bt.onclick = () => modal('Hacer todo lo seguro de una vez',
|
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>
|
`<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 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;
|
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'}
|
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: ''});
|
: 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 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');
|
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[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[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 (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[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 === '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')); }
|
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
|
// 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);
|
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;
|
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);
|
clearInterval(reloj);
|
||||||
PLAN = null; MAPA = null;
|
PLAN = null; MAPA = null;
|
||||||
pintarAvance({activo: false, res, seg: (Date.now() - t0) / 1000, pedidas: m.length, bytes});
|
pintarAvance({activo: false, res, seg: (Date.now() - t0) / 1000, pedidas: m.length, bytes});
|
||||||
|
|||||||
Referencia en una nueva incidencia
Block a user