# Una suma entre dos microapps

Crea un cliente y un proveedor RPC con WU CLI y Astro: archivos completos, permisos, versiones, errores y desmontaje comprobables.

<span class="section-anchor" id="section-1"></span>

## Lo que vas a construir

Un formulario envía `7` y `8` a otra microapp, que valida los números y devuelve `15`. El cliente muestra **7 + 8 = 15**. Después detendrás el servicio, comprobarás el error y volverás a iniciarlo.

Continúa en el workspace `mi-plataforma` de [Añadir una segunda app](/es/docs/guides/add-an-app/). También puedes aplicar esta receta después del [contador compartido entre React y Vue](/es/docs/guides/shared-counter/): las dos apps conservan sus componentes. Aquí añadiremos dos apps pequeñas en JavaScript para concentrarnos en las llamadas RPC.

```text
Shell Astro: concede permisos y monta las dos apps
  cliente-suma → request('ejemplo:suma', { a: 7, b: 8 })
    sumador → handle('ejemplo:suma', ...) → 15
  cliente-suma → muestra «7 + 8 = 15»
```

El cálculo sucede en el navegador. WU coordina la solicitud entre las apps; esta receta no abre una API HTTP. Para estudiar un proveedor con más operaciones y un contrato que se descubre con `provide`/`consume`, continúa después con [Un servicio conectado con WU](/es/docs/guides/browser-backend/).

**Versiones de esta receta:** Node.js 24, WU CLI 1.2.0 y `wu-framework` 4.0.0. Utiliza el CLI que instalaste en [Tu primera microapp](/es/docs/guides/quickstart/).

<span class="section-anchor" id="section-2"></span>

## 1. Añadir el cliente y el proveedor

Detén el servidor con **Ctrl+C**. Desde la raíz `mi-plataforma/`, junto a `wu.config.json`, ejecuta:

```sh
wu add vanilla sumador --no-install
wu add vanilla cliente-suma --no-install
```

El orden es framework, nombre. El CLI crea `mf-sumador/` y `mf-cliente-suma/`, con sus dependencias y entradas HTML, y registra ambas apps.

[Descarga todos los archivos de la receta](/examples/wu-rpc-sum.zip). Copia las tres carpetas del ZIP dentro del workspace, combina las carpetas existentes y reemplaza los archivos del mismo nombre. Copia también `wu.config.json`. Reemplaza también el `index.html` de cada app: debe cargar `src/main.js`, la entrada JavaScript de esta receta. Conserva los demás archivos del workspace.

```text
mi-plataforma/
├── wu.config.json
├── mf-sumador/
│   ├── index.html
│   ├── package.json
│   ├── vite.config.js
│   ├── styles.css
│   ├── wu.json
│   └── src/main.js
├── mf-cliente-suma/
│   ├── index.html
│   ├── package.json
│   ├── vite.config.js
│   ├── styles.css
│   ├── wu.json
│   └── src/main.js
└── shell/
    ├── astro.config.mjs
    └── src/pages/suma.astro
```

La página `suma.astro` reemplaza el ejemplo anterior que hacía la suma dentro del propio host, si lo creaste. Las páginas del workspace y del contador permanecen disponibles. Si personalizaste tu workspace, incorpora las nuevas entradas y grants de la configuración siguiente conservando tus demás ajustes.

También puedes copiar los archivos completos que aparecen a continuación.

<span class="section-anchor" id="section-3"></span>

## 2. Solicitar permisos y concederlos

El proveedor solicita `rpc.provide:ejemplo:suma`; el cliente solicita `rpc.call:ejemplo:suma`. `required: true` hace que la inicialización falle con un diagnóstico si el host no concede ese permiso. El manifest describe la necesidad; los `grants` del host dan la autorización.
<span class="section-anchor" id="section-4"></span>

### `mf-sumador/wu.json`

[Ver archivo completo](/examples/browser/rpc-sum/mf-sumador/wu.json.txt)

```json
{
  "name": "sumador",
  "version": "1.0.0",
  "entry": "src/main.js",
  "styleMode": "isolated",
  "wu": {
    "permissions": [
      { "capability": "rpc.provide", "resource": "ejemplo:suma", "required": true }
    ]
  }
}
```

<span class="section-anchor" id="section-5"></span>

### `mf-cliente-suma/wu.json`

