3.5.0: «Qué va a pasar» se explica con una carpeta, y una barra dice por dónde va

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 todo?», y quería una barra para saber
por dónde va y si hace falta algo.

- Una frase arriba y un solo botón, «Aplicar los N cambios»: un clic lo hace todo, en orden.
- Tres fichas, una por paso, con cambios, peso y cuántos quedan sin marcar; se pulsa una y se ve.
- Un ejemplo animado: la fila más representativa del paso como dos ramas de carpetas, «Ahora» y
  «Después», y lo que se mueve vuela de una a otra; debajo, la frase y el porqué. «Otro ejemplo»
  recorre las filas.
- La lista, corta: de dónde → a dónde y la cuenta; el motivo solo con «ver por qué». Lo que no va
  solo queda aparte y contado (cosméticos «01 - Celular» → «01- Celular», y dudosos), con «marcar
  los cosméticos».
- Al aplicar, el servidor cuenta archivo a archivo (/api/aplicando) y la interfaz lo sondea: barra,
  qué se mueve ahora, y al acabar qué se hizo, qué falló y qué queda sin hacer.
- Los avisos van plegados bajo «N cosas que conviene mirar antes de aplicar».

Pruebas: probar 19, prueba_orden 33, prueba_funcional 30 (comprueba que el avance acaba en tantas
de tantas), prueba_ventana 18.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Este commit está contenido en:
Labstream
2026-10-10 10:59:16 -05:00
padre 50da026373
commit 22bf2a8657
Se han modificado 4 ficheros con 303 adiciones y 81 borrados

Ver fichero

@@ -42,7 +42,7 @@ import actualizador
asistente.O = sys.modules[__name__]
actualizador.O = sys.modules[__name__]
VERSION = "3.4.0"
VERSION = "3.5.0"
BASE = os.path.dirname(os.path.abspath(sys.executable if getattr(sys, "frozen", False) else __file__))
# Empaquetado: el programa corre con el motor que viaja en su propia carpeta. OrganizadorLabstream.exe es
@@ -1859,7 +1859,11 @@ def mover(origen, destino, lote, movidos):
movidos.append((origen, destino))
APLICANDO = {"hechas": 0, "total": 0, "actual": ""}
# hechas/total cuentan filas del plan (es lo que miran «en marcha» y la barra de tareas); unidades cuenta
# archivo a archivo dentro de las filas de archivos sueltos, para que la barra avance suave en una fila de
# 400 clips; actual/archivo dicen qué se está moviendo; lote/fin, qué tanda fue y cuándo acabó.
APLICANDO = {"hechas": 0, "total": 0, "actual": "", "archivo": "", "unidades": 0, "unidades_total": 0,
"movidos": 0, "fallos": 0, "lote": "", "inicio": 0, "fin": 0}
def aplicar(raiz, filas):
@@ -1868,9 +1872,12 @@ def aplicar(raiz, filas):
errores = []
hechas = 0
filas = sorted(filas, key=lambda f: (0 if f.get("archivos") else 1, -len(os.path.normpath(f["origen"]).split(os.sep)), f["origen"].lower()))
APLICANDO.update(hechas=0, total=len(filas), actual="", raiz=_clave(raiz))
unidades_total = sum(len(f["archivos"]) if f.get("archivos") else 1 for f in filas)
fallos = 0
APLICANDO.update(hechas=0, total=len(filas), actual="", archivo="", raiz=_clave(raiz), unidades=0,
unidades_total=unidades_total, movidos=0, fallos=0, lote=lote, inicio=time.time(), fin=0)
for f in filas:
APLICANDO.update(hechas=hechas, actual=f.get("origen_rel") or "")
APLICANDO.update(hechas=hechas, actual=f.get("origen_rel") or "", archivo="")
if f.get("archivos"):
# cada archivo va por su cuenta: antes, un clip abierto en Resolve cortaba el bucle y los
# ciento y pico restantes de esa misma fila no se intentaban, mientras el resumen decía
@@ -1884,6 +1891,7 @@ def aplicar(raiz, filas):
errores.append(f"{f.get('origen_rel')}: no se pudo crear la carpeta destino: {ex}")
continue
for a in f["archivos"]:
APLICANDO.update(unidades=APLICANDO["unidades"] + 1, archivo=os.path.basename(a))
if not os.path.exists(a):
continue
try:
@@ -1892,6 +1900,8 @@ def aplicar(raiz, filas):
fallos_fila += 1
if fallos_fila <= 5:
errores.append(f"{os.path.basename(a)}: {ex}")
APLICANDO.update(movidos=len(movidos), fallos=fallos + fallos_fila)
fallos += fallos_fila
if fallos_fila > 5:
errores.append(f"{f.get('origen_rel')}: y otros {fallos_fila - 5} archivos que tampoco se pudieron mover")
if not fallos_fila:
@@ -1902,10 +1912,15 @@ def aplicar(raiz, filas):
mover(f["origen"], f["destino"], lote, movidos)
hechas += 1
else:
fallos += 1
errores.append(f"{f.get('origen_rel')}: ya no está donde decía el plan (¿se movió fuera de la app?)")
except Exception as ex: # noqa
fallos += 1
errores.append(f"{f.get('origen_rel')}: {ex}")
APLICANDO.update(hechas=len(filas), actual="") # terminado (con o sin errores): la barra de tareas deja de mostrar «Moviendo»
APLICANDO.update(unidades=APLICANDO["unidades"] + 1, movidos=len(movidos), fallos=fallos)
# terminado (con o sin errores): la barra de tareas deja de mostrar «Moviendo»
APLICANDO.update(hechas=len(filas), actual="", archivo="", unidades=unidades_total, movidos=len(movidos),
fallos=fallos, fin=time.time())
# carpetas que quedaron vacías al vaciarlas (p. ej. "Grabacion" tras repartir sus archivos)
for f in filas:
base = f["origen"] if f.get("archivos") else os.path.dirname(f["origen"])
@@ -5583,6 +5598,8 @@ class Manejador(BaseHTTPRequestHandler):
return
if ruta == "/api/estado":
return self._json(estado_general())
if ruta == "/api/aplicando": # la barra de «Aplicar», sondeada mientras corre
return self._json(dict(APLICANDO))
if ruta == "/api/soltado":
r = soltado(q.get("nombre", ""))
return self._json({"ruta": r, "es_archivo": bool(r) and os.path.isfile(r)})
@@ -6225,7 +6242,7 @@ def avance():
if AUTO.get("activo"):
return AUTO["hechas"] / AUTO["total"] if AUTO.get("total") else -1
if APLICANDO.get("total") and APLICANDO.get("hechas", 0) < APLICANDO["total"]:
return APLICANDO["hechas"] / APLICANDO["total"]
return APLICANDO["unidades"] / APLICANDO["unidades_total"] if APLICANDO.get("unidades_total") else APLICANDO["hechas"] / APLICANDO["total"]
if asistente.ESTADO.get("activo"):
return asistente.ESTADO["pct"] / 100 if asistente.ESTADO.get("total") else -1
for a in list(ANALISIS.values()):