Le construí a mi familia una app de itinerario de viaje — con un asistente de IA dentro

Categoría
IA y LLM Local
Publicado
11 julio 2026
Por
Jacob Lloyd — escrito con ayuda de IA, después del proyecto
Tiempo de lectura
11 min de lectura

En palabras simples: Hice un pequeño sitio web solo para el viaje de mi familia a Japón — muestra el plan de cada día, el clima, mapas, y funciona en el móvil incluso sin señal. También tiene un pequeño botón de ayudante de IA que puede responder preguntas sobre el viaje y ajustar el plan. Este artículo es la historia honesta de cómo lo construí con mucha ayuda de la IA: qué funcionó, qué se rompió, y cómo convertí una exportación frágil de una herramienta de diseño en algo seguro de editar y seguro de publicar en la web. Puedes descargar una muestra depurada y abrirla en cualquier navegador.

Mi familia hizo un viaje a Japón, y quería que todos tuvieran el plan en el
bolsillo — cada día detallado, mapas a un toque de distancia, el clima, y esos
pequeños detalles que es fácil olvidar cuando estás con jet lag parado en una
estación de tren. Así que construí una pequeña app web solo para nosotros: un
itinerario privado, protegido con PIN, con un calendario día a día, clima
en vivo, soporte sin conexión y — porque soy quien soy — un asistente de
IA
integrado que puede responder preguntas sobre el viaje e incluso hacer
pequeñas ediciones.

Esta es la historia honesta de su construcción: las partes que funcionaron,
las que se rompieron de formas que no esperaba, y cómo una exportación frágil
de una herramienta de diseño se convirtió en algo que me sentí cómodo
publicando en la web pública. Al final hay una muestra depurada que puedes
descargar y abrir
en cualquier navegador.

Resumen

  • Qué es: una app web de itinerario pensada primero para móvil — días, paradas, clima en vivo, ayudantes de moneda y zona horaria, sin conexión/instalable, con una pestaña de asistente de IA.
  • La idea clave: una exportación de herramienta de diseño hermosa pero frágil se volvió segura de editar en cuanto todo el calendario pasó a depender de un pequeño archivo de datos y un paso de build regeneró la app.
  • La puerta: un PIN del lado del cliente es teatro — la de verdad es una sesión del lado del servidor con bloqueo limitado por tasa.
  • La IA: un modelo local autoalojado, con la correa corta (responde preguntas; solo hace ediciones pequeñas y vigiladas).
  • La lección que se repitió una y otra vez: la única forma de saber si un cambio funcionó era renderizar la app en un navegador real y mirar — nunca confiar en que el archivo "debería" estar bien.

De dónde partió: una preciosa caja negra

Diseñé el aspecto en una herramienta de diseño con IA y lo exporté. Lo que
salió fue un único archivo HTML autocontenido — una app de React completa con
las fuentes y las imágenes integradas — de unos 14 MB en un solo archivo.
Se veía fantástico y funcionó desde el primer doble clic.

También era una caja negra. Toda la app, minificada, vivía en un blob gigante,
y el calendario estaba enterrado dentro. Editar el viaje a mano significaba
hacer cirugía sobre código generado por máquina. Está bien una vez; es una
pesadilla cada vez que cambian los planes — y en un viaje familiar, los planes
cambian a diario.

Así que la primera decisión real fue arquitectónica: dejar de editar la
exportación.

La única idea que hizo posible todo lo demás

En lugar de tocar el bundle de 14 MB, moví todo el calendario a un pequeño y
legible archivo de datos — un content.json con los días, las paradas,
las notas de clima, fotos y costes por parada. Luego un breve paso de
build
lee ese archivo y regenera la app a partir de la exportación
inmaculada.

Esto cambió todo el carácter del proyecto. Ahora “añadir una parada” o
“corregir una hora” es una edición de dos líneas a un archivo de datos que
una persona — o un asistente de IA — puede hacer con seguridad, seguida de un
rebuild. La parte frágil (la app de 14 MB) nunca se toca a mano. Cada widget
que añadí después — una tarjeta de clima en vivo, un reloj doble
tu hora ↔ Tokio, un conversor de moneda bidireccional, paradas
desplegables con fotos y notas de aparcamiento — se genera a partir de esa
misma build impulsada por datos.

La trampa que se comió cada widget: la app se reconstruye a sí misma al arrancar

Aquí está la trampa que más tiempo me costó, escrita para que a ti no te
cueste nada.

La app exportada reconstruye toda la página cuando arranca. Si inyectas tu
propio marcado en el HTML estático — un reloj, un banner, lo que sea — se
borra en el instante en que la app se monta.
Tus IDs de elementos
desaparecen, getElementById devuelve null para siempre, y tu widget
simplemente nunca aparece. Peor aún: si “verificas” buscando en el archivo
HTML guardado, tu marcado está ahí mismo, así que parece estar bien.
Simplemente nunca sobrevive hasta la pantalla.