[Ver archivo completo](/examples/browser/rpc-sum/mf-cliente-suma/wu.json.txt)

```json
{
  "name": "cliente-suma",
  "version": "1.0.0",
  "entry": "src/main.js",
  "styleMode": "isolated",
  "wu": {
    "permissions": [
      { "capability": "rpc.call", "resource": "ejemplo:suma", "required": true }
    ]
  }
}
```

Esta es la configuración completa del workspace después de añadir las dos apps. Los grants del contador conservan el alcance `demo.counter`. Los dos nuevos grants permiten la operación de suma desde la navegación del shell generado.
<span class="section-anchor" id="section-6"></span>

### `wu.config.json`

[Ver archivo completo](/examples/browser/rpc-sum/wu.config.json.txt)

```json
{
  "name": "mi-plataforma",
  "version": "1.1.0",
  "shell": {
    "dir": "shell",
    "port": 4321,
    "framework": "astro",
    "dev_cmd": "npx astro dev",
    "build_cmd": "npx astro build"
  },
  "apps": [
    {
      "name": "mi-plataforma",
      "dir": "mf-mi-plataforma",
      "framework": "react",
      "port": 5001,
      "grants": [
        "store.read:demo.counter",
        "store.write:demo.counter"
      ]
    },
    {
      "name": "inspector",
      "dir": "mf-inspector",
      "framework": "vue",
      "port": 5002,
      "grants": [
        "store.read:demo.counter",
        "store.write:demo.counter"
      ]
    },
    {
      "name": "sumador",
      "dir": "mf-sumador",
      "framework": "vanilla",
      "port": 5003,
      "grants": [
        "rpc.provide:ejemplo:suma"
      ]
    },
    {
      "name": "cliente-suma",
      "dir": "mf-cliente-suma",
      "framework": "vanilla",
      "port": 5004,
      "grants": [
        "rpc.call:ejemplo:suma"
      ]
    }
  ],
  "proxy": {
    "port": 3000,
    "open_browser": true
  }
}
```

<span class="section-anchor" id="section-7"></span>

## 3. Implementar el proveedor

`wu.define` registra el lifecycle de `sumador`. WU entrega `context.wu` al montarlo: esa facade lleva la identidad y los permisos de esta app. El handler valida la entrada, calcula el resultado y muestra qué cliente lo llamó.

`version: '1.0.0'` es la versión de la operación. `allow: ['cliente-suma']` añade una comprobación de identidad: solo ese cliente puede invocarla, aunque otra app tenga un grant para el mismo canal. La función devuelta por `handle` retira el handler; la ejecutamos en `unmount`.
<span class="section-anchor" id="section-8"></span>

### `mf-sumador/src/main.js`

[Ver archivo completo](/examples/browser/rpc-sum/mf-sumador/src/main.js.txt)

```js
import { waitForWu } from 'wu-framework/adapters/shared';

const wu = await waitForWu();

let release;

wu.define('sumador', {
  mount(container, context) {
    container.innerHTML = `
      <link rel="stylesheet" href="/mf-sumador/styles.css">
      <section aria-labelledby="service-title">
        <h2 id="service-title">Servicio de suma</h2>
        <p role="status">Listo para recibir solicitudes.</p>
      </section>
    `;
    const status = container.querySelector('[role="status"]');

    release = context.wu.handle('ejemplo:suma', ({ a, b }, request) => {
      if (!Number.isFinite(a) || !Number.isFinite(b)) {
        throw new TypeError('Escribe dos números finitos.');
      }
      const total = a + b;
      if (!Number.isFinite(total)) {
        throw new RangeError('La suma excede el rango numérico.');
      }
      status.textContent = `Respondí a ${request.caller.appName}: ${a} + ${b} = ${total}`;
      return total;
    }, {
      version: '1.0.0',
      allow: ['cliente-suma'],
    });
  },

  unmount(container) {
    release?.();
    release = undefined;
    container.replaceChildren();
  },
});
```

<span class="section-anchor" id="section-9"></span>

## 4. Implementar el cliente

El cliente no importa la función del proveedor. Envía los dos números mediante `context.wu.request`, espera la respuesta y actualiza su propio DOM. Solicita una versión compatible con `^1.0.0` y limita la espera a 1000 ms.

