Pylarion Logo Pylarion Logo Pylarion
DESARROLLO WEB
15 MIN READ

Construyendo una aplicación RAG con Nuxt y Gemini File Search

Pylarion Pylarion

Pylarion

Equipo de Pylarion. Construimos software confiable donde la tecnología no puede fallar. • 19 de junio de 2026

Construyendo una aplicación RAG con Nuxt y Gemini File Search

Construyendo una Aplicación RAG con Nuxt y Gemini File Search: Tutorial Técnico Completo

La Generación Aumentada por Recuperación, conocida por sus siglas en inglés como RAG (Retrieval-Augmented Generation), representa uno de los paradigmas más relevantes en el desarrollo de aplicaciones modernas de inteligencia artificial. A diferencia de los modelos de lenguaje que operan únicamente sobre su conocimiento preentrenado, una arquitectura RAG permite enriquecer las respuestas del modelo con información externa, actualizada y específica del dominio. En este tutorial técnico se abordará la construcción de una aplicación RAG funcional utilizando el framework Nuxt junto con la API Gemini File Search de Google, cubriendo desde la configuración inicial del entorno hasta la implementación de una interfaz de usuario interactiva.

El interés por las arquitecturas RAG ha crecido exponencialmente en los últimos años, impulsado por la necesidad empresarial de combinar el poder generativo de los grandes modelos de lenguaje con fuentes de datos propias y verificables. Plataformas como Google, con su API Gemini, han democratizado el acceso a estas capacidades al proporcionar herramientas nativas para la indexación, recuperación semántica y generación de respuestas fundamentadas. Nuxt, por su parte, ofrece un entorno full-stack basado en Vue.js que facilita la creación tanto de backends robustos como de interfaces modernas, convirtiéndolo en una elección estratégica para este tipo de proyectos.

¿Qué es RAG y por qué es relevante?

RAG es una técnica que combina dos fases bien diferenciadas: la recuperación de fragmentos de información relevante desde una base de conocimiento indexada, y la generación de una respuesta coherente y precisa por parte de un modelo de lenguaje que recibe dichos fragmentos como contexto adicional. Este enfoque resuelve uno de los problemas fundamentales de los LLM puros: las alucinaciones. Al anclar las respuestas en documentos reales, se incrementa significativamente la fiabilidad y la trazabilidad del sistema.

Desde una perspectiva técnica, el flujo básico de RAG puede descomponerse en los siguientes pasos conceptuales:

  1. Indexación: Los documentos de origen se fragmentan (chunking) y se representan como vectores en un espacio semántico mediante modelos de embeddings.
  2. Recuperación: Ante una consulta del usuario, el sistema calcula la similitud semántica entre la consulta y los fragmentos indexados, devolviendo los más relevantes.
  3. Generación: El modelo de lenguaje recibe la consulta original junto con los fragmentos recuperados como contexto, produciendo una respuesta precisa y fundamentada.
  4. Presentación: La respuesta se entrega al usuario, idealmente con referencias a las fuentes utilizadas.

Gemini File Search automatiza y optimiza las dos primeras etapas de este flujo, gestionando el almacenamiento vectorial y la recuperación semántica de manera transparente para el desarrollador. Esto reduce considerablemente la complejidad de infraestructura que históricamente requería una implementación RAG, como la gestión de bases de datos vectoriales (Pinecone, Weaviate, Chroma) o la orquestación manual de embeddings.

Configuración inicial del proyecto Nuxt

El primer paso consiste en inicializar un proyecto Nuxt en su versión más reciente, asegurándose de habilitar el modo full-stack que permite tanto la renderización en el servidor como la creación de endpoints de API mediante el directorio server/. La instalación se realiza con el siguiente comando:

npx nuxi@latest init nuxt-rag-app
cd nuxt-rag-app
npm install

A continuación, es necesario instalar las dependencias clave del proyecto. La librería oficial de Google para IA generativa es el núcleo de la integración con Gemini, mientras que otras utilidades complementan el procesamiento de documentos y la gestión del entorno:

npm install @google/generative-ai
npm install @google/genai
npm install dotenv

La gestión de variables de entorno es un aspecto crítico de seguridad en cualquier aplicación que consuma APIs externas. En Nuxt, las variables sensibles como la clave de API de Google deben configurarse en el archivo .env en la raíz del proyecto y referenciarse a través del runtime config del framework, garantizando que nunca queden expuestas en el lado del cliente:

# .env
GOOGLE_API_KEY=tu_clave_api_aqui
GEMINI_MODEL=gemini-1.5-pro

El archivo nuxt.config.ts debe actualizarse para exponer estas variables exclusivamente en el servidor, utilizando la propiedad runtimeConfig:

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    googleApiKey: process.env.GOOGLE_API_KEY,
    geminiModel: process.env.GEMINI_MODEL,
    public: {
      // Variables accesibles desde el cliente (no secretas)
    }
  }
})

