En Alpic, creemos que la próxima generación de productos y servicios se diseñará en torno a experiencias centradas en la IA. En estas interfaces, los usuarios colaboran con los modelos en lugar de seguir una secuencia de pasos predefinida, como ocurre en las interfaces tradicionales.
En cuanto OpenAI lanzó el Apps SDK, empezamos a usarlo. En tres meses, desarrollamos dos docenas de Apps para ChatGPT, tanto para uso interno como para nuestros clientes B2B y B2C en sectores como los viajes, el comercio minorista y el SaaS.
Pronto descubrimos que desarrollar Apps para ChatGPT es fundamentalmente distinto de desarrollar aplicaciones web o móviles tradicionales. Los patrones que funcionan bien en la web (obtener datos justo cuando se necesitan, gestionar el estado desde la interfaz, pedirle al usuario que configure las opciones explícitamente, entre otros) suelen fallar en un entorno con agentes y hasta pueden empeorar la experiencia.
En este artículo reunimos las 15 lecciones más importantes que aprendimos al desarrollar Apps para ChatGPT en proyectos reales. También contamos cómo las incorporamos en un framework de código abierto para la comunidad, Skybridge, y en una habilidad de Codex para que los desarrolladores puedan idear, crear, probar y lanzar Apps mucho más rápido.
El problema de los tres cuerpos
Con las aplicaciones web tradicionales, todo era sencillo. Solo había un usuario y una interfaz. En una App para ChatGPT entra en juego un tercer cuerpo, el modelo.
Una de las partes más difíciles de desarrollar para ChatGPT es gestionar cómo circula la información entre los tres. Si el usuario hace clic en el botón “Seleccionar” de tu widget, el cambio se ve en la interfaz, pero el modelo, que lleva el hilo de la conversación, solo se entera si le comunicas ese contexto explícitamente. Si después el usuario pide “Dame más detalles sobre este producto”, el modelo no tiene forma de saber qué producto está viendo.
Llamamos asimetría de contexto a esta situación en la que cada participante conoce una parte del sistema y ninguno tiene el panorama completo. Para desarrollar buenas Apps para ChatGPT, no hace falta mantener todo sincronizado. Hay que decidir qué información compartir, cuándo compartirla y quién necesita verla. Resolver esto marca la diferencia entre una App incómoda de usar y una experiencia fluida con un agente.
1. Conviene compartir solo parte del contexto
Nuestro primer impulso fue “compartir todo en todas partes”. Ese resultó ser uno de nuestros primeros errores.
En la práctica, los distintos componentes de una App para ChatGPT suelen necesitar vistas deliberadamente distintas del mismo estado. ¿Por qué?
- Por rendimiento: los widgets de la interfaz suelen necesitar muchos más datos de los que el modelo podría llegar a necesitar. En una App de reservas de viajes, por ejemplo, el widget puede requerir imágenes, distintas tarifas y opciones precargadas. Enviar todo eso al modelo aumentaría el consumo de tokens, la latencia y la cantidad de información que lo distrae de la tarea.
- Por la lógica de la App: a veces, el diseño exige que solo algunos participantes tengan cierta información. En una de nuestras primeras Apps, un juego de misterio llamado Murder in the Valleys, el modelo necesita saber quién es el asesino para interpretar bien su papel, pero la interfaz y el usuario no deben saberlo. En un juego al estilo de Time’s Up, ocurre lo contrario. La interfaz le muestra al usuario la palabra secreta y el modelo no debe conocerla.
La lección que aprendimos fue que, en lugar de “sincronizar siempre todo”, hay que decidir explícitamente quién necesita saber qué. Formalizamos esa decisión con distintos campos en la salida de la herramienta:
| Campo | Función | Quién puede verlo |
|---|---|---|
| structuredContent | Datos tipados para el widget y el modelo | Tanto el widget como el modelo (mediante las funciones toolOutput y callTool) |
| _meta | Metadatos de la respuesta | Solo el widget. El modelo no tiene acceso. |
Por ejemplo, en el juego Time’s Up, enviábamos la palabra secreta únicamente al widget en el campo _meta. Así, el modelo podía intentar adivinarla a partir de las pistas del usuario.
2. La carga diferida no funciona tan bien en las Apps con IA
Como veníamos del desarrollo web, solíamos recurrir a la carga diferida. Obteníamos los datos cuando el usuario hacía clic, cargábamos los detalles bajo demanda y procurábamos reducir al mínimo la cantidad de datos enviados al inicio.
En ChatGPT, ocurre lo contrario. Las llamadas a herramientas suelen tardar varios segundos debido al aislamiento de seguridad en el sandbox y al razonamiento del modelo.
En la práctica, aprendimos a adelantar la carga todo lo posible. Enviábamos la mayor cantidad de datos posible en la primera respuesta de la herramienta e hidratábamos el widget mediante window.openai.toolOutput. Casi siempre, el resultado fue una experiencia más rápida y una interfaz que respondía mejor.
Por supuesto, si el widget puede obtener datos de forma segura desde un punto de acceso público de una API y no necesita compartirlos con el modelo, puedes usar llamadas XHR tradicionales dentro del widget. Sin embargo, en la mayoría de los casos conviene que el modelo pueda llamar a las herramientas de forma autónoma para que la interacción siga siendo conversacional.
3. El modelo necesita saber qué ve el usuario
Hay un problema sutil pero importante que surge cuando el usuario interactúa con un widget (por ejemplo, selecciona un producto de una lista) y luego hace una pregunta en el chat. Si el modelo no sabe a qué parte de la interfaz se refiere el usuario, no podrá responder correctamente.
Para resolverlo, usamos window.openai.setWidgetState(state), que permite guardar datos específicos del estado. Esos datos se incorporan al contexto del modelo en su siguiente interacción con el usuario.
A medida que nuestras Apps se volvían más complejas, vimos que teníamos que agregar setWidgetState en muchos lugares para que el modelo pudiera seguir la navegación del usuario. Por eso introdujimos una forma declarativa de describir el contexto de la interfaz. En vez de actualizar el modelo de forma imperativa en cada interacción, agregamos un atributo data-llm directamente a los componentes:
<div
data-llm={
selectedTab === "details"
? "User is viewing product details"
: "User is viewing reviews"
}
>
Product details
</div>Para automatizar este proceso, creamos un complemento de Vite que extrae esos atributos y actualiza widgetState automáticamente. El modelo recibe el contexto pertinente de la interfaz en el momento adecuado, sin que los desarrolladores tengan que sincronizar cada interacción a mano.
Puedes encontrar este complemento de Vite (y muchos otros consejos de este artículo) en el framework de código abierto que creamos para compartir con la comunidad lo que aprendimos.
4. Cada tipo de interacción requiere una API distinta
Las Apps para ChatGPT tienen varias vías de comunicación entre el widget, el servidor y el modelo. Cada vía admite un tipo de interacción distinto, por lo que no son intercambiables.
Una de las principales lecciones que aprendimos al desarrollar Apps para ChatGPT fue la importancia de identificar explícitamente esas vías de comunicación y decidir qué mecanismo se encargará de cada parte de la experiencia.
Así se ven esas vías en un diagrama:

