> ## Documentation Index
> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Dibujar en la interfaz con un mod

> Dibuje paneles, una banda sobre el símbolo del sistema, botones y campos de texto desde un mod de Claude Code, maneje pulsaciones e entrada, y mantenga el estado entre redibujos y sesiones.

Un mod puede dibujar su propia interfaz en Claude Code y cambiar partes de la interfaz que Claude Code ya dibuja. Cada lugar donde un mod puede dibujar se llama [sitio de renderizado](/docs/es/plugins/mods/reference#render-sites), como un panel, la banda sobre el símbolo del sistema o el spinner. Claude Code genera el evento [`ui.render`](/docs/es/plugins/mods/reference#interface) cada vez que está a punto de dibujar un sitio de renderizado, y su hook para ese evento devuelve lo que se debe dibujar allí.

Este mapa muestra dónde un mod puede dibujar en una sesión de terminal:

<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-screen-map.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=5fda26b6609c62b68c6f9e528c1590ea" className="dark:hidden" alt="Mapa de una sesión de terminal de Claude Code. Un mod puede agregar un panel como barra lateral a la derecha, una notificación en la esquina superior derecha de la transcripción, una línea de registro en la transcripción, una banda sobre el símbolo del sistema y una línea de estado bajo el símbolo del sistema. Un mod puede redibujar mensajes, filas de llamadas de herramientas y el spinner. El símbolo del sistema es propio de Claude Code." width="600" height="336" data-path="images/mods-screen-map.svg" />

<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-screen-map-dark.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=5b4161581a1bd2c0450b0c8b57bc1225" className="hidden dark:block" alt="Mapa de una sesión de terminal de Claude Code. Un mod puede agregar un panel como barra lateral a la derecha, una notificación en la esquina superior derecha de la transcripción, una línea de registro en la transcripción, una banda sobre el símbolo del sistema y una línea de estado bajo el símbolo del sistema. Un mod puede redibujar mensajes, filas de llamadas de herramientas y el spinner. El símbolo del sistema es propio de Claude Code." width="600" height="336" data-path="images/mods-screen-map-dark.svg" />

En una terminal más estrecha, el panel se sitúa sobre el símbolo del sistema en lugar de junto a la transcripción.

Construya su [primer mod](/docs/es/plugins/mods/create) antes de comenzar aquí. Comience con el ejemplo trabajado, que construye un panel con dos pestañas y un contador, luego lea la sección para cada parte que desee cambiar.

<Note>
  Para buscar una propiedad o límite, consulte la [referencia](/docs/es/plugins/mods/reference#render-sites).
</Note>

<h2 id="build-a-pane-with-tabs">
  Construir un panel con pestañas
</h2>

En esta sección construye un mod que agrega un comando `/hello-tabs` y el comando abre un panel. Un panel es una barra lateral junto a la transcripción en una terminal de pantalla completa ancha, o una región enmarcada sobre el símbolo del sistema de otra manera. Este panel muestra dos pestañas, y la segunda pestaña tiene un botón que suma uno a un contador. El recuento sigue ahí después de reiniciar Claude Code.

El mod terminado se ve así. La grabación abre el panel, cambia a la segunda pestaña, presiona el botón varias veces y vuelve a la primera pestaña:

<Frame>
  <video autoPlay muted loop playsInline controls className="w-full dark:hidden" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-hello-tabs-light.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=49d520094d87b5b44bfe50fa49677f06" aria-label="El comando /hello-tabs se escribe en el símbolo del sistema de Claude Code y se abre un panel enmarcado sobre él, con '1: One' y '2: Two' en la parte superior y el texto 'This is the first tab.' La segunda pestaña muestra un botón 'Add one' junto a 'Count: 1', y el recuento sube a 3. El panel luego vuelve a la primera pestaña." data-path="images/mods-hello-tabs-light.mp4" />

  <video autoPlay muted loop playsInline controls className="w-full hidden dark:block" src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-hello-tabs-dark.mp4?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=ff7a14d713d6e5d3b0000efa8522ea4b" aria-label="El comando /hello-tabs se escribe en el símbolo del sistema de Claude Code y se abre un panel enmarcado sobre él, con '1: One' y '2: Two' en la parte superior y el texto 'This is the first tab.' La segunda pestaña muestra un botón 'Add one' junto a 'Count: 1', y el recuento sube a 3. El panel luego vuelve a la primera pestaña." data-path="images/mods-hello-tabs-dark.mp4" />
</Frame>

Claude Code no tiene un elemento de pestañas integrado, por lo que las pestañas son dos botones en una fila. El mod realiza un seguimiento de cuál está activo y dibuja el contenido de esa pestaña bajo la fila.

<Steps>
  <Step title="Crear el plugin">
    Un mod es un plugin con un manifiesto, un `hooks.json` que apunta a su código y el archivo de código. [Crear un mod](/docs/es/plugins/mods/create#write-a-mod-yourself) explica cada uno. Cree un directorio llamado `hello-tabs` con directorios `.claude-plugin` y `hooks` dentro, luego guarde los dos primeros archivos.

    Guarde el manifiesto como `hello-tabs/.claude-plugin/plugin.json`:

    ```json hello-tabs/.claude-plugin/plugin.json theme={null}
    {
      "name": "hello-tabs",
      "version": "0.1.0",
      "description": "Opens a pane with two tabs and a counter",
      "author": { "name": "Your Name" }
    }
    ```

    Nombre su punto de entrada en `hello-tabs/hooks/hooks.json`:

    ```json hello-tabs/hooks/hooks.json theme={null}
    {
      "modules": ["./register.js"]
    }
    ```
  </Step>

  <Step title="Escribir el código">
    El código realiza tres trabajos, uno en cada hook:

    * Agrega el comando `/hello-tabs`
    * Abre el panel cuando ejecuta ese comando
    * Dibuja el contenido del panel: la fila de pestañas y el cuerpo de la pestaña abierta

    Dos variables a nivel de módulo, `tab` y `count`, mantienen el estado del panel.

    Guarde esto como `hello-tabs/hooks/register.js`:

    ```javascript hello-tabs/hooks/register.js theme={null}
    // The pane's id, used to open the pane and to recognize it when drawing
    const PANE = 'hello-tabs'

    // What the pane shows: which tab is open, and the counter's value
    let tab = 'one'
    let count = 0

    export function register(on) {
      // Runs before your first prompt, and again after a reload
      on('session.start', async ($, e, next) => {
        await $.command.register({ name: 'hello-tabs', description: 'Open the hello-tabs pane' })
        // Load the count an earlier session saved, if there is one
        const saved = await $.store.get('count')
        if (typeof saved === 'number') count = saved
        return next(e)
      })

      // Runs when you type /hello-tabs
      on('command.run', { command: 'hello-tabs' }, async ($) => {
        // Open the pane, give it the keyboard, and let Esc close it
        await $.ui.open({ id: PANE, title: 'Hello tabs', focus: true, closeOnEscape: true })
        // Print nothing in the transcript
        return {}
      })

      // Runs each time Claude Code draws a pane
      on('ui.render', { component: 'Pane' }, async ($, e, next) => {
        // Leave other mods' panes alone
        if (e.requestId !== PANE) return next(e)
        // Get the elements this app can draw
        const { Box, Text, Button } = $.ui.resolve(e)
        // Ask Claude Code to run this hook again
        const redraw = () => $.ui.invalidate('ui.render')

        // One tab: a button that switches to its tab when pressed
        const tabButton = (name, label, hotkey) =>
          Button({
            key: 'tab-' + name,
            label,
            hotkey,
            plain: true,
            // Dim the tab that isn't open
            dimColor: tab !== name,
            onPress: () => {
              tab = name
              redraw()
            },
          })

        // What goes under the tabs, depending on which one is open
        const body =
          tab === 'one'
            ? [Text({ children: ['This is the first tab.'] })]
            : [
                Box({
                  flexDirection: 'row',
                  columnGap: 2,
                  children: [
                    Button({
                      key: 'more',
                      label: 'Add one',
                      hotkey: 'a',
                      onPress: async () => {
                        count += 1
                        redraw()
                        // Save the count so it's there after a restart
                        await $.store.set('count', count)
                      },
                    }),
                    Text({ children: ['Count: ' + count] }),
                  ],
                }),
              ]

        // The whole pane: the row of tabs, a blank line, then the body
        return Box({
          flexDirection: 'column',
          children: [
            Box({
              flexDirection: 'row',
              columnGap: 3,
              children: [tabButton('one', 'One', '1'), tabButton('two', 'Two', '2')],
            }),
            Text({ children: [' '] }),
            ...body,
          ],
        })
      })
    }
    ```

    Cada hook también hace algo que el código no deja claro:

    * **[`session.start`](/docs/es/plugins/mods/reference#session)** también lee el recuento guardado de [`$.store`](#keep-state), un almacén de clave-valor que persiste entre sesiones.
    * **[`command.run`](/docs/es/plugins/mods/api#add-a-command)** solo le dice a Claude Code que el panel existe. Abrir un panel no dibuja nada por sí solo: Claude Code luego genera `ui.render` para preguntar qué va en él.
    * **`ui.render`** devuelve el árbol de elementos, un `Box` que contiene otros cuadros, texto y botones, y lo construye nuevamente desde `tab` y `count` cada vez que se ejecuta.

    Presionar un botón ejecuta su callback `onPress`, que cambia una variable y llama a `redraw`. Claude Code luego ejecuta el hook `ui.render` nuevamente, y el hook construye un nuevo árbol a partir de los nuevos valores. Cada dibujo interactivo utiliza ese ciclo de renderizado: un callback cambia el estado y el hook se renderiza nuevamente desde el nuevo estado.
  </Step>

  <Step title="Abrir el panel">
    En su shell, inicie Claude Code con `claude --plugin-dir ./hello-tabs`. En el símbolo del sistema de Claude Code, ejecute `/hello-tabs`. Se abre un panel con `1: One` y `2: Two` en la parte superior. Presione `2`, luego presione `a`, el atajo de teclado para **Add one**, varias veces. El recuento sube.
  </Step>

  <Step title="Verificar que el recuento fue guardado">
    Presione Esc para cerrar el panel, luego salga de la sesión. En su shell, inicie Claude Code nuevamente con el mismo comando `claude --plugin-dir ./hello-tabs` y en el símbolo del sistema de Claude Code ejecute `/hello-tabs`. El recuento está donde lo dejó.

    Para borrar el recuento, haga que el mod llame a `$.store.delete('count')`. [Mantener estado](#keep-state) cubre cuánto tiempo dura cada tipo de valor.
  </Step>
</Steps>

<h2 id="pick-where-to-draw">
  Elegir dónde dibujar
</h2>

Un hook `ui.render` se ejecuta para cada sitio de renderizado a menos que lo reduzca al que desea dibujar. Para elegir el sitio de renderizado, pase un filtro, llamado [matcher](/docs/es/plugins/mods/events#filter-which-events-a-hook-handles), como segundo argumento a `on`. `{ component: 'Pane' }` ejecuta el hook solo para paneles. En el hook, `e.component` nombra el sitio, `e.surface` dice qué aplicación está dibujando, y `e.props` contiene los datos propios del sitio. Para un panel, `e.requestId` es el `id` con el que lo abrió.

Dos sitios están vacíos hasta que un mod los llena, el panel y la banda. Seleccione una pestaña para ver qué es cada uno y cómo dibujar en él:

<Tabs>
  <Tab title="Panel">
    Un panel es una barra lateral junto a la transcripción en una terminal de pantalla completa ancha, o una región enmarcada sobre el símbolo del sistema de otra manera. Con varios paneles abiertos, cada uno obtiene una pestaña que muestra su título.

    Un panel aparece cuando su mod llama a `$.ui.open` con un `id` que elige, como en `$.ui.open({ id: 'hello-tabs' })`. [Abrir un panel en el momento adecuado](#open-a-pane-at-the-right-time) cubre los otros campos y cuándo un panel espera una terminal más ancha.

    Para dibujar en su panel, filtre en `{ component: 'Pane' }` y verifique que `e.requestId` sea su `id`.
  </Tab>

  <Tab title="Banda sobre el símbolo del sistema">
    La banda es una franja directamente sobre la entrada del símbolo del sistema. Siempre está ahí, y cada mod la comparte.

    Su hook devuelve un árbol para mostrar algo en la banda, o `next(e)` para no mostrar nada. Un árbol reemplaza lo que los mods [después del suyo](/docs/es/plugins/mods/events#the-order-mods-run-in) dibujan allí. Para mantener el suyo, ponga el resultado de `await next(e)` entre los hijos de un [`Box`](#build-a-tree-from-elements) en su árbol.

    Para dibujar en la banda, filtre en `{ component: 'AbovePrompt' }`.
  </Tab>
</Tabs>

<h3 id="change-what-claude-code-already-draws">
  Cambiar lo que Claude Code ya dibuja
</h3>

Claude Code dibuja la mayoría de su interfaz a sí mismo: mensajes, filas de llamadas de herramientas, el spinner y más. Cada una de esas partes es un sitio de renderizado también, por lo que un mod puede cambiar el estilo o reemplazarlo. Para cambiar uno, filtre su hook `ui.render` en su nombre de esta tabla:

| Sitio | Qué es |
| :- | :- |
| `UserMessage`, `AssistantMessage` | Un mensaje en la transcripción |
| `ToolUse`, `ToolResult`, `ToolGroup` | La fila de una llamada de herramienta, su resultado y una ejecución plegada de llamadas |
| `CommandOutput` | La fila que un comando imprimió |
| `AskUserQuestion` | El diálogo que Claude abre para hacerle una pregunta |
| `Spinner`, `ToolProgress`, `TurnDuration` | Líneas de estado para un turno: la línea que se anima mientras Claude trabaja, la línea de progreso en vivo de una herramienta en ejecución y la línea que cierra un turno |
| `InfoNotice`, `SessionMode`, `PromptHint` | Líneas de estado bajo el logo, las etiquetas de modo en el pie de página y la línea de sugerencia bajo el símbolo del sistema |

En un sitio que Claude Code ya dibuja, su hook tiene tres opciones: cambiar un detalle, reemplazar el dibujo o dejarlo solo. Seleccione una pestaña para ver cada una aplicada al spinner. Los ejemplos leen una variable `calls` que otro hook cuenta, como en el [mod de tutorial](/docs/es/plugins/mods/create#write-a-mod-yourself).

<Tabs>
  <Tab title="Cambiar un detalle">
    Para mantener el dibujo de Claude Code y cambiar una parte de él, pase a `next` una copia del evento con `props` cambiados. Este hook cambia el texto después de la palabra del spinner:

    ```javascript theme={null}
    on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
      // Keep Claude Code's spinner, and change the text after its word
      return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
    })
    ```

    El spinner mantiene su animación y su palabra, y su texto sigue a la palabra:

    ```text theme={null}
    Thinking · tool calls: 2…
    ```
  </Tab>

  <Tab title="Reemplazar el dibujo">
    Para dibujar algo propio en el lugar del sitio, devuelva un árbol y no llame a `next`. Este hook dibuja una línea de texto donde estaría el spinner:

    ```javascript theme={null}
    on('ui.render', { component: 'Spinner' }, async ($, e) => {
      const { Text } = $.ui.resolve(e)
      // No call to next, so this line is drawn in the spinner's place
      return Text({ children: ['Claude has made ' + calls + ' tool calls'] })
    })
    ```

    Mientras Claude trabaja, su línea se muestra y el spinner de Claude Code no:

    ```text theme={null}
    Claude has made 2 tool calls
    ```
  </Tab>

  <Tab title="Dejarlo solo">
    Para dejar el sitio como Claude Code lo dibuja, devuelva `next(e)`. Un hook a menudo hace eso para algunos eventos y no para otros. Este hook deja el spinner solo hasta que hay una llamada para contar:

    ```javascript theme={null}
    on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
      // Nothing to show yet, so pass the event on unchanged
      if (calls === 0) return next(e)
      return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
    })
    ```

    Antes de la primera llamada de herramienta, el spinner se ve de la manera que lo hace sin el mod:

    ```text theme={null}
    Thinking…
    ```
  </Tab>
</Tabs>

El símbolo del sistema de permiso no es un sitio de renderizado, por lo que un mod no puede cambiar lo que muestra. El diálogo de pregunta, `AskUserQuestion`, es uno, por lo que un mod puede cambiar eso.

La terminal y la aplicación de escritorio no generan todos los mismos sitios. `Pane`, `AbovePrompt`, `Spinner` y los sitios de transcripción funcionan en ambos. Algunas otras líneas de estado se generan solo en la terminal. La [tabla de sitios de renderizado](/docs/es/plugins/mods/reference#render-sites) enumera dónde se genera cada uno.

<h3 id="open-a-pane-at-the-right-time">
  Abrir un panel en el momento adecuado
</h3>

Un panel aparece solo cuando su mod lo abre. Cómo y cuándo lo abre decide si toma el enfoque del teclado, cuánto espacio solicita y si aparece en absoluto en una terminal estrecha.

Para abrir un panel, llame a [`$.ui.open`](/docs/es/plugins/mods/reference#mods-api-methods) con un `id` que elija. El `id` es el nombre del panel: su hook `ui.render` lo verifica y lo pasa nuevamente para cerrar el panel.

```javascript theme={null}
await $.ui.open({ id: 'hello-tabs', title: 'Hello tabs', focus: true })
```

Para cerrar el panel, llame a `$.ui.close` con el `id` con el que lo abrió:

```javascript theme={null}
await $.ui.close({ id: 'hello-tabs' })
```

Además de `id`, `$.ui.open` toma estos campos opcionales:

| Campo | Qué hace |
| :- | :- |
| `title` | La etiqueta de pestaña del panel cuando hay más de un panel abierto |
| `focus` | Solicita [enfoque del teclado](#know-which-keys-your-mod-can-receive) |
| `closeOnEscape` | Hace que Esc cierre el panel. Pase `true` o deje el campo fuera, porque Claude Code rechaza `false`. |
| `holdToasts` | Mantiene las notificaciones, los pequeños avisos de [`$.ui.toast`](/docs/es/plugins/mods/api#show-something-without-starting-a-turn), hasta que se cierre el panel |
| `rows` | La altura a solicitar cuando el panel se sitúa sobre el símbolo del sistema. El valor predeterminado es un tercio del espacio. |
| `columns` | El ancho a solicitar cuando el panel se sitúa junto a la transcripción |

Para permitir que un comando abra el panel mientras Claude está trabajando, agregue `immediate: true` cuando [registre el comando](/docs/es/plugins/mods/api#add-a-command). Sin él, un comando escrito durante un turno espera a que el turno termine.

<h4 id="when-a-pane-waits-for-a-wider-terminal">
  Cuando un panel espera una terminal más ancha
</h4>

Un panel que su mod abre sin ser solicitado no aparece en una terminal estrecha, por lo que no puede ocupar una pantalla pequeña. Si aparece depende de lo que lo abrió:

* **Abierto por algo que hizo el usuario**, como un comando que ejecutó o un botón que presionó, el panel aparece en cualquier ancho
* **Abierto por su mod actuando por sí solo**, como desde un temporizador o un hook [`turn.start`](/docs/es/plugins/mods/events#follow-a-turn), el panel aparece solo en una terminal de al menos 144 columnas de ancho. Después de que el usuario haya abierto ese panel una vez por sí mismo, 110 columnas es suficiente.

Cuando aparece el panel, `$.ui.open` se resuelve en `{ isPlaced: true }`. Cuando el panel está esperando, `isPlaced` es `false` y `reason` es una cadena que dice por qué. Un panel en espera aparece cuando el usuario lo abre o amplía la terminal. Para decir que algo está disponible sin abrir un panel, llame a `$.ui.toast('Your message')`, que muestra un pequeño aviso que desaparece después de unos segundos.

<h2 id="build-a-tree-from-elements">
  Construir un árbol a partir de elementos
</h2>

Lo que devuelve un hook `ui.render` es un árbol de elementos: una descripción de qué dibujar, hecha de cuadros, texto y controles anidados entre sí. Describe el dibujo y Claude Code lo renderiza en la terminal o en la aplicación de escritorio.

Para obtener los elementos, llame a `$.ui.resolve(e)` en su hook, como en `const { Box, Text, Button } = $.ui.resolve(e)`. Cada elemento es una función. Pasa sus propiedades y pone los elementos y cadenas que van dentro en `children`.

La mayoría de los dibujos utilizan cuatro elementos. Seleccione una pestaña para ver cada uno y cómo la terminal lo dibuja:

<Tabs>
  <Tab title="Texto">
    `Text` dibuja una cadena, con estilo opcional como `bold` y `color`:

    ```javascript theme={null}
    Text({ children: ['This is the first tab.'] })
    ```

    ```text theme={null}
    This is the first tab.
    ```
  </Tab>

  <Tab title="Cuadro">
    `Box` organiza lo que hay dentro, en una fila o una columna. Este pone un botón y una línea de texto uno al lado del otro, dos columnas separadas:

    ```javascript theme={null}
    Box({
      flexDirection: 'row',
      columnGap: 2,
      children: [
        Button({ key: 'more', label: 'Add one', onPress: addOne }),
        Text({ children: ['Count: 0'] }),
      ],
    })
    ```

    ```text theme={null}
    [ Add one ]  Count: 0
    ```
  </Tab>

  <Tab title="Botón">
    `Button` es un control que el usuario puede presionar. Ejecuta su callback `onPress`. Con `plain: true` no tiene corchetes y muestra su atajo de teclado:

    ```javascript theme={null}
    Button({ key: 'more', label: 'Add one', onPress: addOne })
    Button({ key: 'tab-one', label: 'One', hotkey: '1', plain: true, onPress: showTabOne })
    ```

    ```text theme={null}
    [ Add one ]
    1: One
    ```
  </Tab>

  <Tab title="Entrada">
    `Input` es un campo de texto. Ejecuta su callback `onSubmit` con el texto cuando el usuario presiona Enter:

    ```javascript theme={null}
    Input({
      key: 'new-note',
      label: 'Note',
      placeholder: 'Type a note and press Enter',
      value: '',
      submitLabel: 'add',
      onSubmit: addNote,
    })
    ```

    ```text theme={null}
    Note: Type a note and press Enter ⏎ add
    ```
  </Tab>
</Tabs>

Esta tabla enumera cada elemento:

| Elemento | Qué dibuja | Dónde |
| :- | :- | :- |
| `Box` | Un contenedor flex. Toma propiedades de diseño como `flexDirection`, `columnGap`, `padding`, `borderStyle` y `width`. | En todas partes |
| `Text` | Texto con estilo. Toma `color`, `bold`, `dimColor`, `italic` y `wrap`. Un `color` es una clave de tema o un color como `'red'`. Un `wrap` es `'wrap'`, `'truncate'`, `'truncate-start'`, `'truncate-middle'` o `'truncate-end'`. | En todas partes |
| `Button` | Un control que llama a `onPress` | En todas partes |
| `Link`, `Code`, `Markdown` | Un enlace con `href` y una `label` opcional, un bloque de código y texto formateado de la manera que lo son las respuestas de Claude. `Markdown` toma su contenido en una propiedad `text`, no en `children`, y necesita una `key` cuando pasa `onLinkPress`. | En todas partes |
| `Input`, `Select` | Un campo de texto y un selector | Terminal, Escritorio |
| `Svg` | Un documento SVG | Escritorio |
| `Client` | Una región dibujada por un segundo archivo suyo, para animación e entrada de puntero. Ese archivo no obtiene API de mods. Solo llega a sus hooks publicando datos, que llegan como un evento `ui.message`. | Terminal, Escritorio |
| `Raster`, `Image` | Una [cuadrícula de celdas coloreadas](#draw-a-grid-of-colored-cells) y una imagen | Terminal |

Si su módulo es un archivo `.tsx` o `.jsx`, puede escribir el árbol como JSX. Desestructure los elementos de `$.ui.resolve(e)` primero, porque un módulo de hooks no tiene globales de elementos.

Si un árbol utiliza un elemento que la aplicación no tiene, una propiedad que un elemento no toma o un hijo donde no va ninguno, Claude Code dibuja su propia versión del sitio.

En una sesión iniciada con `--plugin-dir`, una línea de transcripción lo dice, como `ui.render (Pane) refused: Text prop "bogusProp" is not allowed; the engine drew its own`. El [registro de depuración](/docs/es/plugins/mods/troubleshoot#read-the-debug-log) lo registra como `ui.render (Pane): a hook returned a tree that does not validate` con la misma razón. Nada más aparece en la sesión, por lo que cuando un dibujo no aparece, verifique esa línea o el registro.

<h3 id="draw-a-grid-of-colored-cells">
  Dibujar una cuadrícula de celdas coloreadas
</h3>

Para un mapa de calor, un gráfico de chispa o un tablero de juego en la terminal, dibuje un `Raster` y no un `Box` para cada celda. Un `Raster` toma una `key`, su tamaño en `columns` y `rows`, y `cells`, que empaqueta cada celda en una cadena. Cada celda son tres números: el punto de código del carácter, su color y su color de fondo. Un color es un número hexadecimal con dos dígitos cada uno para rojo, verde y azul, como `0xc62828` para un rojo, o `0x01000000` para el predeterminado de la terminal.

La aplicación de escritorio no tiene `Raster`, por lo que verifique `e.surface` y dibuje texto allí. Este cuerpo de panel dibuja un mapa de calor de tres por dos:

```javascript theme={null}
// The value that means "use the terminal's default color"
const DEFAULT_COLOR = 0x01000000

// Pack rows of [character, color] pairs into the one string a Raster takes
// One cell is three numbers: the character's code point, its color, and its background
function cellsOf(rows) {
  const numbers = rows.flat().flatMap(([char, color]) => [char.codePointAt(0), color, DEFAULT_COLOR])
  return new Uint8Array(Uint32Array.from(numbers).buffer).toBase64()
}

on('ui.render', { component: 'Pane' }, async ($, e, next) => {
  // Draw only in the pane opened with the id 'heat'
  if (e.requestId !== 'heat') return next(e)
  const { Box, Text, Raster } = $.ui.resolve(e)
  // Two rows of three cells, each a block character and its color
  const rows = [
    [['█', 0x2e7d32], ['█', 0xf9a825], ['█', 0xc62828]],
    [['█', 0x2e7d32], ['█', 0x2e7d32], ['█', 0xf9a825]],
  ]
  if (e.surface !== 'terminal') {
    return Text({ children: ['The heat map needs the terminal.'] })
  }
  return Box({
    flexDirection: 'column',
    children: [Raster({ key: 'grid', columns: 3, rows: 2, cells: cellsOf(rows) })],
  })
})
```

En la terminal, el panel muestra la cuadrícula:

<img src="https://mintcdn.com/claude-code/dgiVO_Od1X1faduV/images/mods-heat-map.svg?fit=max&auto=format&n=dgiVO_Od1X1faduV&q=85&s=b91bcce3bad74bc851149133d4acc5d5" alt="Un panel en la terminal que contiene una pequeña cuadrícula de bloques coloreados, dos filas de tres. La fila superior es verde, ámbar y roja. La fila inferior es verde, verde y ámbar." width="360" height="132" data-path="images/mods-heat-map.svg" />

La matriz `rows` es la parte que cambiaría, y `cellsOf` la convierte en la cadena empaquetada. El hook dibuja solo en un panel cuyo `id` es `heat`, por lo que abra uno con `$.ui.open({ id: 'heat' })` desde un comando, como el ejemplo [`hello-tabs`](#build-a-pane-with-tabs) abre su panel.

Cada carácter tiene que tener un ancho de una celda. Para animar un `Raster` que ya está en pantalla, llame a `$.ui.blit` con el `id` del panel como `requestId`, la `key` del `Raster`, el mismo tamaño y celdas nuevas. Para este ejemplo, eso es `$.ui.blit({ requestId: 'heat', key: 'grid', columns: 3, rows: 2, cells: cellsOf(newRows) })`. Repinta ese elemento sin ejecutar su hook `ui.render` nuevamente.

<h2 id="respond-to-presses-and-typing">
  Responder a pulsaciones y escritura
</h2>

Cuando el usuario presiona un botón, escribe en un campo o elige de una lista que su mod dibujó, Claude Code llama a la función que le dio a ese control, y se ejecuta en su módulo. Cada control toma sus propios callbacks:

* **`Button`**: toma `onPress(e)`, donde `e.surface` es la aplicación de la que proviene la pulsación
* **`Input`**: toma `onSubmit(value)` y `onInput(value)`
* **`Select`**: toma `onSelect(value)` con sus opciones en `options`, una lista de al menos una opción con valores únicos, como `[{ value: 'sm', label: 'Small' }, { value: 'lg', label: 'Large' }]`

Una prueba presiona o escribe en un control por su `key`, así que dé a cada control uno. Cada uso de un control también genera [`ui.press`, `ui.input` o `ui.select`](/docs/es/plugins/mods/reference#interface) con la `key` en `e.element`, y otro mod puede enganchar esos eventos. Su hook se ejecuta antes de su callback, por lo que ve lo que el usuario escribe en su `Input` y puede cambiarlo o responder en lugar de su callback. La API de mods no tiene método que presione el botón de otro mod.

<h3 id="know-which-keys-your-mod-can-receive">
  Enfoque del teclado y atajos de teclado
</h3>

Su mod nunca lee el teclado a sí mismo. El usuario presiona una tecla, Claude Code decide cuál de sus controles es para, y se ejecuta el callback de ese control. Aparte de un [atajo de teclado de dígito en la banda](/docs/es/plugins/mods/reference#elements), eso sucede solo mientras su panel o banda tiene enfoque del teclado. El resto del tiempo, las teclas van al símbolo del sistema.

<h4 id="how-a-pane-gets-keyboard-focus">
  Cómo un panel obtiene enfoque del teclado
</h4>

Un panel obtiene enfoque del teclado de una de tres maneras:

* Su mod lo abre con `focus: true` desde un comando o una pulsación
* El usuario presiona Ctrl+X y luego Tab
* El usuario hace clic en él

Claude Code otorga `focus: true` solo mientras el símbolo del sistema está vacío y nada más tiene enfoque del teclado. Un panel que se abre mientras el usuario está escribiendo no toma sus pulsaciones de teclas.

<h4 id="what-each-key-does">
  Qué hace cada tecla
</h4>

Esta tabla enumera qué hace una tecla mientras su panel o banda tiene enfoque del teclado:

| Tecla | Qué hace |
| :- | :- |
| Tab | Se mueve al siguiente control |
| Arriba y Abajo | Se mueven entre controles mientras su dibujo cabe. Cuando el panel o banda tiene más filas de las que puede mostrar, los desplazan. |
| Enter | Presiona el `Button` enfocado, envía el `Input` enfocado o elige en un `Select` |
| Atajo de teclado de un botón | Presiona ese botón. Mientras un `Input` tiene el enfoque, cada tecla imprimible va al campo. |
| Esc | Devuelve el enfoque del teclado al símbolo del sistema. Con `closeOnEscape: true`, también cierra el panel. |

Un mod no puede vincular Tab o las teclas de flecha a nada más, por lo que un juego se dirige con `w`, `a`, `s` y `d`.

<h4 id="set-a-hotkey-and-the-first-focus">
  Establecer un atajo de teclado y el primer enfoque
</h4>

Dos propiedades en un control deciden cómo el teclado lo alcanza:

* **`hotkey`**: para permitir que el usuario presione un `Button` con una tecla, dé un `hotkey` de un dígito o una letra minúscula, como en `hotkey: 'a'`
* **`autoFocus`**: para elegir qué control tiene el enfoque cuando se abre el panel, agregue `autoFocus: true` a él. Deje la propiedad fuera de los otros, porque Claude Code rechaza `autoFocus: false`.

Cómo se muestra un atajo de teclado depende del botón y la aplicación:

| Botón | En la terminal | En la aplicación de escritorio |
| :- | :- | :- |
| Con corchetes, el predeterminado | `[ Add one ]`, sin atajo de teclado mostrado | La etiqueta con una pequeña tecla al lado |
| Con `plain: true` | `1: One` | La etiqueta con una pequeña tecla al lado |

En la terminal, nombre la tecla en la etiqueta de un botón entre corchetes, o use `plain: true`, para que el usuario pueda ver qué presionar. La [referencia de elementos](/docs/es/plugins/mods/reference#elements) tiene las otras reglas de `Button`: `action`, atajos de teclado de dígitos en la banda y dos botones en un atajo de teclado.

<h3 id="take-typed-input-and-draw-a-row-for-each-item">
  Tomar entrada escrita y dibujar una fila para cada elemento
</h3>

Muchos paneles son un campo de texto con una lista debajo. El ejemplo en esta sección es un panel de notas: escribe una nota y presiona Enter para agregarla, y cada nota tiene un botón `x` que la elimina. Con dos notas agregadas, la terminal dibuja el panel de esta manera:

```text theme={null}
╭──────────────────────────────────────────────────────────╮
│ Note: Type a note and press Enter ⏎ add                ✕ │
│ x buy milk │
│ x call bob │
╰──────────────────────────────────────────────────────────╯
```

El ejemplo utiliza dos técnicas:

* **Tomar entrada escrita**: un `Input` llama a `onSubmit(value)` con el texto del campo cuando el usuario presiona Enter, y `onInput(value)` en cada cambio
* **Dibujar una lista**: asigne sus datos a una fila cada uno, y dé a cada botón de fila su propia `key`

Este hook dibuja el contenido del panel:

```javascript theme={null}
// The list the pane draws
let notes = []

on('ui.render', { component: 'Pane' }, async ($, e, next) => {
  // Draw only in the pane opened with the id 'notes'
  if (e.requestId !== 'notes') return next(e)
  const { Box, Text, Button, Input } = $.ui.resolve(e)
  const redraw = () => $.ui.invalidate('ui.render')

  return Box({
    flexDirection: 'column',
    children: [
      Input({
        key: 'new-note',
        label: 'Note',
        placeholder: 'Type a note and press Enter',
        // Draw the field empty each time, which clears it after a submit
        value: '',
        submitLabel: 'add',
        autoFocus: true,
        // Runs when you press Enter in the field
        onSubmit: async (value) => {
          // Ignore an empty line
          if (!value.trim()) return
          notes = [...notes, value.trim()]
          redraw()
          await $.store.set('notes', notes)
        },
      }),
      // One row for each note: a delete button, then the note's text
      ...notes.map((note, i) =>
        Box({
          flexDirection: 'row',
          columnGap: 1,
          children: [
            Button({
              // A key of its own, so each row's button can be told apart
              key: 'delete-' + i,
              label: 'x',
              plain: true,
              onPress: async () => {
                notes = notes.filter((_, j) => j !== i)
                redraw()
                await $.store.set('notes', notes)
              },
            }),
            Text({ children: [note] }),
          ],
        }),
      ),
    ],
  })
})
```

Para probar el panel:

* **Agregar una nota**: escriba una línea y presione Enter. La línea aparece como una nueva fila y el campo se vacía.
* **Eliminar una nota**: presione Tab hasta que el botón `x` de la nota tenga el enfoque, luego presione Enter. La `x` es la etiqueta del botón y no un atajo de teclado, por lo que escribir la letra no lo presiona.

Cada cambio sigue el mismo ciclo de renderizado que `hello-tabs`: el callback cambia `notes`, llama a `redraw` y guarda la lista en `$.store`.

El campo se vacía después de cada envío debido a su propiedad `value`. `value` es el texto que el campo contiene cuando se dibuja, y la escritura del usuario lo reemplaza hasta que su hook dibuja el campo nuevamente. El ejemplo siempre dibuja el campo con `''`.

El ejemplo guarda las notas y no las carga. Para traerlas de vuelta en la siguiente sesión, léalas en un hook `session.start`, de la manera que `hello-tabs` lee `count`.

Tres propiedades componen la línea del campo, `Note: Type a note and press Enter ⏎ add`:

| Propiedad | En el ejemplo | Qué es |
| :- | :- | :- |
| `label` | `Note` | El texto antes del campo. La terminal dibuja `: ` después. |
| `placeholder` | `Type a note and press Enter` | Texto atenuado que se muestra mientras el campo está vacío |
| `submitLabel` | `add` | La palabra después de `⏎` que dice qué hace Enter |

Enviar un `Input` no inicia un turno a menos que su callback llame a [`$.prompt.submit`](/docs/es/plugins/mods/api#start-a-turn-from-a-background-job).

<h2 id="redraw-when-something-changes">
  Redibujar un sitio
</h2>

Un dibujo es una instantánea: muestra lo que devolvió su hook `ui.render` la última vez que se ejecutó. Para mostrar algo nuevo, el hook tiene que ejecutarse de nuevo. Claude Code lo ejecuta de nuevo para algunos cambios, y su mod solicita el resto.

<h3 id="when-claude-code-redraws-without-being-asked">
  Cuándo Claude Code redibuja sin ser solicitado
</h3>

Claude Code ejecuta su hook `ui.render` de nuevo cuando cambian los props del sitio o cambia el ancho de la terminal. No ejecuta el hook en un temporizador y no puede saber cuándo cambia una variable en su módulo.

<h3 id="redraw-when-your-data-changes">
  Redibujar cuando sus datos cambian
</h3>

Para que sus sitios se redibjen después de que sus propios datos cambien, llame a `$.ui.invalidate('ui.render')`. Este panel cuenta pulsaciones. La devolución de llamada del botón cambia `count` y luego solicita un redibujado:

```javascript theme={null}
let count = 0

on('ui.render', { component: 'Pane' }, async ($, e, next) => {
  if (e.requestId !== 'counter') return next(e)
  const { Box, Text, Button } = $.ui.resolve(e)
  return Box({
    flexDirection: 'row',
    columnGap: 2,
    children: [
      Button({
        key: 'more',
        label: 'Add one',
        onPress: () => {
          count += 1
          // The data changed, so ask Claude Code to draw the pane again
          $.ui.invalidate('ui.render')
        },
      }),
      Text({ children: ['Count: ' + count] }),
    ],
  })
})
```

Cada pulsación aumenta el número en el panel. El ejemplo [`hello-tabs`](#build-a-pane-with-tabs) envuelve la misma llamada en su función `redraw`.

Un valor que mantiene en [`$.state`](#keep-a-value-in-\$-state) no necesita la llamada, porque escribir el valor redibuja los sitios que lo leen.

<h3 id="redraw-on-a-timer">
  Redibujar en un temporizador
</h3>

Para mantener un reloj, una cuenta atrás o un valor de fuera de la sesión actual, redibuje según un cronograma. Inicie un temporizador en el hook `session.start` del módulo. Si el módulo ya tiene uno, como lo hace `hello-tabs`, agregue la línea [`$.clock.every`](/docs/es/plugins/mods/api#run-work-in-the-background) a él:

```javascript theme={null}
on('session.start', async ($, e, next) => {
  // Every 1000 milliseconds, ask Claude Code to draw your sites again
  $.clock.every(1000, () => $.ui.invalidate('ui.render'))
  return next(e)
})
```

Claude Code ahora ejecuta su hook `ui.render` una vez por segundo. El temporizador se detiene cuando el módulo se recarga, y la nueva copia del módulo inicia el suyo propio.

<h3 id="how-often-a-site-can-redraw">
  Con qué frecuencia se puede redibujar un sitio
</h3>

Claude Code limita la frecuencia con la que redibuja un sitio, por lo que su mod puede llamar a `$.ui.invalidate` con la frecuencia que cambien sus datos. El panel visible y la banda tienen un límite más alto que otros sitios, y la [tabla de límites](/docs/es/plugins/mods/reference#limits) tiene los números.

Las llamadas que llegan más rápido que el límite se combinan en un redibujado. Ese redibujado ejecuta su hook una vez, y el hook lee sus datos tal como están en ese momento, por lo que se muestra el valor más reciente y los valores intermedios no se muestran. Una animación no puede ejecutarse más rápido que el límite.

<h2 id="keep-state">
  Mantener estado
</h2>

Un mod tiene tres lugares para mantener un valor, y difieren en cuánto tiempo dura el valor: hasta que el módulo se recarga, hasta que termina la sesión o de una sesión a la siguiente. Elija según cuánto tiempo tenga que durar el valor:

| Mantenerlo en | Dura hasta | Úselo para |
| :- | :- | :- |
| Una variable a nivel de módulo | El módulo se recarga, lo que sucede cada vez que guarda un archivo durante el desarrollo | Valores que puede perder, como `tab` en `hello-tabs` |
| `$.state` | La sesión termina, o el usuario ejecuta `/clear`, `/resume` o `/branch` | Valores de los que depende un dibujo que deben sobrevivir a una recarga |
| `$.store` | Su mod lo elimina, o ninguna sesión lee o escribe el almacén durante [`cleanupPeriodDays`](/docs/es/settings-reference#cleanupperioddays). El almacén es un almacén de clave-valor, guardado como un archivo JSON de su propio plugin bajo `~/.claude/plugins/store/`. | Configuración, historial, cualquier cosa que el usuario espera encontrar la próxima vez |

`$.store.get(key)` se resuelve en el valor o `undefined`, y `$.store.set(key, value)` toma cualquier valor JSON.

<h3 id="keep-a-value-in-state">
  Mantener un valor en `$.state`
</h3>

`$.state` mantiene valores durante la duración de una sesión, y se redibuja por usted. Es estado reactivo: un hook `ui.render` que lee un valor se suscribe a él, por lo que Claude Code redibuja ese sitio cada vez que escribe el valor, y no llama a `$.ui.invalidate`. Un valor en `$.state` también sobrevive a una recarga del módulo, lo que una variable no.

Para configurarlo, declare sus valores, apunte su manifiesto a la declaración, luego defina y use cada valor. Los ejemplos mueven el `count` de `hello-tabs` a `$.state`.

<h4 id="declare-the-values">
  Declarar los valores
</h4>

Declare los valores en un archivo de tipos. La clave externa es el nombre de su plugin, y cada entrada bajo ella es un valor y su tipo. Guarde esto como `hello-tabs/types/index.d.ts`:

```typescript hello-tabs/types/index.d.ts theme={null}
declare module 'claude-code' {
  interface PluginState {
    'hello-tabs': {
      tab: 'one' | 'two'
      count: number
    }
  }
}
```

<h4 id="point-the-manifest-at-the-declaration">
  Apuntar el manifiesto a la declaración
</h4>

Para permitir que `claude plugin validate` verifique su código contra ese archivo, agregue un campo `types` al manifiesto con su ruta:

```json hello-tabs/.claude-plugin/plugin.json theme={null}
{
  "name": "hello-tabs",
  "version": "0.1.0",
  "description": "Opens a pane with two tabs and a counter",
  "author": { "name": "Your Name" },
  "types": "./types/index.d.ts"
}
```

<h4 id="define-read-and-write-a-value">
  Definir, leer y escribir un valor
</h4>

En su módulo, defina cada valor con un predeterminado, léalo mientras dibuja y escríbalo desde un callback. `atom` nombra un valor y su predeterminado, `read` lo devuelve y `update` lo escribe. Los tres ayudantes llaman a `$.state.get` y `$.state.set` por usted:

```javascript theme={null}
import { atom, read, update } from 'claude-code'

// At the top of the module: name the value and give its default
const count = atom({ plugin: 'hello-tabs', key: 'count' }, 0)

// In the ui.render hook: read the value to draw it
const n = await read($, count)

// In a Button: write a new value from the old one
onPress: () => update($, count, (value) => value + 1)
```

Porque el hook `ui.render` leyó `count`, Claude Code ejecuta el hook nuevamente cada vez que el botón lo escribe.

Tres reglas se aplican al código:

* **Escriba `plugin` y `key` como cadenas literales**: `claude plugin validate` las lee de su fuente
* **Declare cada valor en el archivo de tipos**: de lo contrario, la validación falla con `hello-tabs.count is not declared`
* **Escriba desde un callback u otro hook de evento**: un hook `ui.render` puede leer estado y no puede escribirlo, así que escriba desde `onPress`, `onSubmit` u otro hook de evento

<h4 id="change-hello-tabs-to-use-state">
  Cambiar `hello-tabs` para usar `$.state`
</h4>

Para mover `count` en `hello-tabs` a `$.state`, cambie cada línea que lo use:

* **En la parte superior del módulo**: agregue la línea `import` y reemplace `let count = 0` con la línea `atom`
* **En el hook `ui.render`**: agregue la línea `read` antes de `tabButton` y dibuje `'Count: ' + n` en el `Text`
* **En el botón Add one**: reemplace `onPress` con el de [Guardar desde más de una sesión](#save-from-more-than-one-session), que guarda el recuento además de escribirlo
* **En el hook `session.start`**: reemplace las dos líneas que leen `saved` con la llamada `loadCount` de [Cargar un valor guardado nuevamente después de `/clear`](#load-a-saved-value-again-after-clear)

Mantenga `redraw` para los botones de pestaña, porque `tab` sigue siendo una variable.

<h3 id="load-a-saved-value-again-after-clear">
  Cargar un valor guardado nuevamente después de `/clear`
</h3>

Si su mod copia un valor guardado de `$.store` a `$.state` en `session.start`, tiene que copiarlo nuevamente después de `/clear`, `/resume` o `/branch`. Esos comandos devuelven cada valor de `$.state` a su predeterminado, y `session.start` no se dispara nuevamente. [`classic.SessionStart`](/docs/es/plugins/mods/events#hook-the-settings-hook-events) se dispara después de cada uno de ellos, con `e.source` establecido en `clear`, `resume` o `fork`, así que copie el valor nuevamente en un hook en él. De lo contrario, su dibujo muestra el predeterminado, y un callback que guarda el valor de `$.state` escribe el predeterminado sobre lo que almacenó.

Este código carga `count` de ambos hooks. Se basa en la versión de `$.state` de `hello-tabs`, donde `count` es un átomo y `update` se importa. Ponga `loadCount` arriba de `register` y agregue la llamada `loadCount` al hook `session.start` que ya tiene. `classic.SessionStart` también se dispara al inicio y después de la compactación, que no reinicia `$.state`, por lo que el filtro en `source` mantiene el hook a los tres reiniciados:

```javascript theme={null}
// Copy the saved count from $.store into $.state, or 0 if nothing is saved
async function loadCount($) {
  const saved = Number((await $.store.get('count')) ?? 0)
  await update($, count, () => saved)
}

// Runs before your first prompt, and again after a reload
on('session.start', async ($, e, next) => {
  await loadCount($)
  return next(e)
})

// Runs again after /clear, /resume, and /branch, which reports fork
on('classic.SessionStart', { source: ['clear', 'resume', 'fork'] }, async ($, e, next) => {
  await loadCount($)
  return next(e)
})
```

Con ambos hooks en su lugar, el panel muestra el recuento guardado después de `/clear` y no `0`, y la siguiente pulsación de **Add one** suma al recuento guardado.

`loadCount` escribe el valor almacenado sobre el que está en `$.state`, y `session.start` se dispara nuevamente cada vez que el módulo se recarga. Para mantener el almacén actualizado, guarde en cada cambio, como lo hace el botón **Add one**.

Para verificar la recarga sin una sesión, [pruebe el dibujo después de `/clear`](/docs/es/plugins/mods/test#test-a-drawing-after-clear).

<h3 id="save-from-more-than-one-session">
  Guardar desde más de una sesión
</h3>

Cada sesión en su máquina que ejecuta su mod comparte un `$.store`. Un `get` seguido de un `set` no es atómico. Cuando dos sesiones leen un valor, lo cambian y lo escriben de vuelta, compiten, y la segunda escritura reemplaza la primera.

Dos opciones hacen que sea menos probable:

* **Dé a cada elemento su propia clave**: un `set` cambia solo su propia clave, por lo que las sesiones que escriben claves diferentes no se sobrescriben entre sí
* **Lea nuevamente justo antes de escribir**: para un valor que varias sesiones cambian, `get` la clave en el callback y construya el nuevo valor a partir de eso, no de una copia que cargó en `session.start`. La escritura de otra sesión se pierde si llega entre su `get` y su `set`.

Este botón suma uno a lo que el almacén contiene ahora, luego actualiza el dibujo:

```javascript theme={null}
onPress: async () => {
  // Read what the store holds now, which another session may have changed
  const saved = Number((await $.store.get('count')) ?? 0)
  // Save the new count, then show it
  await $.store.set('count', saved + 1)
  await update($, count, () => saved + 1)
}
```

Si una segunda sesión ha presionado su propio botón tres veces desde que esta sesión comenzó, esta pulsación muestra y guarda un recuento que incluye esos tres.

<h2 id="next-steps">
  Próximos pasos
</h2>

* [Reaccionar a eventos](/docs/es/plugins/mods/events): alimente su dibujo desde llamadas de herramientas y turnos
* [Usar la API de mods](/docs/es/plugins/mods/api): alimente su dibujo desde temporizadores y llamadas de modelo
* [Probar un dibujo](/docs/es/plugins/mods/test#test-a-drawing): presione sus botones desde una prueba, en más de una superficie
* [Sitios de renderizado](/docs/es/plugins/mods/reference#render-sites) y [elementos](/docs/es/plugins/mods/reference#elements): propiedades de cada sitio y propiedades de cada elemento