Arquitectura del backend: utilidades y endpoints

La capa de servidor en Nuxt se organiza dentro del directorio server/, que Nitro (el motor de servidor subyacente) procesa automáticamente. Para mantener una arquitectura limpia y reutilizable, se recomienda separar la lógica de negocio en archivos de utilidades ubicados en server/utils/, mientras que los endpoints concretos residen en server/api/. Esta separación favorece la cohesión y facilita las pruebas unitarias de cada componente de forma independiente.

La utilidad principal de conexión con Gemini inicializa el cliente de Google Generative AI y expone funciones atómicas para las operaciones fundamentales del sistema RAG. Esta capa de abstracción desacopla la implementación específica de la API del resto de la lógica de la aplicación:

// server/utils/gemini.ts
import { GoogleGenAI } from '@google/genai'

let client: GoogleGenAI | null = null

export function getGeminiClient(): GoogleGenAI {
  const config = useRuntimeConfig()
  if (!client) {
    client = new GoogleGenAI({ apiKey: config.googleApiKey })
  }
  return client
}

export async function createFileIndex(content: string, displayName: string) {
  const genai = getGeminiClient()
  // Crear un corpus o datastore para indexar el documento
  const file = await genai.files.upload({
    file: new Blob([content], { type: 'text/plain' }),
    config: { displayName }
  })
  return file
}

export async function queryWithRAG(
  query: string,
  fileUris: string[],
  modelName: string
) {
  const genai = getGeminiClient()
  const model = genai.getGenerativeModel({ model: modelName })
  
  const contents = [
    {
      role: 'user',
      parts: [
        ...fileUris.map(uri => ({ fileData: { fileUri: uri, mimeType: 'text/plain' } })),
        { text: query }
      ]
    }
  ]
  
  const result = await model.generateContent({ contents })
  return result.response.text()
}

Con las utilidades base definidas, el siguiente paso es construir los endpoints de la API. Se necesitan al menos tres rutas: una para la indexación de documentos, una para consultar el almacén de datos existente y otra para realizar preguntas al sistema RAG. A continuación se muestra la implementación del endpoint de indexación:

// server/api/index-document.post.ts
import { createFileIndex } from '~/server/utils/gemini'

export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  const { content, displayName } = body

  if (!content || !displayName) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Se requieren los campos content y displayName'
    })
  }

  try {
    const indexedFile = await createFileIndex(content, displayName)
    return {
      success: true,
      fileUri: indexedFile.uri,
      name: indexedFile.name,
      displayName: indexedFile.displayName,
      state: indexedFile.state
    }
  } catch (error: any) {
    throw createError({
      statusCode: 500,
      statusMessage: `Error al indexar documento: ${error.message}`
    })
  }
})

El endpoint de consulta RAG recibe la pregunta del usuario junto con los identificadores de los archivos previamente indexados, invoca la función de generación con contexto y devuelve la respuesta estructurada al cliente. Es importante implementar validaciones robustas en estos puntos de entrada para garantizar la integridad de las peticiones:

// server/api/query.post.ts
import { queryWithRAG } from '~/server/utils/gemini'

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig()
  const body = await readBody(event)
  const { query, fileUris } = body

  if (!query || !fileUris?.length) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Se requieren query y al menos un fileUri'
    })
  }

  try {
    const answer = await queryWithRAG(
      query,
      fileUris,
      config.geminiModel || 'gemini-1.5-pro'
    )
    return {
      success: true,
      query,
      answer,
      sourcesUsed: fileUris.length
    }
  } catch (error: any) {
    throw createError({
      statusCode: 500,
      statusMessage: `Error en la consulta RAG: ${error.message}`
    })
  }
})

Gestión del almacén de datos y estado del sistema

Un aspecto frecuentemente subestimado en las implementaciones RAG es la gestión del ciclo de vida de los documentos indexados. Gemini File API proporciona endpoints para listar, inspeccionar y eliminar archivos, funcionalidades esenciales para mantener el almacén de datos limpio y actualizado. En el contexto de Nuxt, se puede implementar un endpoint adicional que exponga el estado actual de todos los archivos indexados:

// server/api/files.get.ts
import { getGeminiClient } from '~/server/utils/gemini'

export default defineEventHandler(async () => {
  const genai = getGeminiClient()
  
  try {
    const filesResponse = await genai.files.list()
    const files = []
    
    for await (const file of filesResponse) {
      files.push({
        name: file.name,
        displayName: file.displayName,
        uri: file.uri,
        state: file.state,
        createTime: file.createTime,
        expirationTime: file.expirationTime,
        sizeBytes: file.sizeBytes
      })
    }
    
    return { success: true, files, total: files.length }
  } catch (error: any) {
    throw createError({
      statusCode: 500,
      statusMessage: `Error al obtener archivos: ${error.message}`
    })
  }
})

