Inicio como lista con ✓: lo hecho se queda en su sitio, con cuándo y qué
Al terminar algo (elegir el tipo de trabajo, apartar stock, juntar repetidos, la bandeja, convertir) la ficha desaparecía sin decir nada y no se sabía por dónde se iba. Ahora cada paso hecho se queda con su ✓ y «Hecho hoy a las 07:41: …», arriba se cuentan los hechos, y lo que acaba de pasar a hecho se ilumina. Aplicar el plan apunta qué pasos entraron; elegir el tipo y nombrar una carpeta quedan en la bitácora. «Basura y proxies» deja de pedirse cuando lo que queda es lo de «No tocar» o lo que va sin marcar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Este commit está contenido en:
102
organizador.py
102
organizador.py
@@ -42,7 +42,7 @@ import actualizador
|
||||
asistente.O = sys.modules[__name__]
|
||||
actualizador.O = sys.modules[__name__]
|
||||
|
||||
VERSION = "3.7.0"
|
||||
VERSION = "3.7.1"
|
||||
|
||||
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
|
||||
@@ -2162,7 +2162,9 @@ def aplicar(raiz, filas):
|
||||
if os.path.normpath(base).lower() != os.path.normpath(raiz).lower():
|
||||
quitar_vacias(base, raiz)
|
||||
actualizar_inventario(movidos)
|
||||
bitacora({"lote": lote, "op": "fin", "raiz": raiz, "filas": hechas, "movimientos": len(movidos), "errores": errores})
|
||||
# qué pasos del plan entraron (limpiar, fechas, resto, banco): Inicio les pone su ✓ con esto
|
||||
pasos = dict(Counter(f.get("paso") or "resto" for f in filas))
|
||||
bitacora({"lote": lote, "op": "fin", "raiz": raiz, "filas": hechas, "movimientos": len(movidos), "errores": errores, "pasos": pasos})
|
||||
return {"lote": lote, "filas": hechas, "movimientos": len(movidos), "errores": errores}
|
||||
|
||||
|
||||
@@ -6011,6 +6013,62 @@ def _ultimo_orden(raiz):
|
||||
return None
|
||||
|
||||
|
||||
def _nombre_tipo(t):
|
||||
return (R.TIPOS_CLIENTE.get(t) or t or "").split(" · ")[0]
|
||||
|
||||
|
||||
# el orden de la lista de Inicio: lo hecho se queda en su sitio, no se va al final ni desaparece
|
||||
_ORDEN_ACCIONES = ["escanear", "tipo", "entrada", "limpiar", "nombrar", "fechas", "resto", "banco", "vacias", "stock",
|
||||
"repetidos", "convertir", "respaldar"]
|
||||
|
||||
|
||||
def _historial_carpeta(raiz):
|
||||
"""Lo que ya se hizo en esta carpeta, por acción de Inicio: la última vez y qué fue. Con esto cada paso
|
||||
hecho sale con su ✓ en vez de desaparecer de la lista (antes el stock, los repetidos, la bandeja, el
|
||||
tipo de trabajo o la conversión, al terminarlos, simplemente se iban, y no se sabía por dónde se iba)."""
|
||||
ent = leer_bitacora(20000)
|
||||
rl = raiz.lower()
|
||||
deshechos = {e.get("de_lote") for e in ent if e.get("op") == "deshacer"}
|
||||
h = {}
|
||||
|
||||
def pon(clave, ts, texto, n=1):
|
||||
x = h.setdefault(clave, {"ts": 0, "texto": "", "veces": 0})
|
||||
x["veces"] += n
|
||||
if (ts or 0) >= x["ts"]:
|
||||
x["ts"], x["texto"] = ts or 0, texto
|
||||
for e in ent:
|
||||
op, l, ts = e.get("op"), e.get("lote") or "", e.get("ts")
|
||||
if (e.get("raiz") or "").lower() != rl and rl not in [x.lower() for x in (e.get("raices") or [])]:
|
||||
continue
|
||||
if l and l in deshechos:
|
||||
continue
|
||||
if op == "tipo":
|
||||
pon("tipo", ts, f"elegiste «{_nombre_tipo(e.get('tipo'))}»")
|
||||
elif op == "nombrar":
|
||||
pon("nombrar", ts, f"dijiste qué es «{e.get('ejemplo') or e.get('token')}»")
|
||||
elif op == "respaldo":
|
||||
pon("respaldar", ts, f"{_plural(e.get('copiados') or 0, 'archivo copiado', 'archivos copiados')} a {e.get('a') or 'el respaldo'}")
|
||||
elif op == "tanda":
|
||||
pon("convertir", ts, _plural(e.get("clips") or 0, "clip convertido", "clips convertidos"))
|
||||
elif op == "fin":
|
||||
m = e.get("movimientos") or 0
|
||||
if l.startswith("stock-"):
|
||||
pon("stock", ts, _plural(m, "archivo apartado", "archivos apartados") + " a «_Para borrar\\stock»")
|
||||
elif l.startswith("dup-"):
|
||||
pon("repetidos", ts, _plural(m, "copia juntada", "copias juntadas") + " en «Duplicados»")
|
||||
elif l.startswith("vacias-"):
|
||||
pon("vacias", ts, _plural(m, "carpeta vacía borrada", "carpetas vacías borradas"))
|
||||
elif l.startswith("entrada-"):
|
||||
pon("entrada", ts, _plural(m, "archivo traído", "archivos traídos"))
|
||||
elif l.startswith(("optimizar", "descartados-")):
|
||||
pon("convertir", ts, _plural(m, "original reemplazado", "originales reemplazados"))
|
||||
elif not l.startswith(("pieza-", "colores-", "tanda-", "respaldo")):
|
||||
# aplicar el plan: los pasos que entraron (los lotes de antes de la 3.7.1 no lo apuntaban)
|
||||
for p, n in (e.get("pasos") or {"limpiar": 0, "fechas": 0, "resto": 0}).items():
|
||||
pon(p, ts, _plural(n, "cambio aplicado", "cambios aplicados") if n else _plural(m, "movimiento", "movimientos"))
|
||||
return h
|
||||
|
||||
|
||||
def _cuando(ts):
|
||||
"""«hoy a las 05:02», «ayer a las 23:10», «el 3 de octubre a las 11:40»."""
|
||||
if not ts:
|
||||
@@ -6086,7 +6144,18 @@ def acciones(raiz):
|
||||
fila = con.execute("SELECT COUNT(*) n, COALESCE(SUM(bytes),0) b FROM archivos WHERE raiz = ? "
|
||||
"AND (es_basura = 1 OR es_proxy = 1)" + en_papelera, [raiz] + args_pap).fetchone()
|
||||
vieja = con.execute("SELECT n, bytes FROM carpetas WHERE ruta = ?", (os.path.join(raiz, R.PAPELERAS[1]),)).fetchone()
|
||||
if fila["n"]:
|
||||
plan_l = PLANES.get(raiz)
|
||||
marc_l = [f for f in plan_l["filas"] if f.get("paso") == "limpiar" and f.get("marcado")] if plan_l else None
|
||||
sin_l = [f for f in plan_l["filas"] if f.get("paso") == "limpiar" and not f.get("marcado")] if plan_l else []
|
||||
if fila["n"] and marc_l is not None and not marc_l:
|
||||
# lo que queda no lo aparta la app sola (está en «No tocar», o es un proxy sin su original): está hecho
|
||||
# lo seguro; queda mirar
|
||||
añadir(clave="limpiar", titulo="Basura y proxies", boton="Ver", estado="espera",
|
||||
que="Lo seguro ya está apartado. Lo que queda no se aparta sin que lo marques tú.",
|
||||
cifra=f"{_plural(len(sin_l), 'cambio sin marcar', 'cambios sin marcar')} · {_gb(sum(f['bytes'] for f in sin_l))}",
|
||||
detalle=("Hay proxies sin su original: pueden ser la única copia." if any("OJO" in f["motivo"] for f in sin_l) else "")
|
||||
+ _no_tocar_dentro(con, raiz), ir="plan", paso="limpiar")
|
||||
elif fila["n"]:
|
||||
añadir(clave="limpiar", titulo="Apartar basura y proxies", boton="Ver y apartar",
|
||||
que="Archivos de sistema y proxies, que Resolve y Premiere regeneran solos.",
|
||||
cifra=f"{_plural(fila['n'], 'archivo', 'archivos')} · {_gb(fila['b'])}",
|
||||
@@ -6114,7 +6183,7 @@ def acciones(raiz):
|
||||
# sin sumar bytes: una carpeta sin reconocer dentro de otra sin reconocer contaría dos veces
|
||||
# el mismo material, y salían totales mayores que el propio disco
|
||||
con_sug = [t for t in sin if t.get("sugerencia")]
|
||||
añadir(clave="nombrar", titulo="Decir qué son " + _plural(len(sin), "carpeta", "carpetas"), boton="Nombrarlas" if len(sin) > 1 else "Nombrarla",
|
||||
añadir(clave="nombrar", titulo=("Decir qué son " + _plural(len(sin), "carpeta", "carpetas")) if len(sin) > 1 else "Decir qué es una carpeta", boton="Nombrarlas" if len(sin) > 1 else "Nombrarla",
|
||||
que="Sin esto la app no las mueve: se quedan donde están.",
|
||||
cifra=_plural(len(sin), "carpeta", "carpetas") + (f" · {len(seguras)} de un clic" if seguras else ""),
|
||||
detalle=(f"La app propone qué son {len(con_sug)} de ellas mirando lo que tienen dentro"
|
||||
@@ -6298,6 +6367,27 @@ def acciones(raiz):
|
||||
ir="respaldar")
|
||||
|
||||
con.close()
|
||||
hist = _historial_carpeta(raiz)
|
||||
hay = {a["clave"] for a in out}
|
||||
for clave, titulo, que in (("tipo", "Cómo se ordena esta carpeta", f"Como «{_nombre_tipo(r.get('tipo', 'contenido'))}», igual que su cliente."),
|
||||
("entrada", "Bandeja de entrada", "Vacía: lo que entró ya está repartido."),
|
||||
("stock", "Stock de Envato viejo", "No queda stock viejo por apartar."),
|
||||
("repetidos", "Material repetido", "No quedan copias de más por juntar."),
|
||||
("convertir", "Clips de cámara", "No quedan clips que compense convertir.")):
|
||||
if clave in hist and clave not in hay:
|
||||
añadir(clave=clave, titulo=titulo, estado="hecho", que=que, cifra="", detalle="")
|
||||
for a in out:
|
||||
x = hist.get(a["clave"])
|
||||
if not x or "Hecho " in (a.get("detalle") or ""):
|
||||
continue
|
||||
cuando = _cuando(x["ts"])
|
||||
if a["clave"] == "nombrar" and x["veces"] > 1:
|
||||
a["hecho"] = f"Ya nombraste {x['veces']}; la última {cuando}."
|
||||
elif a["estado"] == "hecho":
|
||||
a["hecho"] = f"Hecho {cuando}: {x['texto']}."
|
||||
else:
|
||||
a["hecho"] = f"Última vez {cuando}: {x['texto']}."
|
||||
out.sort(key=lambda a: _ORDEN_ACCIONES.index(a["clave"]) if a["clave"] in _ORDEN_ACCIONES else 99)
|
||||
# «lo seguro» son las acciones que se pueden hacer sin elegir nada: no entran convertir ni respaldar
|
||||
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:
|
||||
@@ -6748,9 +6838,11 @@ class Manejador(BaseHTTPRequestHandler):
|
||||
tipo = d.get("tipo")
|
||||
if not rc or tipo not in PLANTILLAS:
|
||||
return self._json({"error": "carpeta o tipo de trabajo desconocido"}, 400)
|
||||
antes = rc.get("tipo", "contenido")
|
||||
rc["tipo"] = tipo
|
||||
CONFIG.setdefault("clientes", {}).setdefault(rc["cliente"], {})["tipo"] = tipo
|
||||
guardar_json(CONFIG_PATH, CONFIG)
|
||||
bitacora({"op": "tipo", "raiz": k, "tipo": tipo, "antes": antes})
|
||||
olvidar_plan(k)
|
||||
return self._json({"ok": True, "tipo": tipo})
|
||||
if ruta == "/api/clientes":
|
||||
@@ -6785,6 +6877,8 @@ class Manejador(BaseHTTPRequestHandler):
|
||||
DICC["carpetas"][tok] = cub
|
||||
DICC["aprendido"].append({"ts": time.time(), "token": tok, "cubeta": cub})
|
||||
guardar_json(DICC_PATH, DICC)
|
||||
if cub and d.get("raiz"): # para el ✓ de «Decir qué son» en Inicio
|
||||
bitacora({"op": "nombrar", "raiz": _clave(d["raiz"]), "token": tok, "cubeta": cub, "ejemplo": d.get("ejemplo") or tok})
|
||||
return self._json({"ok": True})
|
||||
if ruta == "/api/regla":
|
||||
with LOCK:
|
||||
|
||||
Referencia en una nueva incidencia
Block a user