2.3.0: proyectos por programa, carpetas vacías y mapa

Tres cosas para la reorganización, pedidas juntas.

Proyectos por programa: la plantilla pasa de 01_Proyectos/{nombre} a
01_Proyectos/{app}/{nombre}, con {app} = el programa que abre el archivo
(R.APPS_PROYECTO, R.app_proyecto). Lo que ya viene en su propia carpeta se
queda con el nombre de esa carpeta; lo que se separa es lo suelto. Las
plantillas guardadas se migran solas si nadie las tocó. Entran extensiones
que caían en «otro»: .sesx, .c4d, .blend, .motn, .aepx, .aet, .prel, .idml,
.psdt, .afpub.

Carpetas vacías: carpetas_vacias() las encuentra desde el inventario y las
comprueba en el disco; borrar_vacias() las quita. Es lo único que la app
borra, y solo porque no hay nada dentro: cada una queda en la bitácora como
rmdir y Deshacer las vuelve a crear con su nombre y en el inventario. No
entra en «Hacer las N de una vez». 29 huecos en David Reyes, 40 en N:.

Mapa: pestaña nueva con el árbol que producen las plantillas de verdad
(mapa_estructura, GET /api/estructura). Al tocar una carpeta dice qué guarda
—derivado de ROL_A_CUBETA y ROL_QUE—, cuánto hay hoy, y deja cambiar el
destino ahí mismo. POST /api/plantillas pasa a fusionar cubeta a cubeta:
guardar una sola plantilla borraba las otras diecinueve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Este commit está contenido en:
Labstream
2026-09-16 22:34:12 -05:00
padre 2930320690
commit ba186d4a26
Se han modificado 6 ficheros con 608 adiciones y 29 borrados

Ver fichero

