# -*- 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"])) if __name__ == "__main__": try: unittest.main(verbosity=2, exit=False) finally: shutil.rmtree(TMP, ignore_errors=True)