Es importante considerar que los archivos subidos a través de la API de Gemini tienen una vida útil limitada (actualmente 48 horas en el nivel gratuito), lo que hace necesario implementar estrategias de persistencia en producción. Para entornos empresariales, se recomienda almacenar los URIs de los archivos en una base de datos propia y gestionar proactivamente la reindexación antes del vencimiento, o evaluar las opciones de almacenamiento persistente disponibles en Google AI Studio para workloads de producción.

Implementación de la interfaz de usuario con Nuxt y Vue

La capa de presentación de la aplicación se construye aprovechando el sistema de componentes reactivos de Vue 3 y las capacidades de composición de Nuxt. La interfaz se organiza en torno a tres áreas funcionales principales: un panel de carga de documentos para la indexación, un visualizador del almacén de datos que muestra los archivos activos y sus estados, y un panel de consulta donde el usuario puede realizar preguntas al sistema RAG y visualizar las respuestas generadas.

<!-- pages/index.vue -->
<template>
  <div class="rag-app">
    <!-- Panel de indexación -->
    <section class="indexing-panel">
      <h2>Indexar Documento</h2>
      <input v-model="docName" placeholder="Nombre del documento" />
      <textarea v-model="docContent" placeholder="Contenido a indexar..." />
      <button @click="indexDocument" :disabled="isIndexing">
        {{ isIndexing ? 'Indexando...' : 'Indexar Documento' }}
      </button>
      <p v-if="indexStatus" class="status">{{ indexStatus }}</p>
    </section>

    <!-- Visualizador de archivos -->
    <section class="files-panel">
      <h2>Almacén de Datos</h2>
      <button @click="loadFiles">Actualizar</button>
      <ul>
        <li v-for="file in files" :key="file.name">
          <label>
            <input
              type="checkbox"
              :value="file.uri"
              v-model="selectedUris"
            />
            {{ file.displayName }} — Estado: {{ file.state }}
          </label>
        </li>
      </ul>
    </section>

    <!-- Panel de consulta -->
    <section class="query-panel">
      <h2>Consultar Sistema RAG</h2>
      <textarea v-model="userQuery" placeholder="Escribe tu pregunta..." />
      <button @click="submitQuery" :disabled="isQuerying || !selectedUris.length">
        {{ isQuerying ? 'Generando respuesta...' : 'Consultar' }}
      </button>
      <div v-if="ragAnswer" class="answer-box">
        <h3>Respuesta:</h3>
        <p>{{ ragAnswer }}</p>
      </div>
    </section>
  </div>
</template>

<script setup lang="ts">
const docName = ref('')
const docContent = ref('')
const userQuery = ref('')
const files = ref([])
const selectedUris = ref([])
const ragAnswer = ref('')
const isIndexing = ref(false)
const isQuerying = ref(false)
const indexStatus = ref('')

async function indexDocument() {
  isIndexing.value = true
  indexStatus.value = ''
  try {
    const result = await $fetch('/api/index-document', {
      method: 'POST',
      body: { content: docContent.value, displayName: docName.value }
    })
    indexStatus.value = `✓ Documento indexado: ${result.displayName} (${result.state})`
    await loadFiles()
  } catch (e: any) {
    indexStatus.value = `✗ Error: ${e.message}`
  } finally {
    isIndexing.value = false
  }
}

async function loadFiles() {
  const result = await $fetch('/api/files')
  files.value = result.files
}

async function submitQuery() {
  isQuerying.value = true
  ragAnswer.value = ''
  try {
    const result = await $fetch('/api/query', {
      method: 'POST',
      body: { query: userQuery.value, fileUris: selectedUris.value }
    })
    ragAnswer.value = result.answer
  } finally {
    isQuerying.value = false
  }
}

onMounted(loadFiles)
</script>

La reactividad de Vue 3 simplifica enormemente la gestión de estados asincrónicos en la interfaz. Los flags isIndexing e isQuerying controlan el estado de los botones y proporcionan retroalimentación visual inmediata al usuario durante las operaciones que pueden tardar varios segundos, como la indexación de documentos grandes o la generación de respuestas complejas. La selección múltiple de archivos mediante checkboxes permite al usuario acotar el contexto de recuperación a subconjuntos específicos de la base de conocimiento.

Consideraciones de producción y mejores prácticas

