التحديث التلقائي
Tu aplicación puede enterarse sola de que has publicado una versión nueva. No hay nada que activar ni un segundo sitio que mantener: el catálogo ya publica, para cada ficha, cuál es su última versión estable, con el nombre, el tamaño y el SHA-256 de cada archivo. Publicar una versión ES actualizar ese dato.
Esta página cuenta las dos formas de leerlo, el patrón que recomendamos y lo que conviene saber antes de prometerle a nadie que la aplicación «se actualiza sola».
La vía fácil: Electron
Si tu aplicación está hecha con Electron, no escribas ningún cliente. Fulgeon sirve en /apps/u/{slug}/ el feed que espera electron-updater, que es el actualizador que ya usa casi todo el mundo en Electron. No hay que subir ningún latest.yml ni montar un servidor aparte: los archivos de canal se generan con lo mismo que la ficha, y publicar una versión los cambia.
Son tres líneas en el package.json, dentro del bloque build:
"build": {
"publish": [
{ "provider": "generic", "url": "https://fulgeon.com/apps/u/mi-app/" }
]
}Cambia mi-app por el slug de tu ficha, el que sale en la URL del catálogo y en fulgeon apps. Con eso electron-builder deja la dirección escrita dentro de la aplicación compilada, y el actualizador ya sabe a dónde preguntar.
Y el par de líneas en el proceso principal:
const { autoUpdater } = require('electron-updater');
autoUpdater.checkForUpdatesAndNotify();Llámalo cuando la ventana ya esté pintada, no en la primera línea del arranque (y ten en cuenta que sólo hace algo en la aplicación empaquetada, no en desarrollo). A partir de ahí electron-updater compara versiones, descarga en segundo plano y comprueba el SHA-512 del archivo antes de instalar nada. Ese hash lo calcula Fulgeon mientras recibe tu archivo, así que no tienes que declararlo ni mantenerlo.
Qué archivo tienes que subir para que funcione
Esto es lo que hace que un feed «correcto» no actualice nada. Cada plataforma sólo sabe instalar ciertos formatos, así que en cada archivo de canal sólo se anuncian esos. Si publicas otra cosa, la ficha funciona igual, pero la actualización automática no tiene de dónde tirar.
| Sistema | Formatos que se anuncian, en orden |
|---|---|
| Windows | .exe (NSIS), .msi |
| macOS | .zip, .dmg, .pkg |
| Linux | .appimage, .deb, .rpm, .pacman |
El primero de la lista es el que se instala. Y en macOS eso importa mucho: el mecanismo del sistema sólo sabe actualizar desde un .zip, así que si en la versión sólo hay un .dmg, tus usuarios de Mac lo descargarán pero la instalación automática no llegará a ocurrir. Sube los dos: el .dmg para quien instala a mano desde la ficha y el .zip para el actualizador.
Dos requisitos más, los dos automáticos si publicas normal:
- El archivo tiene que ser subido, no un enlace a otro sitio: un enlace no tiene bytes que verificar y no entra en el feed.
- Tiene que tener papel
primaryy declarar bien su destino (sistema y arquitectura). Eso es lo que decide en qué archivo de canal aparece.
Si además subes el .blockmap que genera electron-builder al lado de cada instalador, las actualizaciones se descargan por diferencias: los pocos megas que han cambiado en vez del instalador entero. Se sube como un archivo más de la versión, con papel other.
Canales
Los nombres de archivo los pide electron-updater solo, según el sistema y la arquitectura del equipo, y todos se sirven en la misma carpeta:
| Archivo | Para quién |
|---|---|
latest.yml | Windows |
latest-mac.yml | macOS |
latest-linux.yml | Linux x64 |
latest-linux-arm64.yml | Linux arm64 (y armv7l, ia32) |
latest es el nombre que da electron-builder al canal estable. Si en tu package.json pones "channel": "beta", pedirá beta.yml, beta-mac.yml y compañía, y ahí saldrán tus versiones de canal beta.
Con una diferencia deliberada respecto a electron-builder: aquí los canales de prelanzamiento son inclusivos. beta.yml anuncia lo más nuevo que haya entre beta y stable, y alpha.yml entre los tres. Así, cuando la estable sustituye a la beta que alguien está probando, esa persona se actualiza a la estable en vez de quedarse encallada en una rama que ya no se toca.
Y como en el manifiesto: sólo versiones publicadas. Un borrador o una versión retirada no aparecen, y una beta no se cuela nunca en latest.yml.
Antes de dar esto por hecho, lee lo que hay que saber antes de prometerlo: en macOS el actualizador exige firma con Developer ID, y sin ella no se instala nada solo por mucho código que le pongas.
La vía general: el manifiesto JSON
Para cualquier otro lenguaje, y también para Electron si prefieres controlarlo tú, está el manifiesto:
curl -sS https://fulgeon.com/api/latest/mi-appDevuelve la última versión estable publicada de esa ficha. No hay clave que mandar ni cabecera que poner, y va con Access-Control-Allow-Origin: *, así que también se puede leer desde una web.
Hay una segunda dirección, GET /descargas/{slug}/ultima.json, con exactamente el mismo contenido. Existe por compatibilidad con aplicaciones que ya la traían escrita dentro; para algo nuevo, usa la primera.
Qué contesta, campo a campo
| Campo | Tipo | Qué es |
|---|---|---|
slug | texto | El identificador de la ficha, el mismo de la URL. |
app | texto | El nombre público de la aplicación. |
version | texto | El número de versión tal y como lo escribiste al publicar. |
canal | texto | Siempre stable. Está para que un cliente pueda comprobarlo. |
fecha | texto o null | Día de publicación en UTC, AAAA-MM-DD. |
publicado_ts | número o null | Lo mismo en segundos epoch, por si prefieres formatearlo tú. |
pagina | texto | La ficha del catálogo. Es a donde se manda a quien dice «quiero la nueva». |
notas | lista de textos | El changelog partido en líneas ya limpias, listas para pintar. Máximo 30 líneas de 500 caracteres. |
changelog | texto | El changelog entero, en crudo. |
ficheros | objeto | Un archivo por plataforma. Es lo que consulta un actualizador. |
artefactos | lista | Todos los archivos principales de la versión, sin colapsar. |
Cada archivo, esté en ficheros o en artefactos, trae siempre las mismas cuatro cosas:
| Campo | Qué es |
|---|---|
nombre | El nombre del archivo. |
url | De dónde se descarga. |
bytes | Su tamaño, o null. |
sha256 | Su SHA-256 en hexadecimal minúscula, o null. |
Las entradas de artefactos llevan además plataforma, arquitectura y cargador, que es lo que declaraste al subir el archivo.
bytes y sha256 van a null cuando el artefacto es un enlace a otro sitio en vez de un archivo subido: ahí no hay nada que medir. Ese null no es un detalle de formato, es información: significa «este archivo no se puede verificar», y la regla sana es no descargarlo automáticamente.
Las claves de ficheros
La clave es sistema-arquitectura, en minúsculas: macos-arm64, windows-x86_64, linux-x86_64. Cuando el archivo vale para cualquier equipo de ese sistema (arquitectura any o universal) la clave es el sistema pelado: macos, windows.
Un cliente mira primero su sistema-arquitectura y, si no está, el sistema a secas. Los dos casos aparecen en el ejemplo de abajo.
Sólo entran los archivos con papel primary. Si dos caen en la misma clave (un .deb y un .AppImage para linux-x86_64), gana el instalador nativo del sistema, luego el portable y por último el comprimido. Los dos siguen enteros en artefactos, y el orden es el mismo en cada consulta.
Las otras dos respuestas
| Código | Cuerpo | Qué significa |
|---|---|---|
404 | {"error": "no encontrada"} | Ese slug no existe, o no tiene ninguna versión estable publicada. |
503 | {"error": "no disponible"} | Fulgeon no ha podido construir el manifiesto. Es un fallo nuestro, y no se cachea. |
Ninguno de los dos significa «no hay versión nueva». Un cliente sano no enseña nada, no registra nada y lo vuelve a intentar mañana.
Un ejemplo real
Esta es la respuesta que dio Clipio el 16 de agosto de 2026, sin retocar nada. Va con fecha a propósito: la versión que verás hoy será otra, y lo que importa de este ejemplo no es el número sino la forma, que no cambia.
curl -sS https://fulgeon.com/api/latest/clipio{
"slug": "clipio",
"app": "Clipio",
"version": "0.7.3",
"canal": "stable",
"fecha": "2026-08-16",
"publicado_ts": 1786842133,
"pagina": "https://fulgeon.com/apps/app/clipio",
"notas": [
"Ya funciona en Mac con Intel: la descarga de Mac es universal."
],
"changelog": "Ya funciona en Mac con Intel: la descarga de Mac es universal.",
"ficheros": {
"macos": {
"nombre": "Clipio-0.7.3-universal.dmg",
"url": "https://fulgeon.com/apps/dl/art_TNLHenvVU9zJLc9L",
"bytes": 203853399,
"sha256": "2f76d52853f99208520a741f195df8109a4fe24741c2f32adb0c03dc9a6adb34"
},
"windows": {
"nombre": "Clipio-Setup-0.7.3.exe",
"url": "https://fulgeon.com/apps/dl/art_AuSRBdRE6kpYA6lg",
"bytes": 93829379,
"sha256": "2f983c562cc220e643b70e1717dd427ac28ef0e6a827e13ff7dd27e8f10e15c2"
}
},
"artefactos": [
{
"nombre": "Clipio-0.7.3-universal.dmg",
"url": "https://fulgeon.com/apps/dl/art_TNLHenvVU9zJLc9L",
"bytes": 203853399,
"sha256": "2f76d52853f99208520a741f195df8109a4fe24741c2f32adb0c03dc9a6adb34",
"plataforma": "macos",
"arquitectura": "any",
"cargador": null
},
{
"nombre": "Clipio-Setup-0.7.3.exe",
"url": "https://fulgeon.com/apps/dl/art_AuSRBdRE6kpYA6lg",
"bytes": 93829379,
"sha256": "2f983c562cc220e643b70e1717dd427ac28ef0e6a827e13ff7dd27e8f10e15c2",
"plataforma": "windows",
"arquitectura": "any",
"cargador": null
}
]
}Qué sale del manifiesto y qué no
- Sólo versiones publicadas y de canal stable. Las betas no salen: quien está probando una beta no quiere que le empujen a otra.
- Una versión retirada (yank) tampoco. Retirar es justo decir «esta no».
- Manda el orden de publicación, no el número de versión. Publicar un parche de una rama vieja no lo convierte en «lo último».
- La respuesta se cachea cinco minutos (
Cache-Control: public, max-age=300, s-maxage=300, stale-while-revalidate=600). Ese es todo el retraso entre publicar y que se vea desde fuera. - Y no decide si hay que actualizar: eso se compara en el equipo, que es el único que sabe qué versión tiene instalada.
El patrón recomendado
Es el que usan las aplicaciones de la casa, y no es una cuestión de buenos modales. En muchas aplicaciones de escritorio el comprobador de actualizaciones es la única puerta de red que hay, y de cómo esté hecha depende lo que se pueda escribir sin mentir en la pantalla de «Acerca de».
Una petición, y que no diga quién eres
Pide el manifiesto con un GET sin parámetros, sin cuerpo, sin cookies y sin credenciales. En concreto: no mandes la versión instalada, ni un identificador de equipo, ni de instalación, ni de licencia.
La comparación se hace en el cliente, que es quien sabe qué tiene puesto. Un servidor que no sabe tu versión no puede contar equipos, ni segmentarlos, ni empujar una versión sólo a unos cuantos. Y como la respuesta es idéntica para todo el mundo, se puede cachear entera en el borde: más rápida para ti y más barata para todos.
Fulgeon cumple su parte: la ruta no lee nada de la petición (ni cabeceras, ni User-Agent, ni query) y la respuesta no lleva Set-Cookie. Si algún día apareciera, es que habría dejado de ser la misma para todos.
Como mucho una vez al día, y nunca al arrancar en seco
Un comprobador que sale a la red en cada arranque es ruido para el servidor y, sobre todo, es tiempo robado al usuario justo cuando abre la aplicación.
Lo que hacemos aquí, por si sirve de referencia: un intento cada 24 horas, guardando en disco cuándo fue el último, y con 20 segundos de margen desde el arranque para que la ventana se pinte primero. Ocho segundos de espera máxima; si el servidor no contesta, hasta mañana.
Verifica el SHA-256 antes de abrir el archivo
De toda la página, esto es lo único que no es opcional. Es lo que impide que una descarga manipulada acabe ejecutándose en el equipo de alguien que confió en ti.
Si tu aplicación descarga el archivo ella sola, el orden es este:
- Si el
sha256de ese archivo viene anull, no lo descargues: avisa y abre la ficha en el navegador. Sin hash no hay descarga. - Descárgalo con un nombre temporal.
- Calcula el SHA-256 del archivo completo y compáralo con el del manifiesto. Si no coincide, bórralo y no digas que hay nada descargado.
- Sólo cuando coincida, renómbralo, enséñalo o ejecútalo. Nunca al revés, y nunca «se verifica mientras se instala».
Comprueba también que la URL de descarga es de fulgeon.com y por HTTPS, y pide sin seguir redirecciones: una redirección hacia otro dominio no es algo que un actualizador tenga que obedecer.
Un ejemplo de los pasos 2 a 4 en Python, sin dependencias:
import hashlib, urllib.request
f = manifiesto["ficheros"].get("windows-x86_64")
if not f or not f["sha256"]:
abrir_en_el_navegador(manifiesto["pagina"]) # sin hash no hay descarga
return
with urllib.request.urlopen(f["url"], timeout=30) as r:
datos = r.read()
if hashlib.sha256(datos).hexdigest() != f["sha256"]:
return # se descarta y no se dice que hay nada descargado
open(destino, "wb").write(datos) # sólo después de cuadrarQue se pueda apagar
Deja una casilla en los ajustes para desactivar la comprobación, y que lo que diga esa casilla se respete de verdad. Quien trabaja sin red, en una máquina aislada o simplemente no quiere que su aplicación hable con nadie, tiene motivos suficientes y no tiene por qué explicarlos.
Dicho de otra forma: si la aplicación promete «no manda nada a ninguna parte», el comprobador de actualizaciones es la excepción, y una excepción que no se puede apagar deja de ser una excepción.
Si falla, silencio
Sin internet, con el servidor caído, con un JSON roto o con un 404: no enseñes ningún error, no reintentes en bucle y no molestes a nadie. No enterarse hoy de que hay una versión nueva no es un problema del usuario.
Un par de defensas baratas mientras estás en ello: corta la respuesta a un tamaño razonable (un manifiesto sano no llega a 4 KB; nosotros cortamos a 128 KB) y trata como «no es más nueva» cualquier versión que no sepas interpretar. Lo que llega de la red no dimensiona nada dentro de tu aplicación.
Antes de prometer que se actualiza sola
Avisar de que hay una versión nueva lo puede hacer cualquier aplicación con lo de arriba. Instalarla sola no siempre se puede, y aquí es donde tropieza todo el mundo:
- macOS. La actualización automática de verdad (descargar, sustituir la aplicación y reabrirla sin que el usuario toque nada) exige que la versión nueva venga firmada con un Developer ID de Apple. El mecanismo del sistema comprueba que la copia nueva está firmada por el mismo equipo que la instalada, y si no lo está se niega. Una aplicación autofirmada o sin firmar no se actualiza sola en macOS por mucho código que le pongas.
- Windows. Técnicamente se puede sin certificado, pero piénsalo dos veces. Sin firma de código, lo único que respalda al binario descargado es el SHA-256 del manifiesto, servido por el mismo dominio que sirve el binario: es una garantía real contra la red, pero no contra quien se haga con el servidor. Y si tu instalador es interactivo (pregunta la carpeta, enseña un asistente), «actualizar sola» significa lanzarle al usuario el asistente entero por detrás, que no es una actualización silenciosa sino un susto.
- Linux. Un
.debo un.rpmlos instala el gestor de paquetes, no tu aplicación. Un AppImage sí puede reemplazarse a sí mismo, pero eso lo tienes que escribir tú.
Cuando no se pueda instalar solo, lo honesto es descargar y que el usuario confirme, o directamente avisar y abrir la ficha. Con el changelog delante y un botón, funciona en los tres sistemas y no rompe nada.
Y dilo así en la interfaz. Prometer una actualización automática que luego no ocurre es peor que no prometer nada: el usuario deja de mirar si hay versiones nuevas, convencido de que ya se encarga la aplicación.