Un campo vacío se envía como `null` para que puedas observar el rechazo de la validación del proveedor. `try`/`catch` presenta los errores sin dejar la interfaz bloqueada. Al desmontar el cliente, se retiran sus listeners y se cancela cualquier solicitud pendiente.
<span class="section-anchor" id="section-10"></span>

### `mf-cliente-suma/src/main.js`

[Ver archivo completo](/examples/browser/rpc-sum/mf-cliente-suma/src/main.js.txt)

```js
import { waitForWu } from 'wu-framework/adapters/shared';

const wu = await waitForWu();

let dispose;

wu.define('cliente-suma', {
  mount(container, context) {
    container.innerHTML = `
      <link rel="stylesheet" href="/mf-cliente-suma/styles.css">
      <section aria-labelledby="client-title">
        <h2 id="client-title">Cliente de suma</h2>
        <form>
          <label>Primer número<input name="a" type="number" step="any" value="7"></label>
          <label>Segundo número<input name="b" type="number" step="any" value="8"></label>
          <button type="submit">Calcular</button>
        </form>
        <output aria-live="polite">Pulsa Calcular para solicitar una suma.</output>
      </section>
    `;
    const form = container.querySelector('form');
    const first = form.elements.namedItem('a');
    const second = form.elements.namedItem('b');
    const button = form.querySelector('button');
    const output = container.querySelector('output');
    const listeners = new AbortController();
    let pending;
    let active = true;

    form.addEventListener('submit', async (event) => {
      event.preventDefault();
      if (pending) return;
      const a = first.value === '' ? null : first.valueAsNumber;
      const b = second.value === '' ? null : second.valueAsNumber;
      pending = new AbortController();
      button.disabled = true;
      output.textContent = 'Calculando…';
      try {
        const total = await context.wu.request('ejemplo:suma', { a, b }, {
          version: '^1.0.0',
          timeout: 1000,
          signal: pending.signal,
        });
        if (active) output.textContent = `${a} + ${b} = ${total}`;
      } catch (error) {
        if (!active) return;
        output.textContent = error.code === 'WU_RPC_NO_HANDLER'
          ? 'El servicio está detenido. Inícialo y vuelve a pulsar Calcular.'
          : `No se pudo calcular: ${error.message}`;
      } finally {
        pending = undefined;
        if (active) button.disabled = false;
      }
    }, { signal: listeners.signal });

    dispose = () => {
      active = false;
      listeners.abort();
      pending?.abort();
    };
  },

  unmount(container) {
    dispose?.();
    dispose = undefined;
    container.replaceChildren();
  },
});
```

<span class="section-anchor" id="section-11"></span>

## 5. Montar ambas apps desde Astro

Esta página configura sus propias apps y grants. Monta primero el servicio y después el cliente. Los botones permiten desmontar y montar el proveedor sin recargar el cliente: podrás verificar que realmente depende de un handler disponible.
<span class="section-anchor" id="section-12"></span>

### `shell/src/pages/suma.astro`

[Ver archivo completo](/examples/browser/rpc-sum/shell/src/pages/suma.astro.txt)