Una vez validado el flujo básico en entorno de desarrollo, existen varios aspectos técnicos que deben abordarse antes de desplegar la aplicación en producción. La gestión de errores debe ser exhaustiva y contemplar no solo los errores de red o de validación de datos, sino también los estados transitorios de la API de Gemini, como archivos en estado PROCESSING que aún no están listos para ser consultados. Se recomienda implementar un mecanismo de polling o webhooks para verificar el estado de los archivos tras la indexación.

"Las arquitecturas RAG no son una solución universal; su efectividad depende directamente de la calidad de los datos indexados, la granularidad del chunking y la relevancia semántica de los fragmentos recuperados como contexto."

Entre las mejores prácticas identificadas para implementaciones RAG robustas en producción, destacan las siguientes recomendaciones:

  • Chunking estratégico: Dividir documentos extensos en fragmentos de entre 512 y 1024 tokens con solapamiento para preservar el contexto entre segmentos adyacentes.
  • Metadatos enriquecidos: Almacenar junto a cada chunk metadatos como la fuente original, la fecha de indexación y la sección del documento para facilitar la trazabilidad de las respuestas.
  • Límites de tasa: Implementar rate limiting en los endpoints de la API para evitar abusos y controlar el consumo de cuota de la API de Google.
  • Caché de respuestas: Para consultas frecuentes y predecibles, implementar una capa de caché (Redis, Memcached) que evite llamadas redundantes a la API generativa.
  • Monitorización de calidad: Registrar métricas de relevancia de las respuestas mediante técnicas de evaluación automática como RAGAS para detectar degradaciones en la calidad del sistema.
  • Seguridad de datos: Para documentos sensibles, evaluar soluciones on-premise o servicios con acuerdos de procesamiento de datos (DPA) antes de enviarlos a APIs externas.

La arquitectura presentada en este tutorial constituye una base sólida y extensible sobre la que construir sistemas RAG más complejos. La combinación de Nuxt como framework full-stack y Gemini File Search como capa de inteligencia artificial ofrece un equilibrio óptimo entre productividad de desarrollo, flexibilidad arquitectónica y potencia generativa. A medida que la API de Gemini continúa evolucionando con nuevas capacidades como el soporte nativo para múltiples modalidades y contextos de mayor longitud, las posibilidades de este tipo de aplicaciones seguirán expandiéndose, abriendo la puerta a sistemas de recuperación de conocimiento cada vez más sofisticados y precisos.

5
Pylarion Pylarion

Pylarion

Desarrollo Web Expert

Equipo de Pylarion. Construimos software confiable donde la tecnología no puede fallar.

Blog / Desarrollo Web / Construyendo una aplicación RAG con Nuxt y Gemini File Search

Comentarios

(0)
Categoría: Desarrollo Web

Artículos Recomendados

Creando un editor colaborativo con Nuxt UI, Jazz e IA
Desarrollo Web
3
0
0

Creando un editor colaborativo con Nuxt UI, Jazz e IA

El autor comparte su experiencia desarrollando un editor de texto enriquecido colaborativo en tiempo real utilizando Nuxt UI y Jazz, asistido en gran medida por inteligencia artificial. Inicialmente planeado como un tutorial paso a paso, el artículo evoluciona hacia una reflexión sobre cómo la IA facilita la programación, una práctica conocida como vibe coding. Se detalla la motivación de construir una solución propia dentro del ecosistema Nuxt para mantener el control completo del código, superando así las limitaciones de varias opciones prefabricadas. El proyecto resultante incluye características clave como soporte multiusuario, comentarios contextuales estilo Notion, carga de imágenes y resolución de respuestas. Además de proporcionar el código base de código abierto para que la comunidad lo clone, el autor ofrece importantes lecciones sobre el desarrollo guiado por agentes de IA, destacando la necesidad de forzarlos a verificar iterativamente su trabajo mediante el navegador.

Leer artículo
Gestión de Estado en Vue: Composables, Provide/Inject y Pinia
Desarrollo Web
2
0
0

Gestión de Estado en Vue: Composables, Provide/Inject y Pinia

Esta guía práctica explica cómo elegir la herramienta adecuada para compartir estado y lógica en aplicaciones Vue.js, evitando arquitecturas complejas. Destaca tres enfoques clave: Los Composables, que utilizan la Composition API para encapsular y reutilizar comportamiento donde cada consumidor obtiene su propio estado independiente, ideales para lógica local como validaciones o fetch de datos. Provide/Inject resulta útil para pasar o inyectar estado a lo largo de un árbol de componentes descendente sin necesidad de recurrir al tedioso paso de props múltiples ('prop drilling'). Por último, Pinia es la solución estándar recomendada para gestionar el estado global y centralizado de la aplicación (como sesiones de usuario o datos del carrito de compras), ya que vive fuera de la jerarquía de los componentes y ofrece integración completa con Devtools.

Leer artículo