diff --git a/reglas.py b/reglas.py index 2a0ca97..9d92ffc 100644 --- a/reglas.py +++ b/reglas.py @@ -602,11 +602,12 @@ def etiqueta_grabacion(nombre: str) -> str: # Plantillas de estructura por tipo de cliente # -------------------------------------------------------------------------- +# «título · cómo se ordena»: la interfaz parte por el punto medio cuando necesita las dos cosas por separado TIPOS_CLIENTE = { - "contenido": "Le hago piezas mes a mes · se ordena por año y mes", - "pauta": "Le hago anuncios y campañas · se ordena por campaña", + "contenido": "Piezas mes a mes · se ordena por año y mes", + "pauta": "Anuncios y campañas · se ordena por campaña", "proyecto": "Un trabajo puntual con su entrega · se ordena por fases", - "interno": "Es material de Labstream · se ordena por tipo de recurso", + "interno": "Material de Labstream · se ordena por tipo de recurso", } # Destinos relativos a la raíz del cliente. Variables: {anio} {mes} ("08- Agosto") {dia} ("15- Viernes") diff --git a/static/index.html b/static/index.html index 4ff1f1c..cc8b5fb 100644 --- a/static/index.html +++ b/static/index.html @@ -166,6 +166,19 @@ .ejtipo div{display:flex;gap:4px 10px;align-items:baseline;flex-wrap:wrap} .ejtipo b{font-family:ui-monospace,Consolas,monospace;font-size:11.5px;font-weight:600;word-break:break-all} .ejtipo span{font-size:12px;color:var(--mut)} + .tipos{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:6px} + .tipo{display:block;position:relative;padding:9px 12px 9px 34px;border:1px solid var(--line);border-radius:8px;cursor:pointer;background:var(--panel)} + .tipo:hover{border-color:var(--acc)} + .tipo:has(input:checked){border-color:var(--acc);background:var(--acc2)} + .tipo:has(input:focus-visible){outline:2px solid var(--acc);outline-offset:1px} + .tipo input{position:absolute;left:12px;top:12px;margin:0} + .tipo b{display:block;font-size:13px} + .tipo span{display:block;font-size:12px;color:var(--mut);margin-top:1px} + .tipo em{display:block;font-size:11.5px;color:var(--mut);font-style:normal;margin-top:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} + .arbol{margin:6px 0 0;padding:10px 12px;background:var(--panel2);border-radius:8px;font-family:ui-monospace,Consolas,monospace;font-size:11.5px;line-height:1.6} + .arbol div{display:flex;gap:10px;align-items:baseline;white-space:pre} + .arbol b{font-weight:600} + .arbol span{font:12px/1.4 Manrope,system-ui,sans-serif;color:var(--mut);white-space:normal} .arbol{font-family:ui-monospace,Consolas,monospace;font-size:12px;line-height:1.55} .arbol .nd{display:flex;gap:10px;align-items:baseline;padding:3px 7px;border-radius:5px;cursor:pointer} .arbol .nd:hover{background:var(--panel2)} @@ -559,7 +572,7 @@

Para empezar

