Saltar al contenido principal

Crea una app de escritorio con Capacitor y Electron (2026)

Convierte tu app de Capacitor en una app de escritorio con Electron: configuración de shell segura, estrategia de plugins, empaquetado para macOS, Windows, Linux y Live Update.

Banner de Capgo para una guía sobre cómo crear una app de escritorio desde un proyecto de Capacitor con Electron

Para crear una app de escritorio a partir de una app Capacitor, envuelve el mismo build web (webDir) en un shell de Electron: un proceso principal que abre una BrowserWindow, un script de preload que expone una pequeña API de escritorio y electron-builder para generar instaladores para macOS, Windows y Linux. Tu UI y lógica de negocio se comparten con iOS y Android, y las funciones exclusivas de escritorio viven en el proceso principal de Electron.

Esta guía cubre el estado actual de Capacitor en Electron en 2026, una configuración segura que funciona con Capacitor 8, cómo se comportan los plugins, el empaquetado y la firma, y cómo publicar actualizaciones web sin nuevos instaladores.

El estado de Capacitor en Electron en 2026

Durante años, la respuesta por defecto era @capacitor-community/electron, que conectaba Electron al Capacitor CLI como una plataforma. Ese proyecto ahora está marcado como no mantenido en su propio README. Su última versión de npm, 5.0.1, se lanzó en septiembre de 2023, requiere Capacitor 5.4 o superior y fue construido contra Electron 26. Electron ha lanzado muchas versiones principales desde entonces, y solo las tres últimas reciben correcciones de seguridad.

Aún puedes usarlo en proyectos antiguos, pero para una app Capacitor 8 la comparación se ve así:

Enfoque Ventajas Contras
Tu propia shell de Electron (esta guía) Electron actual, control total, sin abstracciones extra Escribes unas 60 líneas de main y preload code
@capacitor-community/electron Familiar cap comandos Sin mantenimiento, fijado a versiones antiguas de Capacitor y Electron

El enfoque de shell requiere menos trabajo de lo que parece, porque Capacitor ya genera un build web estático. Electron solo necesita cargarlo.

Cómo se comportan los plugins en Electron

El renderer de Electron es Chromium, así que tu app de Capacitor se ejecuta allí como en un navegador. El core de Capacitor no detecta un bridge nativo y recurre a la implementación web de cada plugin.

Tipo de plugin ¿Funciona en Electron? Qué hacer
Plugin con implementación web (Preferences, Share, Clipboard, muchos plugins de Capgo) Sí, usando la versión web Probar el comportamiento web
Plugin solo nativo (sin implementación web) No, se rechaza como no implementado Escribir un equivalente de Electron detrás de un preload API
Librerías puras de JS Sí Nada

Capacitor.getPlatform() devuelve 'web' en esta configuración y Capacitor.isNativePlatform() devuelve false. Expondremos una bandera de escritorio explícita desde el script de preload para que tu code pueda ramificar limpiamente.

Requisitos

  • Una app de Capacitor cuyo bun run build genera una carpeta estática (la webDir en capacitor.config.ts, a menudo dist o www).
  • Bun y una versión LTS actual de Node.js, ya que las herramientas de Electron siguen ejecutándose en Node.
  • Para la distribución en macOS, un certificado Apple Developer ID. Para Windows, un certificado de firma code.

Paso 1: Instalar Electron y el empaquetador

bun add -d electron electron-builder

Añade un electron/ carpeta en la raíz del proyecto:

my-app/
  capacitor.config.ts
  dist/              # web build, shared with iOS and Android
  electron/
    main.ts
    preload.ts
  electron-builder.yml

Paso 2: Hacer que el build web se cargue desde disco

Electron carga archivos con el file:// protocolo en producción. Dos ajustes importan:

  1. Rutas de assets relativas. Con Vite, configura base: './' para que el build index.html referencie ./assets/... en lugar de /assets/....
  2. Routing. Deep links como /settings no existen como archivos. Usa hash routing para el build de escritorio o asegúrate de que tu router solo navegue en el cliente tras la carga index.html.
// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  base: './',
});

Las rutas relativas también funcionan en iOS y Android, así que no necesitas un build separado.

Paso 3: Escribir el proceso principal

// electron/main.ts
import { app, BrowserWindow, ipcMain, shell } from 'electron';
import path from 'node:path';

const isDev = !app.isPackaged;

async function createWindow() {
  const win = new BrowserWindow({
    width: 1280,
    height: 800,
    minWidth: 800,
    minHeight: 600,
    show: false,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
      sandbox: true,
    },
  });

  win.once('ready-to-show', () => win.show());

  // Open external links in the default browser, never inside the app.
  // Only allow https: so a script cannot launch file: or other protocol handlers.
  win.webContents.setWindowOpenHandler(({ url }) => {
    try {
      if (new URL(url).protocol === 'https:') void shell.openExternal(url).catch(() => {});
    } catch {
      // invalid URL: ignore
    }
    return { action: 'deny' };
  });

  if (isDev) {
    await win.loadURL('http://localhost:5173');
    win.webContents.openDevTools({ mode: 'detach' });
  } else {
    await win.loadFile(path.join(__dirname, '..', '..', 'dist', 'index.html'));
  }
}

ipcMain.handle('desktop:get-version', () => app.getVersion());

app.whenReady().then(createWindow);

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) createWindow();
});

Mantén contextIsolation: true, nodeIntegration: false, y sandbox: true. Tu web app puede cargar scripts de terceros, y ninguno de ellos debería tener acceso a Node.js.

Paso 4: Exponer un pequeño API de escritorio desde el preload

// electron/preload.ts
import { contextBridge, ipcRenderer } from 'electron';

contextBridge.exposeInMainWorld('desktop', {
  isElectron: true,
  platform: process.platform,
  getVersion: () => ipcRenderer.invoke('desktop:get-version') as Promise<string>,
});

Escríbelo en tu code web y úsalo de la misma forma que usarías un plugin:

// src/desktop.ts
declare global {
  interface Window {
    desktop?: {
      isElectron: boolean;
      platform: string;
      getVersion: () => Promise<string>;
    };
  }
}

export const isDesktop = () => Boolean(window.desktop?.isElectron);

Cada función nativa que necesites en desktop se convierte en una ipcMain.handle en el proceso principal y una función en el preload. Valida los argumentos en el proceso principal, ya que el renderer no es de confianza.

Paso 5: Build y ejecutar

Compila los archivos de Electron con Bun. El preload se ejecuta en un sandbox, así que empaquétalo como un único archivo CommonJS:

{
  "main": "electron/dist/main.js",
  "scripts": {
    "build": "vite build",
    "electron:compile": "bun build electron/main.ts --outdir electron/dist --target node --format cjs --external electron && bun build electron/preload.ts --outdir electron/dist --target node --format cjs --external electron",
    "electron:dev": "bun run electron:compile && electron .",
    "electron:pack": "bun run build && bun run electron:compile && electron-builder"
  }
}

Durante el desarrollo, inicia tu servidor de desarrollo en una terminal (bun run dev) y bun run electron:dev en otra. Tendrás hot reload en la ventana de escritorio.

Paso 6: Empaquetar instaladores con electron-builder

# electron-builder.yml
appId: com.example.myapp
productName: My App
directories:
  output: release
files:
  - dist/**/*
  - electron/dist/**/*
  - package.json
mac:
  target: [dmg, zip]
  category: public.app-category.productivity
  hardenedRuntime: true
win:
  target: [nsis]
linux:
  target: [AppImage, deb]
  category: Utility

Ejecuta bun run electron:pack. electron-builder genera builds para el SO en el que se ejecuta. Usa una matriz de runners de macOS, Windows y Linux en CI para producir los tres.

SO Objetivos comunes Firma
macOS .dmg, .zip Certificado Developer ID más notarización, o Gatekeeper bloqueará la app
Windows NSIS .exe, MSI certificado de firma de Code para evitar los avisos de SmartScreen
Linux AppImage, .deb, .rpm Opcional

