Las cuatro funciones que eligió Munka tras la 3.2.0: - Nueva pieza: crea «NN- Título» en su mes con el número que toca y sus subcarpetas; si la pieza ya existe, lo dice. Se deshace mientras siga vacía. - Bandeja de entrada: lo que se deje en _Entrada se reparte por mes, día de rodaje y cámara. «Traer material» copia una tarjeta comprobando cada archivo (se vuelve a leer y se compara la huella), conserva la fecha del rodaje y no toca el origen. - Versiones del mismo clip: dos clips de cámara que se llaman igual, duran lo mismo y tienen la misma forma son el mismo clip; se queda el de más imagen y el otro va a Duplicados. - Orden automático, apagado de fábrica: una tarea del Programador de tareas de Windows lanza «automatico», que lee todas las carpetas y hace solo lo seguro. No toca una carpeta que no se pueda leer entera ni una cuyo plan mueva más de 3.000 archivos, y si el programa ya está abierto se lo pide a él. Desinstalar quita la tarea. De paso: una ruta que empieza por guion bajo ya no pierde el guion al ir a la papelera. Pruebas: prueba_orden.py 24, probar.py 19, prueba_funcional.py 29, prueba_ventana.py 18 desde el código y 17 con el paquete. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
609 líneas
39 KiB
Python
609 líneas
39 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Pruebas del motor de orden. Cada caso reproduce en pequeño algo medido en una carpeta de verdad (la
|
|
de Andrea Galeano, el 4-oct-2026): monta un cliente de ensayo en una carpeta temporal, lo escanea, pide
|
|
el plan y, donde importa, lo aplica y mira cómo queda el disco.
|
|
|
|
py -3.12 prueba_orden.py
|
|
"""
|
|
import os
|
|
import sys
|
|
import json
|
|
import time
|
|
import shutil
|
|
import tempfile
|
|
import unittest
|
|
|
|
AQUI = os.path.dirname(os.path.abspath(__file__))
|
|
TMP = tempfile.mkdtemp(prefix="organilab-motor-")
|
|
APP = os.path.join(TMP, "app")
|
|
os.makedirs(os.path.join(APP, "datos"))
|
|
for _f in ("organizador.py", "reglas.py", "asistente.py", "actualizador.py", "firma.py", "instalar.py", "ventana.py",
|
|
"resolve_bridge.py"):
|
|
shutil.copy(os.path.join(AQUI, _f), APP)
|
|
# un diccionario como el que dejaba la 3.1.1: con una carpeta de la propia app aprendida como grabación
|
|
with open(os.path.join(APP, "datos", "diccionario.json"), "w", encoding="utf-8") as _fh:
|
|
json.dump({"carpetas": {"basura": "GRABACION", "mi carpeta rara": "FOTOS"}, "reglas": [], "aprendido": []}, _fh)
|
|
sys.path.insert(0, APP)
|
|
import organizador as O # noqa: E402 (tiene que importarse desde la copia: sus datos van junto a él)
|
|
import asistente # noqa: E402
|
|
R = O.R
|
|
S = os.sep
|
|
|
|
|
|
def cliente(nombre, tipo="contenido"):
|
|
raiz = os.path.join(TMP, nombre)
|
|
os.makedirs(raiz)
|
|
O.CONFIG["raices"].append({"ruta": raiz, "cliente": nombre, "tipo": tipo})
|
|
return raiz
|
|
|
|
|
|
def mk(raiz, rel, y, m, d, tam=2048):
|
|
"""Crea un archivo con esa fecha de modificación (a mediodía)."""
|
|
p = os.path.join(raiz, rel.replace("/", S))
|
|
os.makedirs(os.path.dirname(p), exist_ok=True)
|
|
with open(p, "wb") as fh:
|
|
fh.write(os.urandom(tam))
|
|
t = time.mktime(time.struct_time((y, m, d, 12, 0, 0, 0, 0, -1)))
|
|
os.utime(p, (t, t))
|
|
return p
|
|
|
|
|
|
def plan(raiz):
|
|
O.escanear(raiz)
|
|
prog = O.PROGRESO[os.path.normpath(raiz)]
|
|
assert prog["estado"] == "listo", prog
|
|
return O.Plan(O.raiz_cfg(raiz)).correr()
|
|
|
|
|
|
def aplicar(raiz, p):
|
|
r = O.aplicar(raiz, [f for f in p["filas"] if f["marcado"]])
|
|
assert not r["errores"], r["errores"]
|
|
return r
|
|
|
|
|
|
def arbol(raiz):
|
|
return sorted(os.path.relpath(os.path.join(b, f), raiz) for b, _, fs in os.walk(raiz) for f in fs)
|
|
|
|
|
|
def nombres(raiz):
|
|
return sorted(os.path.basename(x) for x in arbol(raiz))
|
|
|
|
|
|
def rel(*partes):
|
|
return S.join(partes)
|
|
|
|
|
|
class Reglas(unittest.TestCase):
|
|
def test_un_clip_convertido_sigue_siendo_de_su_camara(self):
|
|
"""166 clips ya convertidos habían perdido su cámara: pasaban por exportados."""
|
|
self.assertEqual(R.firma_archivo("C0001_10bit.mp4"), "SONY")
|
|
self.assertEqual(R.firma_archivo("20251111_Labstream5320_10bit.mp4"), "SONY")
|
|
self.assertEqual(R.firma_archivo("A002_03110416_C042_10bit_10bit.mp4"), "BM_PROXY")
|
|
self.assertEqual(R.firma_archivo("A006_06010242_C114 (2)_10bit.mp4"), "BM_PROXY")
|
|
self.assertIsNone(R.firma_archivo("INMODE_10bit.mp4")) # no era de cámara: sigue sin serlo
|
|
self.assertEqual(R.rol_archivo("C0001_10bit.mp4", ".mp4", R.firma_archivo("C0001_10bit.mp4")), "camara_video")
|
|
self.assertTrue(R.es_conversion("C0001_10bit.mp4"))
|
|
self.assertTrue(R.es_conversion("C0001_10bit_10bit.mp4"))
|
|
self.assertFalse(R.es_conversion("C0001.MP4"))
|
|
self.assertEqual(R.sin_conversion("C0001_10bit_10bit.mp4"), "C0001.mp4")
|
|
self.assertEqual(R.raiz_derivado("C0001_10bit_10bit.mp4"), "C0001")
|
|
self.assertIsNone(R.raiz_derivado("C0001.MP4"))
|
|
|
|
def test_lo_aprendido_vale_por_el_nombre_entero(self):
|
|
"""Confirmar que «Videos» era una grabación convertía en grabación a «videos ia» y a «01- Orden»."""
|
|
d = dict(R.DICCIONARIO_SEMILLA, videos="GRABACION", orden="GRABACION")
|
|
self.assertEqual(R.cubeta_carpeta("Videos", d)[0], "GRABACION")
|
|
self.assertEqual(R.cubeta_carpeta("videos ia", d)[0], "IA") # la palabra de fábrica sí vale
|
|
self.assertIsNone(R.cubeta_carpeta("01- Orden", d)[0])
|
|
self.assertIsNone(R.cubeta_carpeta("Orden nuevo de cosas", d)[0])
|
|
self.assertEqual(R.cubeta_carpeta("04_Revisar-Borrar", R.DICCIONARIO_SEMILLA)[0], "REVISAR")
|
|
|
|
def test_fecha_de_blackmagic(self):
|
|
"""El nombre trae mes y día. Si el archivo no se ha tocado desde el rodaje, el año es seguro."""
|
|
intacto = time.mktime(time.struct_time((2025, 12, 9, 18, 30, 0, 0, 0, -1)))
|
|
reescrito = time.mktime(time.struct_time((2026, 7, 1, 10, 0, 0, 0, 0, -1)))
|
|
self.assertEqual(R.fecha_de_nombre("A014_12091814_C001.braw", intacto), ("2025-12-09", "nombre"))
|
|
self.assertEqual(R.fecha_de_nombre("A014_12091814_C001.mp4", reescrito), ("2025-12-09", "nombre+mtime"))
|
|
self.assertEqual(R.clave_rodaje("A014_12091814_C001_10bit.mp4"), "A014_1209")
|
|
self.assertIsNone(R.clave_rodaje("C0001.MP4"))
|
|
|
|
def test_carpetas_de_tarjeta(self):
|
|
for n in ("DCIM", "100MEDIA", "PRIVATE", "M4ROOT", "CLIP", "A001_0305XY", "100MSDCF"):
|
|
self.assertTrue(R.es_carpeta_de_tarjeta(n), n)
|
|
for n in ("Tomas consultorio", "Entrevista", "Maquinas"):
|
|
self.assertFalse(R.es_carpeta_de_tarjeta(n), n)
|
|
|
|
def test_rodaje_de_una_misma_tarjeta(self):
|
|
"""Los clips de la misma tarjeta y día comparten año, y la madrugada cuenta como la noche anterior."""
|
|
def fila(nombre, fecha, origen):
|
|
return ("x" + S + nombre, "x", "x", nombre, ".mp4", 1, 0.0, "camara_video", "BM_PROXY", "", None, None,
|
|
fecha, origen, 0, 0, 0)
|
|
filas = [fila("A002_03020727_C001.mp4", "2025-03-02", "nombre+mtime"), # reescrito en dic-2025
|
|
fila("A002_03020727_C002.mp4", "2026-03-02", "nombre+mtime"), # reescrito en jul-2026
|
|
fila("A014_12091814_C001.braw", "2025-12-09", "nombre"), # intacto
|
|
fila("A014_12091814_C001.mp4", "2025-12-09", "nombre+mtime"),
|
|
fila("A006_05312107_C001.mp4", "2025-05-31", "nombre+mtime"), # empieza de noche…
|
|
fila("A006_06010000_C002.mp4", "2025-06-01", "nombre+mtime"), # …y sigue de madrugada
|
|
fila("A003_06010400_C019.mp4", "2025-06-01", "nombre+mtime"), # otra tarjeta, misma madrugada
|
|
fila("A009_06011500_C001.mp4", "2025-06-01", "nombre+mtime")] # esa tarde: otro rodaje
|
|
O._fechas_de_rodaje(filas)
|
|
por = {f[3]: (f[12], f[13]) for f in filas}
|
|
self.assertEqual(por["A002_03020727_C002.mp4"], ("2025-03-02", "nombre+mtime")) # el año más antiguo del grupo
|
|
self.assertEqual(por["A014_12091814_C001.mp4"], ("2025-12-09", "nombre")) # el año seguro se contagia
|
|
self.assertEqual(por["A006_06010000_C002.mp4"][0], "2025-05-31")
|
|
self.assertEqual(por["A003_06010400_C019.mp4"][0], "2025-05-31")
|
|
self.assertEqual(por["A009_06011500_C001.mp4"][0], "2025-06-01")
|
|
|
|
|
|
class Arranque(unittest.TestCase):
|
|
def test_se_olvida_lo_que_era_de_la_app(self):
|
|
"""«basura» se aprendió como grabación porque «Por nombrar» preguntaba por _Para borrar\\basura."""
|
|
self.assertNotIn("basura", O.DICC["carpetas"])
|
|
self.assertEqual(O.DICC["carpetas"].get("mi carpeta rara"), "FOTOS") # lo del cliente se conserva
|
|
with open(O.DICC_PATH, encoding="utf-8") as fh:
|
|
self.assertNotIn("basura", json.load(fh)["carpetas"])
|
|
|
|
|
|
class Motor(unittest.TestCase):
|
|
def aprende(self, token, cubeta):
|
|
O.DICC["carpetas"][token] = cubeta
|
|
self.addCleanup(lambda: O.DICC["carpetas"].pop(token, None))
|
|
|
|
def test_un_cajon_no_se_desarma(self):
|
|
"""«Orden» (una organización anterior entera) se aprendió como grabación y el plan la iba a repartir
|
|
por cámara y por mes, proyectos incluidos. Ahora se abre, y cada cosa va a su sitio."""
|
|
raiz = cliente("Cajon")
|
|
mk(raiz, "2026/05- Mayo/03- El cuello/portada vieja.png", 2026, 5, 10)
|
|
mk(raiz, "Orden/01_Grabaciones/Tomas consultorio/20251111_Labstream5320.MP4", 2025, 11, 11)
|
|
mk(raiz, "Orden/01_Grabaciones/Tomas consultorio/20251111_Labstream5321_10bit.mp4", 2026, 1, 10)
|
|
mk(raiz, "Orden/01_Grabaciones/Tomas consultorio/Duplicados/20251111_Labstream5322.MP4", 2025, 11, 11)
|
|
mk(raiz, "Orden/01_Grabaciones/Videos Maquinas/A006_05312107_C001.mp4", 2026, 1, 10)
|
|
mk(raiz, "Orden/01_Grabaciones/Videos Maquinas/A006_06010000_C002.mp4", 2026, 1, 10)
|
|
mk(raiz, "Orden/01_Grabaciones/Videos Maquinas/A006_06010030_C003.braw", 2025, 6, 1)
|
|
mk(raiz, "Orden/02_Proyectos/Mayo/03 - El cuello/Exportado/final.mp4", 2026, 5, 20)
|
|
mk(raiz, "Orden/02_Proyectos/Mayo/03 - El cuello/Portada/p.png", 2026, 5, 20)
|
|
mk(raiz, "Orden/02_Proyectos/Mayo/03 - El cuello/proyecto.prproj", 2026, 5, 20)
|
|
mk(raiz, "Orden/03_Recursos/logo.png", 2026, 5, 20)
|
|
self.aprende("orden", "GRABACION")
|
|
antes = nombres(raiz)
|
|
p = plan(raiz)
|
|
self.assertFalse([f for f in p["filas"] if f["origen_rel"] == "Orden"], "sigue queriendo mover «Orden» entera")
|
|
self.assertTrue(any("guarda dentro carpetas de otras fechas" in a for a in p["avisos"]), p["avisos"])
|
|
destinos = {f["destino_rel"] for f in p["filas"]}
|
|
self.assertIn(rel("2025", "11- Noviembre", "00_Grabacion", "Tomas consultorio", "SONY"), destinos)
|
|
self.assertIn(rel("2025", "11- Noviembre", "00_Grabacion", "Tomas consultorio", "SONY", "Duplicados"), destinos)
|
|
# el rodaje del 31 de mayo siguió de madrugada: todo a mayo de 2025, el año que da el .braw intacto
|
|
self.assertIn(rel("2025", "05- Mayo", "00_Grabacion", "Maquinas", "BLACKMAGIC_MP4"), destinos)
|
|
self.assertIn(rel("2025", "05- Mayo", "00_Grabacion", "Maquinas", "URSA_BRAW"), destinos)
|
|
self.assertFalse([d for d in destinos if "06- Junio" in d], destinos)
|
|
junta = [f for f in p["filas"] if f["accion"] == "renombrar" and f["origen_rel"].endswith("03 - El cuello")]
|
|
self.assertTrue(junta and junta[0]["marcado"] and "se junta" in junta[0]["motivo"], junta)
|
|
aplicar(raiz, p)
|
|
self.assertEqual(nombres(raiz), antes, "se perdió o se duplicó algún archivo")
|
|
for esperado in (rel("2025", "11- Noviembre", "00_Grabacion", "Tomas consultorio", "SONY", "20251111_Labstream5320.MP4"),
|
|
rel("2025", "11- Noviembre", "00_Grabacion", "Tomas consultorio", "SONY", "20251111_Labstream5321_10bit.mp4"),
|
|
rel("2025", "05- Mayo", "00_Grabacion", "Maquinas", "BLACKMAGIC_MP4", "A006_06010000_C002.mp4"),
|
|
rel("2025", "05- Mayo", "00_Grabacion", "Maquinas", "URSA_BRAW", "A006_06010030_C003.braw"),
|
|
rel("2026", "05- Mayo", "03- El cuello", "Exportado", "final.mp4"),
|
|
rel("2026", "05- Mayo", "03- El cuello", "proyecto.prproj"),
|
|
rel("2026", "05- Mayo", "03- El cuello", "portada vieja.png")):
|
|
self.assertTrue(os.path.isfile(os.path.join(raiz, esperado)), "falta " + esperado + "\n" + "\n".join(arbol(raiz)))
|
|
self.assertFalse(os.path.isdir(os.path.join(raiz, "2026", "05- Mayo", "03 - El cuello")), "quedó la pieza gemela")
|
|
# y con todo en su sitio, no queda nada que proponer sobre lo mismo
|
|
self.assertFalse([f for f in O.Plan(O.raiz_cfg(raiz)).correr()["filas"] if f["marcado"]])
|
|
|
|
def test_una_pieza_no_es_una_grabacion(self):
|
|
"""Una pieza numerada cuyo título alguien confirmó como «grabación» se quedaba sin sus archivos."""
|
|
raiz = cliente("Pieza")
|
|
for n in ("final VR1.mp4", "toma 2.mp4", "Musica/tema.wav", "Portada/p.png"):
|
|
mk(raiz, "2026/02- Febrero/03- Se me cayó el parpado/" + n, 2026, 2, 10)
|
|
mk(raiz, "2026/08- Agosto/03- Grabacion Agosto/C0001.MP4", 2026, 8, 5)
|
|
self.aprende("se me cayo el parpado", "GRABACION")
|
|
p = plan(raiz)
|
|
self.assertFalse([f for f in p["filas"] if "parpado" in f["origen_rel"]], "desarma la pieza")
|
|
# la sesión del mes, con nombre de fábrica y clips de cámara, sí se reparte
|
|
self.assertIn(rel("2026", "08- Agosto", "00_Grabacion", "SONY"), {f["destino_rel"] for f in p["filas"]})
|
|
|
|
def test_lo_que_no_es_rodaje_no_va_a_otros(self):
|
|
"""Dentro de una carpeta de grabación había proyectos e imágenes de IA que acababan en 00_Grabacion\\OTROS."""
|
|
raiz = cliente("Mezcla")
|
|
for n in ("C0001.MP4", "C0002.MP4", "C0003.MP4"):
|
|
mk(raiz, "Grabación evento/" + n, 2026, 4, 10)
|
|
mk(raiz, "Grabación evento/guion.docx", 2026, 4, 10)
|
|
mk(raiz, "Grabación evento/proyecto.drp", 2026, 4, 10)
|
|
mk(raiz, "Grabación evento/Export/final.mp4", 2026, 4, 12)
|
|
p = plan(raiz)
|
|
por_archivo = {os.path.basename(a): f for f in p["filas"] for a in (f["archivos"] or [])}
|
|
self.assertEqual(por_archivo["C0001.MP4"]["destino_rel"], rel("2026", "04- Abril", "00_Grabacion", "evento", "SONY"))
|
|
self.assertTrue(por_archivo["C0001.MP4"]["marcado"], "un original de cámara fechado por su archivo debe ir marcado")
|
|
self.assertEqual(por_archivo["proyecto.drp"]["destino_rel"], rel("01_Proyectos", "DaVinci Resolve"))
|
|
self.assertNotIn("00_Grabacion", por_archivo["guion.docx"]["destino_rel"])
|
|
self.assertNotIn("final.mp4", por_archivo) # va con su carpeta, no suelto
|
|
export = [f for f in p["filas"] if f["origen_rel"] == rel("Grabación evento", "Export")]
|
|
self.assertTrue(export and export[0]["destino_rel"] == rel("2026", "04- Abril", "_Exportados"), export)
|
|
self.assertFalse([f for f in p["filas"] if f["destino_rel"].endswith("OTROS")])
|
|
|
|
def test_repaso_de_lo_ya_ordenado(self):
|
|
"""160 clips de diciembre de 2025 vivían en la 00_Grabacion de marzo, y nada los volvía a mirar."""
|
|
raiz = cliente("Repaso")
|
|
g = "2025/03- Marzo/00_Grabacion/"
|
|
mk(raiz, g + "URSA_BRAW/A014_12091814_C001.braw", 2025, 12, 9)
|
|
mk(raiz, g + "URSA_BRAW/A014_12100130_C002.braw", 2025, 12, 10) # la misma noche, ya de madrugada
|
|
mk(raiz, g + "URSA_BRAW/Duplicados/A014_12091814_C001.mp4", 2026, 7, 1) # reescrito después
|
|
mk(raiz, g + "SONY/C0001.MP4", 2025, 12, 9) # sin fecha en el nombre, mismo día
|
|
mk(raiz, g + "SONY/C0002.MP4", 2025, 3, 5) # este sí es de marzo
|
|
mk(raiz, g + "URSA_BRAW/A002_03110342_C036.braw", 2025, 3, 11) # y este
|
|
antes = nombres(raiz)
|
|
p = plan(raiz)
|
|
filas = [f for f in p["filas"] if "ya estaba ordenado" in f["motivo"]]
|
|
movidos = sorted(os.path.basename(a) for f in filas for a in f["archivos"])
|
|
self.assertEqual(movidos, ["A014_12091814_C001.braw", "A014_12091814_C001.mp4", "A014_12100130_C002.braw", "C0001.MP4"])
|
|
self.assertTrue(all(f["marcado"] for f in filas), filas)
|
|
self.assertTrue(any("en un mes que no es el de su rodaje" in a for a in p["avisos"]), p["avisos"])
|
|
aplicar(raiz, p)
|
|
self.assertEqual(nombres(raiz), antes)
|
|
dic = os.path.join(raiz, "2025", "12- Diciembre", "00_Grabacion")
|
|
for esperado in ("URSA_BRAW" + S + "A014_12091814_C001.braw", "URSA_BRAW" + S + "A014_12100130_C002.braw",
|
|
"URSA_BRAW" + S + "Duplicados" + S + "A014_12091814_C001.mp4", "SONY" + S + "C0001.MP4"):
|
|
self.assertTrue(os.path.isfile(os.path.join(dic, esperado)), "falta " + esperado + "\n" + "\n".join(arbol(raiz)))
|
|
mar = os.path.join(raiz, "2025", "03- Marzo", "00_Grabacion")
|
|
self.assertTrue(os.path.isfile(os.path.join(mar, "SONY", "C0002.MP4")))
|
|
self.assertTrue(os.path.isfile(os.path.join(mar, "URSA_BRAW", "A002_03110342_C036.braw")))
|
|
self.assertFalse([f for f in O.Plan(O.raiz_cfg(raiz)).correr()["filas"] if f["marcado"]], "el repaso no termina")
|
|
|
|
def test_carpetas_gemelas_y_papelera_vieja(self):
|
|
"""«Exportados» y «_Exportados» convivían en el mismo mes, y lo apartado, en dos papeleras."""
|
|
raiz = cliente("Gemelas")
|
|
mk(raiz, "2026/03- Marzo/Exportados/a.mp4", 2026, 3, 10)
|
|
mk(raiz, "2026/03- Marzo/_Exportados/b.mp4", 2026, 3, 11)
|
|
mk(raiz, "2026/05- Mayo/EXPORTADOS/c.mp4", 2026, 5, 11)
|
|
mk(raiz, "_Papelera/basura/x.tmp", 2026, 1, 1)
|
|
mk(raiz, "_Para borrar/basura/y.tmp", 2026, 1, 1)
|
|
p = plan(raiz)
|
|
gemela = [f for f in p["filas"] if f["origen_rel"] == rel("2026", "03- Marzo", "Exportados")]
|
|
self.assertTrue(gemela and gemela[0]["destino_rel"] == rel("2026", "03- Marzo", "_Exportados") and gemela[0]["marcado"], gemela)
|
|
vieja = [f for f in p["filas"] if f["origen_rel"] == "_Papelera"]
|
|
self.assertTrue(vieja and vieja[0]["destino_rel"] == "_Para borrar" and vieja[0]["paso"] == "limpiar", vieja)
|
|
self.assertEqual([a["titulo"] for a in O.acciones(raiz)["acciones"] if a["clave"] == "limpiar"], ["Juntar las dos papeleras"])
|
|
aplicar(raiz, p)
|
|
self.assertEqual(arbol(raiz), sorted([rel("2026", "03- Marzo", "_Exportados", "a.mp4"), rel("2026", "03- Marzo", "_Exportados", "b.mp4"),
|
|
rel("2026", "05- Mayo", "_Exportados", "c.mp4"),
|
|
rel("_Para borrar", "basura", "x.tmp"), rel("_Para borrar", "basura", "y.tmp")]))
|
|
|
|
def test_por_nombrar_solo_pregunta_lo_que_importa(self):
|
|
"""De 6 preguntas, 6 sobraban: carpetas de la app, de dentro de la papelera o de dentro de una pieza."""
|
|
raiz = cliente("Nombrar")
|
|
mk(raiz, "_Para borrar/basura/Videos/c.mp4", 2026, 1, 1) # dentro de la papelera
|
|
mk(raiz, "2026/01- Enero/00_Grabacion/Orden/SONY/C0001.MP4", 2026, 1, 5) # una etiqueta de la propia app
|
|
mk(raiz, "2026/01- Enero/01- Pieza/Rara/x.png", 2026, 1, 5) # dentro de una pieza
|
|
mk(raiz, "2026/01- Enero/Sin numero/x.png", 2026, 1, 6) # en un mes: será una pieza
|
|
for n in ("Exportado/f.mp4", "Portada/p.png", "Musica/m.wav", "r.prproj"):
|
|
mk(raiz, "Botox labios/" + n, 2026, 2, 2) # una pieza montada, suelta en la raíz
|
|
for n in ("a.xyz", "b.xyz", "c.xyz"):
|
|
mk(raiz, "Cosas/" + n, 2026, 2, 2)
|
|
for i in range(5):
|
|
mk(raiz, "Tarjeta/C%04d.MP4" % i, 2026, 2, 3) # llena de clips: se reparte sola
|
|
os.makedirs(os.path.join(raiz, "Vacia", "dentro"))
|
|
plan(raiz)
|
|
voc = {t["token"]: t for t in O.vocabulario(raiz)}
|
|
self.assertEqual(sorted(k for k, t in voc.items() if not t["cubeta"]), ["botox labios", "cosas"], sorted(voc))
|
|
for sobra in ("videos", "orden", "rara", "sin numero", "tarjeta", "vacia", "basura", "sony"):
|
|
self.assertNotIn(sobra, voc)
|
|
t = voc["botox labios"]
|
|
self.assertEqual((t["sugerencia"], t["sug_conf"]), ("PIEZA", "alta"), t)
|
|
self.assertNotEqual(voc["cosas"].get("sugerencia"), "GRABACION")
|
|
nombrar = [a for a in O.acciones(raiz)["acciones"] if a["clave"] == "nombrar"][0]
|
|
self.assertIn("2 carpetas", nombrar["cifra"])
|
|
|
|
def test_piezas_gemelas_en_el_mismo_mes(self):
|
|
"""La misma pieza escrita de dos formas en el mismo mes: se juntan, y solo se mueve una de las dos."""
|
|
raiz = cliente("Gemelas de pieza")
|
|
mk(raiz, "2026/05- Mayo/03 - El cuello/a.mp4", 2026, 5, 10)
|
|
mk(raiz, "2026/05- Mayo/03- El cuello/b.mp4", 2026, 5, 10)
|
|
mk(raiz, "2026/06- Junio/Botox/c.mp4", 2026, 6, 10)
|
|
mk(raiz, "2026/06- Junio/02- Botox/d.mp4", 2026, 6, 10)
|
|
p = plan(raiz)
|
|
self.assertEqual(sorted((f["origen_rel"], f["destino_rel"], f["marcado"]) for f in p["filas"]),
|
|
[(rel("2026", "05- Mayo", "03 - El cuello"), rel("2026", "05- Mayo", "03- El cuello"), True),
|
|
(rel("2026", "06- Junio", "Botox"), rel("2026", "06- Junio", "02- Botox"), True)])
|
|
aplicar(raiz, p)
|
|
self.assertEqual(arbol(raiz), [rel("2026", "05- Mayo", "03- El cuello", "a.mp4"), rel("2026", "05- Mayo", "03- El cuello", "b.mp4"),
|
|
rel("2026", "06- Junio", "02- Botox", "c.mp4"), rel("2026", "06- Junio", "02- Botox", "d.mp4")])
|
|
|
|
def test_una_campana_con_fechas_no_es_un_cajon(self):
|
|
"""Abrir lo que guarda meses dentro vale donde las piezas van por mes; una campaña se mueve entera."""
|
|
raiz = cliente("Anuncios", "pauta")
|
|
mk(raiz, "Campaña Navidad/2025/Noviembre/anuncio.mp4", 2025, 11, 20)
|
|
mk(raiz, "Campaña Navidad/2025/Noviembre/guion.docx", 2025, 11, 20)
|
|
p = plan(raiz)
|
|
self.assertFalse([a for a in p["avisos"] if "guarda dentro" in a], p["avisos"])
|
|
self.assertEqual([(f["origen_rel"], f["accion"]) for f in p["filas"]], [("Campaña Navidad", "mover carpeta")])
|
|
|
|
def test_lo_dudoso_no_cuenta_como_pendiente(self):
|
|
"""Con lo seguro hecho, la carpeta está al día aunque quede algo que la app no mueve sola."""
|
|
raiz = cliente("Dudas")
|
|
mk(raiz, "2026/03- Marzo/01 - Botox/final.mp4", 2026, 3, 10) # solo falta un retoque de nombre, sin marcar
|
|
p = plan(raiz)
|
|
self.assertEqual([(f["accion"], f["marcado"]) for f in p["filas"]], [("renombrar", False)])
|
|
O.PLANES[os.path.normpath(raiz)] = p
|
|
a = O.acciones(raiz)
|
|
fechas = [x for x in a["acciones"] if x["clave"] == "fechas"][0]
|
|
self.assertEqual((fechas["estado"], fechas["cifra"]), ("espera", "1 dudoso"))
|
|
self.assertNotIn("fechas", a["automatico"])
|
|
|
|
def test_hacer_lo_seguro_no_mueve_lo_dudoso(self):
|
|
filas = [{"cubeta": "GRABACION", "marcado": False, "confianza": "baja"},
|
|
{"cubeta": "GRABACION", "marcado": True, "confianza": "media"},
|
|
{"cubeta": "BASURA", "marcado": True, "confianza": "alta"},
|
|
{"cubeta": "EXPORTADOS", "marcado": False, "confianza": "baja"}]
|
|
elegidas = asistente.filas_elegidas(filas, {"carpetas": "seguras"})
|
|
self.assertEqual([(f["cubeta"], f["marcado"]) for f in elegidas], [("GRABACION", True), ("BASURA", True)])
|
|
|
|
def test_el_convertidor_no_repite(self):
|
|
"""22 clips se habían convertido dos veces y algunos tres («…_10bit_10bit_10bit.mp4»)."""
|
|
raiz = cliente("Convertidos")
|
|
mk(raiz, "2026/04- Abril/00_Grabacion/SONY/C0001.MP4", 2025, 4, 10)
|
|
mk(raiz, "2026/04- Abril/00_Grabacion/SONY/C0002_10bit.mp4", 2025, 4, 10)
|
|
plan(raiz)
|
|
con = O.db()
|
|
with con:
|
|
con.execute("UPDATE archivos SET analizado = 1, codec = 'hevc', ancho = 1920, alto = 1080, fps = 25, dur = 10, "
|
|
"kbps = 90000 WHERE raiz = ?", (os.path.normpath(raiz),))
|
|
roles = {r["nombre"]: r["rol"] for r in con.execute("SELECT nombre, rol FROM archivos WHERE raiz = ?", (os.path.normpath(raiz),))}
|
|
con.close()
|
|
self.assertEqual(roles, {"C0001.MP4": "camara_video", "C0002_10bit.mp4": "camara_video"})
|
|
cs = {os.path.basename(c["ruta"]): c["excluido"] for c in O.candidatos(os.path.normpath(raiz), 0, "equilibrado", 0)}
|
|
self.assertEqual(cs["C0001.MP4"], "")
|
|
self.assertIn("ya es una conversión", cs["C0002_10bit.mp4"])
|
|
|
|
def test_el_sufijo_del_convertidor_se_puede_cambiar(self):
|
|
"""El sufijo que pone el convertidor es un ajuste: si se cambia, las reglas tienen que enterarse."""
|
|
antes = O.CONFIG["optimizar"].get("sufijo")
|
|
self.addCleanup(lambda: O.CONFIG["optimizar"].__setitem__("sufijo", antes))
|
|
self.assertFalse(R.es_conversion("C0001_arch.mp4"))
|
|
O.CONFIG["optimizar"]["sufijo"] = "_arch"
|
|
O._sufijos_conversion()
|
|
self.assertTrue(R.es_conversion("C0001_arch.mp4"))
|
|
self.assertEqual(R.firma_archivo("C0001_arch.mp4"), "SONY")
|
|
self.assertTrue(R.es_conversion("C0001_10bit.mp4")) # y los de siempre siguen valiendo
|
|
|
|
def test_inventario_de_otra_version(self):
|
|
"""Lo que apunta el escaneo cambió: un inventario viejo se relee antes de planear nada."""
|
|
raiz = cliente("Viejo")
|
|
mk(raiz, "Grabacion/C0001.MP4", 2026, 4, 10)
|
|
plan(raiz)
|
|
self.assertNotEqual(O.acciones(raiz)["acciones"][0]["clave"], "escanear")
|
|
con = O.db()
|
|
with con:
|
|
con.execute("UPDATE escaneos SET reglas = 0 WHERE raiz = ?", (os.path.normpath(raiz),))
|
|
con.close()
|
|
a = O.acciones(raiz)
|
|
self.assertEqual((a["acciones"][0]["clave"], a["acciones"][0]["estado"], a["automatico"]), ("escanear", "toca", []))
|
|
self.assertTrue(any("versión anterior" in x for x in O.Plan(O.raiz_cfg(raiz)).correr()["avisos"]))
|
|
|
|
|
|
class Nuevo(unittest.TestCase):
|
|
"""Lo que entra nuevo: una pieza, el material de una tarjeta, y la pasada automática."""
|
|
|
|
def test_pieza_nueva(self):
|
|
"""Las piezas se creaban a mano: dos «01» en el mismo mes, «03 - X» junto a «03- X», «portasda»."""
|
|
raiz = cliente("Piezas")
|
|
mk(raiz, "2026/10- Octubre/01- Uno/a.mp4", 2026, 10, 1)
|
|
mk(raiz, "2026/10- Octubre/02- Dos/b.mp4", 2026, 10, 2)
|
|
plan(raiz)
|
|
previa = O.nueva_pieza(raiz, "Ozempic face", 2026, 10, crear=False)
|
|
self.assertEqual((previa["rel"], previa["existe"]), (rel("2026", "10- Octubre", "03- Ozempic face"), ""))
|
|
self.assertFalse(os.path.exists(previa["ruta"]), "la vista previa no debe crear nada")
|
|
r = O.nueva_pieza(raiz, "Ozempic face", 2026, 10)
|
|
self.assertTrue(r.get("ok"), r)
|
|
subs = sorted(os.listdir(r["ruta"]))
|
|
self.assertEqual(subs, sorted(O.CONFIG_DEFECTO["pieza"]["subcarpetas"]))
|
|
self.assertIn("ya existe", O.nueva_pieza(raiz, "ozempic FACE", 2026, 10).get("error", "")) # la misma, escrita distinto
|
|
self.assertEqual(O.nueva_pieza(raiz, "07- Otra cosa", 2026, 10, crear=False)["nombre"], "04- Otra cosa") # el número lo pone la app
|
|
for malo in ("", "a/b", "x" * 90):
|
|
self.assertTrue(O.nueva_pieza(raiz, malo, 2026, 10).get("error"), malo)
|
|
mes_nuevo = O.nueva_pieza(raiz, "Primera", 2026, 11)
|
|
self.assertEqual(mes_nuevo["rel"], rel("2026", "11- Noviembre", "01- Primera"))
|
|
# una pieza recién creada no es un hueco que borrar, ni el mes que se creó para ella
|
|
self.assertEqual(O.carpetas_vacias(raiz)["carpetas"], [])
|
|
self.assertFalse([f for f in O.Plan(O.raiz_cfg(raiz)).correr()["filas"]], "el plan quiere mover una pieza recién creada")
|
|
# deshacer la quita si sigue vacía; si ya tiene algo dentro, la deja
|
|
mk(raiz, "2026/11- Noviembre/01- Primera/Guion/guion.docx", 2026, 11, 2)
|
|
d = O.deshacer(mes_nuevo["lote"])
|
|
self.assertTrue(os.path.isfile(os.path.join(mes_nuevo["ruta"], "Guion", "guion.docx")) and d["errores"], d)
|
|
d = O.deshacer(r["lote"])
|
|
self.assertFalse(os.path.exists(r["ruta"]) or d["errores"], d)
|
|
# donde las piezas no van por mes (campañas), sin número
|
|
anuncios = cliente("Piezas de pauta", "pauta")
|
|
self.assertEqual(O.nueva_pieza(anuncios, "Navidad", 2026, 12)["rel"], rel("Campañas", "2026_Navidad"))
|
|
|
|
def test_bandeja_de_entrada(self):
|
|
"""Lo que se deja en _Entrada se reparte solo por rodaje y cámara, y la bandeja queda vacía."""
|
|
raiz = cliente("Bandeja")
|
|
mk(raiz, "2026/09- Septiembre/01- Pieza/a.png", 2026, 9, 1)
|
|
t = "_Entrada/Tarjeta A/PRIVATE/M4ROOT/"
|
|
mk(raiz, t + "CLIP/C0001.MP4", 2026, 9, 20)
|
|
mk(raiz, t + "CLIP/C0001M01.XML", 2026, 9, 20)
|
|
mk(raiz, t + "MEDIAPRO.XML", 2026, 9, 20)
|
|
mk(raiz, t + "THMBNL/C0001T01.JPG", 2026, 9, 20)
|
|
mk(raiz, t + "SUB/C0001S03.MP4", 2026, 9, 20)
|
|
mk(raiz, "_Entrada/A001_09211200_C001.braw", 2026, 9, 21)
|
|
mk(raiz, "_Entrada/Entrevista Dra/C0005.MP4", 2026, 9, 22)
|
|
antes = nombres(raiz)
|
|
plan(raiz)
|
|
self.assertEqual([a["clave"] for a in O.acciones(raiz)["acciones"] if a["clave"] == "entrada"], ["entrada"])
|
|
self.assertGreaterEqual(O.entrada(raiz)["n"], 4)
|
|
r = O.ordenar_entrada(raiz)
|
|
self.assertTrue(r.get("ok") and r["movimientos"] and not r["errores"], r)
|
|
self.assertEqual(nombres(raiz), antes)
|
|
g = os.path.join(raiz, "2026", "09- Septiembre", "00_Grabacion")
|
|
for esperado in (os.path.join(g, R.nombre_dia("2026-09-20"), "SONY", "C0001.MP4"),
|
|
os.path.join(g, R.nombre_dia("2026-09-20"), "SONY", "C0001M01.XML"),
|
|
os.path.join(g, R.nombre_dia("2026-09-21"), "URSA_BRAW", "A001_09211200_C001.braw"),
|
|
os.path.join(g, R.nombre_dia("2026-09-22"), "Entrevista Dra", "SONY", "C0005.MP4"),
|
|
os.path.join(raiz, "_Para borrar", "proxies", "_Entrada", "Tarjeta A", "PRIVATE", "M4ROOT", "SUB", "C0001S03.MP4"),
|
|
os.path.join(raiz, "_Para borrar", "basura", "_Entrada", "Tarjeta A", "PRIVATE", "M4ROOT", "THMBNL", "C0001T01.JPG"),
|
|
os.path.join(raiz, "_Para borrar", "basura", "_Entrada", "Tarjeta A", "PRIVATE", "M4ROOT", "MEDIAPRO.XML")):
|
|
self.assertTrue(os.path.isfile(esperado), "falta " + os.path.relpath(esperado, raiz) + "\n" + "\n".join(arbol(raiz)))
|
|
# la bandeja se queda, vacía y sin el esqueleto de la tarjeta, y no sale como hueco que borrar
|
|
self.assertEqual(os.listdir(os.path.join(raiz, "_Entrada")), [])
|
|
self.assertEqual((r["quedan"], O.entrada(raiz)["n"]), (0, 0))
|
|
self.assertFalse([a for a in O.acciones(raiz)["acciones"] if a["clave"] == "entrada"])
|
|
self.assertNotIn("_Entrada", [c["rel"] for c in O.carpetas_vacias(raiz)["carpetas"]])
|
|
# y se deshace: cada cosa vuelve a su sitio dentro de la tarjeta
|
|
d = O.deshacer(r["lote"])
|
|
self.assertFalse(d["errores"], d)
|
|
self.assertTrue(os.path.isfile(os.path.join(raiz, "_Entrada", "Tarjeta A", "PRIVATE", "M4ROOT", "CLIP", "C0001.MP4")))
|
|
self.assertTrue(os.path.isfile(os.path.join(raiz, "_Entrada", "Entrevista Dra", "C0005.MP4")))
|
|
|
|
def test_traer_de_una_tarjeta(self):
|
|
"""La copia desde la tarjeta se comprueba, conserva la fecha del rodaje y no toca el origen."""
|
|
raiz = cliente("Traer")
|
|
mk(raiz, "2026/08- Agosto/01- Pieza/a.png", 2026, 8, 1)
|
|
tarjeta = os.path.join(TMP, "tarjeta-sd")
|
|
clip = mk(tarjeta, "DCIM/100MEDIA/DJI_0001.MP4", 2026, 8, 10, tam=300_000)
|
|
mk(tarjeta, "System Volume Information/indice.dat", 2026, 8, 10)
|
|
with open(clip, "rb") as fh:
|
|
contenido = fh.read()
|
|
self.assertTrue(O.plan_traer(raiz, os.path.join(raiz, "2026")).get("error")) # de dentro no se trae
|
|
self.assertTrue(O.plan_traer(raiz, os.path.join(TMP, "no-existe")).get("error"))
|
|
p = O.plan_traer(raiz, tarjeta)
|
|
self.assertEqual((p["n"], p["bytes"]), (1, 300_000), p)
|
|
self.assertTrue(p["carpeta"].startswith("Tarjeta "))
|
|
r = O.traer(raiz, tarjeta)
|
|
self.assertEqual((r["estado"], r["copiados"], r["errores"]), ("listo", 1, []), r)
|
|
self.assertTrue(r["ordenado"]["ok"] and not r["ordenado"]["errores"], r["ordenado"])
|
|
final = os.path.join(raiz, "2026", "08- Agosto", "00_Grabacion", "DJI", "DJI_0001.MP4")
|
|
self.assertTrue(os.path.isfile(final), "\n".join(arbol(raiz)))
|
|
with open(final, "rb") as fh:
|
|
self.assertEqual(fh.read(), contenido)
|
|
self.assertLess(abs(os.path.getmtime(final) - os.path.getmtime(clip)), 2, "no conservó la fecha del rodaje")
|
|
with open(clip, "rb") as fh:
|
|
self.assertEqual(fh.read(), contenido, "tocó el origen")
|
|
self.assertEqual(arbol(tarjeta), [rel("DCIM", "100MEDIA", "DJI_0001.MP4"), rel("System Volume Information", "indice.dat")])
|
|
actas = os.listdir(os.path.join(raiz, ".organilab", "entradas"))
|
|
self.assertEqual(len(actas), 1)
|
|
with open(os.path.join(raiz, ".organilab", "entradas", actas[0]), encoding="utf-8-sig") as fh:
|
|
self.assertIn(rel("DCIM", "100MEDIA", "DJI_0001.MP4") + ";300000;", fh.read())
|
|
# una copia que no queda igual que el original no se da por buena
|
|
malo = mk(tarjeta, "DCIM/100MEDIA/DJI_0002.MP4", 2026, 8, 10, tam=50_000)
|
|
real = O.hashlib.blake2b
|
|
vez = []
|
|
|
|
class Trucado: # la segunda lectura (la de comprobación) sale distinta
|
|
def __init__(self, **kw):
|
|
self.h = real(**kw)
|
|
vez.append(self)
|
|
self.malo = len(vez) % 2 == 0
|
|
|
|
def update(self, b):
|
|
self.h.update(b[::-1] if self.malo else b)
|
|
|
|
def digest(self):
|
|
return self.h.digest()
|
|
|
|
def hexdigest(self):
|
|
return self.h.hexdigest()
|
|
O.hashlib.blake2b = Trucado
|
|
try:
|
|
estado = {"bytes": 0}
|
|
destino = os.path.join(raiz, "_Entrada", "prueba", "DJI_0002.MP4")
|
|
with self.assertRaises(OSError):
|
|
O._copiar_comprobando(malo, destino, estado)
|
|
finally:
|
|
O.hashlib.blake2b = real
|
|
self.assertFalse(os.path.exists(destino) or os.path.exists(destino + ".copiando"), "dejó una copia sin comprobar")
|
|
self.assertEqual(estado["bytes"], 0)
|
|
|
|
def test_versiones_del_mismo_clip(self):
|
|
"""39 clips estaban dos veces con el mismo nombre y distinta calidad, y no cabían juntos en su mes."""
|
|
raiz = cliente("Versiones")
|
|
oct_, may = "2025/10- Octubre/00_Grabacion/BLACKMAGIC_MP4/", "2026/05- Mayo/00_Grabacion/BLACKMAGIC_MP4/"
|
|
buena = mk(raiz, oct_ + "A002_03020727_C001.mp4", 2025, 12, 1, tam=9000)
|
|
peor = mk(raiz, may + "A002_03020727_C001.mp4", 2026, 7, 1, tam=3000)
|
|
mk(raiz, oct_ + "C0001.MP4", 2025, 10, 5, tam=5000) # se llaman igual…
|
|
mk(raiz, may + "C0001.MP4", 2026, 5, 5, tam=4000) # …pero son clips distintos: duran distinto
|
|
# dos exportados con el mismo nombre no son un duplicado: son dos entregas de la pieza (la vertical y
|
|
# la horizontal). No son material de cámara, así que ni se leen: `sonda` fallaría si se los pasaran.
|
|
mk(raiz, "2026/02- Febrero/02- Liftera/Exportados/Liftera VR3 Final.mp4", 2026, 2, 20, tam=7000)
|
|
mk(raiz, "2026/02- Febrero/01- Otra/Exportados/Liftera VR3 Final.mp4", 2026, 2, 21, tam=6000)
|
|
p = plan(raiz)
|
|
self.assertTrue([f for f in p["filas"] if "OJO" in f["motivo"]], "sin comparar, las dos versiones chocan en su mes")
|
|
medidas = {buena: (12.0, 3840, 2160), peor: (12.02, 1920, 1080),
|
|
os.path.join(raiz, oct_.replace("/", S), "C0001.MP4"): (10.0, 1920, 1080),
|
|
os.path.join(raiz, may.replace("/", S), "C0001.MP4"): (25.0, 1920, 1080)}
|
|
|
|
def sonda(ruta):
|
|
d, an, al = medidas[ruta]
|
|
return {"codec": "h264", "perfil": "", "pix_fmt": "yuv420p", "ancho": an, "alto": al, "fps": 25.0, "dur": d,
|
|
"kbps": 1000, "alfa": 0, "acodec": "aac", "pistas_audio": 1}
|
|
viejos = (O.ffprobe, O.herramientas)
|
|
O.ffprobe, O.herramientas = sonda, lambda: {"ffprobe": {"ok": True}}
|
|
try:
|
|
self.assertEqual(O.duplicados(raiz, minimo=0)["por_comparar"], 2)
|
|
d = O.duplicados(raiz, minimo=0, calidades=True)
|
|
finally:
|
|
O.ffprobe, O.herramientas = viejos
|
|
self.assertEqual([(g["tipo"], g["queda"], g["mueven"]) for g in d["grupos"]], [("calidad", buena, [peor])], d["grupos"])
|
|
r = O.apartar_duplicados(raiz, d["grupos"])
|
|
self.assertEqual((r["movimientos"], r["errores"]), (1, []), r)
|
|
self.assertTrue(os.path.isfile(os.path.join(os.path.dirname(buena), "Duplicados", "A002_03020727_C001.mp4")))
|
|
self.assertTrue(os.path.isfile(os.path.join(raiz, may.replace("/", S), "C0001.MP4")), "apartó un clip que solo se llamaba igual")
|
|
# ya no chocan: las dos van a su mes, la peor dentro de Duplicados
|
|
p = O.Plan(O.raiz_cfg(raiz)).correr()
|
|
self.assertFalse([f for f in p["filas"] if "OJO" in f["motivo"]], [f["motivo"] for f in p["filas"]])
|
|
aplicar(raiz, p)
|
|
mar = os.path.join(raiz, "2025", "03- Marzo", "00_Grabacion", "BLACKMAGIC_MP4")
|
|
self.assertTrue(os.path.isfile(os.path.join(mar, "A002_03020727_C001.mp4")), "\n".join(arbol(raiz)))
|
|
self.assertTrue(os.path.isfile(os.path.join(mar, "Duplicados", "A002_03020727_C001.mp4")), "\n".join(arbol(raiz)))
|
|
|
|
def test_orden_automatico(self):
|
|
"""La pasada automática hace lo seguro en cada carpeta, y se frena sola cuando no debe seguir."""
|
|
normal = cliente("Auto normal")
|
|
for n in ("C0001.MP4", "C0002.MP4"):
|
|
mk(normal, "2026/04- Abril/Grabacion/" + n, 2026, 4, 10)
|
|
mk(normal, "2026/04- Abril/01 - Botox/final.mp4", 2026, 4, 12) # un retoque de nombre: dudoso, no se toca
|
|
mucho = cliente("Auto demasiado")
|
|
for i in range(6):
|
|
mk(mucho, "2026/04- Abril/Grabacion/C%04d.MP4" % i, 2026, 4, 10)
|
|
todas, ajuste = O.CONFIG["raices"], dict(O.CONFIG["automatico"])
|
|
desconectada = {"ruta": os.path.join(TMP, "no-conectada"), "cliente": "Sin disco", "tipo": "contenido"}
|
|
O.CONFIG["raices"] = [r for r in todas if r["cliente"] == "Auto normal"] + [desconectada]
|
|
ensayo = os.path.join(TMP, "ensayo-tarea")
|
|
os.makedirs(ensayo, exist_ok=True)
|
|
os.environ["ORGANILAB_ENSAYO"] = ensayo
|
|
try:
|
|
res = O.automatico(origen="prueba")
|
|
self.assertEqual([(c["cliente"], c["estado"]) for c in res["carpetas"]], [("Auto normal", "con dudas"), ("Sin disco", "sin conexion")])
|
|
c = res["carpetas"][0]
|
|
self.assertEqual((c["movimientos"], c["dudosos"], c["errores"]), (2, 1, []), c)
|
|
self.assertTrue(os.path.isfile(os.path.join(normal, "2026", "04- Abril", "00_Grabacion", "SONY", "C0001.MP4")))
|
|
self.assertTrue(os.path.isdir(os.path.join(normal, "2026", "04- Abril", "01 - Botox")), "tocó lo dudoso")
|
|
self.assertEqual(O.estado_automatico()["ultimo"]["origen"], "prueba")
|
|
self.assertFalse(O.AUTO["activo"])
|
|
# con más movimientos que el límite, la carpeta no se toca y se dice por qué
|
|
O.CONFIG["raices"] = [r for r in todas if r["cliente"] == "Auto demasiado"]
|
|
O.CONFIG["automatico"]["limite"] = 5
|
|
c = O.automatico(origen="prueba")["carpetas"][0]
|
|
self.assertEqual(c["estado"], "demasiado", c)
|
|
self.assertTrue(os.path.isfile(os.path.join(mucho, "2026", "04- Abril", "Grabacion", "C0000.MP4")), "movió pese al límite")
|
|
# encenderlo da de alta la tarea de Windows (aquí, en una carpeta de ensayo) con la orden «automatico»
|
|
self.assertIn("error", O.configurar_automatico(True, "25:00"))
|
|
r = O.configurar_automatico(True, "03:30")
|
|
self.assertTrue(r.get("ok") and r["activo"] and r["hora"] == "03:30", r)
|
|
with open(os.path.join(ensayo, "tarea.json"), encoding="utf-8") as fh:
|
|
tarea = json.load(fh)
|
|
self.assertTrue(tarea["activo"] and tarea["hora"] == "03:30" and tarea["orden"].endswith(" automatico"), tarea)
|
|
self.assertFalse(O.configurar_automatico(False, "03:30")["activo"])
|
|
finally:
|
|
os.environ.pop("ORGANILAB_ENSAYO", None)
|
|
O.CONFIG["raices"] = todas
|
|
O.CONFIG["automatico"] = ajuste
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
unittest.main(verbosity=2, exit=False)
|
|
finally:
|
|
shutil.rmtree(TMP, ignore_errors=True)
|