@@ -150,14 +150,44 @@ EXT = {
"audio": {".wav", ".mp3", ".aac", ".m4a", ".aif", ".aiff", ".flac", ".ogg", ".wma"},
"imagen": {".jpg", ".jpeg", ".png", ".heic", ".webp", ".gif", ".tif", ".tiff", ".bmp", ".svg"},
"foto_raw": {".arw", ".rw2", ".dng", ".cr2", ".cr3", ".nef", ".raf", ".xmp"},
"diseno": {".psd", ".psb", ".ai", ".af", ".afphoto", ".afdesign", ".indd", ".xd", ".fig", ".sketch", ".eps"},
"proyecto": {".drp", ".drb", ".drt", ".dra", ".prproj", ".aep", ".prin", ".fcpxml", ".aaf", ".edl", ".otio", ".xml", ".comp", ".setting", ".mogrt", ".drfx"},
"diseno": {".psd", ".psb", ".psdt", ".ai", ".af", ".afphoto", ".afdesign", ".afpub", ".indd", ".idml",
".xd", ".fig", ".sketch", ".eps"},
"proyecto": {".drp", ".drb", ".drt", ".dra", ".prproj", ".prel", ".ppj", ".prtl", ".aep", ".aepx", ".aet",
".prin", ".fcpxml", ".aaf", ".edl", ".otio", ".xml", ".comp", ".setting", ".mogrt", ".drfx",
".sesx", ".c4d", ".blend", ".motn"},
"documento": {".pdf", ".docx", ".doc", ".md", ".txt", ".pptx", ".xlsx", ".csv", ".rtf", ".ttml", ".srt", ".vtt", ".pages", ".key", ".numbers"},
"fuente": {".ttf", ".otf", ".woff", ".woff2"},
"comprimido": {".zip", ".rar", ".7z", ".tar", ".gz", ".dmg", ".iso", ".img"},
"lut": {".cube", ".3dl", ".look"},
"programa": {".exe", ".msi", ".pkg", ".app", ".lnk"},
}
# Qué programa abre cada proyecto. «01_Proyectos» se separa por programa porque en la carpeta de un
# cliente conviven .prproj, .aep, .drp y .psd, y lo que cuesta es encontrarlos mezclados. El nombre de
# la carpeta es el del programa tal como se dice en el estudio.
APPS_PROYECTO = {
"Premiere": (".prproj", ".prel", ".ppj", ".prtl", ".prin"),
"After Effects": (".aep", ".aepx", ".aet"),
"DaVinci Resolve": (".drp", ".drb", ".drt", ".dra", ".drfx", ".setting", ".comp"),
"Photoshop": (".psd", ".psb", ".psdt"),
"Illustrator": (".ai", ".eps"),
"InDesign": (".indd", ".idml"),
"Audition": (".sesx",),
"Affinity": (".af", ".afphoto", ".afdesign", ".afpub"),
"Figma y XD": (".fig", ".xd", ".sketch"),
"Cinema 4D": (".c4d",),
"Blender": (".blend",),
"Motion": (".motn",),
"Plantillas": (".mogrt",),
"Intercambio": (".fcpxml", ".aaf", ".edl", ".otio", ".xml"),
}
EXT_A_APP = {e: a for a, exts in APPS_PROYECTO.items() for e in exts}
def app_proyecto(ext: str):
"""«.prproj» -> «Premiere». None si la extensión no es de un proyecto de edición o diseño."""
return EXT_A_APP.get((ext or "").lower())
BASURA_NOMBRES = {".ds_store", "thumbs.db", "desktop.ini", ".localized", ".bridgesort", ".bridgelabelsandratings"}
# solo temporales y cachés que un programa regenera solo. NO se ponen aquí extensiones de datos
# (.dat, .idx, .bak): son de plugins, subtítulos o copias que la gente guarda a propósito.
@@ -326,7 +356,7 @@ CUBETAS = {
"PORTADAS": "Portadas y miniaturas",
"APOYOS": "Tomas de apoyo reutilizables (b-roll)",
"MARCA": "Material de marca (logos, fuentes, fondos, transiciones)",
"PROYECTOS": "Proyectos de edición (Resolve, Premiere, AE, Photoshop)",
"PROYECTOS": "Proyectos de edición, separados por programa (Premiere, After Effects, Resolve, Photoshop…)",
"MUSICA": "Música",
"SFX": "Efectos de sonido",
"VOZ": "Voces / locuciones",
@@ -479,11 +509,14 @@ TIPOS_CLIENTE = {
"interno": "Es material de Labstream · se ordena por tipo de recurso",
}
# Destinos relativos a la raíz del cliente. {anio} {mes} = "08-Agosto" {nombre} {fuente}
# Destinos relativos a la raíz del cliente. Variables: {anio} {mes} ("08- Agosto") {nombre} {sub}
# {fuente} (la cámara) {app} (el programa del proyecto) {rel} (la ruta tal cual venía).
# Una variable vacía se lleva consigo su segmento: «01_Proyectos/{app}/{nombre}» sin programa queda
# en «01_Proyectos/Reel botox».
PLANTILLAS = {
"contenido": {
"MARCA": "00_Marca/{sub}",
"PROYECTOS": "01_Proyectos/{nombre}",
"PROYECTOS": "01_Proyectos/{app}/{nombre}",
"GRABACION": "{anio}/{mes}/00_Grabacion/{sub}",
"PIEZA": "{anio}/{mes}/{nombre}",
"CAMPANA": "Campañas/{anio}_{nombre}",
@@ -505,7 +538,7 @@ PLANTILLAS = {
},
"pauta": {
"MARCA": "00_Marca/{sub}",
"PROYECTOS": "01_Proyectos/{nombre}",
"PROYECTOS": "01_Proyectos/{app}/{nombre}",
"GRABACION": "Material/Grabaciones/{anio}/{mes}/{sub}",
"PIEZA": "Campañas/{anio}_{nombre}",
"CAMPANA": "Campañas/{anio}_{nombre}",
@@ -527,7 +560,7 @@ PLANTILLAS = {
},
"proyecto": {
"MARCA": "00_Marca/{sub}",
"PROYECTOS": "03_Proyecto/{nombre}",
"PROYECTOS": "03_Proyecto/{app}/{nombre}",
"GRABACION": "02_Grabacion/{anio}/{mes}/{sub}",
"PIEZA": "04_Exportados/{nombre}",
"CAMPANA": "{anio}_{nombre}",
@@ -549,7 +582,7 @@ PLANTILLAS = {
},
"interno": {
"MARCA": "Marca/{sub}",
"PROYECTOS": "Proyectos/{nombre}",
"PROYECTOS": "Proyectos/{app}/{nombre}",
"GRABACION": "Grabaciones/{anio}/{mes}/{sub}",
"PIEZA": "Piezas/{anio}/{nombre}",
"CAMPANA": "Campañas/{anio}_{nombre}",
@@ -585,6 +618,33 @@ CARPETAS_PROPIAS = {norm(x) for x in (
"00_Brief", "01_Material", "02_Grabacion", "03_Proyecto", "04_Exportados")}
# Rol de archivo suelto -> cubeta (para archivos que están fuera de sitio)
# Qué es cada rol dicho en llano, para que el mapa pueda explicar qué cae en cada carpeta.
ROL_QUE = {
"camara_raw": "originales RAW de cámara (.braw, .r3d, .ari)",
"camara_video": "clips de cámara (URSA, Sony, DJI, Osmo, Atomos)",
"camara_sidecar": "los .xml que Sony deja junto a cada clip",
"audio_grabado": "audio de grabadora o micro (DJI Mic, Tascam)",
"celular_video": "video de celular y de WhatsApp",
"celular_foto": "fotos de celular y de WhatsApp",
"foto_camara": "fotos de cámara (.arw, .cr3, .nef, .dng)",
"ia": "material generado con IA",
"stock": "tomas de stock (Envato y similares)",
"captura": "capturas de pantalla",
"proxy": "proxies (.lrf y los que crean Resolve y Premiere)",
"video": "video que no viene de cámara: exportados y masters",
"audio": "música y audio (.wav, .mp3, .aac)",
"imagen": "imágenes (.jpg, .png, .tif)",
"diseno": "diseño (.psd, .ai, .indd, Affinity)",
"proyecto": "proyectos de edición (.prproj, .aep, .drp)",
"documento": "documentos, guiones y subtítulos (.pdf, .docx, .srt)",
"fuente": "tipografías (.ttf, .otf)",
"comprimido": "comprimidos (.zip, .rar)",
"lut": "LUTs (.cube, .3dl)",
"programa": "instaladores y ejecutables",
"otro": "lo que no encaja en ninguna otra cosa",
"basura": "basura de sistema y cachés",
}
ROL_A_CUBETA = {
"camara_raw": "GRABACION", "camara_video": "GRABACION", "camara_sidecar": "GRABACION",
"audio_grabado": "GRABACION", "celular_video": "CELULAR", "celular_foto": "CELULAR",