Estas lecciones establecen las bases de una App para ChatGPT. Explican cómo se comparte el contexto, cómo se entera el modelo de lo que ve el usuario y cómo se propagan las distintas interacciones por el sistema. En la siguiente sección partimos de esas bases para explorar sus implicaciones en el diseño de interfaces.
Reinventar la interfaz para la IA
Las Apps para ChatGPT son un entorno completamente nuevo. Pronto aprendimos a dejar de lado nuestras ideas preconcebidas sobre las interfaces y a aprovechar al máximo las nuevas capacidades. En esta sección repasamos lo que tuvimos que aprender sobre diseño de interfaces y los supuestos que tuvimos que abandonar para crear Apps eficaces.
5. La interfaz debe adaptarse a varios modos de visualización y a sus restricciones
Las Apps para ChatGPT no tienen un diseño fijo. Según cómo y cuándo se invoque la App, un mismo widget puede mostrarse en tres modos distintos.
Las Apps pueden aparecer dentro de la conversación, superponerse a ella en modo imagen en imagen (PiP) u ocupar la pantalla completa cuando necesitan más espacio. Los modos PiP y pantalla completa admiten interfaces más complejas, pero también incorporan elementos superpuestos que el widget no controla. Es fundamental respetar las zonas seguras de cada dispositivo, como el espacio que ocupa el botón de cierre siempre visible en los dispositivos móviles, para que el contenido no quede recortado y la interacción sea óptima.
Con el tiempo, identificamos cuándo conviene usar cada modo de visualización:
| Cómo se ve | Cuándo usarlo | |
|---|---|---|
| Dentro de la conversación | Es el modo predeterminado. El widget permanece en el historial de la conversación. | Para interacciones rápidas. |
| Pantalla completa | El widget ocupa toda la pantalla, con la barra del chat en la parte inferior. | Si el widget es complejo y necesita mucho espacio (por ejemplo, un mapa). |
| Imagen en imagen | Tiene el mismo tamaño que dentro de la conversación, pero el widget queda superpuesto al chat. | Si el widget sigue siendo relevante para las preguntas posteriores del usuario una vez generada la respuesta. |
6. Una interfaz integrada necesita coherencia visual
Al principio, una de nuestras dudas era cuánta libertad podíamos tomarnos con el diseño visual de una App para ChatGPT. Como los usuarios se encontraban con una interfaz nueva, esta debía resultarles familiar. El diseño tenía que ser coherente tanto entre nuestras propias Apps como con el resto del ecosistema de ChatGPT. A diferencia de un producto independiente, un widget forma parte de una interfaz existente, donde cualquier inconsistencia visual se nota de inmediato.
Por suerte, el kit de interfaz del Apps SDK de OpenAI nos dio un punto de partida claro.
El kit se basa en Tailwind CSS e incluye componentes listos para usar, íconos y tokens de diseño que siguen el sistema de diseño de ChatGPT. Con él pudimos avanzar rápido y lograr que nuestros widgets se vieran como parte de la interfaz de ChatGPT, incluso cuando creábamos componentes personalizados, como los de nuestra integración con Mapbox.
7. Filtros basados en lenguaje natural
Los paneles tradicionales tienen barras laterales llenas de casillas de verificación y controles deslizantes para seleccionar rangos. En una interfaz con agentes, esto suele ser un paso atrás. Si los usuarios pueden expresar lo que buscan en lenguaje natural, por ejemplo, “Destinos soleados en Europa por menos de 200 dólares”, obligarlos a usar varios controles complica la tarea. Debería bastar con decirlo.
Por eso, en la mayoría de nuestras Apps optamos por una interfaz “sin filtros”. En lugar de incluir una barra lateral con opciones para filtrar y ordenar, le damos al modelo una lista de valores (LOV) para los parámetros de nuestras herramientas.
Así, el modelo puede tomar el mensaje del usuario directamente como entrada sin tener que “adivinar” qué opciones hay. En otras palabras, puede convertir una solicitud en lenguaje natural en los parámetros que requiere la API de nuestro backend. Si el usuario dice “soleado”, el modelo sabe que debe llamar a la herramienta con weather=“sunny”.
8. Los archivos amplían las posibilidades de interacción
Al crear Apps más complejas, aprendimos que los archivos merecen más que un papel secundario como datos de entrada. En las Apps para ChatGPT, los archivos permiten nuevas formas de interacción. En vez de empezar con formularios o filtros, la experiencia puede partir de algo que el usuario ya tiene.
Por ejemplo, en una App de comercio electrónico, el usuario puede subir al chat la foto de un producto, pedirle al modelo que lo identifique y, desde el widget, buscar productos que coincidan o descubrir otros.
Esto es posible porque los archivos pueden circular por ambos lados del sistema. Del lado del modelo, las herramientas pueden usar directamente los archivos que el usuario sube al chat mediante openai/fileParams. Esto permite que el modelo razone a partir de imágenes u otros recursos que aporte el usuario. Del lado de la interfaz, los widgets también pueden trabajar directamente con archivos mediante window.openai.uploadFile y window.openai.getFileDownloadUrl. Así, es posible pedirle al usuario que suba archivos desde la interfaz o generar archivos que pueda descargar y reutilizar.
El paso a producción
Cuando las Apps pasan del desarrollo local a producción, entran en juego otras consideraciones sobre seguridad, configuración y herramientas. A eso dedicamos este tercer grupo de lecciones.
9. Las CSP son el nuevo CORS
Por seguridad, OpenAI renderiza las Apps dentro de dos iframes, uno anidado en el otro. Las políticas de seguridad de contenido (CSP) son un mecanismo nativo de aislamiento de los iframes. Esta configuración las aplica de manera estricta, lo que suele dar lugar al clásico problema de “funciona en local, pero falla en producción”.
En el desarrollo web tradicional, a veces basta con una política permisiva. El Apps SDK exige mucha más precisión.
En el archivo de manifiesto de la App, tienes que declarar con cuidado qué dominios están permitidos para cada tipo de interacción:
| Campo | Función | Ejemplo | Errores frecuentes |
|---|---|---|---|
| connectDomains | Solicitudes a la API y solicitudes XHR | https://api.weather.com | Olvidar que la API de preproducción y la de producción pueden tener dominios distintos. |
| resourceDomains | Imágenes, fuentes y scripts | https://cdn.jsdelivr.net | Usar una CDN genérica como delivr.net sin incluirla en la lista de dominios permitidos. |
| frameDomains | Inserción de iframes | https://www.youtube.com | Insertar un video de YouTube o una instancia de Mapbox sin incluir su dominio en la lista de permitidos. |
| redirectDomains | Enlaces externos que se abren sin advertencias | https://app.alpic.ai | Omitir el dominio de la página de pago o el de la URL de retorno de OAuth. |
Dar prioridad a la configuración de las CSP desde el principio nos ahorró mucho tiempo de depuración en producción.
10. Unos pocos ajustes del widget tienen un gran impacto
Además de las CSP, unos pocos ajustes del widget determinan cómo se reparte el control entre el widget, el modelo y el entorno del host. Es fácil pasarlos por alto, pero definen límites importantes para la navegación, el acceso a las herramientas y la publicación.
Límites del host y de la navegación
widgetDomaines obligatorio para enviar la App a revisión. Define el destino predeterminado del botón “Abrir en <App>” en el modo de pantalla completa. También interviene en la lista de orígenes permitidos, ya que los widgets se renderizan en<widgetDomain>.web-sandbox.oaiusercontent.com. UsamossetOpenInAppUrlpara dirigir a los usuarios a la ruta adecuada según el contexto.
Límites del modelo y de las herramientas
- Las anotaciones de las herramientas deben cumplir las pautas de publicación. Los indicadores como
readOnly,destructiveHintyopenWorldHintson obligatorios y se validan al enviar la App a revisión. - La visibilidad de las herramientas importa. Debes marcar explícitamente como privadas las herramientas que el modelo no deba poder invocar.
Límites de ejecución del widget
widgetAccessiblecontrola si el widget puede llamar a herramientas por su cuenta mediantecallTool.
Cada uno de estos ajustes es pequeño, pero en conjunto determinan si la App funcionará correctamente una vez publicada.
Cómo acelerar las iteraciones
El Apps SDK evoluciona rápidamente y nos entusiasma desarrollar Apps a medida que avanza. Para que el proceso de desarrollo fuera más ágil y eficiente, decidimos crear nuestro propio framework de código abierto y compartirlo con la comunidad. Estas son algunas de las lecciones que aprendimos y que pueden ayudarte a evitar los problemas de desarrollo que encontramos al principio.
11. Para iterar rápido hace falta la recarga en caliente
Una de las primeras cosas que abordamos fue la velocidad de iteración. Los recursos se almacenan en caché con un tiempo de vida (TTL) prolongado y se reenvían mediante JSON-RPC. Por esa combinación, la recarga en caliente de módulos que ofrecen Vite o Next.js no funciona de forma predeterminada en las Apps para ChatGPT.
Después de dedicar bastante tiempo a entender cómo funciona Vite por dentro, creamos un complemento de Vite que permite recargar los widgets en tiempo real dentro de ChatGPT. El complemento intercepta las solicitudes de recursos dirigidas al servidor MCP e inyecta las actualizaciones en tiempo real en el iframe de ChatGPT. Ver de inmediato en ChatGPT los cambios que hacíamos en el IDE redujo considerablemente el tiempo que tardábamos en comprobar el resultado.
12. Algunas pruebas se pueden hacer fuera de ChatGPT
Probar en ChatGPT es lo ideal, pero durante las primeras iteraciones un emulador local puede ayudarte a avanzar más rápido, sobre todo si trabajas en definiciones de herramientas que requieren actualizar la conexión con el servidor MCP personalizado en ChatGPT.
Para acelerar esas primeras iteraciones, creamos un emulador local ligero que simula el entorno del host de ChatGPT e incluye herramientas de depuración y registros específicos de las Apps. Con él podíamos probar cambios en el estado de React y en la disposición de la interfaz en milisegundos. Reservábamos las pruebas en ChatGPT para validar las interacciones con el modelo y los casos límite.
13. Las pruebas en dispositivos móviles requieren soporte específico
Las pruebas en dispositivos móviles plantearon otro desafío. Para probar una App en ChatGPT, necesitas exponer tu servidor local mediante un túnel. Sin embargo, como Vite usa localhost de forma predeterminada, no se puede acceder a esa misma URL desde otros dispositivos.
Para resolverlo, ampliamos nuestro complemento de Vite para que admitiera el reenvío de dominios en los puertos expuestos mediante el túnel. Así pudimos probar las Apps en dispositivos iOS y Android e incorporar la validación en dispositivos móviles a nuestro flujo de trabajo habitual.
14. Las abstracciones conocidas, como los hooks de React, aceleran el desarrollo del frontend
El Apps SDK ofrece capacidades potentes, pero se accede a ellas principalmente a través de API de JavaScript de bajo nivel. Como llevábamos años usando React, queríamos trabajar con conceptos que ya dominábamos.
Por eso incorporamos abstracciones pensadas para React, como los hooks useCallTool, useWidgetState y useLocale. También incorporamos mecanismos más avanzados de gestión del estado, como createStore, que se basa en Zustand y sirve para gestionar flujos de datos complejos. Recuperar patrones conocidos de frontend redujo el código repetitivo y acercó el desarrollo de widgets a los flujos de trabajo del desarrollo web moderno.
Cómo convertir las lecciones en una habilidad de Codex
15. Convierte las lecciones en herramientas reutilizables
A medida que estos patrones se repetían en distintas Apps, nos dimos cuenta de que volver a descubrirlos en cada proyecto nos quitaba tiempo. Para que el desarrollo de Apps para ChatGPT fuera más rápido y predecible, decidimos incorporar estas lecciones directamente en nuestras herramientas, tanto para nuestro equipo como para la comunidad.
De ahí surgieron dos iniciativas complementarias:
- El framework Skybridge: este framework de código abierto para React reúne muchos de los patrones descritos en el artículo en piezas reutilizables. Incluye nuestros hooks (
useCallTool,useToolInfo), las herramientas de desarrollo (HMR y el emulador local) y el atributo data-llm. - La habilidad de Codex chatgpt-apps-builder: sobre la base del framework, creamos una habilidad de Codex específica para acompañar todo el ciclo de vida de una App:
- Generación de ideas: explorar cómo hacer que la App aproveche las capacidades de un agente en lugar de limitarse a adaptar una aplicación web.
- Generación de código: escribir en paralelo el frontend en React y el backend del servidor MCP, ambos preconfigurados con los patrones adecuados de experiencia de usuario y de interfaz.
- Pruebas locales: iniciar servidores de desarrollo y conectar Apps locales a ChatGPT para iterar en tiempo real mediante la recarga en caliente.
- Control de calidad y publicación: ejecutar comprobaciones estructuradas según las pautas de OpenAI para el envío de Apps a revisión, incluida la validación de las CSP, la revisión de las zonas seguras y las pruebas en producción.
- Despliegue de la App: ayudar con los últimos pasos necesarios para lanzar la App y seguir iterando.
Para instalar y usar la habilidad, ejecuta este comando:
npx skills add alpic-ai/skybridge
Conclusión
Desarrollar Apps para ChatGPT exige replantearse cómo circula el contexto, cómo funcionan las interfaces y cómo colaboran los usuarios y los modelos. Muchas de las lecciones de este artículo surgieron al ver dónde dejaban de funcionar los patrones web conocidos al aplicarlos a sistemas con agentes.
Al compartir estas lecciones e incorporarlas en nuestro framework de código abierto y en nuestra habilidad de Codex, esperamos que los equipos dediquen menos tiempo a redescubrir los mismos problemas y más a explorar las posibilidades de este nuevo modelo de interacción. Las Apps para ChatGPT más atractivas irán más allá de adaptar productos existentes. Se diseñarán desde el principio en torno a esta nueva forma de interactuar con la IA.