Veredicto. La app cumple hoy su promesa de no borrar: en la bitácora real (raíz V:\Galeano, 1.154 movimientos, 6 lotes) no hay un solo error. Pero hay dos fallas que sí pueden destruir material de cliente por una ruta que la promesa no cubre: la conversión a 10 bits sobrescribe salidas cuando un clip existe en MP4 y MOV, y el renombrado silencioso con «(2)» rompe el emparejamiento de clips Sony con sus XML. Además, el servidor local acepta órdenes de cualquier página web abierta en el navegador, lo que permite mover archivos o instalar un ejecutable ajeno.
clip.mov_10bit.mp4), rechazar encolar si otro trabajo ya reclama esa salida, y nunca hacer os.remove ni os.replace sobre un archivo existente. En «Reemplazar», volver a comprobar que la salida existe y usar un lote único por reemplazo.Host no sea 127.0.0.1:8765, exigir Content-Type: application/json y un token aleatorio por arranque que la interfaz envíe. Quitar ffmpeg, ffprobe, python_resolve y actualizaciones.url de lo que /api/config puede cambiar.C0001M01.XML en las reglas.La salida se forma quitando la extensión y añadiendo _10bit.mp4. clip.mov y clip.mp4 producen el mismo destino; el segundo trabajo sobrescribe al primero, la verificación aprueba a ambos y «Reemplazar» manda los dos originales a _Para borrar/originales. Solo sobrevive un convertido. El puente con Resolve lo hace explícito con os.remove(dst). El asistente automatiza toda la cadena sin revisión humana.
organizador.py:1272-1278, 1289, 1373, 1487-1553 · resolve_bridge.py:240-241 · asistente.py:267-270
Al fusionar grabaciones de varias tarjetas Sony en un mismo 00_Grabacion/SONY/, el segundo C0001.MP4 pasa a C0001 (2).MP4. Ese nombre ya no se reconoce como cámara y el siguiente plan lo propone mover a Exportados. Su XML queda huérfano y Resolve o Premiere no pueden reenlazar. No hay aviso en el plan.
organizador.py:868-875, 894, 692-750, 1541 · reglas.py:102
La comprobación sigue buscando el nombre viejo «papelera». Como el escaneo marca _Para borrar como basura, el plan propone moverla dentro de sí misma con confianza «alta» y marcada por defecto. Hoy falla con «Cannot move a directory into itself» y deja una carpeta parásita; cualquier cambio futuro en mover() lo convertiría en un automovimiento recursivo.
organizador.py:816, 827 · reglas.py:539-540
Si un clip está abierto en Resolve, os.rename falla y el respaldo shutil.move copia el archivo pero no puede borrar el origen. La excepción sale antes de escribir la bitácora: origen intacto, copia de varios GB en destino, nada registrado. Reintentar crea copias «(2)».
organizador.py:895-899
Deshacer ese lote devuelve a su sitio todos los originales reemplazados en la historia de la app. «Reemplazar» tampoco vuelve a comprobar que el convertido siga existiendo: si se borró entre la verificación y el reemplazo, el original se va sin sustituto.
organizador.py:1538, 972-994
El campo de destino del plan acepta rutas absolutas y ..\.. sin comprobar contención. Entre unidades, shutil.move copia y borra sin comparar tamaño ni hash, y «Deshacer» solo usa os.rename, así que no puede revertirlo.
organizador.py:2104, 436-437, 878-900, 985
Si el destino ya no existe o el origen está ocupado, la entrada se salta sin avisar. El id de lote tiene resolución de segundo; la bitácora del proyecto ya muestra un lote con dos «fin». Las carpetas vaciadas se borran sin registro.
organizador.py:983, 907, 957-969
Una fila de 500 archivos se detiene en el primer fallo. Las rutas bajo _Para borrar/basura/ superan fácilmente 260 caracteres en NAS; no hay comprobación previa ni prefijo \\?\.
organizador.py:913-928
Si no puede crear su propio proyecto, usa el que el editor tiene abierto: cambia resolución y fps, borra todos los trabajos de render en cola, añade bins y timelines, y guarda. Los temporales _render_* quedan dentro de la carpeta de cámara del cliente si algo falla o se cancela.
resolve_bridge.py:105-114, 155-159, 185, 260, 151, 250-255 · organizador.py:1440
Solo se excluye el contexto «Exportados». Un 01- Pieza/final.mp4 se ofrece para convertir y tras «Reemplazar» el master entregado acaba en _Para borrar/originales. «Apartar duplicados» sin selección aplica todos los grupos.
organizador.py:1257, 2166
El patrón solo admite c0001.xml. El XML va a OTROS/ o a 01_Proyectos/Edicion mientras el MP4 va a SONY/. Sin embargo «Reemplazar» depende de ese nombre exacto para moverlo junto al clip.
reglas.py:101-102 · organizador.py:1541
ffprobe no lee BRAW, el análisis lo marca como error y los candidatos exigen análisis correcto. La rama BRAW es inalcanzable: toda la vía Resolve está muerta desde la interfaz.
organizador.py:1218-1226, 1241
Se agrupan solo por nombre y tamaño, sin hash ni siquiera parcial. Clips de cámara a bitrate fijo con igual duración se declaran idénticos y uno se aparta. La pareja «versión» separa también un MOV intermedio de su MP4 master.
organizador.py:1697-1723
Piezas numeradas sin título (2025/3) pasan a 03- Marzo marcadas por defecto.
reglas.py:62 · organizador.py:187-195
Raíces de unidad (N:\) rompen las consultas por prefijo y dejan el mapa vacío. .dat e .idx se tratan como basura. Cualquier carpeta llamada «Cache» se clasifica como proxy. El año de clips Blackmagic se infiere de la fecha de modificación.
organizador.py:1066-1071, 1802 · reglas.py:156, 159, 268-277
Escucha solo en 127.0.0.1, pero no valida Host ni Origin, no pide token y parsea JSON sin mirar Content-Type. Un fetch en modo no-cors desde cualquier sitio abierto en el navegador pasa sin preflight. Con eso se puede reorganizar cualquier carpeta, mover rutas fuera de la raíz vía /api/stock/papelera o copiar el perfil entero a un recurso de red ajeno vía respaldo. /api/respaldo/plan es un GET que escribe config.
organizador.py:2459, 1916-1923, 1615-1629, 2025-2034, 2169-2177
Uno: /api/config acepta cualquier clave, incluida la ruta de ffprobe; un UNC de atacante se ejecuta al analizar. Dos: cambiar actualizaciones.url y llamar a /api/actualizacion/instalar descarga un zip ajeno, lo comprueba contra el sha256 del propio atacante y lo lanza como ejecutable.
organizador.py:2060-2067, 144-150, 1181-1185, 2113 · actualizador.py:47, 62, 115
Sin firma. El sha256 se omite si el campo falta. La URL de descarga puede apuntar a otro host y a http. La cadena de versión se inserta sin validar en el .bat (inyección de comandos) y el nombre de archivo permite ..\ para escribir fuera de la carpeta de actualización.
actualizador.py:49-51, 62, 99, 114-116, 138, 144
os._exit a los 1,5 s salta la espera de conversión, respaldo y asistente. El bat solo espera a ese PID, no a otras instancias del exe. Robocopy sobrescribe archivo a archivo sin copia previa; un fallo a mitad deja exe viejo con _internal nuevo. El bat se escribe en cp1252 sin chcp, así que rutas con acentos fallan en consola OEM 850.
actualizador.py:159, 147-149, 142
Correcto: no hay shell=True, el handler de /static/ no permite traversal, enlaces simbólicos no se siguen, #recycle y @eadir se omiten, datos\ y ffmpeg\ quedan fuera del robocopy.
Cambiar la raíz solo limpia plan y candidatos. Con la tabla de stock de la raíz A en pantalla y la raíz B seleccionada, «Mover a _Para borrar» envía rutas de A con raíz B; el servidor no valida y relpath con .. saca el destino fuera de la papelera. «Copiar respaldo» sigue activo con el plan de otra raíz.
index.html:461, 478, 487 · organizador.py:1615-1629, 2171
api() alerta pero devuelve el objeto de error; los llamadores hacen .filter o .map sobre él y revientan. Si el servidor no responde JSON, la promesa muere sin manejar y botones como «Calcular plan» quedan deshabilitados para siempre.
index.html:419, 470, 481, 584-590, 614, 625
«Encolar y arrancar» no se deshabilita y encolar() hace SELECT y luego INSERT sin bloqueo en hilos distintos. Igual con Verificar, Calcular respaldo y Stock a papelera.
index.html:625, 635, 487, 478 · organizador.py:1289-1295
&.href no operables por teclado; contraste del ámbar por debajo de AA. Google Fonts cargado en una herramienta que puede estar sin red.index.html:648-659, 463, 441, 585-601, 630, 655, 717, 414, 190-197, 8
analizar() no captura excepciones: un «database is locked» durante un escaneo grande deja el análisis en «corriendo» hasta reiniciar. bucle_cola() sin try/finally deja COLA.activa en true y el cierre de ventana nunca termina. copiar_respaldo escribe «corriendo» pero el cierre espera «copiando».
organizador.py:1211-1229, 1452-1474, 2129, 1833, 2486
archivos_bajo() usa LIKE sobre índice binario y hace full scan; se llama una vez por carpeta no reconocida y una vez por cada elemento movido. Mover 12.000 autoguardados de After Effects son 12.000 barridos de 25.000 filas. Duplicados y Mapa hacen os.path.exists por SMB en cada archivo de más de 5 MB.
organizador.py:242-247, 265, 949, 1694, 1059
python_resolve apunta a un archivo que no se distribuye en el empaquetado.resolve_lote_*.json se acumulan en datos\; los bat generados llevan \r\r\n./api/actualizacion/buscar, /api/duplicados GET, y modalTexto nunca conectado.organizador.py:1400-1401, 2023, 2110 · empaquetar.py:47-57 · index.html:510
Tres revisiones en paralelo sobre organizador.py, reglas.py, resolve_bridge.py, actualizador.py, empaquetar.py y static/index.html, con ejecución de las funciones de reglas contra casos límite y comprobación del contenido del zip de la app. Los hallazgos críticos se verificaron por segunda vez leyendo el código. Se revisó además la bitácora real de la copia portátil 1.4.1 en Descargas, que es la que se ha usado sobre el NAS.