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,
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
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:

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))
def instalar(base, version, solo_los_que_hay=False):
"""Da de alta el programa de `base`. Con `solo_los_que_hay` no repone un acceso directo que alguien
quitó a propósito: solo pone al día los que siguen ahí (es lo que se hace tras cada actualización)."""
def _apuntar(base, version):
"""La línea del programa en Configuración › Aplicaciones."""
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:
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(" ", " "))):
winreg.SetValueEx(k, nombre, 0, winreg.REG_SZ, valor)
for nombre, valor in (("NoModify", 1), ("NoRepair", 1), ("EstimatedSize", _peso_kb(base))):
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()}
@@ -307,17 +308,27 @@ def poner(base, version, avance=None):
def al_dia(base, version):
"""Tras una actualización: si este programa está dado de alta, Windows tiene que enseñar la versión
nueva y los accesos directos tienen que apuntar a lo que ahora lo abre."""
"""Tras una actualización: si este programa está dado de alta, Windows tiene que enseñar la versión nueva
y los accesos directos tienen que apuntar a lo que ahora lo abre. Devuelve True si hubo que cambiar algo."""
i = instalado()
if not i or not instalado_aqui(base):
return False
exe, args = orden(base)
viejos = [a for a in map(leer_acceso, (r for r in _accesos() if os.path.exists(r))) if a]
if i["version"] == version and all(os.path.normcase(a["exe"]) == os.path.normcase(exe) and a["args"] == args for a in viejos):
return False
instalar(base, version, solo_los_que_hay=True)
return True
cambio = i["version"] != version
for ruta in _accesos():
if not os.path.exists(ruta):
continue # el que alguien quitó a propósito no se repone
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):

Ver fichero

@@ -1,19 +1,19 @@
{
"nombre": "OrganiLAB",
"version": "3.1.0",
"builtAt": "2026-10-04T17:23:53.000Z",
"archivo": "OrganizadorLabstream-portatil-3.1.0.zip",
"size": 195387012,
"sha256": "1c80a369bcdc8f07c5a9db1dec1bd95dcd390a8e65714ec60251388544ee6523",
"version": "3.1.1",
"builtAt": "2026-10-04T17:34:05.000Z",
"archivo": "OrganizadorLabstream-portatil-3.1.1.zip",
"size": 195387259,
"sha256": "117da3af85e14222893a34c7f992cd7853536bdef81f634b8453742e33aebdde",
"app": {
"archivo": "OrganizadorLabstream-app-3.1.0.zip",
"size": 11574433,
"sha256": "cedd68ade3f61ccd2958e3578bf46be8b92e20d73aade3b7e455585e53898d3e",
"url": "/api/herramientas/OrganizadorLabstream-app-3.1.0.zip"
"archivo": "OrganizadorLabstream-app-3.1.1.zip",
"size": 11574680,
"sha256": "05b9e0f5d35ac1244d8b1b0eb6aa7caefc9799b8d910f3d3ad42d8e22e3c3a2e",
"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": {
"alg": "rsa2048-pkcs1v15-sha256",
"valor": "1ca46a8c6af640d62da3b8a97500661ae2d1e5442b66fb9ba9ee5268e3cfd85c1c1b6c2949484742adbdc758788bb5df323654b6a8533dd1ce3a93e9b974b0870bfa7c2f74fec4f6ef922cf1228c2d290a09a54dd62cabe846cdb40ed79276cc36940283e39db57ccc5f392daaf074672546f38cc3342cbc569d76723d365c2807755d95e8f129fb5e309952da27f645ef6130af7abb3f1356f895a92745ae890ad0c5b9d2a2139bc85ea5b66bcd3b2385d74c2d4b2bc438acf474e8dde041951d0c8f9949b39bf214d38ce3f0a80451f9c1681a9e3e53c2d28abf3e570201cc624b248213392ada7c892743a26c2be40d5170149d72d2dd3cd8bd92e3bff5d7"
"valor": "09ce52a3885faebe34bbb4a8210f460cfa345da578fcb9e73316aec5bdb55ec9977f6cbb9f3d3f8e8925755d6dce94310219d046210fcbcba082134fd088208a9b830086c3bbcbff6acd04259348b70871c935fceb457362619a406f99da2b8abafd69dba7e1400020b90fd7a211d42e72cd49cd0294fbc8fc30e8fbae0ba431e5881f68a6607de7ae44ceadb5b22b007c7a4e5a95347ebdab5b1a645c4814b51a01f95c57e2c0caf9a5a0ccf35ca2f9f53117788b509ff42f4fd8800fe9f38eecb5ee421f3e6dc366b48d344291b21c1b50677fb50e8621733b30d932e0f055b07571b74a0273e528797ae3853851ce35e0eb9a42fe14ae34ca6ea7a6ff2029"
}
}

Ver fichero

@@ -40,7 +40,7 @@ import actualizador
asistente.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__))
# 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.assertEqual(instalar.instalado()["version"], "3.0.1")
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í
self.assertEqual(instalar.desinstalar(tmp)["accesos"], [])
self.assertTrue(instalar.instalado_aqui(base))