From 3d7564b8226b160fdb5761576d76130de5f2aa43 Mon Sep 17 00:00:00 2001 From: MrPokeYT Date: Mon, 2 Feb 2026 16:38:56 +0000 Subject: [PATCH] Subir archivos a "/" --- .env | 8 ++ ai_client.js | 87 +++++++++++++++++ logger.js | 22 +++++ logs.jsonl | 6 ++ main.js | 262 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 385 insertions(+) create mode 100644 .env create mode 100644 ai_client.js create mode 100644 logger.js create mode 100644 logs.jsonl create mode 100644 main.js diff --git a/.env b/.env new file mode 100644 index 0000000..4ffa365 --- /dev/null +++ b/.env @@ -0,0 +1,8 @@ +# URL del servidor Ollama (por defecto localhost) +OLLAMA_URL=http://localhost:11434/api/generate + +# Modelo a utilizar (debe estar instalado en Ollama) +MODEL=openchat + +# Timeout en milisegundos (por defecto 120000 = 2 minutos) +TIMEOUT=120000 \ No newline at end of file diff --git a/ai_client.js b/ai_client.js new file mode 100644 index 0000000..bfd6228 --- /dev/null +++ b/ai_client.js @@ -0,0 +1,87 @@ +// ai_client.js +const OLLAMA_URL = process.env.OLLAMA_URL || "http://localhost:11434/api/generate"; +const MODEL = process.env.MODEL || "openchat"; +const TIMEOUT = parseInt(process.env.TIMEOUT || "120000"); // 2 minutos por defecto + +// Rate limiting +let lastCallTime = 0; +const MIN_CALL_INTERVAL = 2000; // 2 segundos + +// Caché básica +const cache = new Map(); + +async function callLLM(prompt, maxTokens = 1000) { + // Rate limiting + const now = Date.now(); + const timeSinceLastCall = now - lastCallTime; + if (timeSinceLastCall < MIN_CALL_INTERVAL) { + const waitTime = MIN_CALL_INTERVAL - timeSinceLastCall; + console.log(`⏳ Esperando ${waitTime}ms por rate limit...`); + await new Promise(resolve => setTimeout(resolve, waitTime)); + } + + // Verificar caché + const cacheKey = `${prompt}_${maxTokens}`; + if (cache.has(cacheKey)) { + console.log("✅ Respuesta obtenida de caché"); + return cache.get(cacheKey); + } + + try { + // Crear un AbortController manual para timeout + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), TIMEOUT); + + const response = await fetch(OLLAMA_URL, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + model: MODEL, + prompt: prompt, + stream: false, + options: { + num_predict: maxTokens, + temperature: 0.7 + } + }), + signal: controller.signal + }); + + clearTimeout(timeoutId); + lastCallTime = Date.now(); + + if (response.status === 429) { + console.error("⚠️ Error 429: Demasiadas peticiones. Esperando 5 segundos..."); + await new Promise(resolve => setTimeout(resolve, 5000)); + return callLLM(prompt, maxTokens); // Reintentar + } + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Error Ollama: ${response.status} - ${errorText}`); + } + + const data = await response.json(); + + if (!data.response) { + throw new Error("La respuesta de Ollama no contiene el campo 'response'"); + } + + // Guardar en caché + cache.set(cacheKey, data.response); + + return data.response; + } catch (error) { + if (error.name === 'AbortError') { + throw new Error(`⏱️ Timeout: La petición tardó más de ${TIMEOUT/1000} segundos. Intenta con un prompt más corto o aumenta TIMEOUT en .env`); + } + if (error.code === 'ECONNREFUSED') { + throw new Error("🔌 No se puede conectar a Ollama. Asegúrate de que está ejecutándose con 'ollama serve'"); + } + throw error; + } +} + +module.exports = { callLLM }; \ No newline at end of file diff --git a/logger.js b/logger.js new file mode 100644 index 0000000..c20efd8 --- /dev/null +++ b/logger.js @@ -0,0 +1,22 @@ +// logger.js +const fs = require('fs'); +const path = require('path'); + +const LOG_FILE = path.join(__dirname, 'logs.jsonl'); + +function log(entry) { + const logEntry = { + timestamp: new Date().toISOString(), + ...entry + }; + + const logLine = JSON.stringify(logEntry) + '\n'; + + try { + fs.appendFileSync(LOG_FILE, logLine, 'utf8'); + } catch (error) { + console.error("Error escribiendo en logs:", error.message); + } +} + +module.exports = { log }; \ No newline at end of file diff --git a/logs.jsonl b/logs.jsonl new file mode 100644 index 0000000..51d0221 --- /dev/null +++ b/logs.jsonl @@ -0,0 +1,6 @@ +{"timestamp":"2026-01-26T18:24:51.723Z","opcion":"respuesta_guiada","error":"⏱️ Timeout: La petición tardó demasiado","solicitud":"Dime que sabes sobre javascript"} +{"timestamp":"2026-01-26T18:28:08.737Z","opcion":"respuesta_guiada","proveedor":"ollama","modelo":"openchat","solicitud":"Dime que es javascript","parametros":{"max_tokens":1000},"respuesta_length":2034,"duracion_ms":61085} +{"timestamp":"2026-01-26T18:31:54.756Z","opcion":"checklist_tecnico","proveedor":"ollama","modelo":"openchat","tema":"Hardening SSH Debian","prompt_length":390,"parametros":{"max_tokens":1500},"respuesta_length":3428,"duracion_ms":96414} +{"timestamp":"2026-01-26T18:34:25.287Z","opcion":"transformacion_itil","proveedor":"ollama","modelo":"openchat","texto_original_length":27,"parametros":{"max_tokens":1200},"respuesta_length":1356,"duracion_ms":38090} +{"timestamp":"2026-01-26T18:42:05.237Z","opcion":"checklist_tecnico","proveedor":"ollama","modelo":"openchat","tema":"Que es el Kernel?","prompt_length":384,"parametros":{"max_tokens":1500},"respuesta_length":3499,"duracion_ms":104562} +{"timestamp":"2026-01-26T18:46:59.464Z","opcion":"respuesta_guiada","proveedor":"ollama","modelo":"openchat","solicitud":"Que necesita una VPN Para funcionar?","parametros":{"max_tokens":1000},"respuesta_length":1340,"duracion_ms":43898} diff --git a/main.js b/main.js new file mode 100644 index 0000000..3fde5f1 --- /dev/null +++ b/main.js @@ -0,0 +1,262 @@ +// main.js +const readline = require('readline'); +const { callLLM } = require('./ai_client'); +const { log } = require('./logger'); +require('dotenv').config(); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +function question(prompt) { + return new Promise(resolve => { + rl.question(prompt, resolve); + }); +} + +function mostrarMenu() { + console.log("\n" + "=".repeat(60)); + console.log("🤖 ASISTENTE IA - GESTIÓN TÉCNICA IT"); + console.log("=".repeat(60)); + console.log("1. Checklist técnico (salida estructurada)"); + console.log("2. Transformación a plantilla ITIL"); + console.log("3. Respuesta guiada (preguntas + respuesta final)"); + console.log("4. Salir"); + console.log("=".repeat(60)); +} + +async function checklistTecnico() { + console.log("\n📋 CHECKLIST TÉCNICO"); + console.log("-".repeat(60)); + + const tema = await question("Introduce el tema técnico (ej: hardening SSH Debian): "); + + const prompt = `Genera un checklist técnico detallado sobre "${tema}" en formato Markdown. + +Estructura requerida: +# Checklist: ${tema} + +## Items +Para cada item incluye: +- [ ] **Nombre del item**: Breve descripción + - **Justificación**: Por qué es importante + - **Riesgo si no se aplica**: Consecuencias de omitirlo + +Genera entre 5 y 8 items relevantes. Sé específico y práctico.`; + + console.log("\n⏳ Generando checklist...\n"); + + const startTime = Date.now(); + try { + const respuesta = await callLLM(prompt, 1500); + const duration = Date.now() - startTime; + + console.log(respuesta); + + // Logging + log({ + opcion: "checklist_tecnico", + proveedor: "ollama", + modelo: process.env.MODEL || "openchat", + tema: tema, + prompt_length: prompt.length, + parametros: { max_tokens: 1500 }, + respuesta_length: respuesta.length, + duracion_ms: duration + }); + + } catch (error) { + console.error("\n❌ Error:", error.message); + log({ + opcion: "checklist_tecnico", + error: error.message, + tema: tema + }); + } +} + +async function transformacionITIL() { + console.log("\n📄 TRANSFORMACIÓN A PLANTILLA ITIL"); + console.log("-".repeat(60)); + + console.log("Introduce el texto de la incidencia (escribe 'FIN' en una línea aparte para terminar):"); + + let textoIncidencia = ""; + let linea; + while ((linea = await question("")) !== "FIN") { + textoIncidencia += linea + "\n"; + } + + const prompt = `Transforma el siguiente texto de incidencia en una plantilla ITIL estructurada: + +TEXTO ORIGINAL: +${textoIncidencia} + +PLANTILLA ITIL REQUERIDA: + +## Resumen +[Descripción breve y clara del problema] + +## Impacto +[Alto/Medio/Bajo - Afectación al negocio] + +## Urgencia +[Alta/Media/Baja - Tiempo crítico de resolución] + +## Acciones Realizadas +- [Lista de acciones ya tomadas] + +## Evidencias +- [Logs, capturas, datos técnicos relevantes] + +## Diagnóstico Preliminar +[Análisis de la causa probable] + +## Próximos Pasos +1. [Primer paso] +2. [Segundo paso] +3. [Tercer paso] + +Completa la plantilla de forma profesional y técnica.`; + + console.log("\n⏳ Transformando a formato ITIL...\n"); + + const startTime = Date.now(); + try { + const respuesta = await callLLM(prompt, 1200); + const duration = Date.now() - startTime; + + console.log(respuesta); + + // Logging + log({ + opcion: "transformacion_itil", + proveedor: "ollama", + modelo: process.env.MODEL || "openchat", + texto_original_length: textoIncidencia.length, + parametros: { max_tokens: 1200 }, + respuesta_length: respuesta.length, + duracion_ms: duration + }); + + } catch (error) { + console.error("\n❌ Error:", error.message); + log({ + opcion: "transformacion_itil", + error: error.message + }); + } +} + +async function respuestaGuiada() { + console.log("\n❓ RESPUESTA GUIADA"); + console.log("-".repeat(60)); + + const solicitud = await question("Describe el problema (ej: no funciona la VPN): "); + + const prompt = `Un usuario reporta: "${solicitud}" + +Como técnico IT experto, necesitas: + +1. **PREGUNTAS DE ACLARACIÓN** (3-5 preguntas específicas): + - Pregunta 1: [pregunta técnica específica] + - Pregunta 2: [pregunta sobre contexto] + - Pregunta 3: [pregunta sobre síntomas] + - [2 preguntas más si es necesario] + +2. **HIPÓTESIS INICIAL**: + Basándote en la información limitada, cuál es la causa más probable. + +3. **PLAN DE DIAGNÓSTICO** (pasos numerados): + 1. [Primer paso de verificación] + 2. [Segundo paso] + 3. [Tercer paso] + 4. [Cuarto paso] + 5. [Quinto paso si es necesario] + +Proporciona una respuesta estructurada y profesional.`; + + console.log("\n⏳ Analizando y generando preguntas...\n"); + + const startTime = Date.now(); + try { + const respuesta = await callLLM(prompt, 1000); + const duration = Date.now() - startTime; + + console.log(respuesta); + + // Logging + log({ + opcion: "respuesta_guiada", + proveedor: "ollama", + modelo: process.env.MODEL || "openchat", + solicitud: solicitud, + parametros: { max_tokens: 1000 }, + respuesta_length: respuesta.length, + duracion_ms: duration + }); + + } catch (error) { + console.error("\n❌ Error:", error.message); + log({ + opcion: "respuesta_guiada", + error: error.message, + solicitud: solicitud + }); + } +} + +async function main() { + // Verificar variables de entorno + if (!process.env.OLLAMA_URL) { + console.log("⚠️ Usando URL por defecto de Ollama: http://localhost:11434/api/generate"); + } + + let continuar = true; + + while (continuar) { + mostrarMenu(); + const opcion = await question("\nSelecciona una opción (1-4): "); + + switch (opcion.trim()) { + case "1": + await checklistTecnico(); + break; + case "2": + await transformacionITIL(); + break; + case "3": + await respuestaGuiada(); + break; + case "4": + console.log("\n👋 ¡Hasta pronto!"); + continuar = false; + break; + default: + console.log("\n❌ Opción no válida. Por favor, selecciona 1-4."); + } + + if (continuar) { + await question("\nPresiona ENTER para continuar..."); + } + } + + rl.close(); +} + +// Manejo de errores global +process.on('unhandledRejection', (error) => { + console.error('\n❌ Error no manejado:', error.message); + log({ + tipo: "error_no_manejado", + error: error.message, + stack: error.stack + }); +}); + +// Ejecutar +main().catch(error => { + console.error("❌ Error fatal:", error.message); + process.exit(1); +}); \ No newline at end of file