```astro
<!doctype html>
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Una suma entre dos microapps WU</title>
    <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
  </head>
  <body>
    <main>
      <a href="/">Volver al workspace</a>
      <h1>Una suma entre dos microapps</h1>
      <p>El cliente pide el resultado y el servicio calcula la suma.</p>
      <div class="controls">
        <button id="start" disabled>Iniciar servicio</button>
        <button id="stop" disabled>Detener servicio</button>
      </div>
      <p id="status" role="status">Iniciando WU…</p>
      <div id="service"></div>
      <div id="client"></div>
    </main>
    <script>
      import { wu } from 'wu-framework';

      const start = document.querySelector<HTMLButtonElement>('#start')!;
      const stop = document.querySelector<HTMLButtonElement>('#stop')!;
      const status = document.querySelector<HTMLElement>('#status')!;
      let running = false;

      async function setService(enabled: boolean) {
        start.disabled = true;
        stop.disabled = true;
        try {
          if (enabled) await wu.mount('sumador', '#service');
          else await wu.unmount('sumador', { force: true });
          running = enabled;
          status.textContent = enabled
            ? 'Servicio disponible. Pulsa Calcular.'
            : 'Servicio detenido. Pulsa Calcular para comprobar el error.';
        } catch (error) {
          status.textContent = `No se pudo cambiar el servicio: ${error}`;
        } finally {
          start.disabled = running;
          stop.disabled = !running;
        }
      }

      try {
        await wu.init({
          sandbox: 'module',
          apps: [
            {
              name: 'sumador',
              url: `${location.origin}/mf-sumador`,
              grants: ['rpc.provide:ejemplo:suma'],
            },
            {
              name: 'cliente-suma',
              url: `${location.origin}/mf-cliente-suma`,
              grants: ['rpc.call:ejemplo:suma'],
            },
          ],
        });
        await wu.mount('sumador', '#service');
        running = true;
        await wu.mount('cliente-suma', '#client');
        stop.disabled = false;
        status.textContent = 'Servicio disponible. Pulsa Calcular.';
        start.addEventListener('click', () => { void setService(true); });
        stop.addEventListener('click', () => { void setService(false); });
      } catch (error) {
        status.textContent = `No se pudo iniciar WU: ${error}`;
      }
    </script>
  </body>
</html>

<style>
  :global(body) { margin: 0; background: #f4f7f6; color: #172b34; font: 1rem/1.6 system-ui, sans-serif; }
  main { max-width: 48rem; margin: 0 auto; padding: 2rem 1rem; }
  h1 { font-size: clamp(1.6rem, 5vw, 2.5rem); line-height: 1.2; }
  a { color: #175f56; }
  .controls { display: flex; flex-wrap: wrap; gap: .75rem; }
  button { border: 1px solid #536f74; border-radius: .5rem; padding: .6rem 1rem; color: #172b34; background: white; font: inherit; cursor: pointer; }
  button:disabled { opacity: .55; cursor: default; }
  :focus-visible { outline: 3px solid #174bac; outline-offset: 3px; }
  #client { margin-top: 1rem; }
</style>
```

`waitForWu` localiza el runtime del shell para registrar cada app con `define`. Las operaciones RPC usan la facade de `mount`, con identidad, versión y allowlist. Framework 4 conserva esas opciones también en `wu.handle`; usa la facade scoped dentro de una microapp.

<span class="section-anchor" id="section-13"></span>

## 6. Conservar la entrada y el manifest al construir

El `index.html` de cada app es también la entrada del build de Vite. Los dos archivos siguientes apuntan a `src/main.js`. Sustituyen los de la plantilla para que desarrollo y producción ejecuten el mismo proveedor y el mismo cliente.

<span class="section-anchor" id="section-14"></span>

### `mf-sumador/index.html`

[Ver archivo completo](/examples/browser/rpc-sum/mf-sumador/index.html.txt)

```html
<!doctype html>
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Servicio de suma · WU</title>
  </head>
  <body><script type="module" src="/src/main.js"></script></body>
</html>
```

<span class="section-anchor" id="section-15"></span>

### `mf-cliente-suma/index.html`

[Ver archivo completo](/examples/browser/rpc-sum/mf-cliente-suma/index.html.txt)

```html
<!doctype html>
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Cliente de suma · WU</title>
  </head>
  <body><script type="module" src="/src/main.js"></script></body>
</html>
```

<span class="section-anchor" id="section-16"></span>

### Servir los estilos como archivos

Astro genera una hoja CSS externa con `inlineStylesheets: 'never'`. Cada microapp carga su propio `styles.css` desde su Shadow DOM. Así los estilos cumplen una política `style-src 'self'`, sin necesitar estilos inline ni cambiar la CSP del servidor. Si ya aplicaste el contador, conserva esta misma opción en la configuración del shell.

<span class="section-anchor" id="section-17"></span>

### `shell/astro.config.mjs`

[Ver archivo completo](/examples/browser/rpc-sum/shell/astro.config.mjs.txt)

```js
import { defineConfig } from 'astro/config';

export default defineConfig({
  server: { port: 4321 },
  build: { inlineStylesheets: 'never' },
});
```

<span class="section-anchor" id="section-18"></span>

### `mf-sumador/styles.css`

[Ver archivo completo](/examples/browser/rpc-sum/mf-sumador/styles.css.txt)

```css
:host { display: block; font: 1rem/1.6 system-ui, sans-serif; color: #172b34; }
section { padding: 1.5rem; background: #edf7f1; border-radius: 1rem; }
h2 { margin-top: 0; font-size: 1.25rem; }
p { margin-bottom: 0; }
```

<span class="section-anchor" id="section-19"></span>

