3.0.0: OrganiLAB pasa a ser un programa de Windows

- ventana.py: la ventana es un marco Win32 propio con WebView2 dentro,
  manejado por COM con ctypes. Fuera pywebview, pythonnet y clr_loader y
  sus binarios sin firmar. Identidad en la barra de tareas, una sola copia
  abierta por carpeta de datos, selector de carpetas de Windows, rutas
  completas al soltar, avance en la barra de tareas, memoria de posición,
  aviso al cerrar con trabajo en marcha, y nada de navegador (menú, F5,
  zoom, permisos). Si el motor se cae, se monta de nuevo solo.
- arranque.py y empaquetar.py: el paquete ya no lleva binario propio.
  OrganizadorLabstream.exe es pythonw.exe (firmado por la PSF) con otro
  nombre y arranca el programa desde sitecustomize; abre con Smart App
  Control encendido. Fuera PyInstaller. Al empaquetar se comprueba la firma
  de cada binario con Windows y se arranca el motor del paquete.
- instalar.py: accesos directos en Inicio y Escritorio con la identidad de
  la ventana, y la línea en Configuración › Aplicaciones. Se ofrece una vez,
  la primera que se abre un paquete en un equipo.
- Dos copias sobre los mismos datos ya no se pisan: la segunda trae la
  ventana al frente (antes abría el navegador y daba por huérfanas las
  conversiones en marcha de la primera). Si el puerto 8765 está ocupado
  por otro programa, se toma uno libre.
- Por consola, el paquete ya no consulta si hay versión nueva con las
  actualizaciones automáticas apagadas.
- Pruebas: probar.py 17, prueba_funcional.py 26 de 26 y prueba_ventana.py
  (nueva), que abre la ventana de verdad: 17 comprobaciones desde el código
  y 8 arrancando el paquete por su .exe.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Este commit está contenido en:
Labstream
2026-10-04 09:54:31 -05:00
padre 2daffc175a
commit 252b1fd00e
Se han modificado 14 ficheros con 2275 adiciones y 165 borrados

Ver fichero

@@ -26,6 +26,9 @@
button:disabled{opacity:.45;cursor:default;border-color:var(--line)!important;background:var(--panel2)!important;color:var(--mut)!important}
button .spin{margin-right:6px}
.por-que{font-size:12px;color:var(--mut);margin-left:2px}
/* dentro de la ventana del programa, lo que es marco (menú, botones, títulos) no se selecciona ni se arrastra como en una página */
.nativo aside,.nativo nav,.nativo #tareas,.nativo button,.nativo .tabs,.nativo h1,.nativo h2,.nativo h3,.nativo label{-webkit-user-select:none;user-select:none}
.nativo img,.nativo a,.nativo svg{-webkit-user-drag:none}
/* ── lateral ── */
aside{grid-row:1/3;border-right:1px solid var(--line);padding:20px 16px 16px;display:flex;flex-direction:column;gap:4px;position:sticky;top:0;height:100vh;overflow:auto}
.marca{display:flex;align-items:center;gap:10px;margin-bottom:16px}
@@ -1671,9 +1674,9 @@ async function seguirActualizacion(){
ACT_SIGUIENDO = true; const caja = $('#actualizacionCaja'); caja.style.display = ''; irA('carpeta');
for (;;) {
let a; try { a = await api('/api/actualizacion'); } catch (e) { a = null; }
if (!a) { caja.innerHTML = '<h3>Reiniciando…</h3><p class="mut">El programa se está sustituyendo por la versión nueva. La página se recargará sola.</p>'; await new Promise(r => setTimeout(r, 2000)); try { const e = await fetch('/api/estado'); if (e.ok) location.reload(); } catch (e) {} continue; }
if (!a) { caja.innerHTML = '<h3>Reiniciando…</h3><p class="mut">El programa se está sustituyendo por la versión nueva y vuelve a abrirse solo.</p>'; await new Promise(r => setTimeout(r, 2000)); try { const e = await fetch('/api/estado'); if (e.ok) location.reload(); } catch (e) {} continue; }
if (a.estado === 'error') { caja.innerHTML = `<h3>No se pudo actualizar</h3><p class="bad">${esc(a.error)}</p>`; ACT_SIGUIENDO = false; return; }
if (a.estado === 'reiniciando' || a.aplicando) { caja.innerHTML = '<h3>Instalando…</h3><p class="mut">El programa se cierra y vuelve a abrirse con la versión nueva. La página se recargará sola.</p>'; await new Promise(r => setTimeout(r, 2000)); continue; }
if (a.estado === 'reiniciando' || a.aplicando) { caja.innerHTML = '<h3>Instalando…</h3><p class="mut">El programa se cierra y vuelve a abrirse solo con la versión nueva.</p>'; await new Promise(r => setTimeout(r, 2000)); continue; }
caja.innerHTML = `<h3>${a.estado === 'verificando' ? 'Verificando' : 'Descargando'} la versión ${esc(a.disponible ? a.disponible.version : '')}</h3><span class="barra" style="width:100%"><i style="width:${a.pct}%"></i></span><p class="mut">${(a.bytes / 1e6).toFixed(0)} / ${(a.total / 1e6).toFixed(0)} MB${a.error ? ' · ' + esc(a.error) : ''}</p>`;
await new Promise(r => setTimeout(r, 700));
}