3.1.1: la versión que enseña Windows no depende de los accesos directos

Al actualizar a la 3.1.0 la instalación de Munka por su propio botón,
Configuración › Aplicaciones siguió diciendo 3.0.0. Tras actualizarse se
reescribían los dos accesos directos (uno vive en el Escritorio de OneDrive)
antes de apuntar la versión, y si uno no se dejaba reescribir, la versión no
se apuntaba. instalar.al_dia ya no toca un acceso que abre lo que debe, uno
que no se deja no impide lo demás, y la versión se apunta siempre. Con prueba.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Este commit está contenido en:
Labstream
2026-10-04 12:37:25 -05:00
padre 50562aeaab
commit acb945c2ef
Se han modificado 5 ficheros con 64 adiciones y 29 borrados

Ver fichero

@@ -735,6 +735,14 @@ que viaja con cada versión; una prueba comprueba que cada pestaña tiene su apa
Pruebas: `probar.py` 19 de 19, `prueba_funcional.py` 26 de 26, `prueba_ventana.py` 18 desde el código y, Pruebas: `probar.py` 19 de 19, `prueba_funcional.py` 26 de 26, `prueba_ventana.py` 18 desde el código y,
con el paquete, 8 del arranque y 9 de la instalación. con el paquete, 8 del arranque y 9 de la instalación.
3.1.1, la versión que enseña Windows no depende de los accesos directos. Al actualizar la instalación de
Munka a la 3.1.0 por su propio botón, todo fue bien menos un detalle: Configuración › Aplicaciones siguió
diciendo 3.0.0. No se llegó a ver el error (sin consola no queda escrito), pero entre notar la versión vieja y
apuntar la nueva solo había una cosa: reescribir los dos accesos directos, y uno vive en el Escritorio de
OneDrive. Si uno no se dejaba reescribir en ese momento, la versión no se apuntaba; repetido a mano un rato
después, y en el equipo de ensayo, funcionó. Ahora `instalar.al_dia` no toca un acceso que ya abre lo que
debe, uno que no se deja no impide lo demás, y la versión se apunta siempre. Con prueba.
Probado el 9 sep 2026 sobre una copia de ensayo: plan → aplicar → deshacer → volver a aplicar deja el árbol Probado el 9 sep 2026 sobre una copia de ensayo: plan → aplicar → deshacer → volver a aplicar deja el árbol
exacto; conversión ffmpeg verificada y reemplazo con original a `_Para borrar`. El puente de Resolve conectó exacto; conversión ffmpeg verificada y reemplazo con original a `_Para borrar`. El puente de Resolve conectó
con Resolve Studio 21.1 pero no pudo abrir proyecto porque Resolve estaba en el Gestor de proyectos: con Resolve Studio 21.1 pero no pudo abrir proyecto porque Resolve estaba en el Gestor de proyectos:

Ver fichero

