# Un programa, tres apps — web, teléfono y escritorio desde el mismo archivo de Synsema

> Una app de Synsema es un sitio web con HTTPS automático, una app instalable en el teléfono con push nativo y una app de escritorio en su propia ventana con tu ícono — desde un programa renderizado en el servidor y un módulo de API. Sin toolkit nativo, sin segundo código, sin tienda obligatoria.

Published 2026-09-02 · https://synsema.org/es/blog/one-app-web-phone-desktop


La forma habitual de llegar a tres plataformas son tres bases de código, o un framework que envuelve
un navegador y te pide que lo aprendas. Synsema toma el otro camino: la app es un programa
renderizado en el servidor, y cada plataforma es una manera de *distribuir el mismo programa*. El
sitio, la app del teléfono y la de escritorio comparten las rutas, las plantillas, las capacidades y
los secretos, porque son un archivo con tres entradas.

## Tres destinos, un archivo

```sh
synsema init myapp --desktop && cd myapp                          # the scaffold: app.syn, desk.syn, api.syn, public/

synsema serve app.syn --domain app.example.com --tls-auto you@example.com   # the WEBSITE, with Let's Encrypt
                                                                  # the PHONE: the same site installs from the browser (PWA)
synsema build desk.syn -o desk --serve --no-console --icon public/icon.svg  # the DESKTOP app (Windows: desk.exe)
synsema build desk.syn -o desk --serve --icon public/icon.svg --bundle      # macOS .app / Linux folder + install.sh
```

El sitio es `app.syn` bajo `serve`: HTTPS con una bandera, la página renderizada en el servidor, cada
ruta respondiendo también Markdown y JSON a los agentes. La app del teléfono es el mismo sitio: un
manifiesto, un service worker y los íconos hacen que se instale en Android y en iOS con ícono en la
pantalla de inicio, pantalla completa, un shell offline honesto y Web Push nativo — eso es lo que
genera `--pwa`. La app de escritorio es `desk.syn`: la misma página y la misma API atadas a
`127.0.0.1`, abiertas como ventana de app del navegador que ya está en la máquina, con tu ícono en
el ejecutable y sin consola; termina cuando se cierra la última ventana.

## La misma API en todas partes

La API vive una sola vez, en `api.syn`, como un grupo de rutas:

```synsema
-- api.syn
export routes api
    route "GET /api/ping"
        give ok({"pong": true, "at": now()})
    route "POST /api/push/subscribe"
        rate_limit 10 per minute
        expect body {endpoint: text, keys: map}
        ...
```

Las dos entradas lo montan — `mount api.api` — y cada una declara las capacidades que las rutas
necesitan (`require net("fcm.googleapis.com")`, `require secret("VAPID_PRIVATE_KEY")`): un módulo no
lleva `require`, lo lleva la entrada. Los límites de tasa y los timeouts por ruta viajan con el
grupo, así que el servidor público y la app de escritorio local hacen cumplir las mismas reglas. En
una construcción con `--sandbox` o `--cap-set`, el techo viaja con el binario y no se puede levantar
desde adentro.

## Qué recibe cada destino, con honestidad

| | Sitio web | Teléfono (PWA) | Escritorio |
|---|---|---|---|
| Cómo corre | `synsema serve` en tu servidor | el mismo servidor, instalado desde el navegador | un binario en la máquina del usuario |
| Ícono | favicon | ícono de pantalla de inicio, del manifiesto | el ícono del ejecutable (`--icon`), el de la ventana desde la página |
| Ventana | pestaña del navegador | pantalla completa, sin cromo del navegador | ventana de app del navegador, sin barra de direcciones ni consola |
| Push | — | Web Push nativo (Android; iOS una vez instalada, sobre HTTPS) | las notificaciones del navegador y Web Push, mientras el proceso corre |
| Offline | — | el shell abre sin red | el servidor es local |
| Ciclo de vida | lo corrés vos | el teléfono corre el navegador | termina al cerrar la última ventana, o a los 30 s sin ventana |
| Verificado | producción | Android y Windows reales | en vivo en Windows 11; macOS por CI en Apple Silicon; Linux por tests |

Dónde se corta: no hay bandeja del sistema (no existe API web para eso), Firefox no tiene modo app
(una pestaña), iOS necesita un certificado confiable para el service worker y el push, y un `.app`
de macOS descargado sin Developer ID se encuentra con Gatekeeper como cualquier app sin firmar. Las
tiendas son opcionales: la Microsoft Store acepta una PWA directamente, Play la acepta a través de
una Trusted Web Activity y la App Store todavía quiere un envoltorio nativo — nada de lo cual el
lenguaje pretende ocultar.

## Por qué se sostiene

Renderizar en el servidor significa que el teléfono y el escritorio muestran lo que decidió el
servidor, no una copia de la lógica. La negociación de contenido significa que las mismas rutas son
una API para agentes. Las capacidades deny-by-default significan que el binario de escritorio que le
das a alguien solo toca lo que dicen sus líneas `require`, y los secretos quedan sellados dentro del
proceso en todas las plataformas. Y `synsema build` significa que todo el conjunto — motor,
programa, plantillas, `public/` — es un archivo para regalar.

**Probalo:** [instalá Synsema](/es/install) (0.6.19 o más nuevo), `synsema init myapp --desktop`, y
después `synsema serve app.syn` para el sitio y `synsema serve desk.syn` para la ventana. Las dos
guías — [Tu app en el teléfono](https://synsema.dev/es/0.6.x/41b-pwa) y
[Tu app en el escritorio](https://synsema.dev/es/0.6.x/41c-desktop) — listan cada archivo que
escribe el andamiaje y cada bandera, con qué está verificado en dónde.