### `mf-cliente-suma/styles.css`

[Ver archivo completo](/examples/browser/rpc-sum/mf-cliente-suma/styles.css.txt)

```css
:host { display: block; font: 1rem/1.6 system-ui, sans-serif; color: #172b34; }
section { padding: 1.5rem; background: white; border: 1px solid #cbd9d9; border-radius: 1rem; }
h2 { margin-top: 0; font-size: 1.25rem; }
form { display: flex; gap: 1rem; flex-wrap: wrap; align-items: end; }
label { display: grid; gap: .25rem; }
input { box-sizing: border-box; width: 7rem; padding: .65rem; font: inherit; border: 1px solid #6e838b; border-radius: .4rem; }
button { padding: .7rem 1rem; font: inherit; color: white; background: #126153; border: 0; border-radius: .4rem; cursor: pointer; }
button:disabled { opacity: .65; cursor: wait; }
:focus-visible { outline: 3px solid #174bac; outline-offset: 3px; }
output { display: block; margin-top: 1rem; font-weight: 650; }
```

Hay un solo `wu.json` fuente por app, en su raíz. El modo nativo del CLI lo sirve directamente. Vite debe incluirlo también en su salida: este plugin emite tanto el manifest como `styles.css` sin mantener copias adicionales a mano. Al recoger el resultado, `wu build` conserva los permisos y sustituye `entry` por el JavaScript compilado.

<details>
<summary>Archivos completos de dependencias y build incluidos en el ZIP</summary>

<span class="section-anchor" id="section-20"></span>

### `mf-sumador/package.json`

[Ver archivo completo](/examples/browser/rpc-sum/mf-sumador/package.json.txt)

```json
{
  "name": "wu-sumador",
  "private": true,
  "type": "module",
  "engines": { "node": ">=24" },
  "scripts": { "dev": "vite --port 5003", "build": "vite build" },
  "dependencies": { "wu-framework": "4.0.0" },
  "devDependencies": { "vite": "7.3.6" }
}
```

<span class="section-anchor" id="section-21"></span>

### `mf-cliente-suma/package.json`

[Ver archivo completo](/examples/browser/rpc-sum/mf-cliente-suma/package.json.txt)

```json
{
  "name": "wu-cliente-suma",
  "private": true,
  "type": "module",
  "engines": { "node": ">=24" },
  "scripts": { "dev": "vite --port 5004", "build": "vite build" },
  "dependencies": { "wu-framework": "4.0.0" },
  "devDependencies": { "vite": "7.3.6" }
}
```

<span class="section-anchor" id="section-22"></span>

### `mf-sumador/vite.config.js`

[Ver archivo completo](/examples/browser/rpc-sum/mf-sumador/vite.config.js.txt)

```js
import { readFileSync } from 'node:fs';
import { defineConfig } from 'vite';

export default defineConfig({
  base: './',
  build: { target: 'esnext' },
  plugins: [{
    name: 'wu-assets',
    generateBundle() {
      for (const fileName of ['wu.json', 'styles.css']) {
        this.emitFile({
          type: 'asset',
          fileName,
          source: readFileSync(new URL(fileName, import.meta.url), 'utf8'),
        });
      }
    },
  }],
});
```

<span class="section-anchor" id="section-23"></span>

### `mf-cliente-suma/vite.config.js`

[Ver archivo completo](/examples/browser/rpc-sum/mf-cliente-suma/vite.config.js.txt)

```js
import { readFileSync } from 'node:fs';
import { defineConfig } from 'vite';

export default defineConfig({
  base: './',
  build: { target: 'esnext' },
  plugins: [{
    name: 'wu-assets',
    generateBundle() {
      for (const fileName of ['wu.json', 'styles.css']) {
        this.emitFile({
          type: 'asset',
          fileName,
          source: readFileSync(new URL(fileName, import.meta.url), 'utf8'),
        });
      }
    },
  }],
});
```

</details>

<span class="section-anchor" id="section-24"></span>

## 7. Ejecutar y reconocer el resultado

Después de copiar los archivos, desde la raíz del workspace:

```sh
wu install
npm --prefix shell run build
wu dev --native --port 3000 --no-open
```