@@ -274,23 +274,24 @@ def instalado_aqui(base):
return bool(i) and os.path.normcase(os.path.normpath(i["base"])) == os.path.normcase(os.path.normpath(base)) return bool(i) and os.path.normcase(os.path.normpath(i["base"])) == os.path.normcase(os.path.normpath(base))
def instalar(base, version, solo_los_que_hay=False): def _apuntar(base, version):
"""Da de alta el programa de `base`. Con `solo_los_que_hay` no repone un acceso directo que alguien """La línea del programa en Configuración › Aplicaciones."""
quitó a propósito: solo pone al día los que siguen ahí (es lo que se hace tras cada actualización)."""
exe, args = orden(base) exe, args = orden(base)
icono = os.path.join(base, "static", "organilab.ico")
hechos = []
for ruta in _accesos():
if solo_los_que_hay and not os.path.exists(ruta):
continue
hechos.append(acceso_directo(ruta, exe, args, base, icono))
with winreg.CreateKey(winreg.HKEY_CURRENT_USER, CLAVE) as k: with winreg.CreateKey(winreg.HKEY_CURRENT_USER, CLAVE) as k:
for nombre, valor in (("DisplayName", NOMBRE), ("DisplayVersion", version), ("Publisher", "Labstream"), for nombre, valor in (("DisplayName", NOMBRE), ("DisplayVersion", version), ("Publisher", "Labstream"),
("DisplayIcon", icono), ("InstallLocation", base), ("DisplayIcon", os.path.join(base, "static", "organilab.ico")), ("InstallLocation", base),
("UninstallString", f'"{exe}" {args} desinstalar'.replace(" ", " "))): ("UninstallString", f'"{exe}" {args} desinstalar'.replace(" ", " "))):
winreg.SetValueEx(k, nombre, 0, winreg.REG_SZ, valor) winreg.SetValueEx(k, nombre, 0, winreg.REG_SZ, valor)
for nombre, valor in (("NoModify", 1), ("NoRepair", 1), ("EstimatedSize", _peso_kb(base))): for nombre, valor in (("NoModify", 1), ("NoRepair", 1), ("EstimatedSize", _peso_kb(base))):
winreg.SetValueEx(k, nombre, 0, winreg.REG_DWORD, valor) winreg.SetValueEx(k, nombre, 0, winreg.REG_DWORD, valor)
def instalar(base, version):
"""Da de alta el programa de `base`: sus dos accesos directos y su línea en Aplicaciones."""
exe, args = orden(base)
icono = os.path.join(base, "static", "organilab.ico")
hechos = [acceso_directo(ruta, exe, args, base, icono) for ruta in _accesos()]
_apuntar(base, version)
return {"base": base, "accesos": hechos, "orden": f'"{exe}" {args}'.strip()} return {"base": base, "accesos": hechos, "orden": f'"{exe}" {args}'.strip()}
@@ -307,17 +308,27 @@ def poner(base, version, avance=None):
def al_dia(base, version): def al_dia(base, version):
"""Tras una actualización: si este programa está dado de alta, Windows tiene que enseñar la versión """Tras una actualización: si este programa está dado de alta, Windows tiene que enseñar la versión nueva
nueva y los accesos directos tienen que apuntar a lo que ahora lo abre.""" y los accesos directos tienen que apuntar a lo que ahora lo abre. Devuelve True si hubo que cambiar algo."""
i = instalado() i = instalado()
if not i or not instalado_aqui(base): if not i or not instalado_aqui(base):
return False return False
exe, args = orden(base) exe, args = orden(base)
viejos = [a for a in map(leer_acceso, (r for r in _accesos() if os.path.exists(r))) if a] cambio = i["version"] != version
if i["version"] == version and all(os.path.normcase(a["exe"]) == os.path.normcase(exe) and a["args"] == args for a in viejos): for ruta in _accesos():
return False if not os.path.exists(ruta):
instalar(base, version, solo_los_que_hay=True) continue # el que alguien quitó a propósito no se repone
return True try:
a = leer_acceso(ruta)
if a and _misma(a["exe"], exe) and a["args"] == args:
continue # ya abre lo que tiene que abrir: no se toca
acceso_directo(ruta, exe, args, base, os.path.join(base, "static", "organilab.ico"))
cambio = True
except OSError:
pass # un acceso que ahora no se deja reescribir no impide lo demás
if cambio:
_apuntar(base, version)
return cambio
def desinstalar(base=None): def desinstalar(base=None):

Ver fichero

@@ -1,19 +1,19 @@
{ {
"nombre": "OrganiLAB", "nombre": "OrganiLAB",
"version": "3.1.0", "version": "3.1.1",
"builtAt": "2026-10-04T17:23:53.000Z", "builtAt": "2026-10-04T17:34:05.000Z",
"archivo": "OrganizadorLabstream-portatil-3.1.0.zip", "archivo": "OrganizadorLabstream-portatil-3.1.1.zip",
"size": 195387012, "size": 195387259,
"sha256": "1c80a369bcdc8f07c5a9db1dec1bd95dcd390a8e65714ec60251388544ee6523", "sha256": "117da3af85e14222893a34c7f992cd7853536bdef81f634b8453742e33aebdde",
"app": { "app": {
"archivo": "OrganizadorLabstream-app-3.1.0.zip", "archivo": "OrganizadorLabstream-app-3.1.1.zip",
"size": 11574433, "size": 11574680,
"sha256": "cedd68ade3f61ccd2958e3578bf46be8b92e20d73aade3b7e455585e53898d3e", "sha256": "05b9e0f5d35ac1244d8b1b0eb6aa7caefc9799b8d910f3d3ad42d8e22e3c3a2e",
"url": "/api/herramientas/OrganizadorLabstream-app-3.1.0.zip" "url": "/api/herramientas/OrganizadorLabstream-app-3.1.1.zip"
}, },
"notes": "El programa se instala solo en su sitio definitivo (tu carpeta de programas) y trae un manual dentro, en «Manual». Además, dos copias ya no pueden compartir puerto.", "notes": "El programa se instala solo en su sitio definitivo (tu carpeta de programas) y trae un manual dentro, en «Manual». Dos copias ya no pueden compartir puerto, y tras actualizarse Windows enseña siempre la versión nueva.",
"firma": { "firma": {
"alg": "rsa2048-pkcs1v15-sha256", "alg": "rsa2048-pkcs1v15-sha256",
"valor": "1ca46a8c6af640d62da3b8a97500661ae2d1e5442b66fb9ba9ee5268e3cfd85c1c1b6c2949484742adbdc758788bb5df323654b6a8533dd1ce3a93e9b974b0870bfa7c2f74fec4f6ef922cf1228c2d290a09a54dd62cabe846cdb40ed79276cc36940283e39db57ccc5f392daaf074672546f38cc3342cbc569d76723d365c2807755d95e8f129fb5e309952da27f645ef6130af7abb3f1356f895a92745ae890ad0c5b9d2a2139bc85ea5b66bcd3b2385d74c2d4b2bc438acf474e8dde041951d0c8f9949b39bf214d38ce3f0a80451f9c1681a9e3e53c2d28abf3e570201cc624b248213392ada7c892743a26c2be40d5170149d72d2dd3cd8bd92e3bff5d7" "valor": "09ce52a3885faebe34bbb4a8210f460cfa345da578fcb9e73316aec5bdb55ec9977f6cbb9f3d3f8e8925755d6dce94310219d046210fcbcba082134fd088208a9b830086c3bbcbff6acd04259348b70871c935fceb457362619a406f99da2b8abafd69dba7e1400020b90fd7a211d42e72cd49cd0294fbc8fc30e8fbae0ba431e5881f68a6607de7ae44ceadb5b22b007c7a4e5a95347ebdab5b1a645c4814b51a01f95c57e2c0caf9a5a0ccf35ca2f9f53117788b509ff42f4fd8800fe9f38eecb5ee421f3e6dc366b48d344291b21c1b50677fb50e8621733b30d932e0f055b07571b74a0273e528797ae3853851ce35e0eb9a42fe14ae34ca6ea7a6ff2029"
} }
} }