Dos reglas lo arreglaron para siempre:

  1. Inyecta en la plantilla propia de la app, no en la página. Añade tu
    widget donde la app construye su interfaz, para que se renderice como
    parte de la app y sobreviva al arranque.
  2. Conecta el comportamiento con delegación de eventos en document. La
    app vuelve a montar secciones enteras al cambiar de pestaña, destruyendo
    y recreando sus nodos. Un listener puesto en un botón muere en el
    siguiente re-render; un único listener delegado en document (que
    comprueba qué se pulsó) sobrevive a todo.

El corolario es una regla de pruebas que ahora trato como ley: no verifiques
una interfaz web haciendo grep del archivo o comprobando un HTTP 200.

Renderízala en un navegador real (headless), después de que arranque, y
mira de verdad el resultado. Ese es el único chequeo que atrapa esta clase de
fallo — y así atrapé varios, incluido un conversor de moneda que era invisible
hasta que lo moví a la plantilla.

“Autocontenido” no lo era del todo

La exportación decía ser un único archivo autocontenido. No lo era del todo.
Silenciosamente traía su framework de interfaz desde un CDN público en tiempo
de ejecución, ensamblado a partir de fragmentos de URL en el código — así que
una búsqueda de texto plano del CDN no encontraba nada, y el archivo
parecía listo para funcionar sin conexión mientras no lo era en absoluto. En
un hosting web con una política de seguridad estricta, esa petición oculta se
bloqueaba y toda la app fallaba al arrancar. En mi portátil, sin esa política,
funcionaba — así que fue invisible hasta que llegó al servidor real.

La solución fue autoalojar el framework (empaquetarlo junto con la app
para que no haya ninguna petición externa) y, para la muestra descargable,
integrarlo directamente en línea para que todo el conjunto sea realmente
un solo archivo que se abre desde el disco sin ninguna conexión a internet.
Si te llevas una cosa práctica de esta sección: “funciona en mi máquina” y
“funciona detrás de las cabeceras de seguridad de un servidor web real” son
afirmaciones distintas — prueba la segunda.

La puerta: teatro contra un candado de verdad

La primera versión protegía el sitio con un PIN en el JavaScript de la
página. Eso es teatro de seguridad: todo el itinerario está sentado en el
código fuente de la página, así que cualquiera que sepa abrir las
herramientas de desarrollador puede leerlo sin escribir nunca el código.
Bien para ocultar una fiesta sorpresa de una mirada casual; no vale para nada
que realmente te importe.

La versión real movió la puerta al servidor: se envía el PIN, el servidor
lo comprueba e inicia una sesión, y solo entonces entrega el itinerario.
También tiene un bloqueo escalonado por IP — un puñado de intentos
fallidos y esa dirección queda bloqueada durante periodos cada vez más
largos, así que nadie puede disparar cada código de 6 dígitos como
ametralladora.

Voy a ser honesto sobre los límites, porque fingir lo contrario es como la
gente se quema: un PIN de 6 dígitos que solo tiene límite de tasa está bien
para un itinerario familiar de bajo riesgo, pero no es de nivel bancario. Si
estás protegiendo algo sensible, usa una frase de contraseña larga y un
inicio de sesión de verdad. Que el candado corresponda a lo que hay detrás
de la puerta.

El asistente de IA, con la correa corta

La parte divertida: un pequeño asistente — lo llamé Yoshi — que vive en su
propia pestaña. En la versión privada de la familia puede responder preguntas
sobre el viaje (“¿cuál es el plan para el día del acuario?”, “¿qué deberíamos
llevar?”) y hacer ediciones pequeñas y vigiladas al calendario. Todo el
punto es que edita ese mismo archivo de datos seguro, nunca la app frágil —
y está acotado: puede añadir o ajustar, pero no puede borrar días, y los
vuelos y las reservas están bloqueados con llave para que ningún mensaje de
chat pueda tocarlos.

Fundamentalmente, Yoshi corre sobre un modelo local autoalojado — una
pequeña IA en mi propio hardware, no una API de pago en la nube — al que se
llega a través de un relé vigilado. Ese es todo un tema aparte (poner con
seguridad un chatbot alojado en local en la web pública, y lo lento que es
en hardware de consumo), así que lo escribí por separado:
Poner un chatbot en la web pública — con mi propio hardware.

Nota sobre la muestra descargable de abajo: la pestaña del asistente está
ahí para que veas dónde vive, pero en la muestra está deliberadamente
apagada
— no está conectada a ninguna IA y no responderá. Para que funcione
en tu propia copia tendrías que apuntarla a tu propio modelo local. Eso es a
propósito: no voy a distribuir un archivo público que silenciosamente llame a
una IA en ejecución.