Este build inicial incorpora `suma.astro` a `shell/dist/` después de copiar los archivos con el servidor detenido. Si ya existe una salida del shell, CLI 1.2.0 puede reutilizarla al arrancar. Una vez activo `wu dev --native`, el watcher reconstruye el shell cuando cambian sus fuentes y después recarga la página; no necesitas detenerlo y compilar a mano en cada edición. Si la compilación falla, revisa el error en la terminal.

Abre [http://127.0.0.1:3000/suma/](http://127.0.0.1:3000/suma/). Debes ver **Servicio de suma**, **Cliente de suma** y los números `7` y `8`.

| Acción | Resultado visible |
| --- | --- |
| Pulsa **Calcular** | El cliente muestra **7 + 8 = 15**; el servicio indica que respondió a `cliente-suma`. |
| Cambia el segundo número a `3` y calcula | **7 + 3 = 10**. |
| Vacía el primer número y calcula | **No se pudo calcular: Escribe dos números finitos.** |
| Restablece `7` y `8`, pulsa **Detener servicio** y calcula | El cliente explica que el servicio está detenido y cómo volver a intentarlo. |
| Pulsa **Iniciar servicio** y calcula | **7 + 8 = 15** otra vez. |

Al regresar a la página principal también aparecen las nuevas apps en la navegación. Abre `sumador` antes de calcular desde `cliente-suma`; esa navegación monta cada app cuando la visitas. La página `/suma/` realiza ese orden automáticamente.

Para comprobar la versión, cambia temporalmente la versión del handler a `2.0.0`, recarga `/suma/` y calcula. La solicitud `^1.0.0` se rechaza por incompatibilidad. Restaura `1.0.0` al terminar.

<span class="section-anchor" id="section-25"></span>

## 8. Comprobar la versión que publicarás

Detén desarrollo con **Ctrl+C** y ejecuta:

```sh
wu build
wu serve 4100 --mode static
```

Abre [http://127.0.0.1:4100/suma/index.html](http://127.0.0.1:4100/suma/index.html). Repite la suma, la retirada y el nuevo montaje del servicio; después recarga esa URL. Los mismos resultados deben funcionar con los archivos compilados. La salida publicable es `dist/` en la raíz del workspace.

`wu serve 4100 --mode static` resuelve los índices de directorio y devuelve 404 reales para rutas inexistentes. Usa `--mode spa` solo si necesitas fallback al shell para rutas del cliente.

<span class="section-anchor" id="section-26"></span>

## Si algo falla

| Síntoma | Qué comprobar |
| --- | --- |
| `/suma/` no aparece o muestra la suma antigua | Si copiaste la página con desarrollo detenido, ejecuta `npm --prefix shell run build` antes de arrancar. Durante desarrollo, comprueba que la reconstrucción automática del shell no tenga errores. |
| WU informa de un permiso obligatorio ausente | Compara el permiso del `wu.json` con el grant de la app correspondiente en `suma.astro`; para la página principal, comprueba `wu.config.json`. |
| El cliente dice que el servicio está detenido | Pulsa **Iniciar servicio**. En la navegación principal, abre primero `sumador`. |
| La página funciona pero pierde sus estilos | Conserva `build.inlineStylesheets: 'never'` en `shell/astro.config.mjs`; comprueba que existen los dos `styles.css` en `dist/mf-*/` y ejecuta `wu build`. |
| En producción reaparece el contador Vanilla JS | Reemplaza ambos `index.html` por los de esta receta; sus scripts deben cargar `/src/main.js`. Ejecuta `wu build` otra vez. |
| Funciona en desarrollo y falla en producción | Comprueba que copiaste ambos `vite.config.js` y que `dist/mf-sumador/wu.json` y `dist/mf-cliente-suma/wu.json` conservan sus permisos con un `entry` de producción. |
| La solicitud se rechaza por versión | El handler publica `1.0.0` y el cliente solicita `^1.0.0`; deben ser compatibles. |

<span class="section-anchor" id="section-27"></span>

## Continuar con las APIs

Ya utilizaste `init`, `define`, `mount`, `unmount`, `handle` y `request` entre dos apps con permisos diferentes. [Eventos, contratos y RPC](/es/docs/framework/communication/) explica las firmas y las variantes; [la referencia de comunicación](/es/docs/framework/api-events/) enumera las APIs. Para añadir descubrimiento de contratos, cancelación observable y otras operaciones, continúa con [Un servicio conectado con WU](/es/docs/guides/browser-backend/).