Distribución de Apps de Escritorio y Web: Microsoft Store, Mac App Store, PWA y Más

Distribución de Apps de Escritorio y Web: Microsoft Store, Mac App Store, PWA y Más

Guía completa para distribuir apps de escritorio y web: MSIX de Microsoft Store, Mac App Store, notarización de macOS, Electron, PWA, y pipelines de despliegue web.

Por Omar Flores

La distribución móvil tiene Google Play y la App Store — dos tiendas, dos pipelines, caminos bien documentados. La distribución de escritorio y web es un paisaje diferente: cinco o seis canales superpuestos, cada uno con sus propios requisitos de firma, mecanismos de actualización y procesos de revisión. Un desarrollador que publica una app de escritorio multiplataforma en 2026 necesita entender el empaquetado MSIX de Windows, la notarización de macOS, la actualización automática de Electron, la instalabilidad de PWA y el despliegue web — todo al mismo tiempo.

Este post cubre cada canal desde la perspectiva de lo que un desarrollador realmente necesita hacer: las cuentas, las herramientas, la configuración de firma, el proceso de envío y la estrategia de actualización. Ningún post puede ser exhaustivo en cada plataforma, pero este es completo suficiente para llevar una app de producción a través de cada camino de distribución sin toparse con los obstáculos más comunes.


El Panorama de Distribución

Antes de la mecánica, ayuda ver el panorama completo de lo que puedes elegir:

CanalSORevisiónMecanismo de actualizaciónCosto
Microsoft StoreWindowsStore o delta MSIXCuenta gratis, $19 único
Mac App StoremacOSApp Store$99/año (compartido con iOS)
macOS Directo (notarizado)macOSNo (verificación Gatekeeper)Personalizado (Sparkle, etc.)$99/año (cert)
Windows Directo (firmado)WindowsNoSquirrel / personalizadoCertificado de firma requerido
Electron + GitHub ReleasesAmbosNoelectron-updaterGratis
PWACualquiera (navegador)NoService workerGratis
Web (Netlify/Vercel/etc.)NavegadorNoPipeline de despliegueTier gratuito disponible

La mayoría de las apps de escritorio usan más de uno: la tienda para visibilidad, descarga directa para usuarios que lo prefieren, y actualización automática para que los usuarios existentes reciban nuevas versiones sin volver a la tienda.


Microsoft Store

Configuración de Cuenta

Crea una cuenta de desarrollador en partner.microsoft.com/dashboard. Las cuentas individuales cuestan $19 únicos. Las cuentas empresariales cuestan $99 únicos y requieren un número D-U-N-S para verificación — similar al requisito de Apple.

La Microsoft Store permite tanto apps Win32 como apps UWP distribuidas como paquetes MSIX. Desde Windows 10 1903, puedes empaquetar una aplicación Win32 tradicional como MSIX sin reescribirla. Este es el camino para la mayoría de los desarrolladores de escritorio que vienen de apps Win32 existentes.

Empaquetado MSIX

MSIX es el formato de empaquetado moderno de Microsoft. Proporciona instalación/desinstalación limpia, actualizaciones automáticas via la tienda y un directorio de instalación aislado. Para apps distribuidas fuera de la tienda, también habilita actualizaciones delta.

Para una app Electron o Win32 genérica, usa la Herramienta de Empaquetado MSIX (de la Microsoft Store en Windows) o electron-builder con el target appx.

Para una app .NET / WPF / WinForms nativa en Visual Studio:

<!-- Package.appxmanifest -->
<Identity
  Name="TuEmpresa.TuApp"
  Publisher="CN=TuCN, O=TuEmpresa, L=Ciudad, S=Estado, C=MX"
  Version="2.1.0.0" />

<Properties>
  <DisplayName>Tu App</DisplayName>
  <PublisherDisplayName>Tu Empresa</PublisherDisplayName>
  <Logo>Assets\StoreLogo.png</Logo>
</Properties>

<Capabilities>
  <Capability Name="internetClient" />
  <!-- añade solo lo que la app realmente necesita -->
</Capabilities>

El valor Publisher en el manifiesto debe coincidir con el CN del editor en tu certificado de firma de código. Si envías a la Microsoft Store, debe coincidir con la identidad del editor en tu cuenta de Partner Center.

Compilando el MSIX:

# Con msbuild
msbuild TuApp.sln /p:Configuration=Release /p:Platform=x64 /p:AppxBundle=Always

# Con dotnet
dotnet publish -c Release -r win-x64 --self-contained true

Firma de Código para Windows

Para distribución directa (fuera de la tienda), tu MSIX o instalador debe estar firmado con un certificado de firma de código confiable. Los instaladores sin firma activan una advertencia de SmartScreen que hace que muchos usuarios abandonen la instalación.

Opciones para certificados de firma de código:

  • Certificado EV (Validación Extendida) — confianza inmediata, sin advertencia de SmartScreen, más caro (~$300–500/año), requiere token de hardware
  • Certificado OV (Validación de Organización) — confianza después de que se construye reputación a través de descargas, más barato (~$100–200/año)
  • Azure Trusted Signing — nuevo servicio de firma en la nube de Microsoft, menor costo, sin token de hardware, la confianza de SmartScreen se construye automáticamente

Firmando con signtool (incluido en el SDK de Windows):

# Firmar el MSIX
signtool sign `
  /fd SHA256 `
  /a `
  /f certificate.pfx `
  /p $env:CERT_PASSWORD `
  /tr http://timestamp.digicert.com `
  /td SHA256 `
  TuApp.msix

# Verificar la firma
signtool verify /pa /v TuApp.msix

Siempre incluye un timestamp (/tr) al firmar. Sin él, la firma se vuelve inválida cuando el certificado expira, incluso para archivos ya distribuidos.

Envío a la Microsoft Store

En Partner Center:

  1. Crear una nueva app — reserva el nombre de la app. El nombre se reserva globalmente en todos los dispositivos Windows una vez que lo reservas.
  2. Clasificaciones por edad — completa el cuestionario IARC (mismo sistema que Google Play).
  3. Propiedades — categoría, subcategoría, contacto de soporte, URL de política de privacidad.
  4. Listado en tienda — descripción (hasta 10,000 caracteres), lista de características (hasta 20 ítems, 200 caracteres cada uno), capturas de pantalla (mínimo 1, recomendado 4–8 a 1366×768 o mayor), ícono de tile de la app (300×300px).
  5. Paquetes — sube el archivo MSIX. Partner Center valida el manifiesto.
  6. Envío — la revisión típicamente tarda 1–5 días hábiles.

Razones comunes de rechazo:

  • Capacidades del manifiesto que no coinciden con el comportamiento real de la app
  • La app crashea al iniciar — Microsoft ejecuta pruebas automatizadas
  • Política de privacidad faltante para apps que se conectan a internet
  • Capturas de pantalla que no representan la aplicación real
  • App que es solo un wrapper web sin valor añadido

Distribución Directa en Windows (Fuera de la Tienda)

Para usuarios que prefieren descargas directas, proporciona un MSIX firmado o un instalador .exe firmado. Los dos frameworks de instaladores más comunes:

Inno Setup — ligero, con scripts, ampliamente usado para apps Win32:

[Setup]
AppName=Tu App
AppVersion=2.1.0
AppPublisher=Tu Empresa
DefaultDirName={autopf}\TuApp
DefaultGroupName=TuApp
OutputBaseFilename=TuApp-Setup-2.1.0
Compression=lzma2
SolidCompression=yes
SignTool=signtool sign /fd SHA256 /a /f "cert.pfx" /p "$ENV:CERT_PASSWORD" /tr http://timestamp.digicert.com /td SHA256 $f

[Files]
Source: "dist\*"; DestDir: "{app}"; Flags: recursesubdirs

[Icons]
Name: "{group}\TuApp"; Filename: "{app}\TuApp.exe"
Name: "{commondesktop}\TuApp"; Filename: "{app}\TuApp.exe"

[Run]
Filename: "{app}\TuApp.exe"; Description: "Iniciar TuApp"; Flags: nowait postinstall skipifsilent

Mac App Store

Opciones de Distribución en macOS

macOS tiene dos caminos de distribución distintos que requieren la misma membresía de Apple Developer Program ($99/año, compartida con iOS):

Mac App Store — revisado por Apple, instalado via la App Store, en sandbox. Más restrictivo pero más confiable y visible.

Developer ID — firmado y notarizado por Apple pero distribuido directamente (tu sitio web, GitHub releases, etc.). Sin requisito de sandbox, sin revisión, pero requiere notarización para que Gatekeeper permita la instalación.

La mayoría de las apps de escritorio profesionales usan ambos: la Mac App Store para usuarios que la prefieren, y un .dmg notarizado para descarga directa para usuarios que necesitan capacidades que el sandbox restringe.

Envío a la Mac App Store

La Mac App Store usa la misma infraestructura de App Store Connect que iOS. Las diferencias:

  • El build es un bundle de aplicación macOS (.app) archivado y subido via Xcode o Transporter
  • Los entitlements de sandbox son requeridos — la app debe declarar lo que necesita
  • Las capturas de pantalla deben ser de 1280×800 o 1440×900
  • La app no debe contener código de actualización automática que evite el mecanismo de actualización de la Mac App Store

Entitlements para un build de Mac App Store:

<!-- TuApp.entitlements (distribución App Store) -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <!-- requerido para todas las apps de Mac App Store -->
    <key>com.apple.security.app-sandbox</key>
    <true/>

    <!-- acceso a red -->
    <key>com.apple.security.network.client</key>
    <true/>

    <!-- leer archivos que el usuario selecciona -->
    <key>com.apple.security.files.user-selected.read-write</key>
    <true/>
</dict>
</plist>

Notarización para Distribución Directa

Para apps distribuidas fuera de la Mac App Store, la notarización es requerida en macOS 10.15 y posterior. Sin ella, Gatekeeper bloquea la app con una advertencia que los usuarios no pueden evitar fácilmente.

La notarización es el escaneo automatizado de malware de Apple. Envías el binario, Apple lo escanea y devuelve un ticket. Luego grapas el ticket a la app para que pueda verificarse sin conexión.

El flujo de notarización:

# 1. Compilar y firmar con certificado Developer ID
codesign --deep --force --verify --verbose \
  --sign "Developer ID Application: Tu Empresa (TEAMID)" \
  --options runtime \
  --entitlements TuApp.entitlements \
  TuApp.app

# 2. Crear DMG para notarización
hdiutil create -volname "TuApp" -srcfolder TuApp.app \
  -ov -format UDZO TuApp.dmg

# 3. Enviar para notarización
xcrun notarytool submit TuApp.dmg \
  --apple-id "tu@ejemplo.com" \
  --password "$APP_SPECIFIC_PASSWORD" \
  --team-id "TEAMID" \
  --wait

# 4. Grapar el ticket al DMG
xcrun stapler staple TuApp.dmg

# 5. Verificar
xcrun stapler validate TuApp.dmg
spctl --assess --verbose --type install TuApp.dmg

Usa una contraseña específica de app (no tu contraseña de Apple ID) para notarytool. Generala en appleid.apple.com bajo Contraseñas Específicas de App.

El Runtime Endurecido (--options runtime en codesign) es requerido para la notarización. Si tu app usa bibliotecas dinámicas, compilación JIT o ciertos recursos del sistema, puede que necesites añadir excepciones en los entitlements:

<!-- TuApp.entitlements (Developer ID / notarización) -->
<dict>
    <!-- si la app usa un compilador JIT (ej. V8 de Electron) -->
    <key>com.apple.security.cs.allow-jit</key>
    <true/>

    <!-- si la app carga bibliotecas sin firmar -->
    <key>com.apple.security.cs.disable-library-validation</key>
    <true/>
</dict>

Solo añade excepciones que realmente necesites. Cada una es una superficie de seguridad potencial que el servicio de notarización de Apple evalúa.


Electron: Escritorio Multiplataforma

Las apps Electron corren en Windows, macOS y Linux desde una sola base de código. La complejidad de distribución viene de necesitar firmar, empaquetar y actualizar para cada plataforma de forma independiente.

electron-builder

electron-builder es la herramienta estándar de empaquetado y distribución para apps Electron. Maneja MSIX/NSIS para Windows, DMG/PKG para macOS, AppImage/deb/rpm para Linux, e integra la actualización automática.

npm install --save-dev electron-builder

Configuración de build en package.json:

{
  "build": {
    "appId": "com.tuempresa.tuapp",
    "productName": "Tu App",
    "copyright": "Copyright © 2026 Tu Empresa",
    "directories": {
      "output": "dist"
    },
    "files": [
      "dist/**/*",
      "node_modules/**/*",
      "package.json"
    ],
    "win": {
      "target": [
        { "target": "nsis", "arch": ["x64", "arm64"] },
        { "target": "appx", "arch": ["x64"] }
      ],
      "certificateFile": "cert.pfx",
      "certificatePassword": "${env.CERT_PASSWORD}",
      "publisherName": "Tu Empresa",
      "timeStampServer": "http://timestamp.digicert.com"
    },
    "mac": {
      "target": [
        { "target": "dmg", "arch": ["x64", "arm64"] },
        { "target": "zip", "arch": ["x64", "arm64"] }
      ],
      "category": "public.app-category.productivity",
      "identity": "Developer ID Application: Tu Empresa (TEAMID)",
      "hardenedRuntime": true,
      "gatekeeperAssess": false,
      "entitlements": "build/entitlements.mac.plist",
      "entitlementsInherit": "build/entitlements.mac.plist",
      "notarize": {
        "teamId": "TEAMID"
      }
    },
    "linux": {
      "target": ["AppImage", "deb"],
      "category": "Utility"
    },
    "publish": {
      "provider": "github",
      "owner": "tuorg",
      "repo": "tuapp"
    }
  }
}

Actualización Automática con electron-updater

electron-updater (parte de electron-builder) implementa la actualización automática usando GitHub Releases, S3 o un servidor personalizado como fuente de actualización.

// main/updater.js
const { autoUpdater } = require('electron-updater')
const { app, dialog } = require('electron')
const log = require('electron-log')

autoUpdater.logger = log
autoUpdater.logger.transports.file.level = 'info'

function initAutoUpdater() {
  autoUpdater.checkForUpdatesAndNotify()

  autoUpdater.on('update-available', (info) => {
    log.info('Actualización disponible:', info.version)
  })

  autoUpdater.on('update-downloaded', (info) => {
    dialog.showMessageBox({
      type: 'info',
      title: 'Actualización Lista',
      message: `La versión ${info.version} ha sido descargada. Reinicia para instalar.`,
      buttons: ['Reiniciar Ahora', 'Después']
    }).then(({ response }) => {
      if (response === 0) autoUpdater.quitAndInstall()
    })
  })

  autoUpdater.on('error', (err) => {
    log.error('Error de actualización automática:', err)
  })
}

module.exports = { initAutoUpdater }

GitHub Actions para Releases de Electron

# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    strategy:
      matrix:
        os: [macos-latest, windows-latest, ubuntu-latest]
    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '22'

      - run: npm ci

      - name: Build (macOS)
        if: matrix.os == 'macos-latest'
        env:
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          CSC_LINK: ${{ secrets.MAC_CERT_BASE64 }}
          CSC_KEY_PASSWORD: ${{ secrets.MAC_CERT_PASSWORD }}
        run: npx electron-builder build --mac --publish always

      - name: Build (Windows)
        if: matrix.os == 'windows-latest'
        env:
          CERT_PASSWORD: ${{ secrets.WIN_CERT_PASSWORD }}
        run: |
          echo "${{ secrets.WIN_CERT_BASE64 }}" | base64 -d > cert.pfx
          npx electron-builder build --win --publish always

      - name: Build (Linux)
        if: matrix.os == 'ubuntu-latest'
        run: npx electron-builder build --linux --publish always

El flag --publish always sube los artefactos directamente al GitHub Release. El workflow se dispara con tags de versión que creas al estar listo para lanzar:

git tag v2.1.0
git push origin v2.1.0

Progressive Web Apps (PWA)

Una PWA es una app web que usa APIs del navegador para proporcionar una experiencia similar a una app nativa: instalable en la pantalla de inicio o barra de tareas, capacidad offline, notificaciones push y sincronización en segundo plano. La distribución no requiere envío a ninguna tienda.

Requisitos de PWA

Para que una app web sea instalable como PWA, debe cumplir estos criterios en Chrome/Edge:

  1. Servida sobre HTTPS
  2. Tiene un Web App Manifest válido
  3. Tiene un Service Worker registrado con un manejador de eventos fetch
  4. El manifiesto tiene start_url, icons (al menos 192×192 y 512×512), display: standalone o fullscreen
  5. No está ya instalada

Web App Manifest (/manifest.json):

{
  "name": "Tu Aplicación",
  "short_name": "TuApp",
  "description": "Lo que hace tu app, usado en tiendas",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#2196f3",
  "orientation": "any",
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ],
  "screenshots": [
    {
      "src": "/screenshots/desktop.png",
      "sizes": "1280x720",
      "type": "image/png",
      "form_factor": "wide"
    },
    {
      "src": "/screenshots/mobile.png",
      "sizes": "390x844",
      "type": "image/png",
      "form_factor": "narrow"
    }
  ],
  "shortcuts": [
    {
      "name": "Nueva Tarea",
      "url": "/tasks/new",
      "icons": [{ "src": "/icons/new-task.png", "sizes": "96x96" }]
    }
  ]
}

Enlázalo en el <head> de tu HTML:

<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#2196f3" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />

Service Worker — el mínimo viable para instalabilidad:

// public/sw.js
const CACHE_NAME = 'v2.1.0'
const STATIC_ASSETS = [
  '/',
  '/manifest.json',
  '/icons/icon-192.png',
  '/icons/icon-512.png'
]

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS))
  )
  self.skipWaiting()
})

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
    )
  )
  self.clients.claim()
})

self.addEventListener('fetch', (event) => {
  // network first para llamadas API, cache first para assets estáticos
  if (event.request.url.includes('/api/')) {
    event.respondWith(
      fetch(event.request).catch(() => caches.match(event.request))
    )
  } else {
    event.respondWith(
      caches.match(event.request).then((cached) => cached || fetch(event.request))
    )
  }
})

Registra el service worker en el punto de entrada de tu app:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js')
      .then((reg) => console.log('SW registrado:', reg.scope))
      .catch((err) => console.error('Registro de SW fallido:', err))
  })
}

Manejando el Prompt de Instalación

Los navegadores disparan el evento beforeinstallprompt antes de mostrar la UI de instalación. Puedes interceptarlo y mostrar tu propio botón de instalación en el momento correcto:

let deferredPrompt

window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault()
  deferredPrompt = e
  document.getElementById('install-btn').style.display = 'block'
})

document.getElementById('install-btn').addEventListener('click', async () => {
  if (!deferredPrompt) return
  deferredPrompt.prompt()
  const { outcome } = await deferredPrompt.userChoice
  console.log('Resultado de instalación:', outcome)
  deferredPrompt = null
  document.getElementById('install-btn').style.display = 'none'
})

window.addEventListener('appinstalled', () => {
  deferredPrompt = null
})

Muestra el botón de instalación después de que el usuario haya interactuado con la app de manera significativa — no en la primera carga. El navegador no disparará beforeinstallprompt de nuevo si el usuario lo descarta sin un tiempo de espera.

Microsoft Edge Add-ons Store (PWA)

Microsoft Edge soporta enviar PWAs a la Microsoft Store via el programa Edge Add-ons. Esto le da a tu PWA presencia en la tienda en Windows sin necesidad de un paquete MSIX.

En Partner Center, crea una nueva app con la categoría PWA. Proporciona tu URL HTTPS — Microsoft la rastrea, valida el manifiesto y el service worker, y crea un listado. Los usuarios pueden instalar directamente desde la tienda y aparece en la barra de tareas de Windows como una app nativa.

Requisitos:

  • URL HTTPS válida
  • Web App Manifest con los campos requeridos
  • Service worker registrado y funcional
  • URL de política de privacidad

Pipelines de Despliegue Web

Para apps web y PWAs, el pipeline de despliegue es el mecanismo de distribución. Cada push a la rama de producción es un lanzamiento.

Netlify

Netlify es la plataforma de hosting más común para sitios estáticos y JAMstack. Maneja CDN, SSL, vistas previas de ramas y manejo de formularios.

# netlify.toml
[build]
  command = "bun run build"
  publish = "dist"
  environment = { NODE_VERSION = "22" }

[build.environment]
  ASTRO_TELEMETRY_DISABLED = "1"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200    # fallback para SPA

[[headers]]
  for = "/manifest.json"
  [headers.values]
    Content-Type = "application/manifest+json"
    Cache-Control = "public, max-age=86400"

[[headers]]
  for = "/sw.js"
  [headers.values]
    Cache-Control = "no-cache"    # siempre obtener service worker fresco

[[headers]]
  for = "/*.js"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

El service worker debe servirse con no-cache — si el navegador cachea el archivo del service worker, los usuarios no reciben actualizaciones. Los nombres de archivo de assets con hash (.js?hash=abc123) pueden cachearse agresivamente; el SW mismo no puede.

Vercel

Vercel está optimizado para Next.js pero funciona con cualquier framework. Configuración via vercel.json:

{
  "buildCommand": "bun run build",
  "outputDirectory": "dist",
  "framework": "astro",
  "headers": [
    {
      "source": "/sw.js",
      "headers": [{ "key": "Cache-Control", "value": "no-cache" }]
    },
    {
      "source": "/(.*)\\.js",
      "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }]
    }
  ],
  "rewrites": [
    { "source": "/(.*)", "destination": "/index.html" }
  ]
}

Rollback

Cada despliegue debe ser reversible en menos de 60 segundos:

# Netlify
netlify rollback

# Vercel
vercel rollback <deployment-url>

Para despliegues auto-hospedados, usa blue-green o rollback basado en tags:

# Docker + nginx blue-green
docker pull turegistro/tuapp:v2.1.0
docker stop app-green
docker run -d --name app-green -p 3001:3000 turegistro/tuapp:v2.1.0
# verificación de salud
curl -f http://localhost:3001/health || exit 1
# cambiar upstream de nginx
nginx -s reload
# detener contenedor viejo
docker stop app-blue

Versionado Entre Canales

Cuando distribuyes por múltiples canales, los números de versión deben ser consistentes y significativos para los usuarios — aunque los números de build internos difieran por plataforma.

Usa versionado semántico (MAYOR.MENOR.PARCHE) para la versión visible al usuario en todos los canales:

2.1.0 → misma cadena en:
  - "version" en package.json
  - Version de MSIX de Windows (2.1.0.0 — cuarto componente debe ser 0)
  - CFBundleShortVersionString de macOS
  - versionName de Android
  - CFBundleShortVersionString de iOS
  - "version" en el manifest web (si se incluye)

Los contadores de build internos (versionCode, CFBundleVersion, número de build de Windows) pueden diferir por plataforma — son contadores monotónicos legibles por máquina, no la versión orientada al usuario.

Etiqueta los lanzamientos en git con la versión orientada al usuario:

git tag v2.1.0
git push origin v2.1.0

Tu pipeline de CI se dispara con este tag y compila todos los artefactos de plataforma simultáneamente, subiendo cada uno a su canal de distribución respectivo.


Comunicación de Actualizaciones

Los usuarios necesitan saber cuándo hay actualizaciones disponibles. La mecánica difiere por canal — las apps de tienda se actualizan automáticamente o con una notificación, las apps de descarga directa necesitan un mecanismo explícito de actualización — pero el principio de comunicación es el mismo: dile a los usuarios qué cambió, por qué importa y si se requiere reinicio.

Notas de versión escritas para usuarios, no para desarrolladores:

Versión 2.1.0

Nuevo: Vista de calendario para tareas — ve tu semana de un vistazo en la pantalla de Hoy
Nuevo: Tareas recurrentes — configura repeticiones diarias, semanales o mensuales desde el formulario
Mejorado: La búsqueda ahora encuentra tareas por descripción, no solo por título
Corregido: Las tareas con títulos largos ya no se desbordan de la tarjeta en la vista de lista
Corregido: La app restaura correctamente el último proyecto abierto al reiniciar en Windows

Cada línea comienza con el impacto al usuario (Nuevo, Mejorado, Corregido), no con el detalle técnico. El commit interno “fix: truncar título en 48 chars en componente TaskCard” se convierte en “Las tareas con títulos largos ya no se desbordan de la tarjeta.”


La distribución es infraestructura. Es la capa entre el código que escribes y las personas que lo usan. Cada plataforma en este post — la Microsoft Store, la Mac App Store, la actualización automática de Electron, los service workers de PWA, los despliegues de Netlify — tiene su propio costo de mantenimiento y sus propios modos de falla. Los desarrolladores que manejan esto bien son los que automatizaron las partes repetitivas, configuraron el monitoreo para cada canal, y construyeron el rollback en el proceso antes de necesitarlo.

Publicar no es el fin del trabajo. Es el momento en que el trabajo se vuelve real. Construye el pipeline antes de necesitarlo, y mantenlo como la infraestructura que es.

Tags

#devops #tutorial #guide #best-practices #tips #frontend