[{"data":1,"prerenderedAt":4655},["ShallowReactive",2],{"home-content":3,"home-blog":145,"mdc--alo4t8-key":4645},{"id":4,"title":5,"ai":6,"blog":35,"body":60,"cta":61,"description":83,"extension":84,"features":60,"hero":60,"image":85,"meta":86,"navigation":87,"path":88,"pricing":60,"section":89,"seo":111,"stem":113,"testimonials":60,"vuenuxt":114,"__hash__":144},"content/index.yml","Desarrollamos apps y sitio webs con pasion",{"title":7,"description":8,"collapsibleLabel":9,"features":10},"Potencia tus aplicaciones con\u003Cbr/>[Inteligencia Artificial]{.text-primary}","Integramos IA en tus aplicaciones para automatizar tareas, optimizar procesos y generar insights valiosos. Transforma tu negocio con soluciones inteligentes que aprenden y se adaptan.","Ver ejemplo de IA integrada a una aplicación",[11,15,19,23,27,31],{"title":12,"description":13,"icon":14},"Automatización Inteligente","Elimina tareas repetitivas con workflows automatizados impulsados por IA. Reduce costos operativos y libera a tu equipo para enfocarse en lo que realmente importa.","i-lucide-bot",{"title":16,"description":17,"icon":18},"Procesamiento de Datos","Analiza grandes volúmenes de información en segundos. Extrae insights valiosos, detecta patrones y toma decisiones basadas en datos con precisión sorprendente.","i-lucide-brain-circuit",{"title":20,"description":21,"icon":22},"Reportes Personalizados","Genera informes automáticos y personalizados adaptados a cada usuario. Visualiza métricas clave con dashboards interactivos que se actualizan en tiempo real.","i-lucide-file-bar-chart",{"title":24,"description":25,"icon":26},"Predicción y Análisis","Anticípate a las tendencias con modelos predictivos. Predice comportamientos, demanda y riesgos para tomar decisiones proactivas y estratégicas.","i-lucide-trending-up",{"title":28,"description":29,"icon":30},"Asistentes Virtuales","Implementa chatbots y asistentes inteligentes disponibles 24/7. Responde consultas, guía a usuarios y mejora la experiencia del cliente sin intervención humana.","i-lucide-message-square-more",{"title":32,"description":33,"icon":34},"Optimización Continua","Tus aplicaciones mejoran automáticamente con el tiempo. La IA aprende del uso real, optimizando rendimiento y personalizando la experiencia.","i-lucide-sparkles",{"title":36,"description":37,"readMoreLabel":38,"viewAllLabel":39,"viewAllTo":40,"articles":41},"Últimos [artículos]{.text-primary} del blog","Exploramos tendencias, compartimos conocimiento y documentamos nuestro camino en el desarrollo de software y la inteligencia artificial.","Leer más","Ver todos los artículos","/blog",[42,48,54],{"title":43,"description":44,"date":45,"category":46,"icon":18,"to":47},"Cómo la IA está transformando el desarrollo web","Descubrí cómo los modelos de lenguaje y las herramientas de IA están revolucionando la forma en que construimos aplicaciones modernas.","2025-12-15","Inteligencia Artificial","#",{"title":49,"description":50,"date":51,"category":52,"icon":53,"to":47},"Nuxt 4: todo lo que necesitás saber","Un recorrido por las novedades de Nuxt 4, mejoras de rendimiento y cómo migrar tus proyectos existentes sin dolor.","2025-11-28","Frontend","i-simple-icons-nuxtdotjs",{"title":55,"description":56,"date":57,"category":58,"icon":59,"to":47},"Automatización de procesos con Python y FastAPI","Aprende a construir pipelines de automatización robustos que escalan con tu negocio usando Python y FastAPI.","2025-11-10","Backend","i-simple-icons-python",null,{"title":62,"description":63,"buttonLabel":64,"modal":65,"form":68,"successToast":77,"errorToast":80},"¿Listo para tu nueva aplicación?","Cuéntanos tu idea y te ayudamos a convertirla en un producto real, escalable y pensado para crecer desde el día uno.","Contactar",{"title":66,"description":67},"Hablemos de tu proyecto","Déjanos tus datos y una breve descripción.",{"nameLabel":69,"namePlaceholder":70,"emailLabel":71,"emailPlaceholder":72,"projectLabel":73,"projectPlaceholder":74,"cancelLabel":75,"submitLabel":76},"Nombre","Tu nombre","Email","tu@email.com","Proyecto","Cuéntanos qué quieres construir","Cancelar","Enviar",{"title":78,"description":79},"Formulario enviado","Te contactaremos en menos de 24 horas hábiles.",{"title":81,"description":82},"No pudimos enviar el formulario","Inténtalo nuevamente en unos minutos.","Creamos aplicaciones y sitios web de alto rendimiento y calidad.","yml","/lampara.png",{},true,"/",{"title":90,"description":91,"images":92,"features":94},"Tecnologías [modernas]{.text-primary} para resultados excepcionales","Dominamos el ecosistema de desarrollo web moderno. Desde frameworks frontend reactivos hasta backends robustos, usamos las herramientas líderes de la industria para construir aplicaciones escalables y de alto rendimiento.",{"mobile":93,"desktop":93},"/code.png",[95,99,103,107],{"title":96,"description":97,"icon":53,"class":98},"Expertos en Vue & Nuxt","Experiencias de usuario fluidas. Aprovechamos la potencia de Nuxt para crear aplicaciones web ultrarrápidas y optimizadas para SEO desde el primer día.","border-l border-default hover:border-primary pl-4 py-2 transition-colors",{"title":100,"description":101,"icon":59,"class":102},"Python para Backend","APIs modernas y procesamiento de datos eficiente. Usamos Python para construir infraestructuras seguras que crecen al ritmo de tu negocio.","border-l border-default hover:border-primary pt-4 pl-4 py-2 transition-colors",{"title":104,"description":105,"icon":106,"class":102},"Laravel para soluciones PHP","Desarrollo ágil con el ecosistema más maduro del mercado. Ideal para entregar plataformas complejas y seguras en tiempos reducidos.","i-simple-icons-laravel",{"title":108,"description":109,"icon":110,"class":102},"Astro para sitios estáticos","Rendimiento extremo con arquitectura \"Zero JavaScript\". La elección perfecta para landing pages donde cada milisegundo de carga cuenta.","i-simple-icons-astro",{"title":112,"description":83},"Desarrollo de apps y sitios webs - Inkuba","index",{"title":115,"description":116,"reasons":117,"highlights":134},"¿Por qué elegimos [Vue & Nuxt]{.text-primary}?","La decisión está basada en capacidades concretas de Nuxt documentadas oficialmente: estrategias de renderizado flexibles, servidor Nitro integrado y una experiencia full-stack con menos configuración manual.",[118,122,126,130],{"title":119,"description":120,"icon":121},"Renderizado flexible por ruta","Nuxt soporta renderizado universal, client-side, híbrido y edge. Además permite ajustar la estrategia por ruta con route rules según el tipo de página y necesidad de caché.","i-lucide-zap",{"title":123,"description":124,"icon":125},"Nitro integrado y listo para serverless","El motor Nitro aporta soporte cross-platform, rutas API y despliegue serverless sin arquitectura paralela. Frontend y backend conviven en el mismo proyecto con una salida de build portable.","i-lucide-rocket",{"title":127,"description":128,"icon":129},"Menos boilerplate con auto-imports","Nuxt auto-importa componentes, composables y utilidades con tipado e IntelliSense, e incluye en producción solo lo que se usa. Esto reduce código repetitivo y errores de importación.","i-lucide-puzzle",{"title":131,"description":132,"icon":133},"BFF eficiente con cache en endpoints","Con Nitro implementamos una capa BFF en el mismo proyecto, centralizando integración con APIs externas, normalización de datos y estrategias de cache por endpoint para reducir latencia y costo operativo.","i-lucide-search",[135,138,141],{"label":136,"description":137},"SSR · SSG · CSR","Estrategias de renderizado según cada página",{"label":139,"description":140},"Nitro","Servidor, API y despliegue en una sola base",{"label":142,"description":143},"Auto-imports tipados","Menos fricción y mejor mantenibilidad","K8QDrAl3mbvJcsSMJvt49GVCVlVYvLRpRHfBMhJFum8",[146,1740],{"id":147,"title":148,"body":149,"category":1729,"date":1730,"description":1731,"extension":1732,"icon":1733,"image":1734,"meta":1735,"navigation":87,"path":1736,"seo":1737,"stem":1738,"__hash__":1739},"blog/blog/websocket-con-nuxt.md","Cómo usar WebSockets con Nuxt: guía práctica",{"type":150,"value":151,"toc":1712},"minimark",[152,156,159,174,177,182,185,200,203,205,209,216,310,316,318,322,325,330,333,341,620,625,647,649,655,681,684,709,722,724,728,731,736,739,767,1413,1420,1426,1428,1432,1435,1453,1456,1574,1576,1580,1621,1623,1627,1680,1682,1686,1689,1705,1708],[153,154,155],"p",{},"Si quieres actualizar la UI en tiempo real sin hacer polling constante, WebSocket es una excelente opción.",[153,157,158],{},"En este artículo veremos una implementación completa en Nuxt basada en dos piezas:",[160,161,162,166],"ol",{},[163,164,165],"li",{},"Un endpoint WebSocket en el servidor (Nitro).",[163,167,168,169,173],{},"Un composable cliente que escucha y emite eventos con ",[170,171,172],"code",{},"useWebSocket",".",[175,176],"hr",{},[178,179,181],"h2",{"id":180},"cuándo-usar-websocket","¿Cuándo usar WebSocket?",[153,183,184],{},"Usa WebSocket cuando necesites eventos en tiempo real, por ejemplo:",[186,187,188,191,194,197],"ul",{},[163,189,190],{},"cambios de estado de tareas,",[163,192,193],{},"notificaciones colaborativas,",[163,195,196],{},"seguimiento de sesiones activas,",[163,198,199],{},"dashboards que se actualizan solos.",[153,201,202],{},"Si solo necesitas refrescos ocasionales, una petición HTTP puede ser suficiente. Pero si hay interacción frecuente entre usuarios, WebSocket es mejor.",[175,204],{},[178,206,208],{"id":207},"_1-habilitar-websocket-en-nuxt","1) Habilitar WebSocket en Nuxt",[153,210,211,212,215],{},"En Nuxt (Nitro), habilita la funcionalidad WebSocket en ",[170,213,214],{},"nuxt.config.ts",":",[217,218,223],"pre",{"className":219,"code":220,"language":221,"meta":222,"style":222},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","export default defineNuxtConfig({\n  nitro: {\n    experimental: {\n      websocket: true,\n    },\n  },\n});\n","ts","",[170,224,225,249,261,271,286,292,298],{"__ignoreMap":222},[226,227,230,234,237,241,245],"span",{"class":228,"line":229},"line",1,[226,231,233],{"class":232},"s7zQu","export",[226,235,236],{"class":232}," default",[226,238,240],{"class":239},"s2Zo4"," defineNuxtConfig",[226,242,244],{"class":243},"sTEyZ","(",[226,246,248],{"class":247},"sMK4o","{\n",[226,250,252,256,258],{"class":228,"line":251},2,[226,253,255],{"class":254},"swJcz","  nitro",[226,257,215],{"class":247},[226,259,260],{"class":247}," {\n",[226,262,264,267,269],{"class":228,"line":263},3,[226,265,266],{"class":254},"    experimental",[226,268,215],{"class":247},[226,270,260],{"class":247},[226,272,274,277,279,283],{"class":228,"line":273},4,[226,275,276],{"class":254},"      websocket",[226,278,215],{"class":247},[226,280,282],{"class":281},"sfNiH"," true",[226,284,285],{"class":247},",\n",[226,287,289],{"class":228,"line":288},5,[226,290,291],{"class":247},"    },\n",[226,293,295],{"class":228,"line":294},6,[226,296,297],{"class":247},"  },\n",[226,299,301,304,307],{"class":228,"line":300},7,[226,302,303],{"class":247},"}",[226,305,306],{"class":243},")",[226,308,309],{"class":247},";\n",[153,311,312,313,173],{},"Con esto, Nitro puede servir rutas WS como ",[170,314,315],{},"/api/**/ws/**",[175,317],{},[178,319,321],{"id":320},"_2-crear-el-endpoint-websocket-servidor","2) Crear el endpoint WebSocket (servidor)",[153,323,324],{},"En este ejemplo, el endpoint está en:",[153,326,327],{},[170,328,329],{},"server/api/ws/tasks.ts",[153,331,332],{},"La idea es simple:",[186,334,335,338],{},[163,336,337],{},"cuando un cliente se conecta, se suscribe a una sala,",[163,339,340],{},"cuando llega un mensaje, se publica a toda la sala.",[217,342,344],{"className":219,"code":343,"language":221,"meta":222,"style":222},"import type { Peer, Message } from \"crossws\";\n\nconst room = \"tasks\";\n\nexport default defineWebSocketHandler({\n  open(peer) {\n    peer.subscribe(room);\n  },\n  close(peer) {\n    console.log(\"closed WS\");\n  },\n  error(peer, error) {\n    console.log(\"error on WS\", error);\n  },\n  message(peer, message) {\n    peer.publish(room, message.text());\n  },\n});\n",[170,345,346,384,389,410,414,427,442,461,466,480,504,509,528,554,559,578,606,611],{"__ignoreMap":222},[226,347,348,351,354,357,360,363,366,369,372,375,379,382],{"class":228,"line":229},[226,349,350],{"class":232},"import",[226,352,353],{"class":232}," type",[226,355,356],{"class":247}," {",[226,358,359],{"class":243}," Peer",[226,361,362],{"class":247},",",[226,364,365],{"class":243}," Message",[226,367,368],{"class":247}," }",[226,370,371],{"class":232}," from",[226,373,374],{"class":247}," \"",[226,376,378],{"class":377},"sfazB","crossws",[226,380,381],{"class":247},"\"",[226,383,309],{"class":247},[226,385,386],{"class":228,"line":251},[226,387,388],{"emptyLinePlaceholder":87},"\n",[226,390,391,395,398,401,403,406,408],{"class":228,"line":263},[226,392,394],{"class":393},"spNyl","const",[226,396,397],{"class":243}," room ",[226,399,400],{"class":247},"=",[226,402,374],{"class":247},[226,404,405],{"class":377},"tasks",[226,407,381],{"class":247},[226,409,309],{"class":247},[226,411,412],{"class":228,"line":273},[226,413,388],{"emptyLinePlaceholder":87},[226,415,416,418,420,423,425],{"class":228,"line":288},[226,417,233],{"class":232},[226,419,236],{"class":232},[226,421,422],{"class":239}," defineWebSocketHandler",[226,424,244],{"class":243},[226,426,248],{"class":247},[226,428,429,432,434,438,440],{"class":228,"line":294},[226,430,431],{"class":254},"  open",[226,433,244],{"class":247},[226,435,437],{"class":436},"sHdIc","peer",[226,439,306],{"class":247},[226,441,260],{"class":247},[226,443,444,447,449,452,454,457,459],{"class":228,"line":300},[226,445,446],{"class":243},"    peer",[226,448,173],{"class":247},[226,450,451],{"class":239},"subscribe",[226,453,244],{"class":254},[226,455,456],{"class":243},"room",[226,458,306],{"class":254},[226,460,309],{"class":247},[226,462,464],{"class":228,"line":463},8,[226,465,297],{"class":247},[226,467,469,472,474,476,478],{"class":228,"line":468},9,[226,470,471],{"class":254},"  close",[226,473,244],{"class":247},[226,475,437],{"class":436},[226,477,306],{"class":247},[226,479,260],{"class":247},[226,481,483,486,488,491,493,495,498,500,502],{"class":228,"line":482},10,[226,484,485],{"class":243},"    console",[226,487,173],{"class":247},[226,489,490],{"class":239},"log",[226,492,244],{"class":254},[226,494,381],{"class":247},[226,496,497],{"class":377},"closed WS",[226,499,381],{"class":247},[226,501,306],{"class":254},[226,503,309],{"class":247},[226,505,507],{"class":228,"line":506},11,[226,508,297],{"class":247},[226,510,512,515,517,519,521,524,526],{"class":228,"line":511},12,[226,513,514],{"class":254},"  error",[226,516,244],{"class":247},[226,518,437],{"class":436},[226,520,362],{"class":247},[226,522,523],{"class":436}," error",[226,525,306],{"class":247},[226,527,260],{"class":247},[226,529,531,533,535,537,539,541,544,546,548,550,552],{"class":228,"line":530},13,[226,532,485],{"class":243},[226,534,173],{"class":247},[226,536,490],{"class":239},[226,538,244],{"class":254},[226,540,381],{"class":247},[226,542,543],{"class":377},"error on WS",[226,545,381],{"class":247},[226,547,362],{"class":247},[226,549,523],{"class":243},[226,551,306],{"class":254},[226,553,309],{"class":247},[226,555,557],{"class":228,"line":556},14,[226,558,297],{"class":247},[226,560,562,565,567,569,571,574,576],{"class":228,"line":561},15,[226,563,564],{"class":254},"  message",[226,566,244],{"class":247},[226,568,437],{"class":436},[226,570,362],{"class":247},[226,572,573],{"class":436}," message",[226,575,306],{"class":247},[226,577,260],{"class":247},[226,579,581,583,585,588,590,592,594,596,598,601,604],{"class":228,"line":580},16,[226,582,446],{"class":243},[226,584,173],{"class":247},[226,586,587],{"class":239},"publish",[226,589,244],{"class":254},[226,591,456],{"class":243},[226,593,362],{"class":247},[226,595,573],{"class":243},[226,597,173],{"class":247},[226,599,600],{"class":239},"text",[226,602,603],{"class":254},"())",[226,605,309],{"class":247},[226,607,609],{"class":228,"line":608},17,[226,610,297],{"class":247},[226,612,614,616,618],{"class":228,"line":613},18,[226,615,303],{"class":247},[226,617,306],{"class":243},[226,619,309],{"class":247},[621,622,624],"h3",{"id":623},"qué-está-pasando-aquí","Qué está pasando aquí",[186,626,627,635,641],{},[163,628,629,632,633,173],{},[170,630,631],{},"open",": cada cliente entra a la sala ",[170,634,405],{},[163,636,637,640],{},[170,638,639],{},"message",": cualquier mensaje recibido se reenvía a todos los suscritos.",[163,642,643,646],{},[170,644,645],{},"close/error",": útiles para observabilidad y debugging.",[175,648],{},[178,650,652,653,306],{"id":651},"_3-instalar-vueuse-usewebsocket","3) Instalar VueUse (",[170,654,172],{},[153,656,657,658,660,661,668,669,672,673,676,677,680],{},"El composable ",[170,659,172],{}," viene de ",[662,663,667],"a",{"href":664,"rel":665},"https://vueuse.org/core/useWebSocket/",[666],"nofollow","VueUse",", una colección de utilidades para Vue y Nuxt. Proporciona una API reactiva para trabajar con WebSockets: gestiona la conexión, expone el estado (",[170,670,671],{},"status","), los datos recibidos (",[170,674,675],{},"data",") y una función ",[170,678,679],{},"send"," para enviar mensajes.",[153,682,683],{},"Para usarlo en Nuxt, instala el módulo oficial:",[217,685,689],{"className":686,"code":687,"language":688,"meta":222,"style":222},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","npx nuxt@latest module add vueuse\n","bash",[170,690,691],{"__ignoreMap":222},[226,692,693,697,700,703,706],{"class":228,"line":229},[226,694,696],{"class":695},"sBMFI","npx",[226,698,699],{"class":377}," nuxt@latest",[226,701,702],{"class":377}," module",[226,704,705],{"class":377}," add",[226,707,708],{"class":377}," vueuse\n",[153,710,711,712,715,716,718,719,721],{},"Esto añade ",[170,713,714],{},"@vueuse/nuxt"," a tus dependencias y lo registra en ",[170,717,214],{}," automáticamente. Una vez instalado, todos los composables de VueUse (incluido ",[170,720,172],{},") estarán disponibles por auto-import en tu proyecto, sin necesidad de importarlos manualmente.",[175,723],{},[178,725,727],{"id":726},"_4-consumir-websocket-desde-un-composable-cliente","4) Consumir WebSocket desde un composable (cliente)",[153,729,730],{},"La implementación cliente está en:",[153,732,733],{},[170,734,735],{},"composables/useWsTasks.ts",[153,737,738],{},"Puntos importantes del composable:",[186,740,741,744,755,761],{},[163,742,743],{},"encapsula toda la lógica WS en un solo composable reutilizable,",[163,745,746,747,750,751,754],{},"construye la URL WS dinámicamente (",[170,748,749],{},"ws://"," o ",[170,752,753],{},"wss://"," según protocolo),",[163,756,757,758,362],{},"escucha cambios con ",[170,759,760],{},"watch(wsData, ...)",[163,762,763,764,173],{},"evita procesar dos veces el mismo evento con ",[170,765,766],{},"lastUpdate",[217,768,770],{"className":219,"code":769,"language":221,"meta":222,"style":222},"type WsTasksCallback = () => void | Promise\u003Cvoid>;\n\nexport function useWsTasks(callback?: WsTasksCallback) {\n  const lastUpdate = ref\u003Cstring | null>(null);\n\n  if (!import.meta.client) {\n    return {\n      sendData: (_message: string) => {\n        return false;\n      },\n      lastUpdate,\n      wsStatus: ref(\"CLOSED\"),\n    };\n  }\n\n  const url = useRequestURL();\n  const wsProtocol = url.protocol === \"https:\" ? \"wss\" : \"ws\";\n  const wsUrl = `${wsProtocol}://${url.host}/api/ws/tasks`;\n\n  const { status: wsStatus, data: wsData, send } = useWebSocket(wsUrl);\n\n  watch(wsData, async (newVal) => {\n    if (!newVal) return;\n\n    if (newVal !== lastUpdate.value) {\n      lastUpdate.value = newVal;\n      if (callback) {\n        await callback();\n      }\n    }\n  });\n\n  function sendData(message: string): boolean {\n    lastUpdate.value = message;\n    send(message);\n    return true;\n  }\n\n  return {\n    sendData,\n    lastUpdate,\n    wsStatus,\n  };\n}\n",[170,771,772,807,811,835,870,874,902,909,932,942,947,954,976,981,986,990,1007,1055,1096,1101,1147,1152,1179,1198,1203,1226,1242,1256,1269,1275,1281,1291,1296,1321,1337,1351,1360,1365,1370,1378,1386,1393,1401,1407],{"__ignoreMap":222},[226,773,774,777,780,783,786,789,792,795,798,801,804],{"class":228,"line":229},[226,775,776],{"class":393},"type",[226,778,779],{"class":695}," WsTasksCallback",[226,781,782],{"class":247}," =",[226,784,785],{"class":247}," ()",[226,787,788],{"class":393}," =>",[226,790,791],{"class":695}," void",[226,793,794],{"class":247}," |",[226,796,797],{"class":695}," Promise",[226,799,800],{"class":247},"\u003C",[226,802,803],{"class":695},"void",[226,805,806],{"class":247},">;\n",[226,808,809],{"class":228,"line":251},[226,810,388],{"emptyLinePlaceholder":87},[226,812,813,815,818,821,823,826,829,831,833],{"class":228,"line":263},[226,814,233],{"class":232},[226,816,817],{"class":393}," function",[226,819,820],{"class":239}," useWsTasks",[226,822,244],{"class":247},[226,824,825],{"class":436},"callback",[226,827,828],{"class":247},"?:",[226,830,779],{"class":695},[226,832,306],{"class":247},[226,834,260],{"class":247},[226,836,837,840,843,845,848,850,853,855,858,861,863,866,868],{"class":228,"line":273},[226,838,839],{"class":393},"  const",[226,841,842],{"class":243}," lastUpdate",[226,844,782],{"class":247},[226,846,847],{"class":239}," ref",[226,849,800],{"class":247},[226,851,852],{"class":695},"string",[226,854,794],{"class":247},[226,856,857],{"class":695}," null",[226,859,860],{"class":247},">",[226,862,244],{"class":254},[226,864,865],{"class":247},"null",[226,867,306],{"class":254},[226,869,309],{"class":247},[226,871,872],{"class":228,"line":288},[226,873,388],{"emptyLinePlaceholder":87},[226,875,876,879,882,885,887,889,892,894,897,900],{"class":228,"line":294},[226,877,878],{"class":232},"  if",[226,880,881],{"class":254}," (",[226,883,884],{"class":247},"!",[226,886,350],{"class":232},[226,888,173],{"class":247},[226,890,891],{"class":243},"meta",[226,893,173],{"class":247},[226,895,896],{"class":243},"client",[226,898,899],{"class":254},") ",[226,901,248],{"class":247},[226,903,904,907],{"class":228,"line":300},[226,905,906],{"class":232},"    return",[226,908,260],{"class":247},[226,910,911,914,916,918,921,923,926,928,930],{"class":228,"line":463},[226,912,913],{"class":239},"      sendData",[226,915,215],{"class":247},[226,917,881],{"class":247},[226,919,920],{"class":436},"_message",[226,922,215],{"class":247},[226,924,925],{"class":695}," string",[226,927,306],{"class":247},[226,929,788],{"class":393},[226,931,260],{"class":247},[226,933,934,937,940],{"class":228,"line":468},[226,935,936],{"class":232},"        return",[226,938,939],{"class":281}," false",[226,941,309],{"class":247},[226,943,944],{"class":228,"line":482},[226,945,946],{"class":247},"      },\n",[226,948,949,952],{"class":228,"line":506},[226,950,951],{"class":243},"      lastUpdate",[226,953,285],{"class":247},[226,955,956,959,961,963,965,967,970,972,974],{"class":228,"line":511},[226,957,958],{"class":254},"      wsStatus",[226,960,215],{"class":247},[226,962,847],{"class":239},[226,964,244],{"class":254},[226,966,381],{"class":247},[226,968,969],{"class":377},"CLOSED",[226,971,381],{"class":247},[226,973,306],{"class":254},[226,975,285],{"class":247},[226,977,978],{"class":228,"line":530},[226,979,980],{"class":247},"    };\n",[226,982,983],{"class":228,"line":556},[226,984,985],{"class":247},"  }\n",[226,987,988],{"class":228,"line":561},[226,989,388],{"emptyLinePlaceholder":87},[226,991,992,994,997,999,1002,1005],{"class":228,"line":580},[226,993,839],{"class":393},[226,995,996],{"class":243}," url",[226,998,782],{"class":247},[226,1000,1001],{"class":239}," useRequestURL",[226,1003,1004],{"class":254},"()",[226,1006,309],{"class":247},[226,1008,1009,1011,1014,1016,1018,1020,1023,1026,1028,1031,1033,1036,1038,1041,1043,1046,1048,1051,1053],{"class":228,"line":608},[226,1010,839],{"class":393},[226,1012,1013],{"class":243}," wsProtocol",[226,1015,782],{"class":247},[226,1017,996],{"class":243},[226,1019,173],{"class":247},[226,1021,1022],{"class":243},"protocol",[226,1024,1025],{"class":247}," ===",[226,1027,374],{"class":247},[226,1029,1030],{"class":377},"https:",[226,1032,381],{"class":247},[226,1034,1035],{"class":247}," ?",[226,1037,374],{"class":247},[226,1039,1040],{"class":377},"wss",[226,1042,381],{"class":247},[226,1044,1045],{"class":247}," :",[226,1047,374],{"class":247},[226,1049,1050],{"class":377},"ws",[226,1052,381],{"class":247},[226,1054,309],{"class":247},[226,1056,1057,1059,1062,1064,1067,1070,1072,1075,1078,1081,1083,1086,1088,1091,1094],{"class":228,"line":613},[226,1058,839],{"class":393},[226,1060,1061],{"class":243}," wsUrl",[226,1063,782],{"class":247},[226,1065,1066],{"class":247}," `${",[226,1068,1069],{"class":243},"wsProtocol",[226,1071,303],{"class":247},[226,1073,1074],{"class":377},"://",[226,1076,1077],{"class":247},"${",[226,1079,1080],{"class":243},"url",[226,1082,173],{"class":247},[226,1084,1085],{"class":243},"host",[226,1087,303],{"class":247},[226,1089,1090],{"class":377},"/api/ws/tasks",[226,1092,1093],{"class":247},"`",[226,1095,309],{"class":247},[226,1097,1099],{"class":228,"line":1098},19,[226,1100,388],{"emptyLinePlaceholder":87},[226,1102,1104,1106,1108,1111,1113,1116,1118,1121,1123,1126,1128,1131,1133,1135,1138,1140,1143,1145],{"class":228,"line":1103},20,[226,1105,839],{"class":393},[226,1107,356],{"class":247},[226,1109,1110],{"class":254}," status",[226,1112,215],{"class":247},[226,1114,1115],{"class":243}," wsStatus",[226,1117,362],{"class":247},[226,1119,1120],{"class":254}," data",[226,1122,215],{"class":247},[226,1124,1125],{"class":243}," wsData",[226,1127,362],{"class":247},[226,1129,1130],{"class":243}," send",[226,1132,368],{"class":247},[226,1134,782],{"class":247},[226,1136,1137],{"class":239}," useWebSocket",[226,1139,244],{"class":254},[226,1141,1142],{"class":243},"wsUrl",[226,1144,306],{"class":254},[226,1146,309],{"class":247},[226,1148,1150],{"class":228,"line":1149},21,[226,1151,388],{"emptyLinePlaceholder":87},[226,1153,1155,1158,1160,1163,1165,1168,1170,1173,1175,1177],{"class":228,"line":1154},22,[226,1156,1157],{"class":239},"  watch",[226,1159,244],{"class":254},[226,1161,1162],{"class":243},"wsData",[226,1164,362],{"class":247},[226,1166,1167],{"class":393}," async",[226,1169,881],{"class":247},[226,1171,1172],{"class":436},"newVal",[226,1174,306],{"class":247},[226,1176,788],{"class":393},[226,1178,260],{"class":247},[226,1180,1182,1185,1187,1189,1191,1193,1196],{"class":228,"line":1181},23,[226,1183,1184],{"class":232},"    if",[226,1186,881],{"class":254},[226,1188,884],{"class":247},[226,1190,1172],{"class":243},[226,1192,899],{"class":254},[226,1194,1195],{"class":232},"return",[226,1197,309],{"class":247},[226,1199,1201],{"class":228,"line":1200},24,[226,1202,388],{"emptyLinePlaceholder":87},[226,1204,1206,1208,1210,1212,1215,1217,1219,1222,1224],{"class":228,"line":1205},25,[226,1207,1184],{"class":232},[226,1209,881],{"class":254},[226,1211,1172],{"class":243},[226,1213,1214],{"class":247}," !==",[226,1216,842],{"class":243},[226,1218,173],{"class":247},[226,1220,1221],{"class":243},"value",[226,1223,899],{"class":254},[226,1225,248],{"class":247},[226,1227,1229,1231,1233,1235,1237,1240],{"class":228,"line":1228},26,[226,1230,951],{"class":243},[226,1232,173],{"class":247},[226,1234,1221],{"class":243},[226,1236,782],{"class":247},[226,1238,1239],{"class":243}," newVal",[226,1241,309],{"class":247},[226,1243,1245,1248,1250,1252,1254],{"class":228,"line":1244},27,[226,1246,1247],{"class":232},"      if",[226,1249,881],{"class":254},[226,1251,825],{"class":243},[226,1253,899],{"class":254},[226,1255,248],{"class":247},[226,1257,1259,1262,1265,1267],{"class":228,"line":1258},28,[226,1260,1261],{"class":232},"        await",[226,1263,1264],{"class":239}," callback",[226,1266,1004],{"class":254},[226,1268,309],{"class":247},[226,1270,1272],{"class":228,"line":1271},29,[226,1273,1274],{"class":247},"      }\n",[226,1276,1278],{"class":228,"line":1277},30,[226,1279,1280],{"class":247},"    }\n",[226,1282,1284,1287,1289],{"class":228,"line":1283},31,[226,1285,1286],{"class":247},"  }",[226,1288,306],{"class":254},[226,1290,309],{"class":247},[226,1292,1294],{"class":228,"line":1293},32,[226,1295,388],{"emptyLinePlaceholder":87},[226,1297,1299,1302,1305,1307,1309,1311,1313,1316,1319],{"class":228,"line":1298},33,[226,1300,1301],{"class":393},"  function",[226,1303,1304],{"class":239}," sendData",[226,1306,244],{"class":247},[226,1308,639],{"class":436},[226,1310,215],{"class":247},[226,1312,925],{"class":695},[226,1314,1315],{"class":247},"):",[226,1317,1318],{"class":695}," boolean",[226,1320,260],{"class":247},[226,1322,1324,1327,1329,1331,1333,1335],{"class":228,"line":1323},34,[226,1325,1326],{"class":243},"    lastUpdate",[226,1328,173],{"class":247},[226,1330,1221],{"class":243},[226,1332,782],{"class":247},[226,1334,573],{"class":243},[226,1336,309],{"class":247},[226,1338,1340,1343,1345,1347,1349],{"class":228,"line":1339},35,[226,1341,1342],{"class":239},"    send",[226,1344,244],{"class":254},[226,1346,639],{"class":243},[226,1348,306],{"class":254},[226,1350,309],{"class":247},[226,1352,1354,1356,1358],{"class":228,"line":1353},36,[226,1355,906],{"class":232},[226,1357,282],{"class":281},[226,1359,309],{"class":247},[226,1361,1363],{"class":228,"line":1362},37,[226,1364,985],{"class":247},[226,1366,1368],{"class":228,"line":1367},38,[226,1369,388],{"emptyLinePlaceholder":87},[226,1371,1373,1376],{"class":228,"line":1372},39,[226,1374,1375],{"class":232},"  return",[226,1377,260],{"class":247},[226,1379,1381,1384],{"class":228,"line":1380},40,[226,1382,1383],{"class":243},"    sendData",[226,1385,285],{"class":247},[226,1387,1389,1391],{"class":228,"line":1388},41,[226,1390,1326],{"class":243},[226,1392,285],{"class":247},[226,1394,1396,1399],{"class":228,"line":1395},42,[226,1397,1398],{"class":243},"    wsStatus",[226,1400,285],{"class":247},[226,1402,1404],{"class":228,"line":1403},43,[226,1405,1406],{"class":247},"  };\n",[226,1408,1410],{"class":228,"line":1409},44,[226,1411,1412],{"class":247},"}\n",[621,1414,1416,1417,1419],{"id":1415},"por-qué-lastupdate-es-útil","Por qué ",[170,1418,766],{}," es útil",[153,1421,1422,1423,1425],{},"Cuando tú envías un evento, el servidor lo re-publica a todos, incluido el emisor. Con ",[170,1424,766],{}," evitas disparar lógica duplicada en el cliente que originó el mensaje.",[175,1427],{},[178,1429,1431],{"id":1430},"_5-uso-en-una-página-o-componente","5) Uso en una página o componente",[153,1433,1434],{},"Patrón recomendado:",[160,1436,1437,1444,1450],{},[163,1438,1439,1440,1443],{},"montas ",[170,1441,1442],{},"useWsTasks"," con un callback (por ejemplo, recargar tareas),",[163,1445,1446,1447,362],{},"cuando haces una acción local (crear/editar una tarea), llamas ",[170,1448,1449],{},"sendData(...)",[163,1451,1452],{},"el resto de clientes recibe el evento y ejecuta su callback.",[153,1454,1455],{},"Ejemplo:",[217,1457,1459],{"className":219,"code":1458,"language":221,"meta":222,"style":222},"const { sendData, wsStatus } = useWsTasks(async () => {\n  await refreshTasks();\n});\n\nasync function onTaskUpdated() {\n  await saveTask();\n  sendData(`task-updated:${Date.now()}`);\n}\n",[170,1460,1461,1491,1503,1511,1515,1528,1539,1570],{"__ignoreMap":222},[226,1462,1463,1465,1467,1469,1471,1474,1476,1478,1480,1482,1485,1487,1489],{"class":228,"line":229},[226,1464,394],{"class":393},[226,1466,356],{"class":247},[226,1468,1304],{"class":243},[226,1470,362],{"class":247},[226,1472,1473],{"class":243}," wsStatus ",[226,1475,303],{"class":247},[226,1477,782],{"class":247},[226,1479,820],{"class":239},[226,1481,244],{"class":243},[226,1483,1484],{"class":393},"async",[226,1486,785],{"class":247},[226,1488,788],{"class":393},[226,1490,260],{"class":247},[226,1492,1493,1496,1499,1501],{"class":228,"line":251},[226,1494,1495],{"class":232},"  await",[226,1497,1498],{"class":239}," refreshTasks",[226,1500,1004],{"class":254},[226,1502,309],{"class":247},[226,1504,1505,1507,1509],{"class":228,"line":263},[226,1506,303],{"class":247},[226,1508,306],{"class":243},[226,1510,309],{"class":247},[226,1512,1513],{"class":228,"line":273},[226,1514,388],{"emptyLinePlaceholder":87},[226,1516,1517,1519,1521,1524,1526],{"class":228,"line":288},[226,1518,1484],{"class":393},[226,1520,817],{"class":393},[226,1522,1523],{"class":239}," onTaskUpdated",[226,1525,1004],{"class":247},[226,1527,260],{"class":247},[226,1529,1530,1532,1535,1537],{"class":228,"line":294},[226,1531,1495],{"class":232},[226,1533,1534],{"class":239}," saveTask",[226,1536,1004],{"class":254},[226,1538,309],{"class":247},[226,1540,1541,1544,1546,1548,1551,1553,1556,1558,1561,1563,1566,1568],{"class":228,"line":300},[226,1542,1543],{"class":239},"  sendData",[226,1545,244],{"class":254},[226,1547,1093],{"class":247},[226,1549,1550],{"class":377},"task-updated:",[226,1552,1077],{"class":247},[226,1554,1555],{"class":243},"Date",[226,1557,173],{"class":247},[226,1559,1560],{"class":239},"now",[226,1562,1004],{"class":243},[226,1564,1565],{"class":247},"}`",[226,1567,306],{"class":254},[226,1569,309],{"class":247},[226,1571,1572],{"class":228,"line":463},[226,1573,1412],{"class":247},[175,1575],{},[178,1577,1579],{"id":1578},"_6-buenas-prácticas-en-producción","6) Buenas prácticas en producción",[186,1581,1582,1597,1603,1609,1615],{},[163,1583,1584,1588,1589],{},[1585,1586,1587],"strong",{},"Mensajes estructurados",": mejor JSON que texto plano.\n",[186,1590,1591],{},[163,1592,1593,1594],{},"Ejemplo: ",[170,1595,1596],{},"{ \"type\": \"task-updated\", \"taskId\": \"123\" }",[163,1598,1599,1602],{},[1585,1600,1601],{},"Autenticación/autorización",": valida quién puede conectarse y publicar.",[163,1604,1605,1608],{},[1585,1606,1607],{},"Reconexión",": maneja desconexiones temporales de red.",[163,1610,1611,1614],{},[1585,1612,1613],{},"Filtrado por canal/sala",": evita que todos los clientes reciban todo.",[163,1616,1617,1620],{},[1585,1618,1619],{},"Logs y métricas",": clave para depurar problemas en tiempo real.",[175,1622],{},[178,1624,1626],{"id":1625},"_7-problemas-comunes","7) Problemas comunes",[160,1628,1629,1644,1657,1670],{},[163,1630,1631,1634],{},[1585,1632,1633],{},"No conecta en producción con HTTPS",[186,1635,1636],{},[163,1637,1638,1639,1641,1642,173],{},"Usa ",[170,1640,753],{},", no ",[170,1643,749],{},[163,1645,1646,1649],{},[1585,1647,1648],{},"Eventos duplicados",[186,1650,1651],{},[163,1652,1653,1654,1656],{},"Implementa deduplicación (",[170,1655,766],{},", ids de evento, etc.).",[163,1658,1659,1662],{},[1585,1660,1661],{},"La conexión se abre en SSR",[186,1663,1664],{},[163,1665,1666,1667,173],{},"Protege el código con ",[170,1668,1669],{},"import.meta.client",[163,1671,1672,1675],{},[1585,1673,1674],{},"No llega ningún mensaje",[186,1676,1677],{},[163,1678,1679],{},"Verifica que el cliente se suscriba a la misma sala que el servidor publica.",[175,1681],{},[178,1683,1685],{"id":1684},"conclusión","Conclusión",[153,1687,1688],{},"Con Nuxt, montar WebSockets es bastante directo:",[186,1690,1691,1697,1702],{},[163,1692,1693,1694,362],{},"servidor con ",[170,1695,1696],{},"defineWebSocketHandler",[163,1698,1699,1700,362],{},"cliente con ",[170,1701,172],{},[163,1703,1704],{},"y un composable para centralizar la lógica de conexión.",[153,1706,1707],{},"Este enfoque te da una base sólida para funcionalidades colaborativas en tiempo real, manteniendo una arquitectura limpia y reusable.",[1709,1710,1711],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":222,"searchDepth":251,"depth":251,"links":1713},[1714,1715,1716,1719,1721,1725,1726,1727,1728],{"id":180,"depth":251,"text":181},{"id":207,"depth":251,"text":208},{"id":320,"depth":251,"text":321,"children":1717},[1718],{"id":623,"depth":263,"text":624},{"id":651,"depth":251,"text":1720},"3) Instalar VueUse (useWebSocket)",{"id":726,"depth":251,"text":727,"children":1722},[1723],{"id":1415,"depth":263,"text":1724},"Por qué lastUpdate es útil",{"id":1430,"depth":251,"text":1431},{"id":1578,"depth":251,"text":1579},{"id":1625,"depth":251,"text":1626},{"id":1684,"depth":251,"text":1685},"Nuxt","28 de febrero de 2026","Aprende a implementar comunicación en tiempo real con Nuxt usando defineWebSocketHandler en el servidor y useWebSocket en el cliente.","md","i-lucide-radio-tower","/images/blog/websocket-nuxt.png",{},"/blog/websocket-con-nuxt",{"title":148,"description":1731},"blog/websocket-con-nuxt","5UMxK28vdbiwM3-4BKDoTDd5I1sSp9bwEwMqJw_9seo",{"id":1741,"title":1742,"body":1743,"category":46,"date":4637,"description":4638,"extension":1732,"icon":18,"image":4639,"meta":4640,"navigation":87,"path":4641,"seo":4642,"stem":4643,"__hash__":4644},"blog/blog/tutorial-vercel-ai-gateway-nuxt.md","Implementando un Chat con IA en Nuxt usando Vercel AI Gateway",{"type":150,"value":1744,"toc":4609},[1745,1749,1756,1760,1798,1802,1805,1843,1845,1849,1852,1876,1878,1882,1885,1889,2300,2308,2314,2321,2324,2326,2330,2337,2341,2344,2959,2963,2987,2997,2999,3003,3010,3014,3020,3046,3049,3053,3953,3957,3967,4391,4394,4408,4415,4421,4459,4463,4511,4514,4516,4520,4566,4568,4570,4573,4599,4606],[178,1746,1748],{"id":1747},"qué-es-vercel-ai-gateway","¿Qué es Vercel AI Gateway?",[153,1750,1751,1752,1755],{},"Antes de entrar en código, hablemos de ",[1585,1753,1754],{},"Vercel AI Gateway",". Es un servicio que permite gestionar múltiples proveedores de IA desde un único punto de entrada. Esto significa que puedes usar modelos de Anthropic (Claude), OpenAI (GPT), Google (Gemini) y otros, sin cambiar tu implementación cada vez.",[621,1757,1759],{"id":1758},"ventajas-principales","Ventajas principales:",[186,1761,1762,1768,1774,1780,1786,1792],{},[163,1763,1764,1767],{},[1585,1765,1766],{},"Una clave, cientos de modelos",": Accedé a modelos de múltiples proveedores con una sola API key.",[163,1769,1770,1773],{},[1585,1771,1772],{},"API Unificada",": Cambiá entre proveedores y modelos con cambios mínimos en el código.",[163,1775,1776,1779],{},[1585,1777,1778],{},"Alta fiabilidad",": Reintenta automáticamente las solicitudes con otros proveedores si uno falla.",[163,1781,1782,1785],{},[1585,1783,1784],{},"Soporte de embeddings",": Generá embeddings vectoriales para búsqueda, recuperación y otras tareas.",[163,1787,1788,1791],{},[1585,1789,1790],{},"Monitoreo de gastos",": Monitoreá tus gastos en los diferentes proveedores.",[163,1793,1794,1797],{},[1585,1795,1796],{},"Sin recargo en tokens",": Los tokens cuestan lo mismo que directamente con el proveedor, sin ningún recargo, incluso con Bring Your Own Key (BYOK).",[178,1799,1801],{"id":1800},"arquitectura-general","Arquitectura General",[153,1803,1804],{},"El sistema tiene cuatro partes principales:",[160,1806,1807,1816,1825,1834],{},[163,1808,1809,881,1812,1815],{},[1585,1810,1811],{},"El servidor",[170,1813,1814],{},"/server/api/chat.ts","): Donde se procesan los mensajes y se definen las herramientas",[163,1817,1818,881,1821,1824],{},[1585,1819,1820],{},"La interfaz",[170,1822,1823],{},"/app/pages/ia.vue","): El chat donde el usuario interactúa",[163,1826,1827,881,1830,1833],{},[1585,1828,1829],{},"Las herramientas",[170,1831,1832],{},"/shared/utils/tools/","): Funciones especiales que la IA puede usar",[163,1835,1836,881,1839,1842],{},[1585,1837,1838],{},"Componentes Personalizados",[170,1840,1841],{},"/app/components/Tools/","): Componentes Vue que renderizan el resultado de cada herramienta",[175,1844],{},[178,1846,1848],{"id":1847},"prerrequisitos","Prerrequisitos",[153,1850,1851],{},"Antes de empezar, necesitás registrarte en dos servicios:",[160,1853,1854,1865],{},[163,1855,1856,1858,1859,1864],{},[1585,1857,1754],{},": Creá una cuenta en ",[662,1860,1863],{"href":1861,"rel":1862},"https://vercel.com",[666],"Vercel"," para poder obtener el API key del AI Gateway.",[163,1866,1867,1870,1871,1875],{},[1585,1868,1869],{},"API Ninjas",": Para el ejemplo práctico de este tutorial vamos a usar una API externa de horóscopos. Registrate en ",[662,1872,1873],{"href":1873,"rel":1874},"https://api-ninjas.com/",[666]," y obtené tu API key.",[175,1877],{},[178,1879,1881],{"id":1880},"configuración-del-servidor","Configuración del Servidor",[153,1883,1884],{},"Vamos al corazón del sistema: el endpoint de chat. Se usa el SDK de Vercel que facilita enormemente el streaming de respuestas.",[621,1886,1888],{"id":1887},"estructura-básica","Estructura básica",[217,1890,1894],{"className":1891,"code":1892,"language":1893,"meta":222,"style":222},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","// /server/api/chat.ts\nimport { streamText, UIMessage, createGateway, stepCountIs } from \"ai\";\nimport { horoscopeTool } from \"../../shared/utils/tools/horoscopeTool\";\n\nexport default defineLazyEventHandler(async () => {\n  const apiKey = useRuntimeConfig().aiGatewayApiKey; // debes configurar esta variable en tu .env, mas info https://nuxt.com/docs/4.x/api/composables/use-runtime-config\n\n  if (!apiKey) throw new Error(\"Missing AI Gateway API key\");\n\n  const gateway = createGateway({ apiKey });\n  const model = gateway(\"anthropic/claude-sonnet-4-20250514\");\n\n  return defineEventHandler(async (event) => {\n    const { messages }: { messages: UIMessage[] } = await readBody(event);\n\n    const result = streamText({\n      model,\n      system: \"Eres un asistente útil.\",\n      messages,\n      tools: {\n        horoscopeTool,\n      },\n      stopWhen: stepCountIs(8),\n    });\n\n    return result.toUIMessageStreamResponse();\n  });\n});\n","typescript",[170,1895,1896,1902,1939,1961,1965,1984,2009,2013,2048,2052,2076,2100,2104,2126,2168,2172,2187,2194,2210,2217,2226,2233,2237,2256,2265,2269,2284,2292],{"__ignoreMap":222},[226,1897,1898],{"class":228,"line":229},[226,1899,1901],{"class":1900},"sHwdD","// /server/api/chat.ts\n",[226,1903,1904,1906,1908,1911,1913,1916,1918,1921,1923,1926,1928,1930,1932,1935,1937],{"class":228,"line":251},[226,1905,350],{"class":232},[226,1907,356],{"class":247},[226,1909,1910],{"class":243}," streamText",[226,1912,362],{"class":247},[226,1914,1915],{"class":243}," UIMessage",[226,1917,362],{"class":247},[226,1919,1920],{"class":243}," createGateway",[226,1922,362],{"class":247},[226,1924,1925],{"class":243}," stepCountIs",[226,1927,368],{"class":247},[226,1929,371],{"class":232},[226,1931,374],{"class":247},[226,1933,1934],{"class":377},"ai",[226,1936,381],{"class":247},[226,1938,309],{"class":247},[226,1940,1941,1943,1945,1948,1950,1952,1954,1957,1959],{"class":228,"line":263},[226,1942,350],{"class":232},[226,1944,356],{"class":247},[226,1946,1947],{"class":243}," horoscopeTool",[226,1949,368],{"class":247},[226,1951,371],{"class":232},[226,1953,374],{"class":247},[226,1955,1956],{"class":377},"../../shared/utils/tools/horoscopeTool",[226,1958,381],{"class":247},[226,1960,309],{"class":247},[226,1962,1963],{"class":228,"line":273},[226,1964,388],{"emptyLinePlaceholder":87},[226,1966,1967,1969,1971,1974,1976,1978,1980,1982],{"class":228,"line":288},[226,1968,233],{"class":232},[226,1970,236],{"class":232},[226,1972,1973],{"class":239}," defineLazyEventHandler",[226,1975,244],{"class":243},[226,1977,1484],{"class":393},[226,1979,785],{"class":247},[226,1981,788],{"class":393},[226,1983,260],{"class":247},[226,1985,1986,1988,1991,1993,1996,1998,2000,2003,2006],{"class":228,"line":294},[226,1987,839],{"class":393},[226,1989,1990],{"class":243}," apiKey",[226,1992,782],{"class":247},[226,1994,1995],{"class":239}," useRuntimeConfig",[226,1997,1004],{"class":254},[226,1999,173],{"class":247},[226,2001,2002],{"class":243},"aiGatewayApiKey",[226,2004,2005],{"class":247},";",[226,2007,2008],{"class":1900}," // debes configurar esta variable en tu .env, mas info https://nuxt.com/docs/4.x/api/composables/use-runtime-config\n",[226,2010,2011],{"class":228,"line":300},[226,2012,388],{"emptyLinePlaceholder":87},[226,2014,2015,2017,2019,2021,2024,2026,2029,2032,2035,2037,2039,2042,2044,2046],{"class":228,"line":463},[226,2016,878],{"class":232},[226,2018,881],{"class":254},[226,2020,884],{"class":247},[226,2022,2023],{"class":243},"apiKey",[226,2025,899],{"class":254},[226,2027,2028],{"class":232},"throw",[226,2030,2031],{"class":247}," new",[226,2033,2034],{"class":239}," Error",[226,2036,244],{"class":254},[226,2038,381],{"class":247},[226,2040,2041],{"class":377},"Missing AI Gateway API key",[226,2043,381],{"class":247},[226,2045,306],{"class":254},[226,2047,309],{"class":247},[226,2049,2050],{"class":228,"line":468},[226,2051,388],{"emptyLinePlaceholder":87},[226,2053,2054,2056,2059,2061,2063,2065,2068,2070,2072,2074],{"class":228,"line":482},[226,2055,839],{"class":393},[226,2057,2058],{"class":243}," gateway",[226,2060,782],{"class":247},[226,2062,1920],{"class":239},[226,2064,244],{"class":254},[226,2066,2067],{"class":247},"{",[226,2069,1990],{"class":243},[226,2071,368],{"class":247},[226,2073,306],{"class":254},[226,2075,309],{"class":247},[226,2077,2078,2080,2083,2085,2087,2089,2091,2094,2096,2098],{"class":228,"line":506},[226,2079,839],{"class":393},[226,2081,2082],{"class":243}," model",[226,2084,782],{"class":247},[226,2086,2058],{"class":239},[226,2088,244],{"class":254},[226,2090,381],{"class":247},[226,2092,2093],{"class":377},"anthropic/claude-sonnet-4-20250514",[226,2095,381],{"class":247},[226,2097,306],{"class":254},[226,2099,309],{"class":247},[226,2101,2102],{"class":228,"line":511},[226,2103,388],{"emptyLinePlaceholder":87},[226,2105,2106,2108,2111,2113,2115,2117,2120,2122,2124],{"class":228,"line":530},[226,2107,1375],{"class":232},[226,2109,2110],{"class":239}," defineEventHandler",[226,2112,244],{"class":254},[226,2114,1484],{"class":393},[226,2116,881],{"class":247},[226,2118,2119],{"class":436},"event",[226,2121,306],{"class":247},[226,2123,788],{"class":393},[226,2125,260],{"class":247},[226,2127,2128,2131,2133,2136,2139,2141,2143,2145,2147,2150,2152,2154,2157,2160,2162,2164,2166],{"class":228,"line":556},[226,2129,2130],{"class":393},"    const",[226,2132,356],{"class":247},[226,2134,2135],{"class":243}," messages",[226,2137,2138],{"class":247}," }:",[226,2140,356],{"class":247},[226,2142,2135],{"class":254},[226,2144,215],{"class":247},[226,2146,1915],{"class":695},[226,2148,2149],{"class":254},"[] ",[226,2151,303],{"class":247},[226,2153,782],{"class":247},[226,2155,2156],{"class":232}," await",[226,2158,2159],{"class":239}," readBody",[226,2161,244],{"class":254},[226,2163,2119],{"class":243},[226,2165,306],{"class":254},[226,2167,309],{"class":247},[226,2169,2170],{"class":228,"line":561},[226,2171,388],{"emptyLinePlaceholder":87},[226,2173,2174,2176,2179,2181,2183,2185],{"class":228,"line":580},[226,2175,2130],{"class":393},[226,2177,2178],{"class":243}," result",[226,2180,782],{"class":247},[226,2182,1910],{"class":239},[226,2184,244],{"class":254},[226,2186,248],{"class":247},[226,2188,2189,2192],{"class":228,"line":608},[226,2190,2191],{"class":243},"      model",[226,2193,285],{"class":247},[226,2195,2196,2199,2201,2203,2206,2208],{"class":228,"line":613},[226,2197,2198],{"class":254},"      system",[226,2200,215],{"class":247},[226,2202,374],{"class":247},[226,2204,2205],{"class":377},"Eres un asistente útil.",[226,2207,381],{"class":247},[226,2209,285],{"class":247},[226,2211,2212,2215],{"class":228,"line":1098},[226,2213,2214],{"class":243},"      messages",[226,2216,285],{"class":247},[226,2218,2219,2222,2224],{"class":228,"line":1103},[226,2220,2221],{"class":254},"      tools",[226,2223,215],{"class":247},[226,2225,260],{"class":247},[226,2227,2228,2231],{"class":228,"line":1149},[226,2229,2230],{"class":243},"        horoscopeTool",[226,2232,285],{"class":247},[226,2234,2235],{"class":228,"line":1154},[226,2236,946],{"class":247},[226,2238,2239,2242,2244,2246,2248,2252,2254],{"class":228,"line":1181},[226,2240,2241],{"class":254},"      stopWhen",[226,2243,215],{"class":247},[226,2245,1925],{"class":239},[226,2247,244],{"class":254},[226,2249,2251],{"class":2250},"sbssI","8",[226,2253,306],{"class":254},[226,2255,285],{"class":247},[226,2257,2258,2261,2263],{"class":228,"line":1200},[226,2259,2260],{"class":247},"    }",[226,2262,306],{"class":254},[226,2264,309],{"class":247},[226,2266,2267],{"class":228,"line":1205},[226,2268,388],{"emptyLinePlaceholder":87},[226,2270,2271,2273,2275,2277,2280,2282],{"class":228,"line":1228},[226,2272,906],{"class":232},[226,2274,2178],{"class":243},[226,2276,173],{"class":247},[226,2278,2279],{"class":239},"toUIMessageStreamResponse",[226,2281,1004],{"class":254},[226,2283,309],{"class":247},[226,2285,2286,2288,2290],{"class":228,"line":1244},[226,2287,1286],{"class":247},[226,2289,306],{"class":254},[226,2291,309],{"class":247},[226,2293,2294,2296,2298],{"class":228,"line":1258},[226,2295,303],{"class":247},[226,2297,306],{"class":243},[226,2299,309],{"class":247},[621,2301,2303,2304,2307],{"id":2302},"por-qué-streamtext","¿Por qué ",[170,2305,2306],{},"streamText","?",[153,2309,2310,2311,2313],{},"El método ",[170,2312,2306],{}," permite que la respuesta llegue palabra por palabra al usuario, en lugar de esperar a que todo el texto se genere. Esto crea una experiencia mucho más fluida y natural.",[621,2315,2317,2318,2307],{"id":2316},"qué-hace-stopwhen-stepcountis8","¿Qué hace ",[170,2319,2320],{},"stopWhen: stepCountIs(8)",[153,2322,2323],{},"Limita la cantidad de pasos que la IA puede ejecutar en una sola respuesta. Esto evita bucles infinitos si la IA decide llamar herramientas repetidamente.",[175,2325],{},[178,2327,2329],{"id":2328},"las-herramientas-tools","Las Herramientas (Tools)",[153,2331,2332,2333,2336],{},"Aquí viene lo interesante. La IA no solo responde preguntas, ",[1585,2334,2335],{},"puede ejecutar acciones",". Se definen \"herramientas\" que la IA puede invocar según necesite.",[621,2338,2340],{"id":2339},"ejemplo-consultar-el-horóscopo","Ejemplo: Consultar el horóscopo",[153,2342,2343],{},"Vamos a crear una herramienta que consulta el horóscopo diario usando la API de API Ninjas:",[217,2345,2347],{"className":1891,"code":2346,"language":1893,"meta":222,"style":222},"// /shared/utils/tools/horoscopeTool.ts\nimport { tool } from \"ai\";\nimport type { UIToolInvocation } from \"ai\";\nimport { z } from \"zod\";\n\nexport type HoroscopeUIToolInvocation = UIToolInvocation\u003Ctypeof horoscopeTool>;\n\ntype HoroscopeOutput = {\n  date: string;\n  sign: string;\n  horoscope: string;\n};\n\nexport const horoscopeTool = tool({\n  description: \"Returns the daily horoscope for a specific zodiac sign.\",\n  inputSchema: z.object({\n    zodiac: z\n      .literal([\n        \"aries\",\n        \"taurus\",\n        \"gemini\",\n        \"cancer\",\n        \"leo\",\n        \"virgo\",\n        \"libra\",\n        \"scorpio\",\n        \"sagittarius\",\n        \"capricorn\",\n        \"aquarius\",\n        \"pisces\",\n      ])\n      .describe(\"The zodiac sign to get a horoscope for.\"),\n  }),\n  execute: async ({ zodiac }: { zodiac: string }) => {\n    const url = `https://api.api-ninjas.com/v1/horoscope?zodiac=${zodiac}`;\n\n    try {\n      const data: HoroscopeOutput = await $fetch(url, {\n        headers: {\n          \"X-Api-Key\": \"YOUR_API_NINJAS_KEY\", // idealmente deberías obtener esto de una variable de entorno\n        },\n      });\n\n      return data;\n    } catch (error) {\n      throw new Error(`Failed to fetch horoscope: ${String(error)}`);\n    }\n  },\n});\n",[170,2348,2349,2354,2375,2398,2420,2424,2444,2448,2459,2470,2481,2492,2497,2501,2519,2535,2553,2563,2574,2586,2597,2608,2619,2630,2641,2652,2663,2674,2685,2696,2707,2712,2732,2740,2772,2795,2799,2806,2832,2841,2865,2870,2879,2883,2892,2909,2940,2945,2950],{"__ignoreMap":222},[226,2350,2351],{"class":228,"line":229},[226,2352,2353],{"class":1900},"// /shared/utils/tools/horoscopeTool.ts\n",[226,2355,2356,2358,2360,2363,2365,2367,2369,2371,2373],{"class":228,"line":251},[226,2357,350],{"class":232},[226,2359,356],{"class":247},[226,2361,2362],{"class":243}," tool",[226,2364,368],{"class":247},[226,2366,371],{"class":232},[226,2368,374],{"class":247},[226,2370,1934],{"class":377},[226,2372,381],{"class":247},[226,2374,309],{"class":247},[226,2376,2377,2379,2381,2383,2386,2388,2390,2392,2394,2396],{"class":228,"line":263},[226,2378,350],{"class":232},[226,2380,353],{"class":232},[226,2382,356],{"class":247},[226,2384,2385],{"class":243}," UIToolInvocation",[226,2387,368],{"class":247},[226,2389,371],{"class":232},[226,2391,374],{"class":247},[226,2393,1934],{"class":377},[226,2395,381],{"class":247},[226,2397,309],{"class":247},[226,2399,2400,2402,2404,2407,2409,2411,2413,2416,2418],{"class":228,"line":273},[226,2401,350],{"class":232},[226,2403,356],{"class":247},[226,2405,2406],{"class":243}," z",[226,2408,368],{"class":247},[226,2410,371],{"class":232},[226,2412,374],{"class":247},[226,2414,2415],{"class":377},"zod",[226,2417,381],{"class":247},[226,2419,309],{"class":247},[226,2421,2422],{"class":228,"line":288},[226,2423,388],{"emptyLinePlaceholder":87},[226,2425,2426,2428,2430,2433,2435,2437,2440,2442],{"class":228,"line":294},[226,2427,233],{"class":232},[226,2429,353],{"class":393},[226,2431,2432],{"class":695}," HoroscopeUIToolInvocation",[226,2434,782],{"class":247},[226,2436,2385],{"class":695},[226,2438,2439],{"class":247},"\u003Ctypeof",[226,2441,1947],{"class":243},[226,2443,806],{"class":247},[226,2445,2446],{"class":228,"line":300},[226,2447,388],{"emptyLinePlaceholder":87},[226,2449,2450,2452,2455,2457],{"class":228,"line":463},[226,2451,776],{"class":393},[226,2453,2454],{"class":695}," HoroscopeOutput",[226,2456,782],{"class":247},[226,2458,260],{"class":247},[226,2460,2461,2464,2466,2468],{"class":228,"line":468},[226,2462,2463],{"class":254},"  date",[226,2465,215],{"class":247},[226,2467,925],{"class":695},[226,2469,309],{"class":247},[226,2471,2472,2475,2477,2479],{"class":228,"line":482},[226,2473,2474],{"class":254},"  sign",[226,2476,215],{"class":247},[226,2478,925],{"class":695},[226,2480,309],{"class":247},[226,2482,2483,2486,2488,2490],{"class":228,"line":506},[226,2484,2485],{"class":254},"  horoscope",[226,2487,215],{"class":247},[226,2489,925],{"class":695},[226,2491,309],{"class":247},[226,2493,2494],{"class":228,"line":511},[226,2495,2496],{"class":247},"};\n",[226,2498,2499],{"class":228,"line":530},[226,2500,388],{"emptyLinePlaceholder":87},[226,2502,2503,2505,2508,2511,2513,2515,2517],{"class":228,"line":556},[226,2504,233],{"class":232},[226,2506,2507],{"class":393}," const",[226,2509,2510],{"class":243}," horoscopeTool ",[226,2512,400],{"class":247},[226,2514,2362],{"class":239},[226,2516,244],{"class":243},[226,2518,248],{"class":247},[226,2520,2521,2524,2526,2528,2531,2533],{"class":228,"line":561},[226,2522,2523],{"class":254},"  description",[226,2525,215],{"class":247},[226,2527,374],{"class":247},[226,2529,2530],{"class":377},"Returns the daily horoscope for a specific zodiac sign.",[226,2532,381],{"class":247},[226,2534,285],{"class":247},[226,2536,2537,2540,2542,2544,2546,2549,2551],{"class":228,"line":580},[226,2538,2539],{"class":254},"  inputSchema",[226,2541,215],{"class":247},[226,2543,2406],{"class":243},[226,2545,173],{"class":247},[226,2547,2548],{"class":239},"object",[226,2550,244],{"class":243},[226,2552,248],{"class":247},[226,2554,2555,2558,2560],{"class":228,"line":608},[226,2556,2557],{"class":254},"    zodiac",[226,2559,215],{"class":247},[226,2561,2562],{"class":243}," z\n",[226,2564,2565,2568,2571],{"class":228,"line":613},[226,2566,2567],{"class":247},"      .",[226,2569,2570],{"class":239},"literal",[226,2572,2573],{"class":243},"([\n",[226,2575,2576,2579,2582,2584],{"class":228,"line":1098},[226,2577,2578],{"class":247},"        \"",[226,2580,2581],{"class":377},"aries",[226,2583,381],{"class":247},[226,2585,285],{"class":247},[226,2587,2588,2590,2593,2595],{"class":228,"line":1103},[226,2589,2578],{"class":247},[226,2591,2592],{"class":377},"taurus",[226,2594,381],{"class":247},[226,2596,285],{"class":247},[226,2598,2599,2601,2604,2606],{"class":228,"line":1149},[226,2600,2578],{"class":247},[226,2602,2603],{"class":377},"gemini",[226,2605,381],{"class":247},[226,2607,285],{"class":247},[226,2609,2610,2612,2615,2617],{"class":228,"line":1154},[226,2611,2578],{"class":247},[226,2613,2614],{"class":377},"cancer",[226,2616,381],{"class":247},[226,2618,285],{"class":247},[226,2620,2621,2623,2626,2628],{"class":228,"line":1181},[226,2622,2578],{"class":247},[226,2624,2625],{"class":377},"leo",[226,2627,381],{"class":247},[226,2629,285],{"class":247},[226,2631,2632,2634,2637,2639],{"class":228,"line":1200},[226,2633,2578],{"class":247},[226,2635,2636],{"class":377},"virgo",[226,2638,381],{"class":247},[226,2640,285],{"class":247},[226,2642,2643,2645,2648,2650],{"class":228,"line":1205},[226,2644,2578],{"class":247},[226,2646,2647],{"class":377},"libra",[226,2649,381],{"class":247},[226,2651,285],{"class":247},[226,2653,2654,2656,2659,2661],{"class":228,"line":1228},[226,2655,2578],{"class":247},[226,2657,2658],{"class":377},"scorpio",[226,2660,381],{"class":247},[226,2662,285],{"class":247},[226,2664,2665,2667,2670,2672],{"class":228,"line":1244},[226,2666,2578],{"class":247},[226,2668,2669],{"class":377},"sagittarius",[226,2671,381],{"class":247},[226,2673,285],{"class":247},[226,2675,2676,2678,2681,2683],{"class":228,"line":1258},[226,2677,2578],{"class":247},[226,2679,2680],{"class":377},"capricorn",[226,2682,381],{"class":247},[226,2684,285],{"class":247},[226,2686,2687,2689,2692,2694],{"class":228,"line":1271},[226,2688,2578],{"class":247},[226,2690,2691],{"class":377},"aquarius",[226,2693,381],{"class":247},[226,2695,285],{"class":247},[226,2697,2698,2700,2703,2705],{"class":228,"line":1277},[226,2699,2578],{"class":247},[226,2701,2702],{"class":377},"pisces",[226,2704,381],{"class":247},[226,2706,285],{"class":247},[226,2708,2709],{"class":228,"line":1283},[226,2710,2711],{"class":243},"      ])\n",[226,2713,2714,2716,2719,2721,2723,2726,2728,2730],{"class":228,"line":1293},[226,2715,2567],{"class":247},[226,2717,2718],{"class":239},"describe",[226,2720,244],{"class":243},[226,2722,381],{"class":247},[226,2724,2725],{"class":377},"The zodiac sign to get a horoscope for.",[226,2727,381],{"class":247},[226,2729,306],{"class":243},[226,2731,285],{"class":247},[226,2733,2734,2736,2738],{"class":228,"line":1298},[226,2735,1286],{"class":247},[226,2737,306],{"class":243},[226,2739,285],{"class":247},[226,2741,2742,2745,2747,2749,2752,2755,2757,2759,2761,2763,2765,2768,2770],{"class":228,"line":1323},[226,2743,2744],{"class":239},"  execute",[226,2746,215],{"class":247},[226,2748,1167],{"class":393},[226,2750,2751],{"class":247}," ({",[226,2753,2754],{"class":436}," zodiac",[226,2756,2138],{"class":247},[226,2758,356],{"class":247},[226,2760,2754],{"class":254},[226,2762,215],{"class":247},[226,2764,925],{"class":695},[226,2766,2767],{"class":247}," })",[226,2769,788],{"class":393},[226,2771,260],{"class":247},[226,2773,2774,2776,2778,2780,2783,2786,2788,2791,2793],{"class":228,"line":1339},[226,2775,2130],{"class":393},[226,2777,996],{"class":243},[226,2779,782],{"class":247},[226,2781,2782],{"class":247}," `",[226,2784,2785],{"class":377},"https://api.api-ninjas.com/v1/horoscope?zodiac=",[226,2787,1077],{"class":247},[226,2789,2790],{"class":243},"zodiac",[226,2792,1565],{"class":247},[226,2794,309],{"class":247},[226,2796,2797],{"class":228,"line":1353},[226,2798,388],{"emptyLinePlaceholder":87},[226,2800,2801,2804],{"class":228,"line":1362},[226,2802,2803],{"class":232},"    try",[226,2805,260],{"class":247},[226,2807,2808,2811,2813,2815,2817,2819,2821,2824,2826,2828,2830],{"class":228,"line":1367},[226,2809,2810],{"class":393},"      const",[226,2812,1120],{"class":243},[226,2814,215],{"class":247},[226,2816,2454],{"class":695},[226,2818,782],{"class":247},[226,2820,2156],{"class":232},[226,2822,2823],{"class":239}," $fetch",[226,2825,244],{"class":254},[226,2827,1080],{"class":243},[226,2829,362],{"class":247},[226,2831,260],{"class":247},[226,2833,2834,2837,2839],{"class":228,"line":1372},[226,2835,2836],{"class":254},"        headers",[226,2838,215],{"class":247},[226,2840,260],{"class":247},[226,2842,2843,2846,2849,2851,2853,2855,2858,2860,2862],{"class":228,"line":1380},[226,2844,2845],{"class":247},"          \"",[226,2847,2848],{"class":254},"X-Api-Key",[226,2850,381],{"class":247},[226,2852,215],{"class":247},[226,2854,374],{"class":247},[226,2856,2857],{"class":377},"YOUR_API_NINJAS_KEY",[226,2859,381],{"class":247},[226,2861,362],{"class":247},[226,2863,2864],{"class":1900}," // idealmente deberías obtener esto de una variable de entorno\n",[226,2866,2867],{"class":228,"line":1388},[226,2868,2869],{"class":247},"        },\n",[226,2871,2872,2875,2877],{"class":228,"line":1395},[226,2873,2874],{"class":247},"      }",[226,2876,306],{"class":254},[226,2878,309],{"class":247},[226,2880,2881],{"class":228,"line":1403},[226,2882,388],{"emptyLinePlaceholder":87},[226,2884,2885,2888,2890],{"class":228,"line":1409},[226,2886,2887],{"class":232},"      return",[226,2889,1120],{"class":243},[226,2891,309],{"class":247},[226,2893,2895,2897,2900,2902,2905,2907],{"class":228,"line":2894},45,[226,2896,2260],{"class":247},[226,2898,2899],{"class":232}," catch",[226,2901,881],{"class":254},[226,2903,2904],{"class":243},"error",[226,2906,899],{"class":254},[226,2908,248],{"class":247},[226,2910,2912,2915,2917,2919,2921,2923,2926,2928,2931,2934,2936,2938],{"class":228,"line":2911},46,[226,2913,2914],{"class":232},"      throw",[226,2916,2031],{"class":247},[226,2918,2034],{"class":239},[226,2920,244],{"class":254},[226,2922,1093],{"class":247},[226,2924,2925],{"class":377},"Failed to fetch horoscope: ",[226,2927,1077],{"class":247},[226,2929,2930],{"class":239},"String",[226,2932,2933],{"class":243},"(error)",[226,2935,1565],{"class":247},[226,2937,306],{"class":254},[226,2939,309],{"class":247},[226,2941,2943],{"class":228,"line":2942},47,[226,2944,1280],{"class":247},[226,2946,2948],{"class":228,"line":2947},48,[226,2949,297],{"class":247},[226,2951,2953,2955,2957],{"class":228,"line":2952},49,[226,2954,303],{"class":247},[226,2956,306],{"class":243},[226,2958,309],{"class":247},[621,2960,2962],{"id":2961},"cómo-funciona","¿Cómo funciona?",[160,2964,2965,2971,2981],{},[163,2966,2967,2970],{},[1585,2968,2969],{},"Descripción clara",": Se le dice a la IA cuándo debe usar esta herramienta",[163,2972,2973,2976,2977,2980],{},[1585,2974,2975],{},"Validación con Zod",": Se definen qué datos se esperan (en este caso, un signo zodiacal válido usando ",[170,2978,2979],{},"z.literal()"," con un array de valores permitidos)",[163,2982,2983,2986],{},[1585,2984,2985],{},"Ejecución segura",": La función recibe datos validados, consulta la API externa y retorna el resultado",[153,2988,2989,2990,2993,2994,173],{},"La IA lee la descripción y el schema, y decide por sí sola cuándo invocar la herramienta. Si el usuario pregunta \"¿Cuál es mi horóscopo? Soy Leo\", la IA llamará automáticamente a ",[170,2991,2992],{},"horoscopeTool"," con ",[170,2995,2996],{},"zodiac: \"leo\"",[175,2998],{},[178,3000,3002],{"id":3001},"la-interfaz-de-usuario-con-nuxt-ui","La Interfaz de Usuario con Nuxt UI",[153,3004,3005,3006,3009],{},"Para el frontend, no es necesario construir todo desde cero. Se puede aprovechar ",[1585,3007,3008],{},"Nuxt UI",", una librería de componentes que incluye componentes específicos para chat con IA.",[621,3011,3013],{"id":3012},"uchatmessages-el-componente-estrella","UChatMessages: el componente estrella",[153,3015,3016,3019],{},[170,3017,3018],{},"UChatMessages"," es un componente de Nuxt UI que gestiona automáticamente:",[186,3021,3022,3028,3034,3040],{},[163,3023,3024,3027],{},[1585,3025,3026],{},"Renderizado de mensajes",": Muestra texto del usuario y de la IA",[163,3029,3030,3033],{},[1585,3031,3032],{},"Indicadores de escritura",": Animación mientras la IA \"piensa\"",[163,3035,3036,3039],{},[1585,3037,3038],{},"Auto-scroll",": Desplazamiento automático a mensajes nuevos",[163,3041,3042,3045],{},[1585,3043,3044],{},"Estados de error",": Muestra cuando algo falla",[153,3047,3048],{},"Sin este componente, habría que implementar manualmente toda la lógica de streaming, scroll y formateo de mensajes.",[621,3050,3052],{"id":3051},"código-de-la-página","Código de la página:",[217,3054,3058],{"className":3055,"code":3056,"language":3057,"meta":222,"style":222},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C!-- /app/pages/ia.vue -->\n\u003Cscript setup lang=\"ts\">\nimport { Chat } from \"@ai-sdk/vue\";\nimport type { UIMessage } from \"ai\";\nimport type { HoroscopeUIToolInvocation } from \"~~/shared/utils/tools/horoscopeTool\";\n\nconst messages: UIMessage[] = [];\nconst input = ref(\"\");\nconst chat = new Chat({ messages });\n\nconst onSubmit = (e: Event) => {\n  e.preventDefault();\n  const text = input.value.trim();\n  if (!text) return;\n\n  chat.sendMessage({ text });\n  input.value = \"\";\n};\n\u003C/script>\n\n\u003Ctemplate>\n  \u003Cdiv class=\"flex h-screen flex-col overflow-hidden pt-5\">\n    \u003CUChatMessages\n      :messages=\"chat.messages\"\n      :status=\"chat.status\"\n      class=\"flex-1 min-h-0 overflow-y-auto pb-6\"\n    >\n      \u003Ctemplate #content=\"{ message }\">\n        \u003Ctemplate\n          v-for=\"(part, index) in message.parts\"\n          :key=\"`${message.id}-${part.type}-${index}`\"\n        >\n          \u003C!-- Texto del asistente -->\n          \u003CMDC\n            v-if=\"part.type === 'text' && message.role === 'assistant'\"\n            :value=\"part.text\"\n          />\n\n          \u003C!-- Texto del usuario -->\n          \u003Cp\n            v-else-if=\"part.type === 'text' && message.role === 'user'\"\n            class=\"whitespace-pre-wrap\"\n          >\n            {{ part.text }}\n          \u003C/p>\n\n          \u003C!-- Horóscopo -->\n          \u003CToolsHoroscope\n            v-else-if=\"part.type === 'tool-horoscopeTool'\"\n            :invocation=\"part as HoroscopeUIToolInvocation\"\n          />\n        \u003C/template>\n      \u003C/template>\n    \u003C/UChatMessages>\n\n    \u003Cdiv class=\"sticky bottom-5 p-4\">\n      \u003CUChatPrompt\n        v-model=\"input\"\n        placeholder=\"Escribe tu mensaje\"\n        :error=\"chat.error\"\n        @submit=\"onSubmit\"\n      >\n        \u003CUChatPromptSubmit\n          :status=\"chat.status\"\n          @stop=\"chat.stop()\"\n          @reload=\"chat.regenerate()\"\n        />\n      \u003C/UChatPrompt>\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n","vue",[170,3059,3060,3065,3089,3111,3133,3156,3160,3179,3199,3225,3229,3254,3268,3293,3309,3313,3335,3351,3355,3364,3368,3377,3399,3407,3422,3436,3450,3455,3483,3491,3520,3565,3570,3575,3583,3597,3611,3616,3620,3625,3632,3646,3660,3665,3670,3679,3683,3688,3695,3708,3723,3728,3738,3748,3758,3763,3783,3791,3806,3821,3836,3851,3857,3865,3879,3894,3909,3915,3925,3934,3944],{"__ignoreMap":222},[226,3061,3062],{"class":228,"line":229},[226,3063,3064],{"class":1900},"\u003C!-- /app/pages/ia.vue -->\n",[226,3066,3067,3069,3072,3075,3078,3080,3082,3084,3086],{"class":228,"line":251},[226,3068,800],{"class":247},[226,3070,3071],{"class":254},"script",[226,3073,3074],{"class":393}," setup",[226,3076,3077],{"class":393}," lang",[226,3079,400],{"class":247},[226,3081,381],{"class":247},[226,3083,221],{"class":377},[226,3085,381],{"class":247},[226,3087,3088],{"class":247},">\n",[226,3090,3091,3093,3095,3098,3100,3102,3104,3107,3109],{"class":228,"line":263},[226,3092,350],{"class":232},[226,3094,356],{"class":247},[226,3096,3097],{"class":243}," Chat",[226,3099,368],{"class":247},[226,3101,371],{"class":232},[226,3103,374],{"class":247},[226,3105,3106],{"class":377},"@ai-sdk/vue",[226,3108,381],{"class":247},[226,3110,309],{"class":247},[226,3112,3113,3115,3117,3119,3121,3123,3125,3127,3129,3131],{"class":228,"line":273},[226,3114,350],{"class":232},[226,3116,353],{"class":232},[226,3118,356],{"class":247},[226,3120,1915],{"class":243},[226,3122,368],{"class":247},[226,3124,371],{"class":232},[226,3126,374],{"class":247},[226,3128,1934],{"class":377},[226,3130,381],{"class":247},[226,3132,309],{"class":247},[226,3134,3135,3137,3139,3141,3143,3145,3147,3149,3152,3154],{"class":228,"line":288},[226,3136,350],{"class":232},[226,3138,353],{"class":232},[226,3140,356],{"class":247},[226,3142,2432],{"class":243},[226,3144,368],{"class":247},[226,3146,371],{"class":232},[226,3148,374],{"class":247},[226,3150,3151],{"class":377},"~~/shared/utils/tools/horoscopeTool",[226,3153,381],{"class":247},[226,3155,309],{"class":247},[226,3157,3158],{"class":228,"line":294},[226,3159,388],{"emptyLinePlaceholder":87},[226,3161,3162,3164,3166,3168,3170,3172,3174,3177],{"class":228,"line":300},[226,3163,394],{"class":393},[226,3165,2135],{"class":243},[226,3167,215],{"class":247},[226,3169,1915],{"class":695},[226,3171,2149],{"class":243},[226,3173,400],{"class":247},[226,3175,3176],{"class":243}," []",[226,3178,309],{"class":247},[226,3180,3181,3183,3186,3188,3190,3192,3195,3197],{"class":228,"line":463},[226,3182,394],{"class":393},[226,3184,3185],{"class":243}," input ",[226,3187,400],{"class":247},[226,3189,847],{"class":239},[226,3191,244],{"class":243},[226,3193,3194],{"class":247},"\"\"",[226,3196,306],{"class":243},[226,3198,309],{"class":247},[226,3200,3201,3203,3206,3208,3210,3212,3214,3216,3219,3221,3223],{"class":228,"line":468},[226,3202,394],{"class":393},[226,3204,3205],{"class":243}," chat ",[226,3207,400],{"class":247},[226,3209,2031],{"class":247},[226,3211,3097],{"class":239},[226,3213,244],{"class":243},[226,3215,2067],{"class":247},[226,3217,3218],{"class":243}," messages ",[226,3220,303],{"class":247},[226,3222,306],{"class":243},[226,3224,309],{"class":247},[226,3226,3227],{"class":228,"line":482},[226,3228,388],{"emptyLinePlaceholder":87},[226,3230,3231,3233,3236,3238,3240,3243,3245,3248,3250,3252],{"class":228,"line":506},[226,3232,394],{"class":393},[226,3234,3235],{"class":243}," onSubmit ",[226,3237,400],{"class":247},[226,3239,881],{"class":247},[226,3241,3242],{"class":436},"e",[226,3244,215],{"class":247},[226,3246,3247],{"class":695}," Event",[226,3249,306],{"class":247},[226,3251,788],{"class":393},[226,3253,260],{"class":247},[226,3255,3256,3259,3261,3264,3266],{"class":228,"line":511},[226,3257,3258],{"class":243},"  e",[226,3260,173],{"class":247},[226,3262,3263],{"class":239},"preventDefault",[226,3265,1004],{"class":254},[226,3267,309],{"class":247},[226,3269,3270,3272,3275,3277,3280,3282,3284,3286,3289,3291],{"class":228,"line":530},[226,3271,839],{"class":393},[226,3273,3274],{"class":243}," text",[226,3276,782],{"class":247},[226,3278,3279],{"class":243}," input",[226,3281,173],{"class":247},[226,3283,1221],{"class":243},[226,3285,173],{"class":247},[226,3287,3288],{"class":239},"trim",[226,3290,1004],{"class":254},[226,3292,309],{"class":247},[226,3294,3295,3297,3299,3301,3303,3305,3307],{"class":228,"line":556},[226,3296,878],{"class":232},[226,3298,881],{"class":254},[226,3300,884],{"class":247},[226,3302,600],{"class":243},[226,3304,899],{"class":254},[226,3306,1195],{"class":232},[226,3308,309],{"class":247},[226,3310,3311],{"class":228,"line":561},[226,3312,388],{"emptyLinePlaceholder":87},[226,3314,3315,3318,3320,3323,3325,3327,3329,3331,3333],{"class":228,"line":580},[226,3316,3317],{"class":243},"  chat",[226,3319,173],{"class":247},[226,3321,3322],{"class":239},"sendMessage",[226,3324,244],{"class":254},[226,3326,2067],{"class":247},[226,3328,3274],{"class":243},[226,3330,368],{"class":247},[226,3332,306],{"class":254},[226,3334,309],{"class":247},[226,3336,3337,3340,3342,3344,3346,3349],{"class":228,"line":608},[226,3338,3339],{"class":243},"  input",[226,3341,173],{"class":247},[226,3343,1221],{"class":243},[226,3345,782],{"class":247},[226,3347,3348],{"class":247}," \"\"",[226,3350,309],{"class":247},[226,3352,3353],{"class":228,"line":613},[226,3354,2496],{"class":247},[226,3356,3357,3360,3362],{"class":228,"line":1098},[226,3358,3359],{"class":247},"\u003C/",[226,3361,3071],{"class":254},[226,3363,3088],{"class":247},[226,3365,3366],{"class":228,"line":1103},[226,3367,388],{"emptyLinePlaceholder":87},[226,3369,3370,3372,3375],{"class":228,"line":1149},[226,3371,800],{"class":247},[226,3373,3374],{"class":254},"template",[226,3376,3088],{"class":247},[226,3378,3379,3382,3385,3388,3390,3392,3395,3397],{"class":228,"line":1154},[226,3380,3381],{"class":247},"  \u003C",[226,3383,3384],{"class":254},"div",[226,3386,3387],{"class":393}," class",[226,3389,400],{"class":247},[226,3391,381],{"class":247},[226,3393,3394],{"class":377},"flex h-screen flex-col overflow-hidden pt-5",[226,3396,381],{"class":247},[226,3398,3088],{"class":247},[226,3400,3401,3404],{"class":228,"line":1181},[226,3402,3403],{"class":247},"    \u003C",[226,3405,3406],{"class":254},"UChatMessages\n",[226,3408,3409,3412,3414,3416,3419],{"class":228,"line":1200},[226,3410,3411],{"class":393},"      :messages",[226,3413,400],{"class":247},[226,3415,381],{"class":247},[226,3417,3418],{"class":377},"chat.messages",[226,3420,3421],{"class":247},"\"\n",[226,3423,3424,3427,3429,3431,3434],{"class":228,"line":1205},[226,3425,3426],{"class":393},"      :status",[226,3428,400],{"class":247},[226,3430,381],{"class":247},[226,3432,3433],{"class":377},"chat.status",[226,3435,3421],{"class":247},[226,3437,3438,3441,3443,3445,3448],{"class":228,"line":1228},[226,3439,3440],{"class":393},"      class",[226,3442,400],{"class":247},[226,3444,381],{"class":247},[226,3446,3447],{"class":377},"flex-1 min-h-0 overflow-y-auto pb-6",[226,3449,3421],{"class":247},[226,3451,3452],{"class":228,"line":1244},[226,3453,3454],{"class":247},"    >\n",[226,3456,3457,3460,3462,3465,3468,3470,3472,3474,3477,3479,3481],{"class":228,"line":1258},[226,3458,3459],{"class":247},"      \u003C",[226,3461,3374],{"class":254},[226,3463,3464],{"class":247}," #",[226,3466,3467],{"class":393},"content",[226,3469,400],{"class":247},[226,3471,381],{"class":247},[226,3473,2067],{"class":247},[226,3475,3476],{"class":243}," message ",[226,3478,303],{"class":247},[226,3480,381],{"class":247},[226,3482,3088],{"class":247},[226,3484,3485,3488],{"class":228,"line":1271},[226,3486,3487],{"class":247},"        \u003C",[226,3489,3490],{"class":254},"template\n",[226,3492,3493,3496,3498,3500,3503,3505,3508,3511,3513,3515,3518],{"class":228,"line":1277},[226,3494,3495],{"class":232},"          v-for",[226,3497,400],{"class":247},[226,3499,381],{"class":247},[226,3501,3502],{"class":243},"(part",[226,3504,362],{"class":247},[226,3506,3507],{"class":243}," index) ",[226,3509,3510],{"class":247},"in",[226,3512,573],{"class":243},[226,3514,173],{"class":247},[226,3516,3517],{"class":243},"parts",[226,3519,3421],{"class":247},[226,3521,3522,3525,3528,3530,3533,3535,3537,3540,3542,3545,3547,3550,3552,3554,3556,3558,3560,3562],{"class":228,"line":1283},[226,3523,3524],{"class":247},"          :",[226,3526,3527],{"class":393},"key",[226,3529,400],{"class":247},[226,3531,3532],{"class":247},"\"`${",[226,3534,639],{"class":243},[226,3536,173],{"class":247},[226,3538,3539],{"class":243},"id",[226,3541,303],{"class":247},[226,3543,3544],{"class":377},"-",[226,3546,1077],{"class":247},[226,3548,3549],{"class":243},"part",[226,3551,173],{"class":247},[226,3553,776],{"class":243},[226,3555,303],{"class":247},[226,3557,3544],{"class":377},[226,3559,1077],{"class":247},[226,3561,113],{"class":243},[226,3563,3564],{"class":247},"}`\"\n",[226,3566,3567],{"class":228,"line":1293},[226,3568,3569],{"class":247},"        >\n",[226,3571,3572],{"class":228,"line":1298},[226,3573,3574],{"class":1900},"          \u003C!-- Texto del asistente -->\n",[226,3576,3577,3580],{"class":228,"line":1323},[226,3578,3579],{"class":247},"          \u003C",[226,3581,3582],{"class":254},"MDC\n",[226,3584,3585,3588,3590,3592,3595],{"class":228,"line":1339},[226,3586,3587],{"class":393},"            v-if",[226,3589,400],{"class":247},[226,3591,381],{"class":247},[226,3593,3594],{"class":377},"part.type === 'text' && message.role === 'assistant'",[226,3596,3421],{"class":247},[226,3598,3599,3602,3604,3606,3609],{"class":228,"line":1353},[226,3600,3601],{"class":393},"            :value",[226,3603,400],{"class":247},[226,3605,381],{"class":247},[226,3607,3608],{"class":377},"part.text",[226,3610,3421],{"class":247},[226,3612,3613],{"class":228,"line":1362},[226,3614,3615],{"class":247},"          />\n",[226,3617,3618],{"class":228,"line":1367},[226,3619,388],{"emptyLinePlaceholder":87},[226,3621,3622],{"class":228,"line":1372},[226,3623,3624],{"class":1900},"          \u003C!-- Texto del usuario -->\n",[226,3626,3627,3629],{"class":228,"line":1380},[226,3628,3579],{"class":247},[226,3630,3631],{"class":254},"p\n",[226,3633,3634,3637,3639,3641,3644],{"class":228,"line":1388},[226,3635,3636],{"class":393},"            v-else-if",[226,3638,400],{"class":247},[226,3640,381],{"class":247},[226,3642,3643],{"class":377},"part.type === 'text' && message.role === 'user'",[226,3645,3421],{"class":247},[226,3647,3648,3651,3653,3655,3658],{"class":228,"line":1395},[226,3649,3650],{"class":393},"            class",[226,3652,400],{"class":247},[226,3654,381],{"class":247},[226,3656,3657],{"class":377},"whitespace-pre-wrap",[226,3659,3421],{"class":247},[226,3661,3662],{"class":228,"line":1403},[226,3663,3664],{"class":247},"          >\n",[226,3666,3667],{"class":228,"line":1409},[226,3668,3669],{"class":243},"            {{ part.text }}\n",[226,3671,3672,3675,3677],{"class":228,"line":2894},[226,3673,3674],{"class":247},"          \u003C/",[226,3676,153],{"class":254},[226,3678,3088],{"class":247},[226,3680,3681],{"class":228,"line":2911},[226,3682,388],{"emptyLinePlaceholder":87},[226,3684,3685],{"class":228,"line":2942},[226,3686,3687],{"class":1900},"          \u003C!-- Horóscopo -->\n",[226,3689,3690,3692],{"class":228,"line":2947},[226,3691,3579],{"class":247},[226,3693,3694],{"class":254},"ToolsHoroscope\n",[226,3696,3697,3699,3701,3703,3706],{"class":228,"line":2952},[226,3698,3636],{"class":393},[226,3700,400],{"class":247},[226,3702,381],{"class":247},[226,3704,3705],{"class":377},"part.type === 'tool-horoscopeTool'",[226,3707,3421],{"class":247},[226,3709,3711,3714,3716,3718,3721],{"class":228,"line":3710},50,[226,3712,3713],{"class":393},"            :invocation",[226,3715,400],{"class":247},[226,3717,381],{"class":247},[226,3719,3720],{"class":377},"part as HoroscopeUIToolInvocation",[226,3722,3421],{"class":247},[226,3724,3726],{"class":228,"line":3725},51,[226,3727,3615],{"class":247},[226,3729,3731,3734,3736],{"class":228,"line":3730},52,[226,3732,3733],{"class":247},"        \u003C/",[226,3735,3374],{"class":254},[226,3737,3088],{"class":247},[226,3739,3741,3744,3746],{"class":228,"line":3740},53,[226,3742,3743],{"class":247},"      \u003C/",[226,3745,3374],{"class":254},[226,3747,3088],{"class":247},[226,3749,3751,3754,3756],{"class":228,"line":3750},54,[226,3752,3753],{"class":247},"    \u003C/",[226,3755,3018],{"class":254},[226,3757,3088],{"class":247},[226,3759,3761],{"class":228,"line":3760},55,[226,3762,388],{"emptyLinePlaceholder":87},[226,3764,3766,3768,3770,3772,3774,3776,3779,3781],{"class":228,"line":3765},56,[226,3767,3403],{"class":247},[226,3769,3384],{"class":254},[226,3771,3387],{"class":393},[226,3773,400],{"class":247},[226,3775,381],{"class":247},[226,3777,3778],{"class":377},"sticky bottom-5 p-4",[226,3780,381],{"class":247},[226,3782,3088],{"class":247},[226,3784,3786,3788],{"class":228,"line":3785},57,[226,3787,3459],{"class":247},[226,3789,3790],{"class":254},"UChatPrompt\n",[226,3792,3794,3797,3799,3801,3804],{"class":228,"line":3793},58,[226,3795,3796],{"class":393},"        v-model",[226,3798,400],{"class":247},[226,3800,381],{"class":247},[226,3802,3803],{"class":377},"input",[226,3805,3421],{"class":247},[226,3807,3809,3812,3814,3816,3819],{"class":228,"line":3808},59,[226,3810,3811],{"class":393},"        placeholder",[226,3813,400],{"class":247},[226,3815,381],{"class":247},[226,3817,3818],{"class":377},"Escribe tu mensaje",[226,3820,3421],{"class":247},[226,3822,3824,3827,3829,3831,3834],{"class":228,"line":3823},60,[226,3825,3826],{"class":393},"        :error",[226,3828,400],{"class":247},[226,3830,381],{"class":247},[226,3832,3833],{"class":377},"chat.error",[226,3835,3421],{"class":247},[226,3837,3839,3842,3844,3846,3849],{"class":228,"line":3838},61,[226,3840,3841],{"class":393},"        @submit",[226,3843,400],{"class":247},[226,3845,381],{"class":247},[226,3847,3848],{"class":377},"onSubmit",[226,3850,3421],{"class":247},[226,3852,3854],{"class":228,"line":3853},62,[226,3855,3856],{"class":247},"      >\n",[226,3858,3860,3862],{"class":228,"line":3859},63,[226,3861,3487],{"class":247},[226,3863,3864],{"class":254},"UChatPromptSubmit\n",[226,3866,3868,3871,3873,3875,3877],{"class":228,"line":3867},64,[226,3869,3870],{"class":393},"          :status",[226,3872,400],{"class":247},[226,3874,381],{"class":247},[226,3876,3433],{"class":377},[226,3878,3421],{"class":247},[226,3880,3882,3885,3887,3889,3892],{"class":228,"line":3881},65,[226,3883,3884],{"class":393},"          @stop",[226,3886,400],{"class":247},[226,3888,381],{"class":247},[226,3890,3891],{"class":377},"chat.stop()",[226,3893,3421],{"class":247},[226,3895,3897,3900,3902,3904,3907],{"class":228,"line":3896},66,[226,3898,3899],{"class":393},"          @reload",[226,3901,400],{"class":247},[226,3903,381],{"class":247},[226,3905,3906],{"class":377},"chat.regenerate()",[226,3908,3421],{"class":247},[226,3910,3912],{"class":228,"line":3911},67,[226,3913,3914],{"class":247},"        />\n",[226,3916,3918,3920,3923],{"class":228,"line":3917},68,[226,3919,3743],{"class":247},[226,3921,3922],{"class":254},"UChatPrompt",[226,3924,3088],{"class":247},[226,3926,3928,3930,3932],{"class":228,"line":3927},69,[226,3929,3753],{"class":247},[226,3931,3384],{"class":254},[226,3933,3088],{"class":247},[226,3935,3937,3940,3942],{"class":228,"line":3936},70,[226,3938,3939],{"class":247},"  \u003C/",[226,3941,3384],{"class":254},[226,3943,3088],{"class":247},[226,3945,3947,3949,3951],{"class":228,"line":3946},71,[226,3948,3359],{"class":247},[226,3950,3374],{"class":254},[226,3952,3088],{"class":247},[621,3954,3956],{"id":3955},"el-componente-personalizado-del-horóscopo","El componente personalizado del horóscopo",[153,3958,3959,3960,3962,3963,3966],{},"Cuando la IA invoca ",[170,3961,2992],{},", el frontend recibe el resultado como una \"parte\" del mensaje con ",[170,3964,3965],{},"type: 'tool-horoscopeTool'",". Necesitamos un componente que renderice ese resultado:",[217,3968,3970],{"className":3055,"code":3969,"language":3057,"meta":222,"style":222},"\u003C!-- /app/components/Tools/Horoscope.vue -->\n\u003Cscript setup lang=\"ts\">\nimport type { HoroscopeUIToolInvocation } from \"~~/shared/utils/tools/horoscopeTool\";\n\nconst props = defineProps\u003C{\n  invocation: HoroscopeUIToolInvocation;\n}>();\n\u003C/script>\n\n\u003Ctemplate>\n  \u003C!-- Loading state -->\n  \u003Cdiv\n    v-if=\"invocation.state !== 'output-available'\"\n    class=\"my-5 rounded-xl bg-muted px-5 py-6 flex items-center justify-center\"\n  >\n    \u003CUIcon name=\"i-lucide-loader-circle\" class=\"size-6 animate-spin mr-2\" />\n    \u003Cspan class=\"text-sm\">Consultando los astros...\u003C/span>\n  \u003C/div>\n\n  \u003C!-- Result -->\n  \u003Cdiv v-else class=\"my-5 rounded-xl border border-default bg-elevated/40 p-5\">\n    \u003Cdiv class=\"flex items-center gap-2 mb-3\">\n      \u003CUIcon name=\"i-lucide-sparkles\" class=\"size-5 text-primary\" />\n      \u003Ch3 class=\"text-base font-semibold text-highlighted capitalize\">\n        {{ invocation.output.sign }}\n      \u003C/h3>\n      \u003Cspan class=\"text-xs text-muted ml-auto\">\n        {{ invocation.output.date }}\n      \u003C/span>\n    \u003C/div>\n    \u003Cp class=\"text-sm text-muted leading-relaxed\">\n      {{ invocation.output.horoscope }}\n    \u003C/p>\n  \u003C/div>\n\u003C/template>\n",[170,3971,3972,3977,3997,4019,4023,4038,4049,4058,4066,4070,4078,4083,4090,4104,4118,4123,4156,4184,4192,4196,4201,4223,4242,4271,4290,4295,4303,4322,4327,4335,4343,4362,4367,4375,4383],{"__ignoreMap":222},[226,3973,3974],{"class":228,"line":229},[226,3975,3976],{"class":1900},"\u003C!-- /app/components/Tools/Horoscope.vue -->\n",[226,3978,3979,3981,3983,3985,3987,3989,3991,3993,3995],{"class":228,"line":251},[226,3980,800],{"class":247},[226,3982,3071],{"class":254},[226,3984,3074],{"class":393},[226,3986,3077],{"class":393},[226,3988,400],{"class":247},[226,3990,381],{"class":247},[226,3992,221],{"class":377},[226,3994,381],{"class":247},[226,3996,3088],{"class":247},[226,3998,3999,4001,4003,4005,4007,4009,4011,4013,4015,4017],{"class":228,"line":263},[226,4000,350],{"class":232},[226,4002,353],{"class":232},[226,4004,356],{"class":247},[226,4006,2432],{"class":243},[226,4008,368],{"class":247},[226,4010,371],{"class":232},[226,4012,374],{"class":247},[226,4014,3151],{"class":377},[226,4016,381],{"class":247},[226,4018,309],{"class":247},[226,4020,4021],{"class":228,"line":273},[226,4022,388],{"emptyLinePlaceholder":87},[226,4024,4025,4027,4030,4032,4035],{"class":228,"line":288},[226,4026,394],{"class":393},[226,4028,4029],{"class":243}," props ",[226,4031,400],{"class":247},[226,4033,4034],{"class":239}," defineProps",[226,4036,4037],{"class":247},"\u003C{\n",[226,4039,4040,4043,4045,4047],{"class":228,"line":294},[226,4041,4042],{"class":254},"  invocation",[226,4044,215],{"class":247},[226,4046,2432],{"class":695},[226,4048,309],{"class":247},[226,4050,4051,4054,4056],{"class":228,"line":300},[226,4052,4053],{"class":247},"}>",[226,4055,1004],{"class":243},[226,4057,309],{"class":247},[226,4059,4060,4062,4064],{"class":228,"line":463},[226,4061,3359],{"class":247},[226,4063,3071],{"class":254},[226,4065,3088],{"class":247},[226,4067,4068],{"class":228,"line":468},[226,4069,388],{"emptyLinePlaceholder":87},[226,4071,4072,4074,4076],{"class":228,"line":482},[226,4073,800],{"class":247},[226,4075,3374],{"class":254},[226,4077,3088],{"class":247},[226,4079,4080],{"class":228,"line":506},[226,4081,4082],{"class":1900},"  \u003C!-- Loading state -->\n",[226,4084,4085,4087],{"class":228,"line":511},[226,4086,3381],{"class":247},[226,4088,4089],{"class":254},"div\n",[226,4091,4092,4095,4097,4099,4102],{"class":228,"line":530},[226,4093,4094],{"class":393},"    v-if",[226,4096,400],{"class":247},[226,4098,381],{"class":247},[226,4100,4101],{"class":377},"invocation.state !== 'output-available'",[226,4103,3421],{"class":247},[226,4105,4106,4109,4111,4113,4116],{"class":228,"line":556},[226,4107,4108],{"class":393},"    class",[226,4110,400],{"class":247},[226,4112,381],{"class":247},[226,4114,4115],{"class":377},"my-5 rounded-xl bg-muted px-5 py-6 flex items-center justify-center",[226,4117,3421],{"class":247},[226,4119,4120],{"class":228,"line":561},[226,4121,4122],{"class":247},"  >\n",[226,4124,4125,4127,4130,4133,4135,4137,4140,4142,4144,4146,4148,4151,4153],{"class":228,"line":580},[226,4126,3403],{"class":247},[226,4128,4129],{"class":254},"UIcon",[226,4131,4132],{"class":393}," name",[226,4134,400],{"class":247},[226,4136,381],{"class":247},[226,4138,4139],{"class":377},"i-lucide-loader-circle",[226,4141,381],{"class":247},[226,4143,3387],{"class":393},[226,4145,400],{"class":247},[226,4147,381],{"class":247},[226,4149,4150],{"class":377},"size-6 animate-spin mr-2",[226,4152,381],{"class":247},[226,4154,4155],{"class":247}," />\n",[226,4157,4158,4160,4162,4164,4166,4168,4171,4173,4175,4178,4180,4182],{"class":228,"line":608},[226,4159,3403],{"class":247},[226,4161,226],{"class":254},[226,4163,3387],{"class":393},[226,4165,400],{"class":247},[226,4167,381],{"class":247},[226,4169,4170],{"class":377},"text-sm",[226,4172,381],{"class":247},[226,4174,860],{"class":247},[226,4176,4177],{"class":243},"Consultando los astros...",[226,4179,3359],{"class":247},[226,4181,226],{"class":254},[226,4183,3088],{"class":247},[226,4185,4186,4188,4190],{"class":228,"line":613},[226,4187,3939],{"class":247},[226,4189,3384],{"class":254},[226,4191,3088],{"class":247},[226,4193,4194],{"class":228,"line":1098},[226,4195,388],{"emptyLinePlaceholder":87},[226,4197,4198],{"class":228,"line":1103},[226,4199,4200],{"class":1900},"  \u003C!-- Result -->\n",[226,4202,4203,4205,4207,4210,4212,4214,4216,4219,4221],{"class":228,"line":1149},[226,4204,3381],{"class":247},[226,4206,3384],{"class":254},[226,4208,4209],{"class":393}," v-else",[226,4211,3387],{"class":393},[226,4213,400],{"class":247},[226,4215,381],{"class":247},[226,4217,4218],{"class":377},"my-5 rounded-xl border border-default bg-elevated/40 p-5",[226,4220,381],{"class":247},[226,4222,3088],{"class":247},[226,4224,4225,4227,4229,4231,4233,4235,4238,4240],{"class":228,"line":1154},[226,4226,3403],{"class":247},[226,4228,3384],{"class":254},[226,4230,3387],{"class":393},[226,4232,400],{"class":247},[226,4234,381],{"class":247},[226,4236,4237],{"class":377},"flex items-center gap-2 mb-3",[226,4239,381],{"class":247},[226,4241,3088],{"class":247},[226,4243,4244,4246,4248,4250,4252,4254,4256,4258,4260,4262,4264,4267,4269],{"class":228,"line":1181},[226,4245,3459],{"class":247},[226,4247,4129],{"class":254},[226,4249,4132],{"class":393},[226,4251,400],{"class":247},[226,4253,381],{"class":247},[226,4255,34],{"class":377},[226,4257,381],{"class":247},[226,4259,3387],{"class":393},[226,4261,400],{"class":247},[226,4263,381],{"class":247},[226,4265,4266],{"class":377},"size-5 text-primary",[226,4268,381],{"class":247},[226,4270,4155],{"class":247},[226,4272,4273,4275,4277,4279,4281,4283,4286,4288],{"class":228,"line":1200},[226,4274,3459],{"class":247},[226,4276,621],{"class":254},[226,4278,3387],{"class":393},[226,4280,400],{"class":247},[226,4282,381],{"class":247},[226,4284,4285],{"class":377},"text-base font-semibold text-highlighted capitalize",[226,4287,381],{"class":247},[226,4289,3088],{"class":247},[226,4291,4292],{"class":228,"line":1205},[226,4293,4294],{"class":243},"        {{ invocation.output.sign }}\n",[226,4296,4297,4299,4301],{"class":228,"line":1228},[226,4298,3743],{"class":247},[226,4300,621],{"class":254},[226,4302,3088],{"class":247},[226,4304,4305,4307,4309,4311,4313,4315,4318,4320],{"class":228,"line":1244},[226,4306,3459],{"class":247},[226,4308,226],{"class":254},[226,4310,3387],{"class":393},[226,4312,400],{"class":247},[226,4314,381],{"class":247},[226,4316,4317],{"class":377},"text-xs text-muted ml-auto",[226,4319,381],{"class":247},[226,4321,3088],{"class":247},[226,4323,4324],{"class":228,"line":1258},[226,4325,4326],{"class":243},"        {{ invocation.output.date }}\n",[226,4328,4329,4331,4333],{"class":228,"line":1271},[226,4330,3743],{"class":247},[226,4332,226],{"class":254},[226,4334,3088],{"class":247},[226,4336,4337,4339,4341],{"class":228,"line":1277},[226,4338,3753],{"class":247},[226,4340,3384],{"class":254},[226,4342,3088],{"class":247},[226,4344,4345,4347,4349,4351,4353,4355,4358,4360],{"class":228,"line":1283},[226,4346,3403],{"class":247},[226,4348,153],{"class":254},[226,4350,3387],{"class":393},[226,4352,400],{"class":247},[226,4354,381],{"class":247},[226,4356,4357],{"class":377},"text-sm text-muted leading-relaxed",[226,4359,381],{"class":247},[226,4361,3088],{"class":247},[226,4363,4364],{"class":228,"line":1293},[226,4365,4366],{"class":243},"      {{ invocation.output.horoscope }}\n",[226,4368,4369,4371,4373],{"class":228,"line":1298},[226,4370,3753],{"class":247},[226,4372,153],{"class":254},[226,4374,3088],{"class":247},[226,4376,4377,4379,4381],{"class":228,"line":1323},[226,4378,3939],{"class":247},[226,4380,3384],{"class":254},[226,4382,3088],{"class":247},[226,4384,4385,4387,4389],{"class":228,"line":1339},[226,4386,3359],{"class":247},[226,4388,3374],{"class":254},[226,4390,3088],{"class":247},[153,4392,4393],{},"El componente maneja dos estados:",[186,4395,4396,4402],{},[163,4397,4398,4401],{},[1585,4399,4400],{},"Cargando",": Mientras la herramienta se ejecuta, muestra un spinner con el mensaje \"Consultando los astros...\"",[163,4403,4404,4407],{},[1585,4405,4406],{},"Resultado",": Cuando la respuesta llega, muestra una card con el signo, la fecha y el texto del horóscopo",[621,4409,4411,4412,2307],{"id":4410},"qué-hace-la-clase-chat","¿Qué hace la clase ",[170,4413,4414],{},"Chat",[153,4416,4417,4418,4420],{},"El SDK de Vue proporciona una clase ",[170,4419,4414],{}," que maneja todo el estado:",[186,4422,4423,4430,4437,4445,4452],{},[163,4424,4425,4429],{},[1585,4426,4427],{},[170,4428,3418],{},": Array con el historial de conversación",[163,4431,4432,4436],{},[1585,4433,4434],{},[170,4435,3433],{},": Estado actual ('ready', 'streaming', etc.)",[163,4438,4439,4444],{},[1585,4440,4441],{},[170,4442,4443],{},"chat.sendMessage()",": Envía un nuevo mensaje",[163,4446,4447,4451],{},[1585,4448,4449],{},[170,4450,3891],{},": Detiene la generación",[163,4453,4454,4458],{},[1585,4455,4456],{},[170,4457,3906],{},": Reintenta la última respuesta",[621,4460,4462],{"id":4461},"resumen-de-componentes-nuxt-ui-utilizados","Resumen de componentes Nuxt UI utilizados:",[4464,4465,4466,4479],"table",{},[4467,4468,4469],"thead",{},[4470,4471,4472,4476],"tr",{},[4473,4474,4475],"th",{},"Componente",[4473,4477,4478],{},"Función",[4480,4481,4482,4492,4501],"tbody",{},[4470,4483,4484,4489],{},[4485,4486,4487],"td",{},[170,4488,3018],{},[4485,4490,4491],{},"Lista de mensajes con auto-scroll",[4470,4493,4494,4498],{},[4485,4495,4496],{},[170,4497,3922],{},[4485,4499,4500],{},"Input de texto con botón de acción",[4470,4502,4503,4508],{},[4485,4504,4505],{},[170,4506,4507],{},"UChatPromptSubmit",[4485,4509,4510],{},"Botón que cambia según el estado",[153,4512,4513],{},"Gracias a Nuxt UI, se puede implementar toda la interfaz en menos de 100 líneas de código, sin preocuparse por animaciones, scroll o estados de carga.",[175,4515],{},[178,4517,4519],{"id":4518},"flujo-de-funcionamiento","Flujo de Funcionamiento",[160,4521,4522,4528,4537,4543,4554,4560],{},[163,4523,4524,4527],{},[1585,4525,4526],{},"Usuario escribe",": \"¿Cuál es mi horóscopo? Soy Aries\"",[163,4529,4530,4533,4534],{},[1585,4531,4532],{},"Frontend envía",": El mensaje va al endpoint ",[170,4535,4536],{},"/api/chat",[163,4538,4539,4542],{},[1585,4540,4541],{},"IA procesa",": Claude recibe el mensaje y decide qué hacer",[163,4544,4545,4548,4549,2993,4551],{},[1585,4546,4547],{},"Tool invocation",": La IA decide llamar a ",[170,4550,2992],{},[170,4552,4553],{},"zodiac: \"aries\"",[163,4555,4556,4559],{},[1585,4557,4558],{},"API externa",": La herramienta consulta API Ninjas y devuelve el horóscopo",[163,4561,4562,4565],{},[1585,4563,4564],{},"Respuesta al usuario",": El frontend recibe el resultado y renderiza la card del horóscopo",[175,4567],{},[178,4569,1685],{"id":1684},[153,4571,4572],{},"Implementar un chat con IA en Nuxt usando Vercel AI Gateway es sorprendentemente sencillo:",[160,4574,4575,4581,4587,4593],{},[163,4576,4577,4580],{},[1585,4578,4579],{},"Configuras el gateway"," una vez",[163,4582,4583,4586],{},[1585,4584,4585],{},"Defines herramientas"," para las acciones que necesitas",[163,4588,4589,4592],{},[1585,4590,4591],{},"Creas componentes Vue"," para renderizar los resultados",[163,4594,4595,4598],{},[1585,4596,4597],{},"Usas los componentes de Nuxt UI"," que ya manejan el streaming",[153,4600,4601,4602,4605],{},"El resultado es un asistente inteligente que no solo conversa, ",[1585,4603,4604],{},"actúa",": consulta APIs externas, muestra resultados con componentes personalizados y mantiene una conversación fluida gracias al streaming. A partir de aquí, podés agregar más herramientas (gráficos, envío de emails, exportación de archivos, etc.). La posibilidades son infinitas.",[1709,4607,4608],{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":222,"searchDepth":251,"depth":251,"links":4610},[4611,4614,4615,4616,4623,4627,4635,4636],{"id":1747,"depth":251,"text":1748,"children":4612},[4613],{"id":1758,"depth":263,"text":1759},{"id":1800,"depth":251,"text":1801},{"id":1847,"depth":251,"text":1848},{"id":1880,"depth":251,"text":1881,"children":4617},[4618,4619,4621],{"id":1887,"depth":263,"text":1888},{"id":2302,"depth":263,"text":4620},"¿Por qué streamText?",{"id":2316,"depth":263,"text":4622},"¿Qué hace stopWhen: stepCountIs(8)?",{"id":2328,"depth":251,"text":2329,"children":4624},[4625,4626],{"id":2339,"depth":263,"text":2340},{"id":2961,"depth":263,"text":2962},{"id":3001,"depth":251,"text":3002,"children":4628},[4629,4630,4631,4632,4634],{"id":3012,"depth":263,"text":3013},{"id":3051,"depth":263,"text":3052},{"id":3955,"depth":263,"text":3956},{"id":4410,"depth":263,"text":4633},"¿Qué hace la clase Chat?",{"id":4461,"depth":263,"text":4462},{"id":4518,"depth":251,"text":4519},{"id":1684,"depth":251,"text":1685},"15 de diciembre de 2025","Este tutorial te muestra cómo implementar un asistente de IA en Nuxt 4 usando Vercel AI SDK y Vercel AI Gateway. Si estás pensando en añadir inteligencia artificial a tu proyecto, esta guía te dará una visión general clara de cómo hacerlo.","/images/blog/ai-gateway.png",{},"/blog/tutorial-vercel-ai-gateway-nuxt",{"title":1742,"description":4638},"blog/tutorial-vercel-ai-gateway-nuxt","nT4ASE0zB1dDliu_DK2QYAS6VwWxVd5kYSr6xX4h5qE",{"data":4646,"body":4647},{},{"type":4648,"children":4649},"root",[4650],{"type":4651,"tag":153,"props":4652,"children":4653},"element",{},[4654],{"type":600,"value":5},1776969695150]