Las aplicaciones Electron son más grandes que las builds móviles porque incluyen Chromium y Node.js. Espera instaladores de entre 80 y 150 MB, según los objetivos.

Paso 7: Publicar actualizaciones web sin nuevos instaladores

La mayoría de los cambios en una app de Capacitor son cambios web. En móvil puedes desplegarlos con actualizaciones Live Update de Capgo. En escritorio, @capgo/electron-updater hace el mismo trabajo con el mismo modelo: canales, un bundle integrado, bundles descargados y rollback automático si el nuevo bundle no llama a notifyAppReady().

bun add @capgo/electron-updater

Actualiza el proceso principal para que el updater elija el bundle a cargar:

// electron/main.ts (production branch)
import { ElectronUpdater, setupIPCHandlers, setupEventForwarding } from '@capgo/electron-updater';

const updater = new ElectronUpdater({
  appId: 'com.example.myapp',
  autoUpdate: true,
});

// inside createWindow(), instead of loading dist/index.html directly
const builtinPath = path.join(__dirname, '..', '..', 'dist', 'index.html');
await updater.initialize(win, builtinPath);
setupIPCHandlers(updater);
setupEventForwarding(updater, win);
await win.loadFile(updater.getCurrentBundlePath());

Añade el updater bridge al mismo archivo preload:

// electron/preload.ts (next to the desktop API)
import { exposeUpdaterAPI } from '@capgo/electron-updater/preload';

exposeUpdaterAPI();

Y confirma un arranque correcto desde la web app, solo en escritorio:

import { isDesktop } from './desktop';

if (isDesktop()) {
  const { requireUpdater } = await import('@capgo/electron-updater/renderer');
  await requireUpdater().notifyAppReady();
}

Sube un nuevo bundle con el mismo CLI que usas para móvil:

bun run build
bunx @capgo/cli@latest bundle upload --channel=production

Las actualizaciones web no pueden modificar Electron, el proceso principal ni el preload. Para eso, distribuye un nuevo instalador con un auto-updater de binarios. Nuestra guía sobre Auto-actualizaciones de Electron cubre esa ruta, y la página del plugin electron-updater y docs cubren todas las opciones.

Solución de problemas

Ventana en blanco en la app empaquetada. Los assets usan rutas absolutas. Configura base: './' y reconstruye. Abre DevTools en la app empaquetada para confirmar 404s en file:///assets/....

Las rutas muestran una página en blanco tras recargar. El router intentó cargar /route desde el disco. Cambia a hash routing para desktop.

require is not defined o errores de preload. El preload en sandbox no puede cargar paquetes arbitrarios en runtime. Haz un bundle en un único archivo, como en el script de compilación de arriba.

Una llamada a un plugin se rechaza con “not implemented on web”. Ese plugin no tiene implementación web. Añade un IPC handler en el proceso main y llámalo a través de window.desktop.

macOS dice que la app está dañada o es de un desarrollador no identificado. La app no está firmada ni notarizada. Configura un certificado Developer ID y la notarización en electron-builder.

Updates roll back right after install. notifyAppReady() no se llamó dentro del timeout (10 segundos por defecto). Llámalo al principio del startup de tu app.

Resumen

No necesitas un paquete de plataforma Capacitor dedicado para llegar a desktop. Un proceso main de Electron corto, un preload que exponga un API tipado, rutas de assets relativas y electron-builder te dan instaladores para los tres sistemas operativos desde el mismo build web. Añade @capgo/electron-updater y la app de desktop tiene el mismo workflow de live update que tus builds de iOS y Android. Si aún estás eligiendo un runtime de desktop, lee nuestro Comparativa de Electron y Tauri.

Live updates para apps Capacitor

Cuando un bug de la capa web está en producción, publica el fix a través de Capgo en lugar de esperar días a la aprobación de la app store. Los usuarios reciben la actualización en segundo plano mientras los cambios nativos siguen el flujo de revisión habitual.

Lo último de nuestro blog

Capgo te da la información clave que necesitas para crear una app móvil realmente profesional.