Instalarlo
La primera vez que se abre pregunta si lo instala en el equipo: queda en el menú Inicio, en el Escritorio y en Configuración › Aplicaciones. Cómo: se copia con sus datos a tu carpeta de programas y se da de alta desde ahí; no pide permisos de administrador y de la carpeta original no borra nada.
-
Añadir la carpeta de un cliente
Suéltala en la ventana, pulsa «Elegir carpeta…» o pega su ruta. Pregunta el nombre del cliente y qué clase de trabajo le haces, y te enseña cómo va a quedar ordenada. Cómo: al confirmar solo la lee; no mueve nada hasta que tú lo apliques.
+
Añadir la carpeta de un cliente
Suéltala en la ventana, pulsa «Elegir carpeta…» o pega su ruta. Pregunta el nombre del cliente y qué clase de trabajo le haces: cuatro tarjetas, cada una con los clientes que ya van así, y debajo el árbol de carpetas tal como va a quedar. Cómo: al confirmar solo la lee; no mueve nada hasta que tú lo apliques.
Clase de trabajo
Piezas mes a mes, anuncios y campañas, un trabajo puntual o material de Labstream. Cómo: cada clase tiene su plantilla de carpetas, y es lo único que decide dónde se guarda cada cosa.
Tus carpetas
Al abrir no se entra en ninguna: la portada lista las que ya conoce, cada una con su último escaneo, para entrar con un clic.
@@ -1810,20 +1823,40 @@ const EJEMPLO_CUBETAS = [['GRABACION', 'lo que sale de la cámara'], ['PIEZA', ' ['EXPORTADOS', 'lo que ya se entregó'], ['MUSICA', 'música y sonidos de siempre'], ['BASURA', 'lo que sobra, para que lo borres tú']]; function ejemploTipo(ruta, tipo){ - // se arma con la plantilla de verdad, así que el ejemplo es exactamente lo que la app va a hacer + // se arma con la plantilla de verdad, así que el ejemplo es exactamente lo que la app va a hacer. Va como + // árbol: las carpetas que comparten camino (el año, el mes) salen una sola vez, como en el Explorador. const pl = (ESTADO.plantillas || {})[tipo] || {}, h = new Date(); const val = {anio: h.getFullYear(), mes: `${String(h.getMonth() + 1).padStart(2, '0')}- ${MESES_UI[h.getMonth()]}`, - nombre: 'Nombre de la pieza', sub: '', fuente: '', rel: ''}; - const base = (ruta || '').replace(/[\\/]+$/, ''), visto = new Set(), filas = []; + nombre: tipo === 'pauta' ? 'Nombre de la campaña' : 'Nombre de la pieza', sub: '', fuente: '', rel: ''}; + const raiz = {hijos: new Map()}; for (const [cub, que] of EJEMPLO_CUBETAS){ const dest = (pl[cub] || '').replace(/\{(\w+)\}/g, (_, k) => val[k] !== undefined ? String(val[k]) : '') .replace(/\/+$/, '').replace(/\/+/g, '\\'); - if (!dest || visto.has(dest)) continue; - visto.add(dest); - filas.push(`
${esc(base)}\\${esc(dest)}\\${que}
`); - if (filas.length === 4) break; + if (!dest) continue; + let n = raiz; + for (const seg of dest.split('\\')) { if (!n.hijos.has(seg)) n.hijos.set(seg, {hijos: new Map(), que: ''}); n = n.hijos.get(seg); } + n.que = n.que || que; } - return filas.join(''); + const lineas = [`
${esc((ruta || '').replace(/[\\/]+$/, ''))}\\
`]; + const pinta = (nodo, sangria) => { + const hs = [...nodo.hijos.entries()]; + hs.forEach(([nombre, h], i) => { + const ultimo = i === hs.length - 1; + lineas.push(`
${esc(sangria)}${ultimo ? '└─ ' : '├─ '}${esc(nombre)}\\${h.que ? `${esc(h.que)}` : ''}
`); + pinta(h, sangria + (ultimo ? ' ' : '│ ')); + }); + }; + pinta(raiz, ''); + return lineas.join(''); +} +function tarjetasTipo(elegido){ + // las cuatro clases a la vista, cada una con los clientes que ya van así: se elige por parecido + const quien = t => Object.entries(ESTADO.config.clientes || {}).filter(([, c]) => (c.tipo || 'contenido') === t).map(([n]) => n); + return Object.entries(ESTADO.tipos).map(([k, v]) => { + const [titulo, orden] = v.split(' · '), q = quien(k); + return ``; + }).join(''); } function proponerRaiz(ruta){ if (!ruta) return; @@ -1836,13 +1869,13 @@ function proponerRaiz(ruta){ ${clientes.map(c => ` Con ese nombre reúne todas sus carpetas, aunque en cada disco estén escritas distinto. -
- +
+
${tarjetasTipo(tipoDe(sugerido))}
Es lo único que decide en qué carpetas va a guardar su material. Se cambia luego en Ajustes.

Así va a quedar ordenada:

-
+

Al confirmar solo la lee y te arma una lista de lo que haría. No mueve ni un archivo hasta que tú lo apliques.

`, seguro(async () => { - const cliente = $('#nuevoCliente').value.trim() || nombre, tipo = $('#nuevoTipo').value; + const cliente = $('#nuevoCliente').value.trim() || nombre, tipo = tipoElegido(); const raices = ESTADO.config.raices.filter(r => r.ruta.toLowerCase() !== ruta.toLowerCase()); raices.push({ruta, cliente, tipo}); await api('/api/raices', {raices}); if (!ESTADO.config.clientes[cliente]) { ESTADO.config.clientes[cliente] = {tipo, alias: [nombre]}; await api('/api/clientes', {clientes: ESTADO.config.clientes}); } @@ -1850,12 +1883,13 @@ function proponerRaiz(ruta){ await api('/api/escanear', {raiz: raizActual()}); await cargarEstado(); irA('carpeta'); aviso('Carpeta añadida. Escaneando…', 'ok'); })); - const sel = $('#nuevoTipo'), inp = $('#nuevoCliente'); - const pinta = () => $('#ejemploTipo').innerHTML = ejemploTipo(ruta, sel.value); - sel.value = tipoDe(sugerido); sel.onchange = pinta; + const inp = $('#nuevoCliente'); + const tipoElegido = () => { const r = document.querySelector('#nuevoTipo input:checked'); return r ? r.value : 'contenido'; }; + const pinta = () => $('#ejemploTipo').innerHTML = ejemploTipo(ruta, tipoElegido()); + $$('#nuevoTipo input').forEach(r => r.onchange = pinta); // si escribe el nombre de un cliente que ya existe, manda el tipo que ese cliente ya tiene: dos // carpetas del mismo cliente ordenadas con plantillas distintas serían un lío - inp.oninput = () => { const c = ESTADO.config.clientes[inp.value.trim()]; if (c && c.tipo && c.tipo !== sel.value) { sel.value = c.tipo; pinta(); } }; + inp.oninput = () => { const c = ESTADO.config.clientes[inp.value.trim()]; const r = c && c.tipo && document.querySelector(`#nuevoTipo input[value="${c.tipo}"]`); if (r && !r.checked) { r.checked = true; pinta(); } }; pinta(); }