1. El problema de los túneles «aleatorios»
Cuando ejecutas por primera vez ngrok http 3000 o un Cloudflare Quick Tunnel rápido, se siente como magia: zas — y tu http://localhost:3000 se convierte en https://random-1234.tunnelprovider.com. Puedes copiar la URL en el Dev Mode de ChatGPT, y GPT carga tu App encantado.
Luego reinicias el túnel… y obtienes un dominio nuevo. La URL antigua en la configuración de la app de Dev en ChatGPT de repente se convierte en un «enlace roto», GPT escribe honestamente «App unavailable», y tú vuelves a los ajustes, cambias la URL, pulsas Save, esperas a que se actualice y odias en silencio todo ese stack.
Para «trastear una tarde» una sola vez es tolerable. Pero cuando:
- retocas el App todos los días;
- quieres enseñar una versión intermedia a un colega/manager;
- en paralelo levantas staging y producción,
reconectar Dev Mode para cada nueva URL aleatoria se convierte en sufrimiento puro.
Además, si ya has añadido al App algo que depende del dominio (por ejemplo, un OAuth redirect URI o webhooks), cada URL nueva los rompe también. Aparece un efecto dominó: cambias el túnel — y tienes que corregir la configuración del App, el redirect‑URL en el proveedor de OAuth y los ajustes del receptor de webhook.
De aquí nace la idea clave de la lección: un dev‑URL estable no es un lujo, sino una herramienta para preservar la salud mental del desarrollador.
Insight
ChatGPT tiene timeouts muy estrictos al trabajar con tu aplicación, y es fácil subestimarlos. MCP‑tool‑call tiene un límite de tiempo: máximo 2 minutos — después de eso la plataforma simplemente considera que la llamada ha fallado, aunque tu servidor siga haciendo algo.
Aún más estricta es la registro de la aplicación (Store o Dev Mode): para leer el manifiesto, los recursos y las descripciones de las herramientas, ChatGPT da alrededor de 20 segundos. Si en ese tiempo tu servidor MCP no ha logrado inicializarse, devolver la lista de tools/resources, etc., el registro del App fallará por timeout.
Recomendación: toda la inicialización pesada debe ocurrir antes de que vayas a Dev Mode o al Store. Calentado de conexiones a la BD, carga de configuraciones grandes, cachés perezosos — mejor hacerlo de antemano, por ejemplo, invocando el servidor una vez vía MCP Jam o con un script interno. Desde el punto de vista de la plataforma, el servidor MCP debe estar «caliente» y responder en segundos, no «despertarse» durante el registro.
2. Qué es un túnel «maduro»
Dejemos claro en qué se diferencia un túnel «maduro» de lo que lanzaste al principio del curso.
El modo temprano (módulo 2) era así:
# Ejemplo con ngrok
ngrok http 3000
# Obtenemos: https://random-abc123.ngrok-free.app
Tomabas esa URL de un solo uso y la pegabas en Dev Mode. En la siguiente ejecución de ngrok, la URL ya era otra, y la configuración de ChatGPT quedaba obsoleta.
En el enfoque «maduro» tienes:
- un subdominio estático con el proveedor de túnel (o tu propio dominio);
- el mismo dominio siempre se reenvía a tu localhost:3000;
- puedes reiniciar el túnel, la máquina, el router, pero la URL permanece igual.
Estos subdominios estáticos están disponibles, por ejemplo:
- en ngrok — un dominio estático gratuito por cuenta;
- en Cloudflare Tunnel — mediante un túnel con nombre y enlace a tu propio dominio.
Y la aplicación de ChatGPT en Dev Mode está configurada exactamente para esa única URL y ya no te molesta.
Formalmente, los requisitos para nuestro túnel «maduro» son:
- un mismo dominio público HTTPS estable;
- certificado TLS válido (el proveedor se encarga);
- una config que describa: «todo lo que llegue a https://dev.yourdomain.com, reenviarlo a http://localhost:3000»;
- opcional — medidas mínimas de seguridad (al menos no exponer la URL en StackOverflow).
3. Configurar un dev‑URL estable: ejemplo con Cloudflare Tunnel
En el curso recomendamos Cloudflare Tunnel como herramienta principal, porque encaja bien tanto en dev como en escenarios más serios. Ya en el módulo 2 viste un ejemplo de configuración básica; ahora lo llevamos hasta un dev‑URL permanente.
Supongamos que tenemos la aplicación didáctica GiftGenius, y queremos una URL estable giftgenius-dev.yourdomain.com.
Pasos mínimos (simplificado, sin atarnos a la UI de Cloudflare):
- Vincula el dominio a tu cuenta de Cloudflare (una vez, desde su panel).
- Instala cloudflared localmente e inicia sesión.
brew install cloudflare/cloudflare/cloudflared # macOS
cloudflared login # abrirá el navegador para autorizar
3. Crea un túnel con nombre:
cloudflared tunnel create giftgenius-dev
4. Configura la ruta en ~/.cloudflared/config.yml:
tunnel: giftgenius-dev
credentials-file: /Users/you/.cloudflared/giftgenius-dev.json
ingress:
- hostname: giftgenius-dev.yourdomain.com
service: http://localhost:3000 # nuestro servidor de desarrollo Next.js
- service: http_status:404
5. Inicia el túnel:
cloudflared tunnel run giftgenius-dev
Ahora, mientras estén ejecutándose npm run dev y cloudflared tunnel run, tu Next.js local será accesible mediante la URL permanente https://giftgenius-dev.yourdomain.com. Y es exactamente la que indicarás en la configuración de ChatGPT Dev Mode.
Cómo encaja con nuestra aplicación
Si abres en el navegador la URL de tu aplicación que introduces en ChatGPT al conectar el Dev‑App:
https://giftgenius-dev.yourdomain.com/mcp
verás una respuesta (error) — algo como:
{"jsonrpc":"2.0","error":{"code":-32000,"message":"Method not allowed."},"id":null}
Es absolutamente normal, ya que el servidor en /mcp no espera una solicitud GET. El resto de partes de la app — el widget, el endpoint MCP /mcp, las rutas de API — van por el mismo túnel; no necesitas recordar un dominio nuevo cada vez.
4. Alternativa: subdominio estable en ngrok
Si ya te has acostumbrado a ngrok, puedes «madurarlo» de forma similar usando un static domain. Desde 2023, ngrok ofrece incluso en el plan gratuito la posibilidad de fijar un subdominio estático del tipo myapp-dev.ngrok-free.app.
Esquema mínimo:
# ~/.config/ngrok/ngrok.yml
authtoken: <tu token>
tunnels:
giftgenius-dev:
addr: 3000
proto: http
domain: giftgenius-dev.ngrok-free.app
Inicio:
ngrok start giftgenius-dev
Como resultado, la URL https://giftgenius-dev.ngrok-free.app será permanente, y esa es la que darás a ChatGPT Dev Mode como URL base de la aplicación.
La filosofía es la misma:
- nada de direcciones «aleatorias»;
- solo cambia el estado interno del túnel (arriba/abajo), no el dominio;
- no hay que reconectar Dev Mode.
Cloudflare y ngrok en este sentido son como sabores diferentes de helado. A algunos les gustan sus propios dominios y el control fino de DNS (Cloudflare), otros prefieren «configuras el YAML y listo» (ngrok). Para el curso, ambos enfoques son válidos; lo importante es un URL estable.
5. Esquema: ChatGPT Dev Mode ↔ túnel ↔ stack local
Para formalizar un poco lo que ocurre, dibujemos un esquema.
flowchart TD
ChatGPT["ChatGPT (Dev Mode)"]
AppCfg["Dev App (config: https://giftgenius-dev...)"]
Tunnel["Túnel Cloudflare/ngrok (giftgenius-dev...)"]
Next["Servidor de desarrollo Next.js localhost:3000 + MCP handler"]
ChatGPT --> AppCfg
AppCfg -->|"en la configuración se indica https://giftgenius-dev.../.well-known/openai-app"| Tunnel
Tunnel -->|"Proxy HTTPS → HTTP"| Next
ChatGPT nunca sabe qué estás ejecutando en tu portátil. Para él solo existe un único endpoint HTTPS. Qué hay detrás — Vercel, un túnel local, Kubernetes — es cosa tuya. Y en esta lección nos interesa precisamente la estabilidad de ese endpoint HTTPS para el desarrollo local.
Solo queda hacer que dentro de nuestra aplicación esa dirección también sea una única «fuente de la verdad», y no se disperse por strings hardcodeadas — a eso se dedica la siguiente sección.
6. Variables de entorno y baseURL en el código
Para que todo esto funcione sin sorpresas, conviene definir una vez en el código de Next.js la «URL externa base de la aplicación» y apoyarse solo en ella.
Por ejemplo, en la carpeta app/lib/config.ts de nuestra app GiftGenius podemos crear:
// app/lib/config.ts
export const baseUrl =
process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000"; // reserva (fallback)
export const mcpEndpoint = `${baseUrl}/mcp`; // URL del servidor MCP
Y en .env.local durante el desarrollo indicar:
NEXT_PUBLIC_APP_URL=https://giftgenius-dev.yourdomain.com
Entonces:
- dentro del widget y de cualquier enlace, siempre usas baseUrl;
- para ChatGPT Dev Mode y el navegador todo se ve consistente;
- si mañana te mudas a un staging de Vercel con dominio https://giftgenius-staging.vercel.app, basta con cambiar solo la variable de entorno.
Esto es especialmente importante para:
- callback‑URL (por ejemplo, para OAuth, manejadores de webhook);
- enlaces que muestras al usuario en el widget (botón «Abrir en el navegador» mediante openExternal);
- cualquier URL absoluta en la lógica de la aplicación.
De momento hablamos solo del dev‑URL, pero la idea arquitectónica de «una única fuente de la verdad para baseUrl» funciona igual de bien y se lleva sin esfuerzo a staging/producción.
7. Actualizar la URL en ChatGPT Dev Mode
Bien, ya tenemos un dominio estable bonito. ¿Cómo convivir con él en Dev Mode?
La lógica es:
- En la configuración del Dev‑App indicas una vez la URL raíz: https://giftgenius-dev.yourdomain.com/
- ChatGPT va a por el manifiesto (.well-known/openai-app) y después usa esas mismas raíces para acceder a MCP (/mcp), estáticos, etc.
- Si solo cambias el código (widget de React, handlers de MCP, estilos), no hace falta cambiar la URL en absoluto. Basta con que el túnel esté levantado y el servidor de Next.js responda.
- Si cambias el propio dominio (rara vez, por ejemplo de ngrok a Cloudflare), tienes que entrar una vez en Dev Mode y cambiar el endpoint.
En algunos casos ChatGPT cachea el manifiesto, y los cambios pueden no aparecer al instante. En la interfaz de Dev Mode suele haber un botón tipo «Reload configuration / Refresh App», pero en el peor de los casos ayuda lo trivial: «desconectar y volver a conectar el App con la misma URL».
Importante: mientras no cambies la URL, Dev Mode «recoge» automáticamente las nuevas versiones de código. El disparador principal para el App es el dominio, no el commit‑hash.
8. Cambiar entre dev / staging / prod en Dev Mode
Un dominio dev estable es solo el primer peldaño. Para no ahogarte en el caos de URL a medida que el proyecto crece, conviene entender desde ya cómo encaja el túnel de dev en el esquema general de entornos (dev/staging/prod) y Dev Mode. Aunque staging y prod son más bien tema de la siguiente lección sobre Vercel, Dev Mode ya sabe trabajar con varios entornos.
Para entender lo que sigue, ayuda esta tabla:
| Entorno | URL base | Dónde se ejecuta el código |
|---|---|---|
| Local | |
Next.js local + MCP a través de túnel |
| Staging | |
Vercel Preview / despliegue de staging |
| Prod | |
Vercel Production |
Hay dos variantes de trabajo con Dev Mode.
La primera — un solo Dev‑App, pero de vez en cuando actualizas en su configuración la URL para probar staging o prod (con cuidado). Este enfoque vale en etapas tempranas, pero es fácil confundirse: hoy probaste local, mañana staging, pasado mañana olvidaste cambiar y, sin querer, lanzas peticiones a prod a través del Dev‑App.
La segunda — más saludable: varios Dev‑Apps, cada uno con una vinculación clara al entorno:
- GiftGenius Dev → giftgenius-dev.yourdomain.com;
- GiftGenius Staging → giftgenius-staging.vercel.app;
- GiftGenius (productivo, vía Store) → giftgenius.vercel.app.
En esta lección vamos paso a paso, poniendo orden al menos en el dev‑URL. En la siguiente, verás cómo vincular Vercel y los despliegues de preview a staging/producción.
9. Trabajo en equipo: varios desarrolladores y un túnel
Mientras tenemos un dominio de dev y un desarrollador introvertido, el túnel es tu amigo personal. Pero en cuanto llega un equipo al proyecto, los túneles y entornos empiezan a cruzarse, y es importante no montar una «guerra por un subdominio».
Imagina que dos desarrolladores deciden usar el mismo subdominio estático, digamos giftgenius-dev.ngrok-free.app. Ambos ejecutan ngrok start giftgenius-dev. En el mejor de los casos, uno de los túneles no se levantará (conflicto de dominio); en el peor, os iréis «pisando» la sesión, y ChatGPT a veces llegará a uno y a veces a otro.
Hay varias estrategias.
La más simple — dominios de dev personales:
- alex.dev.giftgenius.app;
- maria.dev.giftgenius.app.
Y a cada uno su Dev‑App en ChatGPT, por ejemplo GiftGenius Dev (Alex) y GiftGenius Dev (Maria). Así cada uno trabaja tranquilo en local sin molestar al otro.
Un camino más «de equipo» — un endpoint de staging común:
- Todas las personas del equipo tienen su túnel de dev personal (para su depuración local).
- Además, hay un staging en Vercel, donde se fusionan las feature branches y al que mira un Dev‑App común GiftGenius Staging.
Este enfoque es habitual en equipos reales:
- la feature nace en local y se depura a través del túnel personal;
- tras el pull request y el merge, la testean todos en staging (sin túneles, simplemente con la URL de Vercel).
10. Seguridad del túnel de dev (corto y sin paranoia)
Un túnel es una forma cómoda de exponer tu servidor local a internet. E internet, como sabemos, está lleno de bots, escáneres y gente a la que le gusta comprobar si olvidaste la contraseña admin/admin.
Cosas básicas a recordar ya en etapa de dev:
- el túnel da acceso externo a todo lo que cuelga de ese puerto; no expongas también ahí la admin de la BD, phpMyAdmin ni «mi CRM de prueba sin contraseña»;
- no publiques la URL del túnel en repositorios abiertos y chats;
- apaga el túnel cuando no trabajes (y apaga el portátil a veces — también necesita descanso).
Medidas más serias como Basic Auth, comprobación de cabeceras especiales o token en la URL las veremos en los módulos de seguridad. Lo importante ahora es solo una cosa: el túnel es una herramienta de desarrollo, no un servidor protegido. La producción vivirá en un hosting de verdad, como Vercel, con otros mecanismos de protección.
11. Práctica: configuramos un dev‑URL estable para nuestra aplicación
Pasemos de la teoría y los avisos a la práctica: vinculémoslo todo a nuestra aplicación didáctica en Next.js (plantilla Apps SDK).
Supongamos que la estructura del proyecto es así:
apps/
web/ # Next.js App + widget
mcp-server/ # (opcional) MCP aparte, o handler /mcp en web
En la práctica puedes alojar MCP directamente en Next.js, en app/api/mcp/route.ts, pero el principio es el mismo.
Paso 1. Editamos .env.local
Añadimos ahí el dev‑URL estable del túnel:
NEXT_PUBLIC_APP_URL=https://giftgenius-dev.yourdomain.com
En el código de dev ya usamos baseUrl de esa variable (ver arriba). Si no lo usabas, es el momento de extraerlo.
Paso 2. Iniciamos el servidor de dev y el túnel
cd apps/web
npm run dev # Next.js en localhost:3000
# terminal aparte
cloudflared tunnel run giftgenius-dev
Comprobamos en el navegador que https://giftgenius-dev.yourdomain.com abre y muestra tu App.
Paso 3. Conectamos en ChatGPT Dev Mode
En la interfaz de ChatGPT (sección para desarrolladores):
- crea o edita GiftGenius Dev;
- en el campo URL/Endpoint indica https://giftgenius-dev.yourdomain.com/;
- guarda.
Después, ChatGPT consulta el manifiesto en /.well-known/openai-app y luego empieza a ejecutar tu App sobre ese dominio.
Ahora puedes:
- cambiar el código del widget, los MCP‑handlers, los estilos;
- reiniciar npm run dev;
- reiniciar cloudflared tunnel run giftgenius-dev;
y aun así no volver a tocar los ajustes del Dev‑App, mientras el dominio siga siendo el mismo.
12. Cómo se ve en la lógica del código: ejemplo con openExternal
Para cerrar el ejemplo con lo que ya hicimos en lecciones anteriores, añadamos en el widget un botón «Abrir interfaz completa en el navegador», que también use el dev‑URL estable.
Supongamos que tenemos el componente React del widget GiftWidget:
// app/components/GiftWidget.tsx
"use client";
import { baseUrl } from "../lib/config"; // tomamos baseUrl de env
export function GiftWidget() {
const handleOpenFull = () => {
window.openai.openExternal({
// abrimos la página de la aplicación en una pestaña aparte
url: `${baseUrl}/full`,
label: "Abrir interfaz completa",
});
};
return (
<div>
<button onClick={handleOpenFull}>
Modo completo
</button>
</div>
);
}
Si NEXT_PUBLIC_APP_URL apunta al túnel, entonces:
- en desarrollo local se abrirá la página https://giftgenius-dev.yourdomain.com/full;
- tras desplegar en staging — https://giftgenius-staging.vercel.app/full;
- en prod — el dominio de producción.
Y de nuevo — una única fuente de la verdad para el dominio: cambiamos el entorno, no el código.
13. Mini estrategia: cómo pensar el túnel «en serio»
Resumiendo en un modelo mental simple, podemos considerar que:
- el túnel es simplemente un cable temporal entre tu portátil y un dominio público estable;
- ChatGPT Dev Mode solo conoce el dominio, y le da igual dónde se ejecuta físicamente el código;
- cuanto menos cambies el dominio, menos tiempo pasarás en los ajustes de ChatGPT y de los proveedores OAuth;
- el túnel de dev es solo una línea en tu mapa general de entornos, junto a staging (Vercel preview) y prod (Vercel production).
La siguiente lección muestra cómo este cable se sustituye por un hosting completo en Vercel, y cómo relacionarlo con ramas de Git, despliegues de preview y producción.
14. Errores típicos al trabajar con un túnel «maduro»
Error n.º 1: «He configurado un dominio estático, pero sigo usando URLs aleatorias».
A veces el desarrollador crea una vez un bonito giftgenius-dev.yourdomain.com, pero por costumbre ejecuta ngrok http 3000 sin config. Resultado: ChatGPT mira a un dominio, pero el código corre detrás de otro. Si ya tienes un dev‑URL estable — úsalo únicamente y levanta el túnel mediante la config (túnel/perfil con nombre).
Error n.º 2: localhost:3000 hardcodeado.
Sucede cuando en un componente React o en un handler MCP escriben fetch("http://localhost:3000/api/..."). En local medio funciona, pero en Dev Mode y, más aún, en staging/prod se rompe de inmediato. Siempre extrae la URL base a la config (baseUrl, NEXT_PUBLIC_APP_URL) y úsala donde necesites enlaces absolutos.
Error n.º 3: Editar la URL constantemente en Dev Mode en lugar de un túnel estable.
Si te pillas pensando «bueno, una vez más cambio la URL en los ajustes, qué más da», — es una señal. Configurar un subdominio estático en ngrok/Cloudflare lleva 10–15 minutos una vez, pero ahorra horas durante el desarrollo.
Error n.º 4: Un dominio estático compartido para todo el equipo sin reglas.
Dos desarrolladores, un dominio giftgenius-dev.ngrok-free.app, y ambos levantan el túnel cuando quieren. Resultado — conflicto de túneles, respuestas que «desaparecen misteriosamente» en Dev Mode y depuración del tipo «en mi máquina funcionaba». Para el equipo, siempre dominios de dev personales o un dominio de staging en un hosting real.
Error n.º 5: Tratar el túnel como «casi producción».
A veces alguien piensa: «Si tengo una URL HTTPS estable vía túnel, dejemos pasar usuarios reales/pagos por ahí». Es camino al dolor: se apaga el portátil — muere la app; se cae internet — lo mismo; y la seguridad, en el mejor de los casos, es simbólica. El túnel es una herramienta de dev. Para tráfico real están Vercel y el resto de la infraestructura seria, a la que llegaremos en la próxima lección.
Error n.º 6: Olvidar sincronizar variables de entorno y Dev Mode.
A menudo cambian NEXT_PUBLIC_APP_URL en .env.local, pero se olvidan de cambiar la URL en Dev Mode (o al revés). Como resultado, el widget genera enlaces a un dominio y ChatGPT accede a otro. Ten una tabla sencilla «entorno ↔ dominio ↔ App en ChatGPT» y actualízala cuando haya cambios — es más barato que adivinar cuál URL es la verdadera ahora.
GO TO FULL VERSION