Ver fichero

@@ -40,7 +40,7 @@ import actualizador
asistente.O = sys.modules[__name__] asistente.O = sys.modules[__name__]
actualizador.O = sys.modules[__name__] actualizador.O = sys.modules[__name__]
VERSION = "3.1.0" VERSION = "3.1.1"
BASE = os.path.dirname(os.path.abspath(sys.executable if getattr(sys, "frozen", False) else __file__)) BASE = os.path.dirname(os.path.abspath(sys.executable if getattr(sys, "frozen", False) else __file__))
# Empaquetado: el programa corre con el motor que viaja en su propia carpeta. OrganizadorLabstream.exe es # Empaquetado: el programa corre con el motor que viaja en su propia carpeta. OrganizadorLabstream.exe es

Ver fichero

@@ -150,6 +150,22 @@ class Windows(unittest.TestCase):
self.assertTrue(instalar.al_dia(base, "3.0.1")) self.assertTrue(instalar.al_dia(base, "3.0.1"))
self.assertEqual(instalar.instalado()["version"], "3.0.1") self.assertEqual(instalar.instalado()["version"], "3.0.1")
self.assertFalse(os.path.exists(escritorio)) self.assertFalse(os.path.exists(escritorio))
# un acceso directo que no se deja reescribir (pasó con el del Escritorio en OneDrive) no impide
# apuntar la versión nueva; y uno que ya abre lo que debe ni se toca
open(os.path.join(base, "OrganizadorLabstream.exe"), "wb").close() # ahora el programa se abre con otra cosa
de_verdad, intentos = instalar.acceso_directo, []
instalar.acceso_directo = lambda *a, **k: (intentos.append(a[0]), (_ for _ in ()).throw(OSError("ocupado")))[1]
try:
self.assertTrue(instalar.al_dia(base, "3.0.2"))
finally:
instalar.acceso_directo = de_verdad
self.assertEqual(intentos, [inicio])
self.assertEqual(instalar.instalado()["version"], "3.0.2")
self.assertTrue(instalar.al_dia(base, "3.0.2")) # ahora sí se deja: lo pone al día
self.assertTrue(instalar._misma(instalar.leer_acceso(inicio)["exe"], os.path.join(base, "OrganizadorLabstream.exe")))
self.assertFalse(instalar.al_dia(base, "3.0.2")) # y ya no queda nada que cambiar
os.remove(os.path.join(base, "OrganizadorLabstream.exe"))
instalar.al_dia(base, "3.0.2")
# quitarlo desde otra copia del programa no toca lo de esta; desde esta, sí # quitarlo desde otra copia del programa no toca lo de esta; desde esta, sí
self.assertEqual(instalar.desinstalar(tmp)["accesos"], []) self.assertEqual(instalar.desinstalar(tmp)["accesos"], [])
self.assertTrue(instalar.instalado_aqui(base)) self.assertTrue(instalar.instalado_aqui(base))