diff --git a/RAG/Dockerfile b/RAG/Dockerfile
index d20a6b5..df49f83 100644
--- a/RAG/Dockerfile
+++ b/RAG/Dockerfile
@@ -3,6 +3,7 @@ WORKDIR /app
COPY package.json package-lock.json tsconfig.json ./
RUN npm ci
COPY src ./src
+COPY public ./public
RUN npm run build
FROM node:22-bookworm-slim AS runtime
@@ -11,5 +12,6 @@ ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
+COPY public ./public
EXPOSE 3000
CMD ["node", "dist/server.js"]
diff --git a/RAG/docs/API_RAG.md b/RAG/docs/API_RAG.md
new file mode 100644
index 0000000..bc52c10
--- /dev/null
+++ b/RAG/docs/API_RAG.md
@@ -0,0 +1,392 @@
+# API del RAG
+
+**Proyecto:** Workspace de tools IA para empresas
+**Modulo:** RAG
+**Ultima actualizacion:** 2026-04-05
+**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
+**Estado:** Operativa en VPS2
+
+---
+
+## Proposito
+
+Dejar una referencia rapida y practica de la API del servicio `RAG` para poder conectarlo facilmente desde modelos, agentes, flujos de `n8n` y otras aplicaciones.
+
+---
+
+## Base URL actual
+
+Produccion actual en `VPS2`:
+
+```text
+https://rag.por-correo.com
+```
+
+---
+
+## Endpoints disponibles
+
+### 1. `GET /health`
+
+Sirve para comprobar si el servicio esta levantado y si la conexion con `Qdrant` esta operativa.
+
+Ejemplo:
+
+```bash
+curl -sS "https://rag.por-correo.com/health"
+```
+
+Respuesta esperada aproximada:
+
+```json
+{
+ "ok": true,
+ "service": "rag",
+ "environment": "production",
+ "embeddings": {
+ "provider": "openrouter",
+ "model": "qwen/qwen3-embedding-8b"
+ },
+ "vectorStore": {
+ "ok": true,
+ "kind": "qdrant"
+ }
+}
+```
+
+---
+
+### 2. `POST /ingest`
+
+Sirve para ingerir una fuente en el RAG.
+
+Puede usarse sobre:
+- un archivo
+- una carpeta
+
+Payload base:
+
+```json
+{
+ "sourceType": "folder",
+ "sourceRef": "/ruta/a/la/carpeta",
+ "mode": "mechanical",
+ "tags": ["workspace", "global-docs"]
+}
+```
+
+Campos:
+
+- `sourceType`: `file` o `folder`
+- `sourceRef`: ruta de la fuente
+- `mode`: `mechanical` o `interactive`
+- `tags`: etiquetas opcionales para clasificar la fuente
+
+Ejemplo documental:
+
+```bash
+curl -sS -X POST "https://rag.por-correo.com/ingest" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "sourceType": "folder",
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs",
+ "mode": "mechanical",
+ "tags": ["workspace", "global-docs"]
+ }'
+```
+
+Ejemplo codigo:
+
+```bash
+curl -sS -X POST "https://rag.por-correo.com/ingest" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "sourceType": "folder",
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src",
+ "mode": "mechanical",
+ "tags": ["rag", "module-code"]
+ }'
+```
+
+Respuesta esperada aproximada:
+
+```json
+{
+ "accepted": true,
+ "filesDiscovered": 10,
+ "documentsProcessed": 10,
+ "chunksStored": 45,
+ "collectionName": "rag_chunks"
+}
+```
+
+---
+
+### 3. `POST /retrieve`
+
+Sirve para recuperar contexto del RAG sin pedir una respuesta final al modelo.
+
+Es ideal para:
+- agentes IA
+- nodos tool de n8n
+- flujos que quieran contexto rico y trazable
+
+Payload base:
+
+```json
+{
+ "mode": "documental",
+ "intent": "specific",
+ "query": "que tenemos pendiente por hacer en este workspace",
+ "scope": {
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
+ }
+}
+```
+
+Campos:
+
+- `mode`:
+ - `documental`
+ - `codigo`
+ - `auto`
+- `intent`:
+ - `specific`
+ - `bootstrap`
+- `query`: consulta del usuario o del flujo
+- `scope` opcional:
+ - `sourceId`
+ - `sourceRef`
+ - `tags`
+
+#### Ejemplo `retrieve` documental
+
+```bash
+curl -sS -X POST "https://rag.por-correo.com/retrieve" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "mode": "documental",
+ "intent": "specific",
+ "query": "que tenemos pendiente por hacer en este workspace",
+ "scope": {
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
+ }
+ }'
+```
+
+#### Ejemplo `retrieve` bootstrap
+
+```bash
+curl -sS -X POST "https://rag.por-correo.com/retrieve" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "mode": "documental",
+ "intent": "bootstrap",
+ "query": "dame un mapa inicial del workspace y sus lineas de trabajo principales",
+ "scope": {
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
+ }
+ }'
+```
+
+#### Ejemplo `retrieve` codigo
+
+```bash
+curl -sS -X POST "https://rag.por-correo.com/retrieve" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "mode": "codigo",
+ "intent": "specific",
+ "query": "como se construye source_id en el rag",
+ "scope": {
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src"
+ }
+ }'
+```
+
+Respuesta resumida esperada:
+
+```json
+{
+ "mode": "documental",
+ "intent": "specific",
+ "summary": "...",
+ "topics": ["PENDIENTES_GENERALES.md"],
+ "criticalPoints": [],
+ "items": [
+ {
+ "chunkId": "chk:...",
+ "documentId": "doc:...",
+ "sourceId": "src:...",
+ "title": "PENDIENTES_GENERALES.md",
+ "sectionTitle": "Resumen rapido",
+ "content": "...",
+ "score": 0.87
+ }
+ ],
+ "followUpRefs": ["doc:..."],
+ "scope": {
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
+ }
+}
+```
+
+---
+
+### 4. `POST /answer`
+
+Sirve para pedir al RAG una respuesta final apoyada en el contexto recuperado.
+
+Es util para:
+- agentes conversacionales
+- respuestas listas para usuario
+- pruebas rapidas desde n8n o aplicaciones
+
+Payload base:
+
+```json
+{
+ "mode": "documental",
+ "intent": "specific",
+ "query": "que tenemos pendiente por hacer en este workspace",
+ "scope": {
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
+ }
+}
+```
+
+#### Ejemplo `answer` documental
+
+```bash
+curl -sS -X POST "https://rag.por-correo.com/answer" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "mode": "documental",
+ "intent": "specific",
+ "query": "que tenemos pendiente por hacer en este workspace",
+ "scope": {
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
+ }
+ }'
+```
+
+#### Ejemplo `answer` codigo
+
+```bash
+curl -sS -X POST "https://rag.por-correo.com/answer" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "mode": "codigo",
+ "intent": "specific",
+ "query": "como se construye source_id en el rag",
+ "scope": {
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src"
+ }
+ }'
+```
+
+Respuesta resumida esperada:
+
+```json
+{
+ "mode": "codigo",
+ "intent": "specific",
+ "answer": "...",
+ "summary": "...",
+ "topics": ["ids.ts"],
+ "criticalPoints": [],
+ "citations": [
+ {
+ "chunkId": "chk:...",
+ "documentId": "doc:...",
+ "title": "ids.ts",
+ "sectionTitle": "export function buildSourceId(...)",
+ "startLine": 20,
+ "endLine": 25
+ }
+ ],
+ "scope": {
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src"
+ }
+}
+```
+
+---
+
+## Recomendacion practica para n8n
+
+Si quieres probarlo rapido desde `n8n`:
+
+1. Usa un nodo `HTTP Request`
+2. Metodo `POST`
+3. URL:
+
+```text
+https://rag.por-correo.com/retrieve
+```
+
+o
+
+```text
+https://rag.por-correo.com/answer
+```
+
+4. Body en JSON
+5. Empieza usando `answer` si quieres una respuesta directa
+6. Usa `retrieve` si quieres dar el contexto al agente y que el modelo final responda por su cuenta
+
+Payload recomendado para primera prueba en n8n:
+
+```json
+{
+ "mode": "documental",
+ "intent": "specific",
+ "query": "que tenemos pendiente por hacer en este workspace",
+ "scope": {
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
+ }
+}
+```
+
+---
+
+## Nota importante
+
+El `scope` es clave para no mezclar fuentes distintas.
+
+Ejemplos de `scope` utiles:
+
+- documentacion global del workspace:
+
+```json
+{
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
+}
+```
+
+- documentacion del modulo RAG:
+
+```json
+{
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/docs"
+}
+```
+
+- codigo del modulo RAG:
+
+```json
+{
+ "sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src"
+}
+```
+
+---
+
+## Siguiente evolucion natural
+
+Mas adelante esta API podra exponerse tambien mediante un `MCP`, pero a dia de hoy ya puede consumirse directamente por HTTP desde:
+
+- modelos
+- agentes
+- flujos de `n8n`
+- aplicaciones propias
diff --git a/RAG/docs/DESPLIEGUE_EASYPANEL.md b/RAG/docs/DESPLIEGUE_EASYPANEL.md
index 30c1025..5b43782 100644
--- a/RAG/docs/DESPLIEGUE_EASYPANEL.md
+++ b/RAG/docs/DESPLIEGUE_EASYPANEL.md
@@ -4,7 +4,7 @@
**Modulo:** RAG
**Ultima actualizacion:** 2026-04-05
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
-**Estado:** Base operativa definida
+**Estado:** V1 desplegada en VPS2
---
@@ -259,3 +259,24 @@ El siguiente salto natural es solo operativo:
- construir la imagen Docker sin errores de red
- montarla en EasyPanel siguiendo este patron
+
+---
+
+## Estado real alcanzado
+
+El despliegue ya se ha completado en `VPS2`.
+
+Estado confirmado:
+- dominio activo: `https://rag.por-correo.com`
+- servicio escuchando correctamente en produccion
+- `Qdrant` operativo por red interna
+- pruebas superadas de `health`, `retrieve` y `answer`
+
+Consulta documental ya validada:
+- `que tenemos pendiente por hacer en este workspace`
+
+Consulta de codigo ya validada:
+- `como se construye source_id en el rag`
+
+Pendiente de mejora ya detectado:
+- sustituir mas adelante el modelo actual de `answer` por una opcion alineada con la estrategia tecnica del proyecto
diff --git a/RAG/docs/PLAYGROUND.md b/RAG/docs/PLAYGROUND.md
new file mode 100644
index 0000000..a050807
--- /dev/null
+++ b/RAG/docs/PLAYGROUND.md
@@ -0,0 +1,83 @@
+# Playground del RAG
+
+**Proyecto:** Workspace de tools IA para empresas
+**Modulo:** RAG
+**Ultima actualizacion:** 2026-04-05
+**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
+**Estado:** Implementado en codigo, pendiente de redeploy
+
+---
+
+## Tecnologia elegida
+
+Se ha elegido una interfaz web estatica simple, servida por el propio backend `Express` del RAG.
+
+### Por que esta opcion
+
+- evita crear un segundo servicio independiente solo para pruebas
+- no añade otro framework de frontend ni otro pipeline de build innecesario
+- permite iterar rapido sobre el RAG real usando su propia API
+- es suficiente para una herramienta interna de evaluacion y ajuste
+
+---
+
+## Ubicacion dentro del modulo
+
+El playground queda dentro de:
+
+```text
+RAG/public/playground/
+```
+
+Archivos creados:
+
+- `RAG/public/playground/index.html`
+- `RAG/public/playground/app.js`
+- `RAG/public/playground/styles.css`
+
+El backend lo sirve desde:
+
+```text
+/playground
+```
+
+---
+
+## Que permite probar
+
+1. `health`
+2. `ingest`
+3. `retrieve`
+4. `answer`
+5. `answer` sin RAG para comparar impacto del contexto
+
+Tambien permite:
+
+- cambiar `mode`
+- cambiar `intent`
+- ajustar `scope`
+- usar presets para docs, docs del modulo y codigo del RAG
+
+---
+
+## Idea de uso
+
+Este playground no sustituye a clientes finales ni al futuro MCP.
+
+Su papel es:
+
+- probar rapido el comportamiento del RAG
+- comparar respuesta con y sin RAG
+- validar cambios en ingesta y retrieval
+- detectar donde el sistema necesita ajustes
+
+---
+
+## Relacion con MCP
+
+En esta primera fase el playground usa la API HTTP del propio servicio.
+
+Mas adelante se podra:
+
+- mantener como herramienta interna de evaluacion
+- o ampliarlo para probar tambien la capa MCP cuando exista
diff --git a/RAG/docs/SISTEMA_RAG_BASE.md b/RAG/docs/SISTEMA_RAG_BASE.md
index 2f1b35d..8824747 100644
--- a/RAG/docs/SISTEMA_RAG_BASE.md
+++ b/RAG/docs/SISTEMA_RAG_BASE.md
@@ -2,9 +2,9 @@
**Proyecto:** Workspace de tools IA para empresas
**Modulo:** RAG
-**Ultima actualizacion:** 2026-04-02
+**Ultima actualizacion:** 2026-04-05
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
-**Estado:** En definicion
+**Estado:** V1 operativa desplegada
---
@@ -87,6 +87,28 @@ Si el sistema esta bien planteado, deberiamos poder usarlo como una capa de cont
---
+## Estado actual de la v1
+
+La v1 ya ha alcanzado un estado operativo real.
+
+Estado confirmado:
+- desplegada en `VPS2`
+- dominio activo: `https://rag.por-correo.com`
+- conexion operativa con `Qdrant`
+- soporte funcional para `documental` y `codigo`
+- endpoints operativos: `GET /health`, `POST /ingest`, `POST /retrieve`, `POST /answer`
+
+Pruebas funcionales ya superadas:
+- consulta documental sobre pendientes del workspace
+- consulta conceptual sobre caracteristicas del RAG
+- consulta tecnica en modo codigo sobre la construccion de `source_id`
+
+Pendientes de evolucion de esta base:
+- sustituir el modelo actual de `answer` por una alternativa alineada con la decision de no depender de OpenAI en esa capa
+- seguir refinando retrieval y answer segun resultados de uso real
+
+---
+
## Alcance de este documento
Este documento define el que y el para que del sistema RAG base.
diff --git a/RAG/docs/STACK_TECNICO_V1.md b/RAG/docs/STACK_TECNICO_V1.md
index 263a54f..9c4463c 100644
--- a/RAG/docs/STACK_TECNICO_V1.md
+++ b/RAG/docs/STACK_TECNICO_V1.md
@@ -2,7 +2,7 @@
**Proyecto:** Workspace de tools IA para empresas
**Modulo:** RAG
-**Ultima actualizacion:** 2026-04-02
+**Ultima actualizacion:** 2026-04-05
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
**Estado:** Acordado para v1
@@ -218,4 +218,20 @@ La v1 se construira con una idea clara:
## Pendiente inmediato
-Generar la imagen Docker final del servicio y dejarlo listo para desplegar en EasyPanel.
+- Revisar y sustituir mas adelante el modelo actual de `answer` por una opcion alineada con la estrategia de no depender de OpenAI para esa capa.
+- Documentar la metodologia reutilizable para desplegar futuros servicios correctamente en EasyPanel.
+
+---
+
+## Estado actual de despliegue
+
+La v1 ya esta desplegada y funcionando en `VPS2` mediante EasyPanel.
+
+Estado confirmado:
+- servicio activo en `https://rag.por-correo.com`
+- `Qdrant` conectado correctamente por red interna
+- variables de entorno cargadas en produccion
+- `health`, `retrieve` y `answer` probados con exito
+
+Observacion abierta:
+- aparece un warning de deprecacion relacionado con `punycode`, pero no esta bloqueando el funcionamiento del servicio
diff --git a/RAG/public/playground/app.js b/RAG/public/playground/app.js
new file mode 100644
index 0000000..f15694b
--- /dev/null
+++ b/RAG/public/playground/app.js
@@ -0,0 +1,129 @@
+const healthButton = document.getElementById("healthButton");
+const ingestButton = document.getElementById("ingestButton");
+const queryButton = document.getElementById("queryButton");
+const presetDocs = document.getElementById("presetDocs");
+const presetRagDocs = document.getElementById("presetRagDocs");
+const presetCode = document.getElementById("presetCode");
+
+const healthResult = document.getElementById("healthResult");
+const mainResult = document.getElementById("mainResult");
+const compareResult = document.getElementById("compareResult");
+
+const queryInput = document.getElementById("queryInput");
+const queryMode = document.getElementById("queryMode");
+const queryIntent = document.getElementById("queryIntent");
+const queryOperation = document.getElementById("queryOperation");
+const scopeSourceRef = document.getElementById("scopeSourceRef");
+const scopeTags = document.getElementById("scopeTags");
+const compareWithoutRag = document.getElementById("compareWithoutRag");
+
+const ingestSourceType = document.getElementById("ingestSourceType");
+const ingestSourceRef = document.getElementById("ingestSourceRef");
+const ingestMode = document.getElementById("ingestMode");
+const ingestTags = document.getElementById("ingestTags");
+
+function format(value) {
+ return JSON.stringify(value, null, 2);
+}
+
+function splitTags(value) {
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
+}
+
+async function request(url, payload, method = "POST") {
+ const response = await fetch(url, {
+ method,
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: payload ? JSON.stringify(payload) : undefined
+ });
+
+ const data = await response.json();
+ if (!response.ok) {
+ throw new Error(data.error || `HTTP ${response.status}`);
+ }
+
+ return data;
+}
+
+healthButton.addEventListener("click", async () => {
+ healthResult.textContent = "Comprobando...";
+ try {
+ const response = await fetch("/health");
+ const data = await response.json();
+ healthResult.textContent = format(data);
+ } catch (error) {
+ healthResult.textContent = String(error);
+ }
+});
+
+ingestButton.addEventListener("click", async () => {
+ mainResult.textContent = "Ejecutando ingesta...";
+ try {
+ const data = await request("/ingest", {
+ sourceType: ingestSourceType.value,
+ sourceRef: ingestSourceRef.value,
+ mode: ingestMode.value,
+ tags: splitTags(ingestTags.value)
+ });
+ mainResult.textContent = format(data);
+ } catch (error) {
+ mainResult.textContent = String(error);
+ }
+});
+
+queryButton.addEventListener("click", async () => {
+ mainResult.textContent = "Consultando...";
+ compareResult.textContent = compareWithoutRag.checked ? "Comparando..." : "Desactivada.";
+
+ const payload = {
+ mode: queryMode.value,
+ intent: queryIntent.value,
+ query: queryInput.value,
+ scope: {
+ sourceRef: scopeSourceRef.value,
+ tags: splitTags(scopeTags.value)
+ }
+ };
+
+ try {
+ const endpoint = queryOperation.value === "answer" ? "/answer" : "/retrieve";
+ const data = await request(endpoint, payload);
+ mainResult.textContent = format(data);
+
+ if (compareWithoutRag.checked && queryOperation.value === "answer") {
+ const comparison = await request("/answer/direct", {
+ query: queryInput.value
+ });
+ compareResult.textContent = format(comparison);
+ }
+ } catch (error) {
+ mainResult.textContent = String(error);
+ compareResult.textContent = compareWithoutRag.checked ? String(error) : "Desactivada.";
+ }
+});
+
+presetDocs.addEventListener("click", () => {
+ queryMode.value = "documental";
+ queryIntent.value = "specific";
+ queryOperation.value = "answer";
+ queryInput.value = "que tenemos pendiente por hacer en este workspace";
+ scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs";
+});
+
+presetRagDocs.addEventListener("click", () => {
+ queryMode.value = "documental";
+ queryIntent.value = "bootstrap";
+ queryOperation.value = "retrieve";
+ queryInput.value = "dame un mapa inicial del modulo RAG, su arquitectura, decisiones, estado actual y documentos clave";
+ scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/docs";
+});
+
+presetCode.addEventListener("click", () => {
+ queryMode.value = "codigo";
+ queryIntent.value = "specific";
+ queryOperation.value = "answer";
+ queryInput.value = "como se construye source_id en el rag";
+ scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src";
+});
diff --git a/RAG/public/playground/index.html b/RAG/public/playground/index.html
new file mode 100644
index 0000000..79f9985
--- /dev/null
+++ b/RAG/public/playground/index.html
@@ -0,0 +1,114 @@
+
+
+
+
+
+ RAG Playground
+
+
+
+
+
+
+
RAG Playground
+
Pruebas rapidas del RAG
+
Interfaz interna para probar ingesta, retrieve, answer y comparacion sin RAG desde el mismo servicio.
+
+
+
+
+
+
+
Ingesta
+
+
+
+
+
+
+
+
+
Consulta
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Resultado principal
+
Sin ejecutar aun.
+
+
+
Comparacion sin RAG
+
Desactivada.
+
+
+
+
+
Estado / health
+
Sin comprobar.
+
+
+
+
+
+
diff --git a/RAG/public/playground/styles.css b/RAG/public/playground/styles.css
new file mode 100644
index 0000000..9e4048d
--- /dev/null
+++ b/RAG/public/playground/styles.css
@@ -0,0 +1,142 @@
+:root {
+ color-scheme: dark;
+ --bg: #0d1321;
+ --panel: #151d31;
+ --panel-2: #1c2742;
+ --text: #e8ecf8;
+ --muted: #9aa6c8;
+ --accent: #72e0a8;
+ --accent-2: #7ca8ff;
+ --border: #2e3a5f;
+}
+
+* { box-sizing: border-box; }
+
+body {
+ margin: 0;
+ font-family: Inter, ui-sans-serif, system-ui, sans-serif;
+ background: linear-gradient(180deg, #0b1020 0%, #12192b 100%);
+ color: var(--text);
+}
+
+.layout {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 32px;
+}
+
+.hero, .panel {
+ background: rgba(21, 29, 49, 0.92);
+ border: 1px solid var(--border);
+ border-radius: 20px;
+ box-shadow: 0 18px 50px rgba(0,0,0,0.2);
+}
+
+.hero {
+ padding: 24px 28px;
+ display: flex;
+ justify-content: space-between;
+ gap: 20px;
+ align-items: flex-start;
+ margin-bottom: 24px;
+}
+
+.eyebrow {
+ margin: 0 0 8px;
+ color: var(--accent);
+ font-size: 12px;
+ text-transform: uppercase;
+ letter-spacing: 0.14em;
+}
+
+h1, h2 { margin: 0 0 12px; }
+.lead { margin: 0; color: var(--muted); max-width: 720px; }
+
+.grid {
+ display: grid;
+ gap: 24px;
+ grid-template-columns: 1fr 2fr;
+ margin-bottom: 24px;
+}
+
+.results-grid { grid-template-columns: 1fr 1fr; }
+.panel-wide { min-width: 0; }
+
+.panel {
+ padding: 24px;
+}
+
+label {
+ display: block;
+ margin-bottom: 14px;
+ color: var(--muted);
+ font-size: 14px;
+}
+
+input, textarea, select, button {
+ width: 100%;
+ border-radius: 12px;
+ border: 1px solid var(--border);
+ background: var(--panel-2);
+ color: var(--text);
+ padding: 12px 14px;
+ font: inherit;
+}
+
+textarea { resize: vertical; }
+
+button {
+ width: auto;
+ cursor: pointer;
+ background: linear-gradient(135deg, var(--accent) 0%, #4fceff 100%);
+ color: #0b1020;
+ font-weight: 700;
+ border: none;
+}
+
+button.secondary {
+ background: transparent;
+ color: var(--text);
+ border: 1px solid var(--border);
+}
+
+.row, .actions {
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+}
+
+.row > label {
+ flex: 1 1 220px;
+}
+
+.checkbox {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.checkbox input {
+ width: auto;
+}
+
+pre {
+ background: #0b1020;
+ border: 1px solid var(--border);
+ border-radius: 16px;
+ padding: 16px;
+ overflow: auto;
+ min-height: 180px;
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+
+@media (max-width: 980px) {
+ .grid, .results-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .hero {
+ flex-direction: column;
+ }
+}
diff --git a/RAG/src/app.ts b/RAG/src/app.ts
index ec54bc6..e5695e4 100644
--- a/RAG/src/app.ts
+++ b/RAG/src/app.ts
@@ -1,4 +1,6 @@
import express from "express";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
import { env } from "./config/env.js";
import { AnswerService } from "./modules/answer/service.js";
import { IngestService } from "./modules/ingest/service.js";
@@ -10,6 +12,9 @@ import { QdrantVectorStoreClient } from "./modules/vectorstore/client.js";
import type { ChunkMode, RetrieveIntent, RetrieveScope } from "./shared/types/rag.js";
export function createApp() {
+ const __filename = fileURLToPath(import.meta.url);
+ const __dirname = path.dirname(__filename);
+ const publicDir = path.resolve(__dirname, "../public");
const app = express();
const embeddingProvider = new OpenRouterEmbeddingProvider();
const vectorStore = new QdrantVectorStoreClient();
@@ -18,6 +23,11 @@ export function createApp() {
const answerService = new AnswerService(retrieveService);
app.use(express.json({ limit: "5mb" }));
+ app.use(express.static(publicDir));
+
+ app.get("/playground", (_req, res) => {
+ res.sendFile(path.join(publicDir, "playground", "index.html"));
+ });
app.get("/health", async (_req, res) => {
const vectorHealth = await vectorStore.healthcheck();
@@ -82,5 +92,15 @@ export function createApp() {
}
});
+ app.post("/answer/direct", async (req, res) => {
+ try {
+ const query = String(req.body.query ?? "");
+ const result = await answerService.answerWithoutRag(query);
+ res.json(result);
+ } catch (error) {
+ res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown direct answer error" });
+ }
+ });
+
return app;
}
diff --git a/RAG/src/modules/answer/service.ts b/RAG/src/modules/answer/service.ts
index 1f05901..be661e4 100644
--- a/RAG/src/modules/answer/service.ts
+++ b/RAG/src/modules/answer/service.ts
@@ -1,6 +1,6 @@
import OpenAI from "openai";
import { env } from "../../config/env.js";
-import type { AnswerResponse, ChunkMode, RetrieveIntent, RetrieveScope, RetrievedItem } from "../../shared/types/rag.js";
+import type { AnswerResponse, ChunkMode, DirectAnswerResponse, RetrieveIntent, RetrieveScope, RetrievedItem } from "../../shared/types/rag.js";
import { RetrieveService } from "../retrieve/service.js";
function buildPrompt(query: string, summary: string, items: RetrievedItem[]): string {
@@ -75,4 +75,30 @@ export class AnswerService {
scope: retrieved.scope
};
}
+
+ async answerWithoutRag(query: string): Promise {
+ if (!env.answerApiKey) {
+ throw new Error("Missing ANSWER_API_KEY for answer provider");
+ }
+
+ const completion = await this.client.chat.completions.create({
+ model: env.answerModel,
+ temperature: 0.2,
+ messages: [
+ {
+ role: "system",
+ content: "Responde con claridad y brevedad a la pregunta del usuario sin contexto adicional externo."
+ },
+ {
+ role: "user",
+ content: query
+ }
+ ]
+ });
+
+ return {
+ model: env.answerModel,
+ answer: completion.choices[0]?.message?.content?.trim() || "No se pudo generar respuesta."
+ };
+ }
}
diff --git a/RAG/src/shared/types/rag.ts b/RAG/src/shared/types/rag.ts
index 3fd7b42..8b4e376 100644
--- a/RAG/src/shared/types/rag.ts
+++ b/RAG/src/shared/types/rag.ts
@@ -76,3 +76,8 @@ export interface AnswerResponse {
}>;
scope?: RetrieveScope;
}
+
+export interface DirectAnswerResponse {
+ model: string;
+ answer: string;
+}
diff --git a/docs/HISTORIAL_SESIONES.md b/docs/HISTORIAL_SESIONES.md
index 4d645eb..965ccc7 100644
--- a/docs/HISTORIAL_SESIONES.md
+++ b/docs/HISTORIAL_SESIONES.md
@@ -56,6 +56,12 @@ Este archivo registra agentes y sesiones de trabajo de este workspace.
- Contraste del patron manual de `webfetch` con la documentacion oficial de EasyPanel, dejando definido que `RAG` deberia montarse como `App Service` con fuente Git o imagen publicada para quedar bien integrado.
- Verificacion de que Forgejo ya esta operativo en `git.por-correo.com` y que el acceso Git por SSH en puerto `2222` responde correctamente, dejando preparado el camino de despliegue integrado para `RAG`.
- Creacion del repo remoto `paco/rag-service` en Forgejo y subida correcta del estado actual del workspace mediante Git SSH.
+- Despliegue correcto de `RAG` en EasyPanel como `App Service` usando Forgejo y `Dockerfile`, con dominio activo `https://rag.por-correo.com`.
+- Pruebas satisfactorias en produccion de `health`, `retrieve` y `answer`, tanto en modo documental como en modo codigo.
+- Deteccion y anotacion como pendiente del cambio futuro del modelo actual de `answer`.
+- Creacion de `RAG/docs/API_RAG.md` como referencia operativa de la API para conectar rapidamente el servicio desde n8n, agentes y otras aplicaciones.
+- Implementacion de un playground web interno servido por el propio backend del RAG para probar `health`, `ingest`, `retrieve`, `answer` y comparacion sin RAG.
+- Creacion de `RAG/docs/PLAYGROUND.md` para documentar la tecnologia elegida, su ubicacion y su papel dentro del modulo.
- Reorganizacion de RAG como modulo raiz independiente con documentacion propia en `RAG/docs/`.
- Ajuste del indice documental global para reflejar la separacion entre documentacion global y documentacion por tool.
- Creacion de `docs/TASK.md` para descomponer lineas de trabajo amplias en puntos de analisis y acuerdos.
@@ -71,6 +77,7 @@ Este archivo registra agentes y sesiones de trabajo de este workspace.
- Existe ya una primera implementacion funcional compilable del servicio RAG, pendiente de probar con claves reales y Qdrant activo.
- El servicio ya ha sido probado con embeddings reales y vector store remoto operativo.
- El servicio soporta ya modo documental y modo codigo con pruebas reales sobre documentacion y codigo del propio modulo.
+- La v1 del RAG ya esta desplegada y operativa en VPS2 con dominio publico funcional.
---
diff --git a/docs/INDICE_DOCUMENTACION.md b/docs/INDICE_DOCUMENTACION.md
index db9c724..80df2c2 100644
--- a/docs/INDICE_DOCUMENTACION.md
+++ b/docs/INDICE_DOCUMENTACION.md
@@ -272,6 +272,40 @@ Recoger dudas concretas sobre como se desplego `webfetch` en `VPS2` para reutili
---
+### `RAG/docs/API_RAG.md`
+
+**Ubicacion:** `RAG/docs/API_RAG.md`
+
+**Proposito:**
+Documentar de forma practica la API del servicio RAG, con endpoints, payloads y ejemplos listos para conectar desde n8n, agentes o aplicaciones.
+
+**Cuando leerlo:**
+- al integrar el RAG desde n8n o cualquier otro cliente HTTP
+- al necesitar ejemplos listos de `health`, `ingest`, `retrieve` y `answer`
+
+**Cuando actualizarlo:**
+- cuando cambie algun endpoint o payload del servicio
+- cuando se añadan nuevos modos o patrones de integracion
+
+---
+
+### `RAG/docs/PLAYGROUND.md`
+
+**Ubicacion:** `RAG/docs/PLAYGROUND.md`
+
+**Proposito:**
+Documentar la tecnologia, ubicacion y utilidad del playground interno del RAG para pruebas y evaluacion.
+
+**Cuando leerlo:**
+- al querer probar el RAG con interfaz web interna
+- al revisar por que se eligio esta forma de playground y no otra
+
+**Cuando actualizarlo:**
+- cuando cambie la interfaz de prueba
+- cuando el playground se amplie o se conecte tambien por MCP
+
+---
+
### `sesion_actual_opencode.md`
**Ubicacion:** `docs/sesion_actual_opencode.md`
@@ -298,4 +332,4 @@ Instruccion universal para detectar la sesion activa de OpenCode del workspace a
## Estadistica global
-**Total de documentos indexados:** 15
+**Total de documentos indexados:** 17
diff --git a/docs/PENDIENTES_GENERALES.md b/docs/PENDIENTES_GENERALES.md
index d71a9e5..3e1ee6c 100644
--- a/docs/PENDIENTES_GENERALES.md
+++ b/docs/PENDIENTES_GENERALES.md
@@ -1,7 +1,7 @@
# Pendientes Generales
**Proyecto:** Workspace de tools IA para empresas
-**Ultima actualizacion:** 2026-04-02
+**Ultima actualizacion:** 2026-04-05
**Ultima modificacion por:** Agente tools IA para potenciar servicios empresariales
**Estado:** Activo
@@ -45,8 +45,20 @@ Diseñar una base RAG simple, modular y facil de conectar a otros servicios ya e
- Elegir estrategia inicial de almacenamiento y vectorizacion.
- Definir interfaz de integracion con servicios externos.
- Identificar un primer caso real de uso para validacion.
+- Revisar y sustituir mas adelante el modelo actual de `answer` por una opcion alineada con la decision de no depender de OpenAI para esa capa.
-**Estado:** Pendiente
+**Estado:** En marcha
+
+**Estado actual resumido:**
+- La v1 del servicio RAG ya esta desplegada y operativa en `VPS2`.
+- Dominio activo: `https://rag.por-correo.com`
+- Modos ya funcionales: `documental` y `codigo`
+- Endpoints operativos: `health`, `ingest`, `retrieve`, `answer`
+
+**Pendientes inmediatos reales:**
+- Sustituir el modelo actual de `answer` por una opcion no-OpenAI.
+- Crear una metodologia reutilizable de despliegue correcto en EasyPanel para futuros servicios.
+- Valorar refinados adicionales en retrieval y answer segun uso real.
---
@@ -123,9 +135,9 @@ Evaluar si Obsidian puede aportar valor como capa de organizacion, fuente de con
## Proximos pasos sugeridos
-1. Definir el alcance minimo viable del sistema RAG base.
-2. Diseñar la estructura inicial MCP del workspace.
-3. Documentar el punto de integracion entre Retell y servicios externos como n8n.
+1. Sustituir el modelo actual de `answer` por una alternativa alineada con la estrategia del proyecto.
+2. Documentar la metodologia correcta para desplegar futuros servicios en EasyPanel.
+3. Diseñar la estructura inicial MCP del workspace.
---
@@ -133,7 +145,7 @@ Evaluar si Obsidian puede aportar valor como capa de organizacion, fuente de con
| Linea | Estado |
|-------|--------|
-| Sistema basico RAG reutilizable | Pendiente |
+| Sistema basico RAG reutilizable | En marcha |
| Estructura MCP para integracion de tools | Pendiente |
| Retell conectado con herramientas externas | Pendiente |
| Posible potenciacion del RAG con Obsidian | Pendiente |