# Convertí un sitio renderizado en el servidor en una app instalable (PWA) con push nativo

> synsema init --pwa le da a cualquier sitio de Synsema un manifiesto, un service worker, íconos y Web Push. Se instala en Android, iOS y escritorio con ícono en la pantalla de inicio y pantalla completa — nada nativo, sin tienda.

Published 2026-09-02 · https://synsema.org/es/blog/installable-app-pwa-native-push


Una app web instalable necesita tres cosas que un sitio normal no tiene: un manifiesto, un service
worker y unas líneas en el `<head>`. Synsema genera todo eso, y las notificaciones push encima, así
que un sitio que renderiza en el servidor se vuelve una app en el teléfono sin cliente nativo.

## Generalo

```sh
synsema init myapp --pwa && cd myapp
synsema serve app.syn
```

Obtenés `public/manifest.webmanifest`, `public/sw.js` (cachea el shell, nunca `/api/`, nunca una
respuesta fallida), `public/app.js` (registra el worker, muestra el botón de instalar, se suscribe al
push), un ícono SVG con los tamaños PNG generados a partir de él, la API en `api.syn` (un grupo
`export routes` con las rutas de push y sus límites de tasa por ruta, montado por `app.syn` — desde
0.6.19) y un `push_keys.syn` de una sola vez que imprime tu par de claves VAPID para el `.env`.

Del lado del servidor es el `serve` que ya conocés:

```synsema
require serve(8080)
require file.read("index.html")

serve on 8080
    static "/" from "./public" cache "1h"     -- sw.js must live at /sw.js (scope "/")
    route "GET /"
        give render("index.html", {"title": "My app"})
```

El `.webmanifest` se sirve como `application/manifest+json`, que es lo que hace que los navegadores
lo acepten.

## Push, de punta a punta

La página lee la clave pública de `GET /api/push/config`, se suscribe y manda la suscripción a
`POST /api/push/subscribe`. Enviar es un builtin:

```synsema
let r be push_send(sub, {"title": "Order shipped", "body": "Arrives Tuesday"}, {"vapid": keys})
when r["gone"]
    forget_subscription(sub)      -- 404/410: the browser dropped it
```

Declará un `require net(...)` por cada servicio de push al que entregues (FCM, Windows, Mozilla,
Apple) y mantené el envío detrás de `requires auth` o dentro de un cron — nunca en una ruta pública.

## Dónde se instala

Android y los Chromium de escritorio muestran el cartel de instalación; iOS instala desde *Compartir
→ Agregar a la pantalla de inicio* y recibe push una vez instalada, sobre un certificado confiable.
Esa última parte es una sola bandera — mirá
[HTTPS automático con una bandera](/es/blog/automatic-https-lets-encrypt-one-flag). Firefox no tiene
instalación ni modo app: ahí el sitio funciona como sitio.

## Y en el escritorio, como app de verdad

Los mismos archivos son una app de escritorio: `synsema init --desktop` agrega `desk.syn`, que sirve
la misma página y la misma `api.syn` en `127.0.0.1`, la abre como ventana de app del navegador que ya
está en la máquina y termina cuando se cierra la última ventana. `synsema build desk.syn -o desk
--serve --no-console --icon public/icon.svg` la convierte en un `.exe` con tu ícono (un `.app` en
macOS, una carpeta lanzadora en Linux con `--bundle`). Cómo funciona, y qué hace cada sistema
operativo con eso, está en
[Distribuí tu app web como app de escritorio](/es/blog/desktop-app-one-binary-browser-window).

**¿Ya tenés un sitio?** Corré `synsema init --pwa` adentro: no se pisa nada tuyo, los archivos de
fábrica caen al lado de los tuyos. Los detalles y las notas de iOS están en la
[documentación](https://synsema.dev/es/0.6.x/41b-pwa), y podés probar el lenguaje primero en
[try.synsema.org](https://try.synsema.org).