Sin conexión, instalable, y a prueba de andar de viaje

El último conjunto de detalles tenía que ver con la realidad de usar esto en
un viaje: wifi de hotel irregular, sin datos en el avión, roaming que va y
viene. Así que la app se convirtió en una PWA instalable — añádela a tu
pantalla de inicio y se abre como una app de verdad — y funciona sin
conexión
, sirviendo todo el itinerario desde el dispositivo una vez que lo
has cargado. La única regla que cuidé con atención: solo cachea el itinerario
después de un desbloqueo exitoso, para que la caché sin conexión nunca
salte la puerta. En línea, siempre vuelve a comprobar la puerta; sin
conexión, confía en la copia que ya está en tu propio móvil. Para un plan de
viaje familiar, ese es el trato correcto.

(Los service workers tienen bordes afilados — una versión antigua en caché
puede seguir sirviendo una página obsoleta mucho después de haber publicado
un arreglo. Aprendí a publicar siempre un worker actualizado, nunca solo
borrar el antiguo, para que los navegadores de verdad recojan el cambio.)

Probarla como si fuera real, porque lo es

Como la familia estaba usando esto mientras viajaba, traté cada cambio como
un despliegue: construir, renderizar la página real en un navegador headless
después de arrancar, manejar las pestañas y botones de verdad, y solo
entonces publicar. A mitad del viaje envié pequeños arreglos en vivo de esta
forma — una insignia de “HOY” que resalta el día actual (anclada a la hora
de Tokio, no a la del móvil de quien mira), clima actualizado, un par de
detalles de maquetación — cada uno verificado primero en un navegador real.
Es la misma lección que la trampa del rearranque, aprendida de nuevo: que
el archivo exista no es que la función funcione.

Pruébala — descarga la muestra

La descarga de abajo es una muestra pública depurada de la app. Cada
nombre, fecha, dirección y detalle es contenido de demostración ficticio; es
un plan de Tokio ficticio de 7 días. Abre index.html en cualquier navegador
— sin instalación, sin servidor, sin cuenta. Funciona sin conexión; solo la
tarjeta de clima en vivo y los enlaces de mapas llegan a internet.

Dos advertencias honestas. Primero, es un único archivo de ~14 MB porque
toda la app, las fuentes y las imágenes están integradas para que funcione
con cero dependencias — eso lo hace lento en la primera apertura; una
build de producción dividiría y comprimiría todo eso. Segundo, como se
mencionó, el asistente de IA está apagado en la muestra — la pestaña se
abre y te lo dice.

Constrúyela tú mismo

El recuadro de “impleméntalo tú mismo” al principio no es un eslogan — este
artículo está pensado para entregarse directamente a un asistente de IA. La
receta, condensada:

  1. Pon el plan en un archivo de datos. Días, paradas, notas, clima — un
    único archivo legible que sea la única fuente de verdad.
  2. Genera la app a partir de él. Un paso de build determinista convierte
    los datos en la interfaz, para que las ediciones siempre sean a los
    datos, nunca al código generado.
  3. Si partes de una exportación de herramienta de diseño, respeta su
    arranque.
    Inyecta en la plantilla de la app, usa eventos delegados, y
    autoaloja todo lo que descargue.
  4. Protege en el servidor, no en la página. Una sesión más un bloqueo
    limitado por tasa gana siempre a un PIN en JavaScript.
  5. Hazla sin conexión primero si viaja — cachea solo después de un
    desbloqueo real.
  6. Verifica en un navegador, después de arrancar. No “el archivo
    existe”. La pantalla.

Trampas, recopiladas

  • La exportación reconstruye el DOM al arrancar — cualquier cosa que
    inyectes en la página se borra; en su lugar, inyecta en la plantilla de la
    app y delega eventos en document.
  • “Autocontenido” puede no serlo — una exportación puede descargar su
    framework en tiempo de ejecución; autoalójalo, y pruébalo detrás de las
    cabeceras de seguridad de tu servidor real, no solo en local.
  • Un PIN del lado del cliente es teatro — el contenido está en la
    página; protege en el servidor.
  • Un PIN de 6 dígitos solo tiene límite de tasa, no es irrompible — bien
    para bajo riesgo, mal para cualquier cosa sensible.
  • Los service workers cachean con agresividad — publica un worker
    actualizado para desplegar un arreglo; borrar el archivo antiguo no lo
    desregistra.
  • “El archivo existe” ≠ “la función funciona” — renderiza y mira, cada
    vez.

Descargas

Gratis para uso personal. Si te ahorra una tarde, el botón del café está aquí cerca.


← Más de IA y LLM Local