
ESPOLBot CALENDARIO|FAQS
Description
Categories
🤖 AI & Machine Learning
Nodes Used
n8n-nodes-base.setn8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.code
PriceGratuit
Views0
Last Updated11/28/2025
workflow.json
{
"id": "qVwL4UVRZaWLVZtQ",
"meta": {
"instanceId": "8a5440a5ddd0e75b8a9a2e3575b14d94d566aa1e1140eba5b0cc9b9eb91aaea3",
"templateCredsSetupCompleted": true
},
"name": "ESPOLBot CALENDARIO|FAQS",
"tags": [],
"nodes": [
{
"id": "6d06b9e5-4027-4626-8315-2b02a3087727",
"name": "Construir_Prompt_Gemini1",
"type": "n8n-nodes-base.code",
"position": [
-1008,
2448
],
"parameters": {
"jsCode": "const preguntaUsuario = $json.pregunta_usuario;\nconst faqsRelevantes = $json.contexto_faqs || [];\n\nlet contextoFAQs = '';\nif (faqsRelevantes.length > 0) {\n contextoFAQs = 'Aqui hay informacion relevante de la base de datos:\\n\\n';\n contextoFAQs += faqsRelevantes.map((faq, index) => {\n return `${index + 1}. P: ${faq.pregunta}\\n R: ${faq.respuesta}${faq.enlaces ? `\\n Link: ${faq.enlaces}` : ''}`;\n }).join('\\n\\n');\n} else {\n contextoFAQs = 'No encontre informacion especifica en la base de datos para esta pregunta.';\n}\n\nconst prompt = `Eres un asistente de ESPOL. Un estudiante te pregunto:\n\n\"${preguntaUsuario}\"\n\n${contextoFAQs}\n\nIMPORTANTE:\n- La pregunta del usuario ES VALIDA y CLARA\n- Si hay informacion arriba, usala para responder\n- Si NO hay informacion especifica, di algo como: \"No tengo informacion especifica sobre [tema], pero puedes contactar a ESPOL\"\n- NUNCA digas \"no has hecho ninguna pregunta\"\n\nContactos generales:\n- Email: [email protected]\n- Telefono: (04) 2269-269\n- Web: https://www.espol.edu.ec\n\nResponde de forma COMPLETA, clara y util. Incluye TODOS los detalles necesarios:`;\n\nreturn [{\n json: {\n prompt: prompt\n }\n}];"
},
"typeVersion": 2
},
{
"id": "95fcc718-5c12-423d-b062-b9935d3f8506",
"name": "Buscar_FAQs_Relevantes",
"type": "n8n-nodes-base.code",
"position": [
-1264,
2448
],
"parameters": {
"jsCode": "function limpiarTexto(texto) {\n if (!texto) return '';\n return texto\n .toLowerCase()\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[¿?¡!.,;:]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n// Leer la pregunta del Telegram Trigger\n// En runOnceForAllItems, usar .all() para acceder al primer (y normalmente único) mensaje\nconst telegramInput = $('Telegram Trigger - Inicio').all()[0]?.json || {};\nconst preguntaUsuario = telegramInput.message?.text || '';\nconst chatId = telegramInput.message?.chat?.id || '';\n\n// Las FAQs vienen del input (MongoDB)\nconst todasLasFAQs = $input.all();\n\nconst preguntaLimpia = limpiarTexto(preguntaUsuario);\nconst palabrasUsuario = preguntaLimpia.split(' ').filter(p => p.length > 2);\n\nconst faqsRelevantes = [];\n\nconsole.log(`========================================`);\nconsole.log(`Pregunta usuario: \"${preguntaUsuario}\"`);\nconsole.log(`Chat ID: ${chatId}`);\nconsole.log(`Palabras clave: ${palabrasUsuario.join(', ')}`);\nconsole.log(`Total FAQs en MongoDB: ${todasLasFAQs.length}`);\n\nfor (const faq of todasLasFAQs) {\n const preguntaFAQ = faq.json.PREGUNTA || faq.json.pregunta || '';\n if (!preguntaFAQ) continue;\n \n const preguntaFAQLimpia = limpiarTexto(preguntaFAQ);\n \n let coincidencias = 0;\n for (const palabra of palabrasUsuario) {\n if (preguntaFAQLimpia.includes(palabra)) {\n coincidencias++;\n }\n }\n \n if (coincidencias >= 2) {\n const score = coincidencias / palabrasUsuario.length;\n \n faqsRelevantes.push({\n pregunta: preguntaFAQ,\n respuesta: faq.json.RESPUESTA || faq.json.respuesta || '',\n enlaces: faq.json.ENLACES || faq.json.enlaces || null,\n similitud: Math.round(score * 100) / 100,\n coincidencias: coincidencias\n });\n }\n}\n\nfaqsRelevantes.sort((a, b) => b.coincidencias - a.coincidencias);\nconst top10 = faqsRelevantes.slice(0, 10);\n\nconsole.log(`FAQs encontradas: ${top10.length}`);\nif (top10.length > 0) {\n console.log(`Top 5 candidatas:`);\n top10.slice(0, 5).forEach((faq, i) => {\n console.log(` ${i+1}. \"${faq.pregunta.substring(0, 70)}...\" (${faq.coincidencias} palabras)`);\n });\n} else {\n console.log(`⚠️ NO SE ENCONTRARON FAQs`);\n}\nconsole.log(`========================================`);\n\nreturn [{\n json: {\n pregunta_usuario: preguntaUsuario,\n contexto_faqs: top10,\n total_encontradas: top10.length,\n chat_id: chatId\n }\n}];"
},
"typeVersion": 2
},
{
"id": "d5e736d4-9d75-44d5-b1c8-dfe94b289e68",
"name": "Respuesta Usuario1",
"type": "n8n-nodes-base.switch",
"position": [
-1616,
1184
],
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "3874c471-84a4-4058-8a35-0cc833b8617d",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.callback_query.data }}",
"rightValue": "feedback_yes"
}
]
}
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a53cc39a-2efc-4196-9425-1afba6fc8d7d",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.callback_query.data }}",
"rightValue": "feedback_no"
}
]
}
}
]
},
"options": {}
},
"typeVersion": 3.3
},
{
"id": "d9139b9c-e970-4c60-88b8-47bd105ab0aa",
"name": "Sí - Agradecimiento1",
"type": "n8n-nodes-base.telegram",
"position": [
-1264,
1008
],
"webhookId": "d64de90b-2449-4bcc-8815-f43c2e94c0b5",
"parameters": {
"text": "🎉 ¡Genial! Me alegra mucho saber que pude ayudarte 💪 Si necesitas algo más, estoy aquí para ti 🤖",
"chatId": "={{ $json.result.id }}",
"additionalFields": {
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "9c41f1ef-12bf-4620-9719-821461334db6",
"name": "No - Mensaje Feedback1",
"type": "n8n-nodes-base.telegram",
"position": [
-1264,
1152
],
"webhookId": "606e94b8-5fcd-4977-852e-d0030aacf8a6",
"parameters": {
"text": "=😔 Lamento que la información no te haya sido útil.\n¿Podrías contarme qué necesitabas exactamente para mejorar mi ayuda? 🤖",
"chatId": "={{ $json.callback_query.message.chat.id }}",
"forceReply": {
"force_reply": true
},
"replyMarkup": "forceReply",
"additionalFields": {
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "cc0258e3-303c-44e5-8c96-80287ba1ab88",
"name": "Comandos1",
"type": "n8n-nodes-base.switch",
"position": [
-1248,
1664
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "/help",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "62d19c71-6b1b-4d67-9a45-8aff710df07c",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json[\"message\"] && $json[\"message\"][\"text\"] === \"/help\" }}",
"rightValue": "/start"
}
]
},
"renameOutput": true
},
{
"outputKey": "/faqs",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "4a392957-3709-4c6e-a7ac-6f6004836860",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json[\"message\"] && $json[\"message\"][\"text\"] === \"/faqs\" }}",
"rightValue": ""
}
]
},
"renameOutput": true
},
{
"outputKey": "/contact",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "32b9b535-3e0a-4d3f-8691-fdc1e955a9ce",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json[\"message\"] && $json[\"message\"][\"text\"] === \"/contact\" }}",
"rightValue": ""
}
]
},
"renameOutput": true
},
{
"outputKey": "/events",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "9393b68b-7c44-48cf-b2d0-d23289673ff6",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json[\"message\"] && $json[\"message\"][\"text\"] === \"/events\" }}",
"rightValue": ""
}
]
},
"renameOutput": true
},
{
"outputKey": "/feedback",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "df2037b1-36f0-4428-bdd4-b5118f687d8f",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json[\"message\"] && $json[\"message\"][\"text\"] === \"/feedback\" }}",
"rightValue": ""
}
]
},
"renameOutput": true
},
{
"outputKey": "/start",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "58d0352b-c98a-4ac7-bf60-f6a6e6919744",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json[\"message\"] && $json[\"message\"][\"text\"] === \"/start\" }}",
"rightValue": ""
}
]
},
"renameOutput": true
}
]
},
"options": {},
"looseTypeValidation": true
},
"typeVersion": 3.3
},
{
"id": "79a97506-c1e2-4049-a62a-e5820b681a0d",
"name": "Inicio - Feedback",
"type": "n8n-nodes-base.telegram",
"position": [
-880,
1312
],
"webhookId": "c87b9628-09ed-468b-ab95-9b97e4c4f0ef",
"parameters": {
"text": "=💬 *Enviar Feedback*\n\n¿Tienes alguna sugerencia o comentario sobre el bot? \n\n📝 Escribe tu mensaje a continuación y lo revisaremos.\n\n🤖 *¡Tu opinión es importante!* \n\n💡 *Tip:* Los botones 👍 👎 aparecen después de cada respuesta para que califiques la información.",
"chatId": "={{ $json.message.chat.id }}",
"replyMarkup": "inlineKeyboard",
"inlineKeyboard": {
"rows": [
{
"row": {
"buttons": [
{
"text": "👍 Sí, me ayudó",
"additionalFields": {
"callback_data": "feedback_yes"
}
},
{
"text": "👎 No, no me ayudó",
"additionalFields": {
"callback_data": "feedback_no"
}
}
]
}
}
]
},
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "accfeb10-fcb6-4b1e-a1bb-a40ed82ebcdd",
"name": "FAQs",
"type": "n8n-nodes-base.telegram",
"position": [
-880,
784
],
"webhookId": "04237280-d7f2-4b1e-96cf-37343b850b8b",
"parameters": {
"text": "💬 Sección de Consultas – ChatBot ESPOL\n\nEstás en la sección de preguntas frecuentes.\nAquí puedes escribir cualquier duda o consulta sobre la universidad —por ejemplo, temas de matrícula, eventos, horarios o servicios estudiantiles— y te responderé enseguida.\n\n✍️ Escribe tu pregunta para continuar.\nPor ejemplo: “Necesito información sobre los eventos de esta semana” o “¿Dónde puedo comunicarme con admisiones?”",
"chatId": "={{ $('Telegram Trigger - Inicio').item.json.message.chat.id }}",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "ccd1f1ed-5aa2-402c-a40e-5650ee6066d7",
"name": "Leer FAQs de MongoDB",
"type": "n8n-nodes-base.mongoDb",
"position": [
-1520,
2448
],
"parameters": {
"options": {},
"collection": "espol_faqs"
},
"credentials": {
"mongoDb": {
"id": "gYgPeYFM9Twdvd1B",
"name": "MongoDB account 2"
}
},
"typeVersion": 1.2
},
{
"id": "6529ea5d-0af3-4acc-b81b-087730a484b6",
"name": "Telegram Trigger - Inicio",
"type": "n8n-nodes-base.telegramTrigger",
"position": [
-1872,
1744
],
"webhookId": "472c70d1-8362-4668-83dc-3e7c2985bc08",
"parameters": {
"updates": [
"message",
"callback_query",
"*"
],
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "5bfddfd3-92ac-432f-aa64-3f19ab58f30a",
"name": "Respuesta Contact",
"type": "n8n-nodes-base.telegram",
"position": [
-880,
960
],
"webhookId": "57a49a16-8222-4da4-8df5-06b6325c4083",
"parameters": {
"text": "=📞 *Información de Contacto ESPOL*\n\n🌐 Web: https://www.espol.edu.ec\n📧 Email: [email protected]\n📱 Teléfono: (04) 2269-269\n\n📍 Campus Gustavo Galindo Velasco\nKm. 30.5 Vía Perimetral\nGuayaquil, Ecuador\n\n📧 Otros contactos:\nComunicación: [email protected]\nRelaciones Externas y Vinculación Corporativa: [email protected]\nPostgrados: [email protected]\nTransparencia: [email protected]",
"chatId": "={{ $('Telegram Trigger - Inicio').item.json.message.chat.id }}",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "8aa3525d-815a-486a-92cb-9d9e5da991e5",
"name": "Respuesta Events",
"type": "n8n-nodes-base.telegram",
"position": [
-880,
1136
],
"webhookId": "8688a44d-b766-45c4-b314-3ef4dc105e1d",
"parameters": {
"text": "=📅 *Próximos Eventos Académicos*\n\nPara ver el calendario completo de eventos, visita:\nhttps://www.espol.edu.ec/es/calendario-academico\n\nO pregúntame sobre fechas específicas, por ejemplo:\n\"¿Cuándo son las vacaciones?\"\n\"¿Cuándo es el examen de admisión?\"",
"chatId": "={{ $json.message.chat.id }}",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "767c3c89-dfb0-4505-8c87-a0208fe31aba",
"name": "Limpiar texto Gemini",
"type": "n8n-nodes-base.code",
"position": [
-480,
2448
],
"parameters": {
"jsCode": "const geminiResponse = $input.first().json;\nconst chatId = $('Buscar_FAQs_Relevantes').first().json.chat_id;\n\nlet texto = '';\n\ntry {\n if (geminiResponse.candidates && geminiResponse.candidates[0] && geminiResponse.candidates[0].content && geminiResponse.candidates[0].content.parts) {\n texto = geminiResponse.candidates[0].content.parts[0].text;\n } else if (geminiResponse.content && geminiResponse.content.parts) {\n texto = geminiResponse.content.parts[0].text;\n } else if (geminiResponse.text) {\n texto = geminiResponse.text;\n } else {\n texto = \"Lo siento, hubo un problema al procesar la respuesta.\";\n }\n} catch (error) {\n texto = \"Lo siento, ocurrio un error.\";\n}\n\nif (texto && texto.length > 0) {\n texto = texto.replace(/\\*\\*/g, '').replace(/`/g, '').trim();\n}\n\nreturn [{\n json: {\n texto_limpio: texto,\n chat_id: chatId\n }\n}];"
},
"typeVersion": 2
},
{
"id": "7cabbbc4-e002-4c04-a4d9-b5c3633e98af",
"name": "Get a chat",
"type": "n8n-nodes-base.telegram",
"position": [
-1488,
1056
],
"webhookId": "4540ece6-38eb-40ac-bb7c-862f2e198807",
"parameters": {
"chatId": "={{ $json.callback_query.message.chat.id }}",
"resource": "chat"
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "b5cdf1bf-6f19-4e62-a812-022e5fe6073c",
"name": "Switch_Que_BD_Leer",
"type": "n8n-nodes-base.switch",
"position": [
-1856,
2304
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "=Consultar Calendario",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "9404fad9-6063-443e-9a4c-80c86a20a6a9",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.es_consulta_calendario }}",
"rightValue": ""
}
]
},
"renameOutput": true
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"typeVersion": 3.3
},
{
"id": "09ddecdf-9223-4371-bd53-9d836315b7a3",
"name": "Detector_Calendario_Pre",
"type": "n8n-nodes-base.code",
"position": [
-2064,
2304
],
"parameters": {
"jsCode": "// ===== DETECTOR PRE-LECTURA DE CALENDARIO =====\n\nfunction limpiarTexto(texto) {\n if (!texto) return '';\n return texto\n .toLowerCase()\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[¿?¡!.,;:]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n// Procesar todos los items entrantes del Trigger\nconst inputItems = $('Telegram Trigger - Inicio').all();\nconst resultados = [];\n\nfor (const item of inputItems) {\n const preguntaUsuario = item.json.message?.text || '';\n const chatId = item.json.message?.chat?.id || '';\n const preguntaLimpia = limpiarTexto(preguntaUsuario);\n\n // ===== PALABRAS CLAVE DE CALENDARIO =====\n const palabrasClaveCalendario = {\n eventos: [\n 'matricula', 'matriculas', 'matriculacion',\n 'inicio de clases', 'fin de clases', 'clases',\n 'primer termino', 'segundo termino', 'tercer termino',\n 'examenes', 'examen', 'prueba', 'pruebas',\n 'evaluaciones', 'evaluacion',\n 'suspension', 'feriado', 'feriados',\n 'vacaciones', 'receso',\n 'cambio de carrera', 'cambio de materia',\n 'retiro', 'retiros',\n 'solicitudes', 'solicitud',\n 'inscripciones', 'inscripcion',\n 'graduacion', 'grado',\n 'titulacion',\n 'becas', 'beca',\n 'nivelacion'\n ],\n \n tiempo: [\n 'cuando', 'fecha', 'fechas', 'dia', 'dias',\n 'plazo', 'plazos', 'periodo', 'periodos',\n 'termino', 'terminos', 'semestre', 'año',\n 'inicio', 'fin', 'cierre', 'apertura',\n 'hasta cuando', 'desde cuando',\n 'calendario', 'cronograma',\n 'horario', 'horarios',\n 'proxima', 'proximo', 'siguiente'\n ],\n \n terminos: [\n '2024-2025', '2025-2026',\n 'PAO', 'PAOI', 'PAOII', 'IPAO', 'IIPAO', 'PAO1', 'PAO2',\n 'primer', 'segundo', 'tercer',\n 'trimestre', 'bimestre', 'semestral'\n ]\n };\n\n // Combinar todas las palabras clave\n const todasLasPalabrasClave = [\n ...palabrasClaveCalendario.eventos,\n ...palabrasClaveCalendario.tiempo,\n ...palabrasClaveCalendario.terminos\n ];\n\n // ===== DETECCIÓN =====\n let palabrasEncontradas = [];\n let puntuacion = 0;\n\n for (const palabra of todasLasPalabrasClave) {\n if (preguntaLimpia.includes(palabra)) {\n palabrasEncontradas.push(palabra);\n if (palabrasClaveCalendario.eventos.includes(palabra)) {\n puntuacion += 2;\n } else {\n puntuacion += 1;\n }\n }\n }\n\n // Decisión: ¿Es consulta de calendario?\n const esConsultaCalendario = puntuacion >= 2 || palabrasEncontradas.length >= 2;\n\n // LOGS\n console.log(`========================================`);\n console.log(`🔍 DETECTOR PRE-LECTURA DE CALENDARIO`);\n console.log(`Pregunta: \"${preguntaUsuario}\"`);\n console.log(`Palabras encontradas: ${palabrasEncontradas.join(', ') || 'ninguna'}`);\n console.log(`Puntuación: ${puntuacion}`);\n console.log(`¿Es calendario? ${esConsultaCalendario ? '✅ SÍ' : '❌ NO'}`);\n console.log(`${esConsultaCalendario ? '→ Irá a BD_Calendario' : '→ Irá a FAQs (flujo normal)'}`);\n console.log(`========================================`);\n\n resultados.push({\n json: {\n pregunta_usuario: preguntaUsuario,\n chat_id: chatId,\n es_consulta_calendario: esConsultaCalendario,\n palabras_encontradas: palabrasEncontradas,\n puntuacion: puntuacion\n }\n });\n}\n\nreturn resultados;"
},
"typeVersion": 2
},
{
"id": "81ea9f6c-121c-4bc5-99e4-e947dae8bf7a",
"name": "Mensaje de Gemini",
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"position": [
-800,
2448
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "models/gemini-2.5-flash-lite-preview-06-17",
"cachedResultName": "models/gemini-2.5-flash-lite-preview-06-17"
},
"options": {
"temperature": 0.3,
"maxOutputTokens": 2000
},
"messages": {
"values": [
{
"content": "={{ $json.prompt }}"
}
]
},
"simplify": false
},
"credentials": {
"googlePalmApi": {
"id": "5akz9ZmFGR6TDmkP",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1,
"alwaysOutputData": false
},
{
"id": "8116c6c4-e4ca-4450-89ab-17df67b4b886",
"name": "Enviar Respuesta sobre faqs",
"type": "n8n-nodes-base.telegram",
"position": [
-224,
2448
],
"webhookId": "0096bd46-0086-4c23-87a8-a63bfebca2d8",
"parameters": {
"text": "={{ $json.texto_limpio }}\n\n💬 ¿Deseas revisar otra consulta o hacer una nueva pregunta?",
"chatId": "={{ $json.chat_id }}",
"replyMarkup": "inlineKeyboard",
"inlineKeyboard": {
"rows": [
{
"row": {
"buttons": [
{
"text": "👍 Sí",
"additionalFields": {
"callback_data": "=feedback_yes"
}
},
{
"text": "👎 No",
"additionalFields": {
"callback_data": "=feedback_no"
}
}
]
}
}
]
},
"additionalFields": {
"parse_mode": "HTML",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "5f4eed47-a777-4d27-a80a-bcc145808f42",
"name": "Guardado en cvs Feedback",
"type": "n8n-nodes-base.googleSheets",
"position": [
-1264,
1296
],
"parameters": {
"columns": {
"value": {
"id": "={{ $json.message.chat.id }}",
"Nombre": "={{ $json.message.reply_to_message.chat.first_name }}",
"Comentario": "={{ $json.message.text }}"
},
"schema": [
{
"id": "id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "id",
"defaultMatch": true,
"canBeUsedToMatch": true
},
{
"id": "Nombre",
"type": "string",
"display": true,
"required": false,
"displayName": "Nombre",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Comentario",
"type": "string",
"display": true,
"required": false,
"displayName": "Comentario",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1dcEsIPMMnjBtQ1YXy5Zc50OWwBVOcWXpxkJUJWZmE4E/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1dcEsIPMMnjBtQ1YXy5Zc50OWwBVOcWXpxkJUJWZmE4E",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1dcEsIPMMnjBtQ1YXy5Zc50OWwBVOcWXpxkJUJWZmE4E/edit?usp=drivesdk",
"cachedResultName": "Feedback"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "uBrRRX1SVbzPprTl",
"name": "Google Sheets account 6"
}
},
"typeVersion": 4.7
},
{
"id": "cdf6ad08-7f31-467e-9feb-9d3a8e1c9ba6",
"name": "Construir_Prompt_Calendario1",
"type": "n8n-nodes-base.code",
"position": [
-1008,
2160
],
"parameters": {
"jsCode": "const preguntaUsuario = $json.consulta || $json.pregunta_usuario || $json.pregunta || '';\nconst chatId = $json.chat_id;\n\n// Preferir el nuevo esquema del buscador\nlet eventos = Array.isArray($json.resultados) ? $json.resultados : [];\n\n// Compatibilidad con el arreglo antiguo { eventos_calendario: [...] }\nif ((!eventos || eventos.length === 0) && Array.isArray($json.eventos_calendario)) {\n eventos = $json.eventos_calendario.map((e) => ({\n PERIODO_FECHAS: e.periodo || e.PERIODO_FECHAS || '',\n ACTIVIDADES_GRADO: e.actividades_grado || e.ACTIVIDADES_GRADO || '',\n PROCESOS_GRADO: e.procesos_grado || e.PROCESOS_GRADO || '',\n ACTIVIDADES_FORMACION: e.actividades_formacion || e.ACTIVIDADES_FORMACION || '',\n fecha_inicio_ts: e.fecha_inicio_ts ?? null,\n score: e.score ?? null,\n }));\n}\n\n// Señal de “demasiadas coincidencias” y facetas sugeridas (si vienen del buscador)\nconst demasiadas = Boolean($json.demasiadas_coincidencias);\nconst filtros = $json.filtros_sugeridos || null;\n\n// === Funciones auxiliares ===\nfunction safe(v) { return (v == null ? '' : String(v)); }\n\nfunction fmtFecha(ts) {\n if (ts == null) return '';\n try {\n const d = new Date(Number(ts));\n if (isNaN(d.getTime())) return '';\n const dd = String(d.getDate()).padStart(2, '0');\n const mm = String(d.getMonth() + 1).padStart(2, '0');\n const yyyy = d.getFullYear();\n return `${yyyy}-${mm}-${dd}`;\n } catch { return ''; }\n}\n\n// === Determinar PAO actual según la fecha del sistema ===\nconst ahora = new Date();\nconst mesActual = ahora.getMonth() + 1;\nconst anioActual = ahora.getFullYear();\n\nlet paoActual = '';\nif (mesActual >= 5 && mesActual <= 9) paoActual = 'PAO I';\nelse if (mesActual >= 9 || mesActual <= 2) paoActual = 'PAO II';\nelse paoActual = 'Vacaciones o PAE';\n\nconst contextoTiempo = `📆 Fecha actual: ${anioActual}-${String(mesActual).padStart(2, '0')} (${paoActual} en curso)`;\n\n// === Ordenar eventos por relevancia y fecha ===\nconst eventosOrdenados = [...eventos].sort((a, b) => {\n const sa = (a.score == null) ? -Infinity : Number(a.score);\n const sb = (b.score == null) ? -Infinity : Number(b.score);\n if (sb !== sa) return sb - sa;\n\n const ta = (a.fecha_inicio_ts == null) ? Number.POSITIVE_INFINITY : Number(a.fecha_inicio_ts);\n const tb = (b.fecha_inicio_ts == null) ? Number.POSITIVE_INFINITY : Number(b.fecha_inicio_ts);\n return ta - tb;\n});\n\nlet contextoCalendario = '';\n\nif (eventosOrdenados.length > 0) {\n contextoCalendario = '📅 He encontrado estas coincidencias (ordenadas por relevancia):\\n\\n';\n\n // Mostrar solo el top 10\n const toShow = eventosOrdenados.slice(0, 10);\n\n contextoCalendario += toShow.map((ev, idx) => {\n const periodo = safe(ev.PERIODO_FECHAS || ev.periodo);\n const ag = safe(ev.ACTIVIDADES_GRADO || ev.actividades_grado);\n const pg = safe(ev.PROCESOS_GRADO || ev.procesos_grado);\n const af = safe(ev.ACTIVIDADES_FORMACION || ev.actividades_formacion);\n const fechaISO = fmtFecha(ev.fecha_inicio_ts);\n const score = (ev.score != null) ? Number(ev.score).toFixed(3) : '';\n\n let info = `${idx + 1}. 📌 ${periodo || '(sin fecha)'}`;\n if (fechaISO) info += `\\n 🗓️ Inicio (estimado): ${fechaISO}`;\n if (ag.trim()) info += `\\n 🎓 Actividades de Grado: ${ag}`;\n if (pg.trim()) info += `\\n 📋 Procesos: ${pg}`;\n if (af.trim()) info += `\\n 🔧 Formación Técnica: ${af}`;\n if (score) info += `\\n 🔎 score:${score}`;\n return info;\n }).join('\\n\\n');\n\n // Sugerencias si hay demasiadas coincidencias\n if (demasiadas) {\n const tips = [];\n if (filtros?.posibles_anios?.length) tips.push(`• especifica año: ${filtros.posibles_anios.slice(-3).join(' / ')}`);\n if (filtros?.posibles_meses?.length) tips.push(`• añade mes: ${filtros.posibles_meses.slice(0, 4).join(' / ')}`);\n if (filtros?.posibles_tipos_evaluacion?.length) tips.push(`• tipo de evaluación: ${filtros.posibles_tipos_evaluacion.join(' / ')}`);\n if (filtros?.posibles_ciclos?.length) tips.push(`• ciclo: ${filtros.posibles_ciclos.join(' / ')}`);\n\n if (tips.length) {\n contextoCalendario += '\\n\\n⚠️ Hay muchas coincidencias. Sugerencias para refinar:\\n' + tips.join('\\n');\n }\n }\n\n} else {\n contextoCalendario = 'No encontré información específica en el calendario académico para tu consulta.';\n}\n\n// === Reglas de interpretación PAO/PAE ===\nconst reglasPAO = `\nREGLAS DE INTERPRETACIÓN (PAO/PAE y feriados):\n• Si un evento dice “vacaciones” y la fecha cae ENTRE finales de febrero y los primeros días de mayo ⇒ corresponde a VACACIONES del PAO II.\n• Desde inicios de mayo hasta inicios/mediados de septiembre ⇒ corresponde a PAO I.\n• Si un evento cae entre finales de septiembre y la 1.ª o 2.ª semana de febrero ⇒ corresponde a PAO II (período lectivo).\n• En PAO II, si aparecen “vacaciones estudiantiles” del 28 de diciembre al 1 de enero ⇒ son feriado (no vacaciones entre PAOs).\n• PAE (Programa de Acompañamiento/Evaluación) ocurre normalmente desde inicios de marzo hasta finales de abril o inicios de mayo.\n• Si dentro de un PAO aparece “vacaciones”, interprétalas como feriados específicos, no como cambio de PAO.\n• Si el evento no indica explícitamente PAO I o II, infiérelo con estas reglas. Si hay ambigüedad o la fecha cae fuera de rango, indica que no hay información específica.\n`.trim();\n\n// === Construcción final del prompt ===\nconst prompt = `Eres un asistente de ESPOL especializado en calendario académico.\n\n${contextoTiempo}\n\nPregunta del estudiante:\n\"${preguntaUsuario}\"\n\n${contextoCalendario}\n\n${reglasPAO}\n\nINSTRUCCIONES IMPORTANTES (ESTRICTAS):\n1) Si existe la lista anterior, elige SOLO 1 evento que sea la MEJOR COINCIDENCIA para la pregunta.\n • Considera intención (evaluación/vacaciones/inicio/fin), ordinal (primera/segunda/tercera),\n y año/mes implícitos o explícitos.\n • Si ningún evento es claramente pertinente, responde que NO hay información específica.\n2) No inventes datos. No cites eventos que no estén arriba.\n3) Si das un resultado, preséntalo claro y breve con emojis, destacando FECHA y ACTIVIDAD.\n4) Si NO hay información específica, sugiere contactar a:\n 📧 Email: [email protected]\n 📞 Teléfono: (04) 2269-269\n 🌐 Web: https://www.espol.edu.ec\n5) Si identificas el PAO (I o II) por las fechas, indícalo entre paréntesis o nota breve (“según fechas corresponde al PAO I/II”).\n6) Responde de forma clara, organizada y útil.`;\n\n// === Salida para el siguiente nodo (Gemini) ===\nreturn [{\n json: {\n prompt,\n chat_id: chatId\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "728f012c-ceed-400d-a643-8f1353d9f8f1",
"name": "Message a model1",
"type": "@n8n/n8n-nodes-langchain.googleGemini",
"position": [
-800,
2160
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "models/gemini-2.5-flash-preview-05-20",
"cachedResultName": "models/gemini-2.5-flash-preview-05-20"
},
"options": {
"temperature": 0.3,
"maxOutputTokens": 200000000
},
"messages": {
"values": [
{
"content": "={{ $json.prompt }}"
}
]
},
"simplify": false
},
"credentials": {
"googlePalmApi": {
"id": "5akz9ZmFGR6TDmkP",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "4af01328-9345-4714-a79d-ea81b2a16a38",
"name": "Limpiar_Texto_Gemini1",
"type": "n8n-nodes-base.code",
"position": [
-480,
2160
],
"parameters": {
"jsCode": "// ---- 1) Extracción robusta del texto de Gemini ----\nfunction extraerTextoGemini(payload) {\n // Formato más común: candidates[0].content.parts[*].text\n try {\n const parts = payload?.candidates?.[0]?.content?.parts;\n if (Array.isArray(parts) && parts.length > 0) {\n // Concatenar todos los parts .text\n const textos = parts\n .map(p => (typeof p?.text === 'string' ? p.text : ''))\n .filter(Boolean);\n if (textos.length) return textos.join('\\n').trim();\n }\n } catch {}\n\n // Alternativos:\n if (typeof payload?.text === 'string' && payload.text.trim()) {\n return payload.text.trim();\n }\n\n if (Array.isArray(payload?.content?.parts) && payload.content.parts.length > 0) {\n const textos2 = payload.content.parts\n .map(p => (typeof p?.text === 'string' ? p.text : ''))\n .filter(Boolean);\n if (textos2.length) return textos2.join('\\n').trim();\n }\n\n // Último recurso: stringify recortado\n try {\n const s = JSON.stringify(payload);\n if (s && s.length) return s.substring(0, 8000);\n } catch {}\n\n return 'Lo siento, no pude procesar tu consulta.';\n}\n\nconst respuestaGemini = extraerTextoGemini($json);\n\n// ---- 2) Recuperar chat_id desde varios nodos/ubicaciones ----\nlet chatId = $json.chat_id;\n\nif (!chatId) {\n // Tu prompt actual\n try { chatId = $('Construir_Prompt_Calendario1').first()?.json?.chat_id; } catch {}\n}\nif (!chatId) {\n // Nombre anterior\n try { chatId = $('Construir_Prompt_Calendario').first()?.json?.chat_id; } catch {}\n}\nif (!chatId) {\n try { chatId = $('Buscar_Eventos_Calendario').first()?.json?.chat_id; } catch {}\n}\nif (!chatId) {\n try { chatId = $('Detector_Calendario_Pre').first()?.json?.chat_id; } catch {}\n}\nif (!chatId) {\n // Telegram Trigger\n try { chatId = $('Telegram Trigger - Inicio').first()?.json?.message?.chat?.id; } catch {}\n}\n\n// ---- 3) Limpieza / saneamiento para enviar por Telegram con parse_mode HTML ----\nfunction escapeHTML(s) {\n // Evita que Telegram interprete etiquetas si usas parse_mode: \"HTML\"\n return s.replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n}\n\nfunction limpiarRespuesta(texto) {\n if (!texto) return 'Lo siento, no obtuve una respuesta válida.';\n\n // Eliminar bloques de código ```...``` (incluye variantes con lenguaje)\n texto = texto.replace(/```[\\s\\S]*?```/g, '');\n\n // Eliminar comillas invertidas inline `\n texto = texto.replace(/`+/g, '');\n\n // Convertir enlaces markdown [texto](url) → \"texto (url)\"\n texto = texto.replace(/\\[([^\\]]+)\\]\\((https?:\\/\\/[^\\s)]+)\\)/g, '$1 ($2)');\n\n // Quitar markdown simple de estilo: **negritas**, *itálicas*, __subrayado__ _\n texto = texto.replace(/\\*\\*(.*?)\\*\\*/g, '$1')\n .replace(/\\*(.*?)\\*/g, '$1')\n .replace(/__(.*?)__/g, '$1')\n .replace(/_(.*?)_/g, '$1');\n\n // Quitar encabezados markdown (##, ###, etc.)\n texto = texto.replace(/^\\s{0,3}#{1,6}\\s+/gm, '');\n\n // Normalizar saltos y espacios\n texto = texto.replace(/\\r/g, '')\n .replace(/\\n{3,}/g, '\\n\\n')\n .replace(/[ \\t]+\\n/g, '\\n')\n .trim();\n\n // Escapar HTML para Telegram (parse_mode: \"HTML\")\n texto = escapeHTML(texto);\n\n // Límite prudente para Telegram (4096 es el duro). Dejamos margen.\n const LIMITE = 3500;\n if (texto.length > LIMITE) {\n texto = texto.substring(0, LIMITE - 20).trimEnd() + '… (mensaje acortado)';\n }\n\n return texto;\n}\n\nconst textoLimpio = limpiarRespuesta(respuestaGemini);\n\n// ---- 4) Logs útiles ----\nconsole.log(`========================================`);\nconsole.log(`🧹 LIMPIEZA DE TEXTO`);\nconsole.log(`Chat ID encontrado: ${chatId}`);\nconsole.log(`Texto original (100): ${String(respuestaGemini).substring(0, 100)}...`);\nconsole.log(`Texto limpio (100): ${textoLimpio.substring(0, 100)}...`);\nconsole.log(`========================================`);\n\n// ---- 5) Salida ----\nreturn [{\n json: {\n texto_limpio: textoLimpio,\n chat_id: chatId,\n respuesta_original: respuestaGemini\n }\n}];"
},
"typeVersion": 2
},
{
"id": "84c7ffb2-d72d-432a-ac54-9892d31ac5b9",
"name": "Enviar Respuesta sobre calendario1",
"type": "n8n-nodes-base.telegram",
"position": [
-240,
2160
],
"webhookId": "c4b9e9ad-3d55-4653-b1eb-e4621c6425d8",
"parameters": {
"text": "={{ $json.texto_limpio }}\n\n💬 ¿Deseas revisar otra consulta o hacer una nueva pregunta?",
"chatId": "={{ $json.chat_id }}",
"replyMarkup": "inlineKeyboard",
"inlineKeyboard": {
"rows": [
{
"row": {
"buttons": [
{
"text": "👍 Sí",
"additionalFields": {
"callback_data": "=feedback_yes"
}
},
{
"text": "👎 No",
"additionalFields": {
"callback_data": "=feedback_no"
}
}
]
}
}
]
},
"additionalFields": {
"parse_mode": "HTML",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "144e290f-5f0a-400b-b82b-269d90d5f32a",
"name": "Calendario Académico",
"type": "n8n-nodes-base.googleSheets",
"position": [
-1520,
2160
],
"parameters": {
"options": {
"outputFormatting": {
"values": {
"date": "FORMATTED_STRING",
"general": "UNFORMATTED_VALUE"
}
},
"returnFirstMatch": false
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1817620056,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo/edit#gid=1817620056",
"cachedResultName": "CALENDAR"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo/edit?usp=drivesdk",
"cachedResultName": "BASE-DATOS-FAQ_ESPOL"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "aXzbiC4KJQoHD5SM",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "21267be1-e42b-4362-aefc-42bf96705258",
"name": "Buscar_Eventos_Calendario1",
"type": "n8n-nodes-base.code",
"position": [
-1264,
2160
],
"parameters": {
"jsCode": "const detectorItems = $('Detector_Calendario_Pre').all();\nconst detector =\n Array.isArray(detectorItems) && detectorItems.length > 0\n ? detectorItems[0].json\n : {};\nconst preguntaUsuario = detector.pregunta_usuario || '';\nconst chatId = detector.chat_id ?? null;\nconst filas = $input.all().map(it => it.json || {});\n\nconst HOY = new Date();\nconst ANIO_ACTUAL = HOY.getFullYear();\nconst ANIO_SIG = ANIO_ACTUAL + 1;\n\n// ---------- helpers ----------\nfunction limpiarTexto(t) {\n if (!t) return '';\n return String(t)\n .toLowerCase()\n .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n .replace(/[^a-z0-9ñ\\s]/gi, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nconst STOP = new Set([\n \"de\",\"la\",\"que\",\"el\",\"en\",\"y\",\"a\",\"los\",\"del\",\"se\",\"las\",\"por\",\"un\",\"para\",\"con\",\"no\",\"una\",\n \"su\",\"al\",\"lo\",\"como\",\"mas\",\"más\",\"pero\",\"sus\",\"le\",\"ya\",\"o\",\"fue\",\"ha\",\"si\",\"sí\",\"porque\",\n \"muy\",\"sin\",\"sobre\",\"tambien\",\"también\",\"entre\",\"cuando\",\"todo\",\"esta\",\"está\",\"ser\",\"son\",\n \"dos\",\"han\",\"hay\",\"donde\",\"quien\",\"quién\",\"desde\",\"cada\",\"cual\",\"cuál\",\"cuales\",\"cuáles\"\n]);\nfunction tokenizar(t) { return limpiarTexto(t).split(' ').filter(x => x && x.length > 2 && !STOP.has(x)); }\n\nfunction baseEs(w) {\n w = limpiarTexto(w);\n if (!w) return '';\n w = w.replace(/\\b1(ra|era)\\b/g, 'primera')\n .replace(/\\b2da\\b/g, 'segunda')\n .replace(/\\b3ra\\b/g, 'tercera');\n if (w.endsWith('es')) w = w.slice(0, -2);\n else if (w.endsWith('s')) w = w.slice(0, -1);\n w = w.replace(/evaluacione?$/,'evaluacion');\n return w;\n}\nfunction matchToken(userTok, rowTok) {\n const u = baseEs(userTok), r = baseEs(rowTok);\n if (!u || !r) return false;\n if (u === r) return true;\n if (u.length >= 5 && (r.includes(u) || u.includes(r))) return true;\n return false;\n}\n\nfunction detectarIntencion(pregunta) {\n const p = limpiarTexto(pregunta);\n if (/(vacaciones|receso|feriado|descanso)/.test(p)) return \"vacaciones\";\n if (/(evaluacion|evaluación|examen|parcial|prueba|evaluar|evaluaciones)/.test(p)) return \"evaluacion\";\n if (/(inicio|empieza|matriculacion|matriculación|induccion|inducción|apertura|arranque)/.test(p)) return \"inicio\";\n if (/(final|fin|cierre|termina|culmina|conclusion|conclusión)/.test(p)) return \"fin\";\n if (/(eleccion|elección|votacion|votación)/.test(p)) return \"elecciones\";\n return \"otro\";\n}\nfunction detectarOrdinal(p) {\n const t = limpiarTexto(p);\n if (/\\b(primera|1ra|1era)\\b/.test(t) || /\\bevaluacion\\s*i\\b/.test(t)) return 1;\n if (/\\b(segunda|2da)\\b/.test(t) || /\\bevaluacion\\s*ii\\b/.test(t)) return 2;\n if (/\\b(tercera|3ra)\\b/.test(t) || /\\bevaluacion\\s*iii\\b/.test(t)) return 3;\n return 0;\n}\n\nconst MESES = { ene:0, enero:0, feb:1, febrero:1, mar:2, marzo:2, abr:3, abril:3, may:4, mayo:4, jun:5, junio:5, jul:6, julio:6, ago:7, agosto:7, sep:8, sept:8, septiembre:8, oct:9, octubre:9, nov:10, noviembre:10, dic:11, diciembre:11 };\n\nfunction normalizarPeriodo(p) {\n if (!p) return '';\n return String(p).replace(/([0-9])([a-zA-Z])/g, '$1 $2').replace(/\\s*-\\s*/g, ' - ').replace(/\\s+/g, ' ').trim();\n}\nfunction parseFechaInicio(periodo) {\n const txt = normalizarPeriodo(periodo).toLowerCase();\n\n // \"13 - 17 julio 2026\"\n let m = /(\\d{1,2})\\s*-\\s*(\\d{1,2})\\s+([a-záéíóúñ]+)\\s+(\\d{4})/.exec(txt);\n if (m) {\n const d1 = parseInt(m[1], 10);\n const mes = MESES[m[3].normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')];\n const year = parseInt(m[4], 10);\n if (mes != null) return new Date(year, mes, d1, 0, 0, 0, 0).getTime();\n }\n\n // \"30 marzo - 03 abril 2026\"\n m = /(\\d{1,2})\\s+([a-záéíóúñ]+)\\s*-\\s*(\\d{1,2})\\s+([a-záéíóúñ]+)\\s+(\\d{4})/.exec(txt);\n if (m) {\n const d1 = parseInt(m[1], 10);\n const mes1 = MESES[m[2].normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')];\n const year = parseInt(m[5], 10);\n if (mes1 != null) return new Date(year, mes1, d1, 0, 0, 0, 0).getTime();\n }\n\n // sin año → asume actual\n m = /(\\d{1,2})\\s*-\\s*(\\d{1,2})\\s+([a-záéíóúñ]+)/.exec(txt);\n if (m) {\n const d1 = parseInt(m[1], 10);\n const mes1 = MESES[m[3].normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')];\n if (mes1 != null) return new Date(ANIO_ACTUAL, mes1, d1, 0, 0, 0, 0).getTime();\n }\n m = /(\\d{1,2})\\s+([a-záéíóúñ]+)\\s*-\\s*(\\d{1,2})\\s+([a-záéíóúñ]+)/.exec(txt);\n if (m) {\n const d1 = parseInt(m[1], 10);\n const mes1 = MESES[m[2].normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')];\n if (mes1 != null) return new Date(ANIO_ACTUAL, mes1, d1, 0, 0, 0, 0).getTime();\n }\n return null;\n}\nfunction extraerAniosTexto(texto) {\n const t = String(texto || '');\n const m = t.match(/\\b(20\\d{2})\\b/g);\n if (!m) return new Set();\n return new Set(m.map(x => parseInt(x, 10)));\n}\nfunction extraerAniosPregunta(p) { return extraerAniosTexto(p); }\nfunction extraerMesesPregunta(p) {\n const t = limpiarTexto(p);\n const meses = new Set();\n Object.keys(MESES).forEach(k => { if (new RegExp(`\\\\b${k}\\\\b`).test(t)) meses.add(MESES[k]); });\n return meses;\n}\nfunction yearFromTs(ts) { if (ts == null) return null; try { return new Date(ts).getFullYear(); } catch { return null; } }\n\nconst intencion = detectarIntencion(preguntaUsuario);\nconst ordinalBuscado = detectarOrdinal(preguntaUsuario);\nconst tokensUsuario = tokenizar(preguntaUsuario);\nconst setTokens = new Set(tokensUsuario);\nconst aniosPregunta = extraerAniosPregunta(preguntaUsuario);\nconst mesesPregunta = extraerMesesPregunta(preguntaUsuario);\nconst HARD_FILTER_BY_YEAR = aniosPregunta.size > 0;\n\n// ---------- scoring ----------\nconst PESOS = { PERIODO_FECHAS: 0.8, ACTIVIDADES_GRADO: 1.0, PROCESOS_GRADO: 0.6, ACTIVIDADES_FORMACION: 0.9 };\nconst BONUS_INTENCION = 2.2;\nconst BONUS_FRASE_EXACTA = 1.0;\nconst BONUS_TIENE_EVALUACION = 0.7;\nconst BONUS_ORDINAL_MATCH = 1.5;\nconst PENALIZA_ORD_DISTINTO = 0.5;\nconst PENALIZA_SIN_EVAL_CON_ORD = 0.35;\n\nconst BONUS_ANIO_ACTUAL_STRONG = 1.1;\nconst PENALIZA_ANIO_NO_ACTUAL = 0.6;\nconst BONUS_MATCH_ANIO_PREGUNTA = 1.2;\nconst BONUS_MATCH_MES_ACTUAL = 0.6;\nconst BONUS_ANIO_EXPLICITO = 0.3;\n\nconst UMBRAL_SCORE_MIN = 0;\nconst UMBRAL_DEMASIADAS = 25; // ← si hay más que esto, sugerimos refinar\n\nif (!preguntaUsuario || !limpiarTexto(preguntaUsuario)) {\n return [{ json: { ok:false, motivo:\"pregunta_vacia\", mensaje:\"No se recibió una pregunta (pregunta_usuario).\", chat_id:chatId } }];\n}\nif (!Array.isArray(filas) || filas.length === 0) {\n return [{ json: { ok:false, motivo:\"sin_datos\", mensaje:\"No llegaron filas desde Google Sheets.\", chat_id:chatId } }];\n}\n\nfunction filaTokens(row) {\n const F = (k) => { const v = row[k]; return (v == null) ? '' : String(v); };\n const actTecKey = 'ACTIVIDADES DE FORMACIÓN TÉCNICA Y TECNOLÓGICA';\n const campos = {\n row_number: row.row_number ?? null,\n PERIODO_FECHAS: F('PERIODO_FECHAS'),\n ACTIVIDADES_GRADO: F('ACTIVIDADES_GRADO'),\n PROCESOS_GRADO: F('PROCESOS_GRADO'),\n ACTIVIDADES_FORMACION: F(actTecKey) || F('ACTIVIDADES_FORMACION') || ''\n };\n const textoTotal = [campos.PERIODO_FECHAS, campos.ACTIVIDADES_GRADO, campos.PROCESOS_GRADO, campos.ACTIVIDADES_FORMACION].filter(Boolean).join(' | ');\n const normTotal = limpiarTexto(textoTotal);\n const toks = {\n PERIODO_FECHAS: tokenizar(campos.PERIODO_FECHAS),\n ACTIVIDADES_GRADO: tokenizar(campos.ACTIVIDADES_GRADO),\n PROCESOS_GRADO: tokenizar(campos.PROCESOS_GRADO),\n ACTIVIDADES_FORMACION: tokenizar(campos.ACTIVIDADES_FORMACION),\n TOTAL: tokenizar(textoTotal)\n };\n const aniosTexto = extraerAniosTexto(textoTotal);\n const tsInicio = parseFechaInicio(campos.PERIODO_FECHAS);\n const anioTs = yearFromTs(tsInicio);\n const aniosFila = new Set(aniosTexto);\n if (anioTs != null) aniosFila.add(anioTs);\n let mesInicio = null;\n if (tsInicio != null) mesInicio = new Date(tsInicio).getMonth();\n\n return { campos, textoTotal, normTotal, toks, aniosFila, tsInicio, mesInicio };\n}\nfunction filaTieneOrdinalEvaluacion(norm) {\n const hasEval = /\\bevaluacion\\b/.test(norm);\n const primera = hasEval && (/\\bprimera\\b/.test(norm) || /\\bevaluacion\\s*i\\b/.test(norm) || /\\bi\\s*evaluacion\\b/.test(norm));\n const segunda = hasEval && (/\\bsegunda\\b/.test(norm) || /\\bevaluacion\\s*ii\\b/.test(norm) || /\\bii\\s*evaluacion\\b/.test(norm));\n const tercera = hasEval && (/\\btercera\\b/.test(norm) || /\\bevaluacion\\s*iii\\b/.test(norm) || /\\biii\\s*evaluacion\\b/.test(norm));\n return { hasEval, primera, segunda, tercera };\n}\nfunction intersect(setA, setB) { for (const x of setA) if (setB.has(x)) return true; return false; }\n\nfunction puntuar(row) {\n const { campos, textoTotal, normTotal, toks, aniosFila, tsInicio, mesInicio } = filaTokens(row);\n if (!normTotal) return null;\n\n if (HARD_FILTER_BY_YEAR) {\n if (aniosFila.size > 0 && !intersect(aniosFila, aniosPregunta)) return null;\n // si no hay año en la fila, la dejamos pasar con score bajo (puedes excluirla retornando null)\n }\n\n let score = 0, coincidencias = 0;\n const matchPorCampo = { PERIODO_FECHAS: [], ACTIVIDADES_GRADO: [], PROCESOS_GRADO: [], ACTIVIDADES_FORMACION: [] };\n const matchTokens = [];\n\n for (const t of setTokens) {\n let hit = false;\n for (const campo of Object.keys(PESOS)) {\n const arr = toks[campo];\n if (arr && arr.some(rt => matchToken(t, rt))) {\n score += PESOS[campo];\n matchPorCampo[campo].push(t);\n hit = true;\n }\n }\n if (hit) { coincidencias++; matchTokens.push(t); }\n }\n\n const INTENT_TERMS = {\n vacaciones: [\"vacaciones\",\"receso\",\"feriado\",\"descanso\",\"estudiantiles\"],\n evaluacion: [\"evaluacion\",\"evaluaciones\",\"examen\",\"parcial\",\"prueba\",\"ciclo\",\"semana de evaluacion\"],\n inicio: [\"inicio\",\"empieza\",\"apertura\",\"induccion\",\"matriculacion\",\"novatos\"],\n fin: [\"final\",\"fin\",\"cierre\",\"termina\",\"culmina\",\"conclusion\",\"proceso final\"],\n elecciones: [\"eleccion\",\"votacion\"]\n };\n if (intencion !== 'otro') {\n const terms = INTENT_TERMS[intencion] || [];\n if (terms.some(term => normTotal.includes(limpiarTexto(term)))) score += BONUS_INTENCION;\n }\n\n const ord = filaTieneOrdinalEvaluacion(normTotal);\n if (intencion === 'evaluacion' && ord.hasEval) score += BONUS_TIENE_EVALUACION;\n if (ordinalBuscado) {\n const ok = (ordinalBuscado === 1 && ord.primera) || (ordinalBuscado === 2 && ord.segunda) || (ordinalBuscado === 3 && ord.tercera);\n if (ok) score += BONUS_ORDINAL_MATCH;\n else {\n if (ord.primera || ord.segunda || ord.tercera) score -= PENALIZA_ORD_DISTINTO;\n if (!ord.hasEval) score -= PENALIZA_SIN_EVAL_CON_ORD;\n }\n }\n\n if (HARD_FILTER_BY_YEAR) {\n if (aniosFila.size > 0 && intersect(aniosFila, aniosPregunta)) score += BONUS_MATCH_ANIO_PREGUNTA;\n } else {\n if (aniosFila.size > 0) {\n if (aniosFila.has(ANIO_ACTUAL)) score += BONUS_ANIO_ACTUAL_STRONG;\n else score -= PENALIZA_ANIO_NO_ACTUAL;\n }\n }\n\n if (mesesPregunta.size > 0 && mesInicio != null) {\n if (mesesPregunta.has(mesInicio)) {\n if (!HARD_FILTER_BY_YEAR && (aniosFila.size === 0 || aniosFila.has(ANIO_ACTUAL))) score += BONUS_MATCH_MES_ACTUAL;\n else score += BONUS_MATCH_MES_ACTUAL * 0.6;\n }\n }\n\n if (/\\b20\\d{2}\\b/.test(normTotal)) score += BONUS_ANIO_EXPLICITO;\n\n const frase = limpiarTexto(preguntaUsuario);\n if (frase && normTotal.includes(frase)) score += BONUS_FRASE_EXACTA;\n\n const len = normTotal.split(' ').length || 1;\n score = score / Math.pow(len, 0.03);\n\n return {\n row_number: campos.row_number,\n PERIODO_FECHAS: campos.PERIODO_FECHAS,\n ACTIVIDADES_GRADO: campos.ACTIVIDADES_GRADO,\n PROCESOS_GRADO: campos.PROCESOS_GRADO,\n ACTIVIDADES_FORMACION: campos.ACTIVIDADES_FORMACION,\n preview: textoTotal,\n score: Number(score.toFixed(4)),\n coincidencias,\n match_tokens: matchTokens,\n match_por_campo: matchPorCampo,\n fecha_inicio_ts: tsInicio,\n mes_inicio: mesInicio,\n anios_fila: [...aniosFila]\n };\n}\n\n// ---------- ejecutar y ordenar ----------\nconst evaluadas = [];\nfor (const row of filas) {\n const r = puntuar(row);\n if (!r) continue;\n if (r.score > UMBRAL_SCORE_MIN || r.coincidencias > 0) evaluadas.push(r);\n}\n\nevaluadas.sort((a, b) => {\n if (b.score !== a.score) return b.score - a.score;\n if (b.coincidencias !== a.coincidencias) return b.coincidencias - a.coincidencias;\n const ta = a.fecha_inicio_ts ?? Number.POSITIVE_INFINITY;\n const tb = b.fecha_inicio_ts ?? Number.POSITIVE_INFINITY;\n return ta - tb;\n});\n\n// chat_ids válidos (si existen en la data)\nconst chatIds = $input.all()\n .map(it => it.json?.CHAT_ID)\n .filter(id => id !== undefined && id !== null && String(id).trim() !== '')\n .map(id => String(id).trim());\nconst chat_ids_validos = [...new Set(chatIds)];\n\n// ---------- sin resultados ----------\nif (evaluadas.length === 0) {\n const sugerencias = { email: \"[email protected]\", telefono: \"(04) 2269-269\", web: \"https://www.espol.edu.ec\" };\n const yearInfo = aniosPregunta.size > 0 ? ` para el/los año(s): ${[...aniosPregunta].join(', ')}` : ` para el año ${ANIO_ACTUAL}`;\n const mensaje = [\n `😕 No encontré información específica${yearInfo}.`,\n \"Puedes comunicarte por:\",\n `📧 ${sugerencias.email}`,\n `📞 ${sugerencias.telefono}`,\n `🌐 ${sugerencias.web}`\n ].join('\\n');\n\n return [{ json: {\n ok: false,\n consulta: preguntaUsuario,\n intencion_detectada: intencion,\n mensaje,\n sugerencias_contacto: sugerencias,\n chat_id: chatId,\n chat_ids_validos\n }}];\n}\n\n// ---------- construir sugerencias si hay demasiadas coincidencias ----------\nfunction nombreMes(idx) {\n const nombres = ['enero','febrero','marzo','abril','mayo','junio','julio','agosto','septiembre','octubre','noviembre','diciembre'];\n return (idx >=0 && idx <=11) ? nombres[idx] : null;\n}\nfunction recolectarFacetas(rows) {\n const anios = new Set();\n const meses = new Set();\n const tiposEval = new Set(); // primera/segunda/tercera\n const ciclos = new Set(); // Ciclo 1/2/3\n\n for (const r of rows) {\n (r.anios_fila || []).forEach(y => anios.add(y));\n if (typeof r.mes_inicio === 'number') meses.add(r.mes_inicio);\n\n const norm = limpiarTexto(`${r.PERIODO_FECHAS} ${r.ACTIVIDADES_GRADO} ${r.PROCESOS_GRADO} ${r.ACTIVIDADES_FORMACION}`);\n if (/\\bprimera\\b/.test(norm) || /\\bevaluacion\\s*i\\b/.test(norm)) tiposEval.add('primera');\n if (/\\bsegunda\\b/.test(norm) || /\\bevaluacion\\s*ii\\b/.test(norm)) tiposEval.add('segunda');\n if (/\\btercera\\b/.test(norm) || /\\bevaluacion\\s*iii\\b/.test(norm)) tiposEval.add('tercera');\n\n const m = norm.match(/\\bciclo\\s*(1|2|3)\\b/);\n if (m) ciclos.add(`Ciclo ${m[1]}`);\n }\n\n return {\n anios: [...anios].sort((a,b)=>a-b),\n meses: [...meses].sort((a,b)=>a-b).map(nombreMes).filter(Boolean),\n tiposEval: [...tiposEval],\n ciclos: [...ciclos]\n };\n}\n\nlet sugerencia_reformulacion = null;\nlet filtros_sugeridos = null;\nconst DEMASIADAS = evaluadas.length > UMBRAL_DEMASIADAS;\n\nif (DEMASIADAS) {\n const facetas = recolectarFacetas(evaluadas);\n filtros_sugeridos = {\n posibles_anios: facetas.anios,\n posibles_meses: facetas.meses,\n posibles_tipos_evaluacion: facetas.tiposEval,\n posibles_ciclos: facetas.ciclos\n };\n\n const tips = [];\n if (facetas.anios.length > 0) tips.push(`• especifica año: **${facetas.anios.slice(-3).join(' / ')}**`);\n if (facetas.meses.length > 0) tips.push(`• añade mes: **${facetas.meses.slice(0,4).join(' / ')}**`);\n if (facetas.tiposEval.length > 0) tips.push(`• tipo de evaluación: **${facetas.tiposEval.join(' / ')}**`);\n if (facetas.ciclos.length > 0) tips.push(`• ciclo: **${facetas.ciclos.join(' / ')}**`);\n\n // ejemplo guiado\n const ejemplo = [];\n if (intencion === 'evaluacion') {\n const ejA = facetas.anios[0] ?? ANIO_ACTUAL;\n const ejMes = facetas.meses[0] ?? 'julio';\n ejemplo.push(`Ej.: \"¿Cuándo es la **primera evaluación** de **${ejMes} ${ejA}** en PAE?\"`);\n } else if (facetas.meses.length > 0 || facetas.anios.length > 0) {\n const ejA = facetas.anios[0] ?? ANIO_ACTUAL;\n const ejMes = facetas.meses[0] ?? 'abril';\n ejemplo.push(`Ej.: \"Actividades formativas de **${ejMes} ${ejA}**\"`);\n }\n\n sugerencia_reformulacion =\n `🔎 He encontrado **${evaluadas.length}** coincidencias. ` +\n `Para llegar más rápido a lo que necesitas, prueba a refinar tu pregunta:\\n` +\n (tips.length ? tips.join('\\n') + '\\n' : '') +\n (ejemplo.length ? ejemplo.join('\\n') : '');\n}\n\n// ---------- mensaje formateado (todas las coincidencias) ----------\nconst encabezado = `📅 Encontré ${evaluadas.length} coincidencia(s) para:\\n“${preguntaUsuario}”`;\nconst cuerpo = evaluadas.map((e, i) => {\n const partes = [];\n partes.push(`${i+1}. 📌 ${e.PERIODO_FECHAS || '(sin fecha)'}`);\n if (e.ACTIVIDADES_GRADO?.trim()) partes.push(` 🎓 Actividades de Grado: ${e.ACTIVIDADES_GRADO}`);\n if (e.PROCESOS_GRADO?.trim()) partes.push(` 📋 Procesos: ${e.PROCESOS_GRADO}`);\n if (e.ACTIVIDADES_FORMACION?.trim()) partes.push(` 🔧 Formación Técnica: ${e.ACTIVIDADES_FORMACION}`);\n return partes.join('\\n');\n}).join('\\n\\n');\n\nconst mensaje_formateado = sugerencia_reformulacion\n ? `${encabezado}\\n\\n${cuerpo}\\n\\n${sugerencia_reformulacion}`\n : `${encabezado}\\n\\n${cuerpo}`;\n\nreturn [{\n json: {\n ok: true,\n consulta: preguntaUsuario,\n intencion_detectada: intencion,\n ordinal_detectado: detectarOrdinal(preguntaUsuario) || null,\n anios_detectados_en_pregunta: [...aniosPregunta],\n meses_detectados_en_pregunta: [...mesesPregunta],\n total_evaluadas: filas.length,\n total_encontrados: evaluadas.length,\n demasiadas_coincidencias: DEMASIADAS,\n umbral_demasiadas: UMBRAL_DEMASIADAS,\n filtros_sugeridos, // ← facetas para construir botones/quick-replies si quieres\n resultados: evaluadas, // ← TODAS las coincidencias ordenadas\n mensaje_formateado,\n chat_id: chatId,\n chat_ids_validos: chat_ids_validos\n }\n}];"
},
"typeVersion": 2
},
{
"id": "2f04c332-95f8-4251-a7d8-c3183c3bf341",
"name": "Mensaje Help",
"type": "n8n-nodes-base.telegram",
"position": [
-880,
608
],
"webhookId": "cec2b109-dbe4-4246-a86e-ab226fd460a7",
"parameters": {
"text": "=🤖 Bienvenido al ChatBot ESPOL\n\n👋 ¡Hola! Soy ChatBot ESPOL, tu asistente virtual de la Escuela Superior Politécnica del Litoral.\nEstoy aquí para responder tus preguntas sobre la universidad: matrículas, eventos, horarios, docentes, servicios estudiantiles y mucho más.\n\n💬 Solo escribe tu duda y te responderé al instante.\nPor ejemplo:\n\n“¿Cuándo empiezan las matrículas?”\n“¿Qué eventos hay esta semana?”\n“¿Dónde puedo contactar a Bienestar Estudiantil?”\n\nTambién puedes usar estos comandos:\n\n/help – Ver los comandos disponibles\n\n/faqs – Acceder a la sección de consultas y preguntas frecuentes\n\n/contact – Mostrar contactos y canales oficiales\n\n/events – Ver próximos eventos\n\n/feedback – Enviar sugerencias o comentarios sobre el bot o los servicios\n\n🎓 Tu información, siempre a un mensaje de distancia.\n¿Sobre qué te gustaría consultar hoy?",
"chatId": "={{ $json.message.chat.id }}",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "58882226-f879-4ae5-b653-025ae55e833f",
"name": "BIENVENIDA",
"type": "n8n-nodes-base.telegram",
"position": [
-880,
1504
],
"webhookId": "67736a18-8268-4b0b-a904-77b356cf3059",
"parameters": {
"text": "=👋🏽 ¡Qué más, politécnico o futuro politécnico! \n\nBienvenido al bot oficial de ayuda para toda la comunidad de la ESPOL 🐢💛💙. \n\nAquí puedes preguntarme lo que necesites sobre tu vida politécnica o lo que se viene en la U: \n📅 Fechas importantes del calendario académico. \n🎓 Preguntas frecuentes de todas las facultades — tanto pregrado como postgrado. \n🎟️ Info sobre becas, clubes, cursos, eventos, transporte, servicios y más. \n\nY pilas 👀, porque cada semana te voy a mandar un recordatorio con lo más relevante que se viene para que no se te pase nada — ni matrícula, ni retiro, ni feriado 😎. Así que tranqui, compa, pregunta nomás.\n\n¡Vamos con todo, politécnico! 🚀💪",
"chatId": "={{ $json.message.chat.id }}",
"additionalFields": {
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "5f1bf72a-e7f4-405f-a1fc-549008b73747",
"name": "Google Gemini Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
-672,
1680
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.5-flash-lite"
},
"credentials": {
"googlePalmApi": {
"id": "5akz9ZmFGR6TDmkP",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "bef601ae-58f3-4785-a49e-4a7225c34ed9",
"name": "BD_ESPOL",
"type": "n8n-nodes-base.googleSheetsTool",
"position": [
-528,
1712
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "id",
"value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Sheet', ``, 'string') }}"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo/edit?usp=drivesdk",
"cachedResultName": "BASE-DATOS-FAQ_ESPOL"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "aXzbiC4KJQoHD5SM",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "c925a437-914c-4e86-8619-2a670ee53c40",
"name": "PREGUNTAS AL AZAR DE GUIA",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-672,
1504
],
"parameters": {
"text": "=El usuario acaba de iniciar el bot con /start.\nNo le muestres un mensaje de bienvenida.\nSólo sugiérele 5 preguntas al azar (diferentes cada vez) que podría hacerle al bot, relacionadas con la información que está guardada en BD_ESPOL (preguntas frecuentes de facultades, calendario académico, becas, clubes, cursos, eventos, etc.).\n\nEjemplo de formato esperado:\n\n👋🏽 ¡Qué más, futuro politécnico! Bienvenido al bot de ayuda de la ESPOL 🐢💛💙\nAquí puedes preguntar lo que necesites sobre tu vida universitaria o lo que se viene en el semestre.\n\nPuedes empezar con algo como:\n• ¿Cuándo abren las matrículas?\n• ¿Qué clubes hay en la ESPOL?\n• ¿Dónde puedo ver las fechas de titulación?\n• ¿Quien es el coordinador de la FIMCP?\n• ¿Cuando empiezan las vacaciones del IIPAO 2025?\n¡Dale nomás, pregunta lo que necesites que el bot te ayuda al instante! 😎",
"options": {
"systemMessage": "=Eres un asistente politécnico amigable y juvenil, que habla con tono guayaco, relajado y positivo. Estás conectado a una base de datos llamada BD_ESPOL, que contiene información sobre preguntas frecuentes, calendario académico, becas, clubes, cursos, eventos, servicios, vida universitaria, transporte, titulación y reglamentos.\n\nTu objetivo es ayudar a los estudiantes y futuros politécnicos a orientarse en la ESPOL.\n\nCada vez que un usuario nuevo inicia conversación, no debes volver a saludar, ya que el bot principal ya lo hizo. Solo debes explicarle brevemente cómo puede usar el bot y mostrarle 5 preguntas aleatorias distintas cada vez que podría intentar para descubrir las funciones del bot.\n\nEl mensaje debe sonar natural, como si fueras un compañero universitario de Guayaquil, usando expresiones relajadas pero respetuosas.\nEvita usar asteriscos, puntos o cualquier formato Markdown. Usa guiones (-) para enumerar las preguntas.\n\nLas preguntas deben cubrir temas variados: calendario académico, becas, facultades, servicios, transporte, vida universitaria, titulación y eventos.\nCada vez que respondas, cambia el orden o tipo de preguntas para que no sean las mismas.\n\nEjemplo de estilo y estructura:\nAquí puedes preguntar sobre fechas, becas, clubes o eventos\nMira, puedes probar con algo como\n\n- ¿Cuándo empiezan las clases?\n\n- ¿Qué becas hay disponibles?\n\n- ¿Dónde veo los cursos extracurriculares?\n\n- ¿Qué eventos hay esta semana?\n\n- ¿Cómo puedo unirme a un club estudiantil?\n\nTermina siempre con una frase motivadora tipo\n\n¡Dale, pregunta nomás!\n\n¡Pruébalo y ve lo bacán que está esto!"
},
"promptType": "define"
},
"typeVersion": 3
},
{
"id": "b74244ac-ed9a-4979-95ac-d15a620c7df4",
"name": "Wait",
"type": "n8n-nodes-base.wait",
"position": [
-368,
1504
],
"webhookId": "d38e8f38-68a5-4b15-b8c3-4dce9f102f38",
"parameters": {},
"typeVersion": 1.1
},
{
"id": "3104667b-6c32-4a83-9627-23609865000e",
"name": "GUIA DE COMO PREGUNTAR",
"type": "n8n-nodes-base.telegram",
"position": [
-208,
1504
],
"webhookId": "4e6cea20-85ce-4b9a-b931-8fa22d93b87b",
"parameters": {
"text": "={{ $json.output }}",
"chatId": "={{ $('Telegram Trigger - Inicio').item.json.message.chat.id }}",
"additionalFields": {
"parse_mode": "HTML",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "dff25489-2b4d-49da-9398-9efa73bbe8f5",
"name": "Google Gemini Chat Model1",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
-3168,
1840
],
"parameters": {
"options": {}
},
"credentials": {
"googlePalmApi": {
"id": "5akz9ZmFGR6TDmkP",
"name": "Google Gemini(PaLM) Api account"
}
},
"typeVersion": 1
},
{
"id": "7e30adff-e90b-495b-af22-d82ab8a464a3",
"name": "BD_CALENDARIO1",
"type": "n8n-nodes-base.googleSheets",
"position": [
-3872,
1664
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1817620056,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo/edit#gid=1817620056",
"cachedResultName": "CALENDAR"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo/edit?usp=drivesdk",
"cachedResultName": "BASE-DATOS-FAQ_ESPOL"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "aXzbiC4KJQoHD5SM",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "6ee54764-d999-48bc-a498-d4ded389b2f9",
"name": "GENERADOR DE ANUNCIOS1",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-3168,
1664
],
"parameters": {
"text": "=Convierte el siguiente texto en un mensaje casual ecuatoriano:\n\nPeriodo: {{ $json[\"RESULTADOS[0].PERIODO_FECHAS\"] }}\nActividades: {{ $json[\"RESULTADOS[0].ACTIVIDADES_GRADO\"] }}\nProcesos: {{ $json[\"RESULTADOS[0].PROCESOS_GRADO\"] }}\nFormacion tecnica: {{ $json['RESULTADOS[0][\\'ACTIVIDADES DE FORMACIÓN TÉCNICA Y TECNOLÓGICA\\']'] }}\n",
"options": {
"systemMessage": "=Eres un redactor ecuatoriano experto en comunicación universitaria. Tu tarea es convertir los datos de tres semanas (anterior, actual y siguiente) en un solo mensaje casual, fresco y cercano, como si lo dijera un estudiante politécnico. \n\nSigue estas reglas:\n\n1. **Prioriza la semana del medio (la actual)**. Es la principal. Describe sus actividades con entusiasmo, usando un tono motivador y natural.\n2. Luego menciona brevemente lo ocurrido **la semana pasada (primer item)**, con frases tipo: \n “Si te lo perdiste la semana pasada…” o “Por si no te enteraste…”.\n3. Finalmente, habla de **la semana siguiente (último item)**, pero nunca digas “la próxima semana” a secas. \n Siempre menciona explícitamente **la fecha del periodo**, por ejemplo: \n “Y para la semana del 17 al 21 de noviembre…” \n Usa frases como: “prepárate para”, “se viene”, “atentos que en la semana del…”.\n4. Usa expresiones naturales de Ecuador como *pilas, ñaño, pana, de una, qué bestia, bacán, full, a romperla*.\n5. Separa las secciones con saltos de línea y mantén un estilo cercano y fluido, tipo mensaje de Telegram o redes.\n6. No inventes información fuera del JSON, solo reformula y organiza lo recibido.\n7. El resultado final debe ser un **solo texto en español ecuatoriano**, sin etiquetas, ni formato JSON, ni comillas.\n\nEjemplo de estructura esperada:\n\n¡Qué más, mi gente politécnica! Esta semana del 10 al 14 de noviembre se viene con todo 💪. \nSeguimos a full con las actividades formativas y los panas de Formación Técnica también están dándole duro. ¡Qué bestia la energía que se siente en el campus! \n\nSi te lo perdiste la semana pasada, hubo evaluaciones y descansos para recargar pilas. ¡Espero que hayas aprovechado! 😎 \n\nY atentos para la semana del 17 al 21 de noviembre, que llega la PRIMERA EVALUACIÓN del grado. ¡A romperla con todo el ñeque, panas! 📚🔥\n"
},
"promptType": "define"
},
"typeVersion": 3
},
{
"id": "91ec2456-dae0-4a1c-9e19-883df9771bd8",
"name": "Sticky Note20",
"type": "n8n-nodes-base.stickyNote",
"position": [
-4208,
1376
],
"parameters": {
"color": 5,
"width": 2224,
"height": 656,
"content": "# 📢 **SISTEMA GENERADOR DE ANUNCIOS** 🧠\n"
},
"typeVersion": 1
},
{
"id": "e145604d-3454-4370-847b-c88a46e7973f",
"name": "Sticky Note21",
"type": "n8n-nodes-base.stickyNote",
"position": [
-4176,
1488
],
"parameters": {
"color": 5,
"width": 256,
"height": 368,
"content": "## RECORDATORIO SEMANAL\nCADA DOMINGO A LAS 07:00PM SE NOTIFICARÁ AL USUARIO SOBRE LOS ANUNCIOS IMPORTANTES DE LA SEMANA ENTRANTE"
},
"typeVersion": 1
},
{
"id": "bb5ecbad-5350-4010-a658-bd868d7bdcad",
"name": "Sticky Note22",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3920,
1488
],
"parameters": {
"color": 5,
"width": 208,
"height": 368,
"content": "## CALENDARIO\nEL SISTEMA TENDRÁ A SU DISPOSICIÓN A TIEMPO REAL CALENDARIO ACADÉMICO DE ESPOL"
},
"typeVersion": 1
},
{
"id": "1b0a4908-baec-4ce6-b482-ce5ac6707fbe",
"name": "Sticky Note23",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3712,
1488
],
"parameters": {
"color": 5,
"width": 496,
"height": 368,
"content": "## AUTOMATIZACIÓN\nEL SISTEMA COMPARARÁ LA FECHA ACTUAL Y ENTREGARÁ LAS ACTIVIDADES DE LA SEMANA ENTRANTE"
},
"typeVersion": 1
},
{
"id": "ffcd4a54-58f6-4718-be90-678e020d12b8",
"name": "Sticky Note24",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3216,
1488
],
"parameters": {
"color": 5,
"width": 368,
"height": 496,
"content": "## ANUNCIO AUTOMÁTICO\nUN AGENTE DE IA, TOMARÁ LA INFORMACIÓN DE LAS ACTIVIDADES DE LA SEMANA Y GENERARÁ UN MENSAJE PERSONALIZADO AL USUARIO PARA MANTENERLO SIEMPE AL DÍA"
},
"typeVersion": 1
},
{
"id": "65cf8025-fbd1-4c53-892c-3c39af419f6a",
"name": "Sticky Note25",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2848,
1456
],
"parameters": {
"color": 5,
"width": 336,
"height": 320,
"content": "## GENERADOR DE AUDIO\nPARA MEJORAR LA EXPERIENCIA DEL USUARIO, EL TEXTO SE TRANSFORMA A VOZ "
},
"typeVersion": 1
},
{
"id": "2010027d-9238-4bab-8025-0abcc28dd958",
"name": "Sticky Note26",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2512,
1408
],
"parameters": {
"color": 5,
"width": 352,
"height": 304,
"content": "## ENVIO DE VOZ\nEL MENSAJE DE VOZ PERSONALIZADO SE ENVÍA AUTOMATICAMENTE CADA DOMINGO A LAS 07:00PM AL USUARIO VÍA TELEGRAM"
},
"typeVersion": 1
},
{
"id": "8c041a63-8828-4f90-b670-85e018f73157",
"name": "ENVIO DE MENSAJE DE VOZ1",
"type": "n8n-nodes-base.telegram",
"position": [
-2400,
1536
],
"webhookId": "ae4eba6e-2e6a-4a50-a4ec-6c527850b3c7",
"parameters": {
"file": "={{ $('GENERADOR DE MENSAJE DE VOZ').item.json.URL }}",
"chatId": "={{ $json.chatId }}",
"operation": "sendAudio",
"additionalFields": {
"caption": "={{ \n(() => {\n const today = new Date();\n const dayOfWeek = today.getDay(); // 0=domingo\n const diffMonday = (dayOfWeek === 0 ? -6 : 1 - dayOfWeek) + 7; // +7 => próxima semana\n const diffFriday = diffMonday + 4;\n\n const monday = new Date(today);\n monday.setDate(today.getDate() + diffMonday);\n\n const friday = new Date(today);\n friday.setDate(today.getDate() + diffFriday);\n\n const fmt = d => `${d.getDate().toString().padStart(2, '0')}/${(d.getMonth() + 1).toString().padStart(2, '0')}`;\n\n return `Anuncio Semanal - Semana del ${fmt(monday)} al ${fmt(friday)}`;\n})()\n}}\n"
}
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "9681b6de-a5db-443e-89a5-e6a927b92b89",
"name": "ENVIO DE MENSAJE DE TEXTO1",
"type": "n8n-nodes-base.telegram",
"position": [
-2384,
1824
],
"webhookId": "fc1657ce-66de-4cae-940f-86f0afae3ae5",
"parameters": {
"text": "={{ $json.text }}",
"chatId": "={{ $json.chatId }}",
"additionalFields": {
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"id": "ovdN9afPb1hJiiET",
"name": "ESPOLBot"
}
},
"typeVersion": 1.2
},
{
"id": "245f9062-c877-46f0-9df3-d722308dca44",
"name": "Sticky Note27",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2512,
1728
],
"parameters": {
"color": 5,
"width": 352,
"height": 272,
"content": "## ENVIO DE TEXTO\nSE ENVÍA AUTOMATICAMENTE EL MENSAJE"
},
"typeVersion": 1
},
{
"id": "67e74e9f-13b3-4f5f-80f4-e35e75558704",
"name": "OPTIMIZADOR",
"type": "n8n-nodes-base.code",
"position": [
-2800,
1808
],
"parameters": {
"jsCode": "// Tu array de chat IDs\nconst chatIds = $('ACTIVIDADES DE LA SEMANA1').first().json.CHAT_IDS_VALIDOS; // reemplaza con tus IDs\n\n// Mensaje a enviar\nconst mensaje = $input.first().json.output;\n\n// Creamos un item por cada chat ID\nreturn chatIds.map(id => ({\n json: {\n chatId: id,\n text: mensaje\n }\n}));\n"
},
"typeVersion": 2
},
{
"id": "23d73abe-5b2b-42c5-b50f-fea043f4cd1a",
"name": "OPTIMIZADOR1",
"type": "n8n-nodes-base.code",
"position": [
-2640,
1584
],
"parameters": {
"jsCode": "// Tu array de chat IDs\nconst chatIds = $('ACTIVIDADES DE LA SEMANA1').first().json.CHAT_IDS_VALIDOS; // reemplaza con tus IDs\n\n// Mensaje a enviar\nconst mensaje = $input.first().json.output;\n\n// Creamos un item por cada chat ID\nreturn chatIds.map(id => ({\n json: {\n chatId: id,\n text: mensaje\n }\n}));\n"
},
"typeVersion": 2
},
{
"id": "5dfc42b7-8684-4e92-95ca-7e1154345402",
"name": "GENERADOR DE MENSAJE DE VOZ",
"type": "n8n-nodes-base.httpRequest",
"position": [
-2800,
1584
],
"parameters": {
"url": "https://ttsmp3.com/makemp3_new.php",
"method": "POST",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
},
"sendBody": true,
"contentType": "form-urlencoded",
"bodyParameters": {
"parameters": [
{
"name": "msg",
"value": "={{ $json.output }}"
},
{
"name": "lang",
"value": "Mia"
},
{
"name": "source",
"value": "ttsmp3"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "7008a1aa-c9fb-4777-aac2-63f51cb88d49",
"name": "OBTENER_ID1",
"type": "n8n-nodes-base.set",
"position": [
-880,
1712
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "fc9b6408-8e37-4395-935f-33985719dae3",
"name": "chat_id",
"type": "number",
"value": "={{ $json.message.chat.id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "0310e05e-bc18-48de-952d-2c7e5163ab1f",
"name": "AGREGA ID_UNICAS1",
"type": "n8n-nodes-base.googleSheets",
"position": [
-624,
1856
],
"parameters": {
"columns": {
"value": {
"CHAT_ID": "={{ $json.chat_id }}"
},
"schema": [
{
"id": "PERIODO_FECHAS",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "PERIODO_FECHAS",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ACTIVIDADES_GRADO",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "ACTIVIDADES_GRADO",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "PROCESOS_GRADO",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "PROCESOS_GRADO",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ACTIVIDADES DE FORMACIÓN TÉCNICA Y TECNOLÓGICA",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "ACTIVIDADES DE FORMACIÓN TÉCNICA Y TECNOLÓGICA",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "CHAT_ID",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "CHAT_ID",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"CHAT_ID"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1817620056,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo/edit#gid=1817620056",
"cachedResultName": "CALENDAR"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo/edit?usp=drivesdk",
"cachedResultName": "BASE-DATOS-FAQ_ESPOL"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "aXzbiC4KJQoHD5SM",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "c797bd9d-e22d-40af-b685-0ecff2c2e838",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-976,
320
],
"parameters": {
"color": 5,
"width": 1040,
"height": 1712,
"content": "# 🏷️ **SISTEMA DE TAGS - REFERENCIA RÁPIDA** ✨\n\n## ESTE **SISTEMA DE TAGS** PERMITE ORGANIZAR Y CLASIFICAR LA INFORMACIÓN. CADA TAG FACILITA FILTRAR, IDENTIFICAR Y ACCEDER RÁPIDAMENTE A LOS NODOS, MEJORANDO LA CLARIDAD Y EFICIENCIA."
},
"typeVersion": 1
},
{
"id": "52455d30-745d-4373-a56e-770a643192b7",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-416,
1680
],
"parameters": {
"color": 5,
"width": 432,
"height": 304,
"content": "## 👋 **BIENVENIDA AL USUARIO** 🎉\nAL INGRESAR UN NUEVO USUARIO SE ENVIA UN MENSAJE DE BIENVENIDA Y GUÍA DE USO DEL BOT.\n\n\n## 🆔 **OBTENCIÓN DE LAS IDs** 🔍\nGUARDA EN UNA BASE DE DATOS LA ID DE CADA USUARIO NUEVO QUE INGRESE AL BOT "
},
"typeVersion": 1
},
{
"id": "97535bd5-a38d-4d93-8eb8-11ea593ec658",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3328,
416
],
"parameters": {
"color": 5,
"width": 608,
"height": 288,
"content": "## MANIPULACIÓN Y ORGANIZACIÓN DE DATOS\nUSO DE LOS NODOS HTML, SPLIT-OUT Y CODE PARA OBTENER LOS DATOS REQUERIDOS Y MANTENER UN ORDEN DE SALIDA"
},
"typeVersion": 1
},
{
"id": "6f2891fd-2874-4273-aeeb-5092191df6ec",
"name": "Split Out",
"type": "n8n-nodes-base.splitOut",
"position": [
-3056,
560
],
"parameters": {
"options": {},
"fieldToSplitOut": "PERIODOS-FECHAS, ACTIVIDADES_GRADO, PROCESOS_GRADO, ACTIVIDADES_TECNICAS-TECNOLÓGICAS"
},
"typeVersion": 1
},
{
"id": "525e2001-f72e-4f12-b0b8-1773498e9298",
"name": "HTML",
"type": "n8n-nodes-base.html",
"position": [
-3248,
560
],
"parameters": {
"options": {},
"operation": "extractHtmlContent",
"extractionValues": {
"values": [
{
"key": "PERIODOS-FECHAS",
"cssSelector": "table tbody tr td:first-child",
"returnArray": true
},
{
"key": "ACTIVIDADES_GRADO",
"cssSelector": "table tbody tr td:nth-child(2)",
"returnArray": true
},
{
"key": "PROCESOS_GRADO",
"cssSelector": "table tbody tr td:nth-child(3)",
"returnArray": true
},
{
"key": "ACTIVIDADES_TECNICAS-TECNOLÓGICAS",
"cssSelector": "table tbody tr td:nth-child(4)",
"returnArray": true
}
]
}
},
"typeVersion": 1.2
},
{
"id": "bc233dd1-fa9b-4d75-ab77-4542eb14b535",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2720,
416
],
"parameters": {
"color": 5,
"width": 256,
"height": 288,
"content": "## BASE DE DATOS\nAGREGA Y ACTUALIZA LA BASE DE DATOS CON LAS FECHAS Y ACTIVIDADES DEL CALENDARIO"
},
"typeVersion": 1
},
{
"id": "f3e79e09-fe05-4b71-a2cd-5810a88e8252",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3552,
416
],
"parameters": {
"color": 5,
"width": 224,
"height": 288,
"content": "## SCRAPPING\nPARA LA OBTENCIÓN DE FECHAS Y ACTIVIDADES DE TODOS LOS PERIODOS LECTIVOS"
},
"typeVersion": 1
},
{
"id": "6a1fe95d-ab71-42a9-8906-dca393412815",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3824,
416
],
"parameters": {
"color": 5,
"width": 272,
"height": 288,
"content": "## TEMPORIZADOR DE 7 DÍAS\nCADA SEMANA SE ACTUALIZARÁN LAS BASES DE DATOS DEL CALENDARIO ACADÉMICO"
},
"typeVersion": 1
},
{
"id": "4fc9dc13-c0a9-424e-8fab-873ae62f7c09",
"name": "BD-CALENDARIO",
"type": "n8n-nodes-base.googleSheets",
"position": [
-2656,
560
],
"parameters": {
"columns": {
"value": {
"PERIODO_FECHAS": "={{ $json[\"PERIODOS-FECHAS\"] }}",
"PROCESOS_GRADO": "={{ $json.PROCESOS_GRADO }}",
"ACTIVIDADES_GRADO": "={{ $json.ACTIVIDADES_GRADO }}",
"ACTIVIDADES DE FORMACIÓN TÉCNICA Y TECNOLÓGICA": "={{ $json[\"ACTIVIDADES_TECNICAS-TECNOLÓGICAS\"] }}"
},
"schema": [
{
"id": "PERIODO_FECHAS",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "PERIODO_FECHAS",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ACTIVIDADES_GRADO",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "ACTIVIDADES_GRADO",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "PROCESOS_GRADO",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "PROCESOS_GRADO",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ACTIVIDADES DE FORMACIÓN TÉCNICA Y TECNOLÓGICA",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "ACTIVIDADES DE FORMACIÓN TÉCNICA Y TECNOLÓGICA",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "CHAT_ID",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "CHAT_ID",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"PERIODO_FECHAS"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1817620056,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo/edit#gid=1817620056",
"cachedResultName": "CALENDAR"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo/edit?usp=drivesdk",
"cachedResultName": "BASE-DATOS-FAQ_ESPOL"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "aXzbiC4KJQoHD5SM",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "3c9d1e36-949b-41c0-82d3-14fe677e407e",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
-4208,
320
],
"parameters": {
"color": 5,
"width": 2224,
"height": 1056,
"content": "# 📅 **CALENDARIO (OBTENCIÓN - VÍA SCRAPING WEB)** 🔍\n\n\n"
},
"typeVersion": 1
},
{
"id": "db4dfee5-023e-4736-8b12-4f62879a3907",
"name": "SCRRAPPING",
"type": "n8n-nodes-base.httpRequest",
"position": [
-3488,
560
],
"parameters": {
"url": "https://www.espol.edu.ec/es/vida-politecnica/calendario-grado",
"options": {
"timeout": 60000
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "User-Agent",
"value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "d23cb762-8460-41b9-b291-179baa9985d2",
"name": "CORRECTOR DE FECHAS",
"type": "n8n-nodes-base.code",
"position": [
-2880,
560
],
"parameters": {
"jsCode": "// Recibir items\nconst itemsIn = $input.all();\nlet currentYear = '';\nconst results = [];\n\nfunction detectMonth(text) {\n // Detecta el mes en el texto (minúsculas para evitar errores)\n const months = {\n enero: 1, febrero: 2, marzo: 3, abril: 4, mayo: 5, junio: 6,\n julio: 7, agosto: 8, septiembre: 9, octubre: 10, noviembre: 11, diciembre: 12\n };\n const lower = text.toLowerCase();\n for (const [month, num] of Object.entries(months)) {\n if (lower.includes(month)) return num;\n }\n return null;\n}\n\nfor (const item of itemsIn) {\n const data = item.json;\n let period = data[\"PERIODOS-FECHAS\"] ? String(data[\"PERIODOS-FECHAS\"]).trim() : \"\";\n\n // Detectar si hay un año explícito\n const yearMatch = period.match(/\\b(20\\d{2})\\b/);\n if (yearMatch) {\n currentYear = parseInt(yearMatch[1]);\n }\n\n // Si es encabezado, mantener igual\n if (/^(VACACIONES|PAE|PAO)/i.test(period)) {\n results.push({ json: { ...data } });\n continue;\n }\n\n // Detectar meses del período\n const firstMonth = detectMonth(period.split('-')[0] || '');\n const secondMonth = detectMonth(period.split('-')[1] || '');\n\n // Si tenemos dos meses y el segundo es menor que el primero → cambio de año\n if (firstMonth && secondMonth && secondMonth < firstMonth) {\n currentYear++;\n }\n\n // Si no hay año en el texto, agregar el actual\n if (!/\\b20\\d{2}\\b/.test(period) && currentYear) {\n period = `${period} ${currentYear}`;\n }\n\n results.push({\n json: {\n ...data,\n \"PERIODOS-FECHAS\": period\n }\n });\n}\n\nreturn results;\n"
},
"typeVersion": 2
},
{
"id": "d3138f0c-9007-4d94-ab5f-308ff5fca401",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-4144,
720
],
"parameters": {
"color": 5,
"width": 1680,
"height": 624,
"content": "# 💾 **BASES DE DATOS** 🗂️ \n\n## - **FIMCP** (FACULTAD DE INGENIERÍA EN MECÁNICA Y CIENCIAS DE LA PRODUCCIÓN) ⚙️\n\n## - **FIEC** (FACULTAD DE INGENIERÍA EN ELECTRICIDAD Y COMPUTACIÓN) 💻\n\n## - **FICT** (FACULTAD DE INGENIERÍA EN CIENCIAS DE LA TIERRA) 🌎\n\n## - **FCNM** (FACULTAD DE CIENCIAS NATURALES Y MATEMÁTICAS) 🔬\n\n## - **FCSH** (FACULTAD DE CIENCIAS SOCIALES Y HUMANÍSTICAS) 📚\n\n## - **FADCOM** (FACULTAD DE ARTE, DISEÑO Y COMUNICACIÓN AUDIOVISUAL) 🎨\n\n## - **FIMCM** (FACULTAD DE INGENIERÍA MARÍTIMA Y CIENCIAS DEL MAR) ⚓\n\n"
},
"typeVersion": 1
},
{
"id": "aae3673d-ab7d-421c-8955-2321e55717b1",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1968,
320
],
"parameters": {
"color": 3,
"width": 976,
"height": 1136,
"content": "# 💬 FLUJO: RESPUESTAS DEL USUARIO & FEEDBACK\n\n## 🎯 PROPÓSITO\n\n## GESTIONAR LA **RESPUESTA DEL USUARIO** TRAS UNA INTERACCIÓN CON EL BOT Y REGISTRAR SU **RETROALIMENTACIÓN** EN UNA HOJA DE CÁLCULO.\n\n## 🔗 FLUJO RESUMIDO\n\n## 1. **RESPUESTA USUARIO1 (SWITCH):**\n DETECTA SI EL USUARIO ELIGE CONTINUAR O ENVIAR FEEDBACK (POR EJEMPLO: “SÍ” O “NO”).\n\n## 2. **GET A CHAT:**\n OBTIENE LOS DATOS DEL CHAT ACTUAL PARA MANTENER LA CONVERSACIÓN ACTIVA.\n\n## 3. **NO – MENSAJE FEEDBACK1:**\n ENVÍA UN MENSAJE SOLICITANDO AL USUARIO QUE DESCRIBA SU EXPERIENCIA O MOTIVO DE LA RESPUESTA NEGATIVA.\n\n## 4. **GUARDADO FEEDBACK1:**\n REGISTRA EL MENSAJE DE RETROALIMENTACIÓN DIRECTAMENTE EN UNA **GOOGLE SHEET** PARA SU ANÁLISIS POSTERIOR.\n\n\n"
},
"typeVersion": 1
},
{
"id": "4bccb5c4-73cf-413b-a8e9-494e8af81b71",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-672,
592
],
"parameters": {
"color": 5,
"width": 688,
"height": 128,
"content": "## 🆘 **AYUDA (/HELP)** 💡\nMUESTRA UNA LISTA DE COMANDOS DISPONIBLES Y EXPLICA BREVEMENTE PARA QUÉ SIRVE CADA UNO, AYUDANDO AL USUARIO A ENTENDER CÓMO USAR EL BOT.\n\n\n"
},
"typeVersion": 1
},
{
"id": "33fba8d9-8d5a-4328-9f3f-9a847cd1eafc",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-672,
768
],
"parameters": {
"color": 5,
"width": 688,
"height": 128,
"content": "## ❓ **CONSULTAS (/FAQS)** 💬\nMANDA A LA SECCIÓN DE PREGUNTAS FRECUENTES Y CONSULTAS. AQUÍ PUEDES CONSULTAR TEMAS DE MATRÍCULA, EVENTOS, HORARIOS O SERVICIOS ESTUDIANTILES Y RECIBIR RESPUESTAS INMEDIATAS.\n\n"
},
"typeVersion": 1
},
{
"id": "ffb51e14-36fb-4cbf-98e1-7f507786aa3f",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-672,
944
],
"parameters": {
"color": 5,
"width": 688,
"height": 128,
"content": "## 📇 **CONTACTOS (/CONTACT)** 📞\nMUESTRA CONTACTOS Y CANALES OFICIALES DE LA ESPOL.\n(WEBS - CORREOS - ENLACES - NÚMEROS - DIRECCIÓN)\n\n"
},
"typeVersion": 1
},
{
"id": "fced05c4-2d46-4083-98e7-d5e852cea1cd",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-672,
1120
],
"parameters": {
"color": 5,
"width": 688,
"height": 128,
"content": "## 📅 **EVENTOS (/EVENTS)** 🎉\nMUESTRA PRÓXIMOS EVENTOS ACADÉMICOS DE ESPOL\n\n"
},
"typeVersion": 1
},
{
"id": "85c34e8b-d150-493b-9e3c-6bdad3cdb2d9",
"name": "Sticky Note13",
"type": "n8n-nodes-base.stickyNote",
"position": [
-672,
1296
],
"parameters": {
"color": 5,
"width": 688,
"height": 128,
"content": "## 📝 **FEEDBACK (/FEEDBACK)** ⭐\nPERMITE ENVIAR OPINIONES, SUGERENCIAS O COMENTARIOS SOBRE EL FUNCIONAMIENTO DEL BOT, FACILITANDO LA RETROALIMENTACIÓN DE LOS USUARIOS CON EL FIN DE MEJORAR EL SERVICIO Y OPTIMIZAR LA EXPERIENCIA GENERAL DEL USUARIO.\n\n\n"
},
"typeVersion": 1
},
{
"id": "f58f8a5d-646c-4ebd-9c43-dfe65a0c6514",
"name": "Sticky Note14",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1968,
1456
],
"parameters": {
"color": 3,
"width": 976,
"height": 576,
"content": "# 🚀 **INICIO DE AUTOMATIZACIÓN** ⚙️\n\n\n"
},
"typeVersion": 1
},
{
"id": "dea1510f-eae5-4f48-9b9d-cf61ba080faa",
"name": "Sticky Note15",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1936,
1600
],
"parameters": {
"color": 3,
"width": 224,
"height": 304,
"content": "## TRIGGER TELEGRAM\nSE ACTIVA EN CUALQUIER INTERACCIÓN DEL USUARIO CON EL BOT\n"
},
"typeVersion": 1
},
{
"id": "506b453a-061b-4644-b318-237fc1b660b9",
"name": "Sticky Note16",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1712,
1536
],
"parameters": {
"color": 3,
"width": 384,
"height": 400,
"content": "## CLASIFICADOR DE MENSAJES\n### - 0: FEEDBACK \n### - 1: COMENTARIO\n### - 2: TAGS\n### - Fallback: MENSAJE \n"
},
"typeVersion": 1
},
{
"id": "797b1458-c900-40af-89fb-866cf15e0ee8",
"name": "Switch",
"type": "n8n-nodes-base.switch",
"position": [
-1712,
1712
],
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "0e1c1bb4-3315-4632-a9dc-5f917baf41da",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.callback_query !== undefined }}",
"rightValue": "0"
}
]
}
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "23c2e3a0-e7c8-46de-b504-2cabd445ed54",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ ($json.message?.reply_to_message?.text || '') }}",
"rightValue": "Lamento que la información no te haya sido útil"
}
]
}
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "318d7097-ff17-4636-b7bf-958de3da6788",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.message?.text?.startsWith('/') }}",
"rightValue": ""
}
]
}
}
]
},
"options": {
"ignoreCase": true,
"fallbackOutput": "extra"
},
"looseTypeValidation": true
},
"typeVersion": 3.3
},
{
"id": "d69dcc76-b210-4b80-893a-1649643e602d",
"name": "Sticky Note17",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1328,
1600
],
"parameters": {
"color": 3,
"width": 320,
"height": 304,
"content": "\n## CLASIFICADOR DE TAGS\n\n"
},
"typeVersion": 1
},
{
"id": "76454d3f-9393-486c-a229-696c60ce324e",
"name": "Sticky Note18",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3120,
720
],
"parameters": {
"color": 5,
"width": 528,
"height": 624,
"content": "## - **ADMISIÓN** 📝\n\n## - **NIVELACIÓN** 🎯\n\n## - **BECAS** 🎓\n\n## - **MATRÍCULAS** 🧾\n\n## - **CERTIFICADOS Y TITULACIÓN** 📄\n\n## - **VIDA UNIVERSITARIA** 🏫\n\n## - **SERVICIOS DE SALUD Y BIENESTAR** ❤️\n\n## - **CULTURA Y DEPORTES** 🏆\n\n## - **TRANSPORTE** 🚌\n\n\n\n"
},
"typeVersion": 1
},
{
"id": "f75dd036-9cc5-4c9d-92ae-34bb89bbf4cd",
"name": "Sticky Note19",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2592,
720
],
"parameters": {
"color": 5,
"width": 560,
"height": 624,
"content": "## - **HISTORIA DE LA ESPOL** 🏛️\n\n## - **SOSTENIBILIDAD** 🌱\n\n## - **CENTROS DE INVESTIGACIÓN** 🔬\n\n## - **VINCULACIÓN CON LA SOCIEDAD** 🤝\n\n## - **REGLAMENTOS** 📘\n\n## - **PROGRAMAS INTERNACIONALES** 🌍\n\n## - **VOLUNTARIADO** 🙌\n\n## - SERVICIOS: **CELEX**, **CONDUESPOL** Y MAS 💬\n\n## - **ALUMNI** Y **CIB** 🧠"
},
"typeVersion": 1
},
{
"id": "6a8a52db-c44d-4174-a92f-cd113b5a0d2a",
"name": "Sticky Note28",
"type": "n8n-nodes-base.stickyNote",
"position": [
-4208,
-192
],
"parameters": {
"width": 2224,
"height": 496,
"content": "# 🤖 **GRUPO #10 – CHATBOT POLITÉCNICO DE PREGUNTAS FRECUENTES Y CALENDARIO ACADÉMICO EN TELEGRAM** 📅💬\n\n# 🧩 **WORKFLOW CREADO POR:**\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n# 🧑🏫 **MENTOR: JAREN** 🌟\n\n\n\n\n\n\n\n\n"
},
"typeVersion": 1
},
{
"id": "4602e289-d4d7-4dd0-9777-e148b1175a20",
"name": "Sticky Note29",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3952,
112
],
"parameters": {
"width": 1760,
"height": 80,
"content": "# ✨ **PROYECTO DE AUTOMATIZACIÓN INTELIGENTE CON ENFOQUE EN ASISTENCIA UNIVERSITARIA ✨**"
},
"typeVersion": 1
},
{
"id": "0cde6cc8-dd35-4fb3-afc9-1ec73921b970",
"name": "Sticky Note30",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2592,
-16
],
"parameters": {
"width": 448,
"height": 96,
"content": "# 👩💻 **NICOLE GUEVARA** 🚀\n"
},
"typeVersion": 1
},
{
"id": "08dc6029-171d-44d5-92b1-eaeb6cc57bc0",
"name": "Sticky Note31",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3328,
-16
],
"parameters": {
"width": 496,
"height": 96,
"content": "# 👩💻 **DOMÉNICA AMORES** 💡"
},
"typeVersion": 1
},
{
"id": "a35f356d-1afd-4aa9-bfbf-d43ac88909b7",
"name": "Sticky Note32",
"type": "n8n-nodes-base.stickyNote",
"position": [
-4048,
-16
],
"parameters": {
"width": 464,
"height": 96,
"content": "# 👨💻 **ADRIÁN VILLAMAR** ⚙️"
},
"typeVersion": 1
},
{
"id": "e4d2da34-4840-4228-8d25-54cb5325a6f2",
"name": "Sticky Note33",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1936,
1024
],
"parameters": {
"color": 2,
"width": 272,
"height": 384,
"content": "## 💡 *ESTE FLUJO PERMITE RECOPILAR COMENTARIOS DE LOS USUARIOS Y ALMACENARLOS DE FORMA AUTOMÁTICA, MEJORANDO EL SEGUIMIENTO DE LA SATISFACCIÓN Y LAS MEJORAS DEL BOT.*"
},
"typeVersion": 1
},
{
"id": "107e8fe1-081d-4541-a73e-d7aac4e11c11",
"name": "Sticky Note34",
"type": "n8n-nodes-base.stickyNote",
"position": [
-4208,
2048
],
"parameters": {
"width": 4272,
"height": 672,
"content": ""
},
"typeVersion": 1
},
{
"id": "ae2d015b-3691-459b-b4c8-2ad948c176ba",
"name": "Sticky Note35",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2192,
2064
],
"parameters": {
"width": 2224,
"height": 608,
"content": "# 🤖 **SISTEMA DE GENERACIÓN DE MENSAJES AUTOMÁTICOS Y PERSONALIZADOS** 💬\n"
},
"typeVersion": 1
},
{
"id": "090a8f45-775a-45be-afe5-ada7e40f426c",
"name": "Sticky Note36",
"type": "n8n-nodes-base.stickyNote",
"position": [
-4160,
2064
],
"parameters": {
"width": 1904,
"height": 608,
"content": "# 🧠 **CEREBRO DE LA GENERACIÓN DE MENSAJES AUTOMÁTICOS Y PERSONALIZADOS**\n\n## 🤖 ESTE FLUJO EN N8N FUNCIONA COMO UN ASISTENTE INTELIGENTE QUE ANALIZA LOS MENSAJES DEL USUARIO Y ENVÍA RESPUESTAS AUTOMÁTICAS Y PERSONALIZADAS 💬⚡.\n\n# 📅 1.- CONSULTA DE CALENDARIO ACADÉMICO:\n\n## - CUANDO EL USUARIO PREGUNTA SOBRE FECHAS O EVENTOS DEL CALENDARIO, EL SISTEMA BUSCA LA INFORMACIÓN EN UNA HOJA DE CÁLCULO 📖, CREA UN PROMPT 🧩 Y GENERA UNA RESPUESTA CLARA QUE LUEGO SE ENVÍA POR TELEGRAM 📲.\n\n# 💡 2.- CONSULTA DE PREGUNTAS FRECUENTES (FAQ):\n\n## - SI EL MENSAJE DEL USUARIO ES UNA DUDA GENERAL, EL SISTEMA CONECTA CON LA BASE DE DATOS MONGODB 🍃, IDENTIFICA LAS RESPUESTAS RELEVANTES 🔍 Y ENVÍA UN MENSAJE AUTOMÁTICO CON LA INFORMACIÓN SOLICITADA 💬✨.\n\n"
},
"typeVersion": 1
},
{
"id": "876d72d2-2dbc-44a8-a3f3-98b6fdece7a4",
"name": "Sticky Note37",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1968,
-192
],
"parameters": {
"width": 2032,
"height": 496,
"content": "# 🔗 ENLACES DEL BOT Y BASE DE DATOS\n\n# 🤖 BOT DE TELEGRAM: \n\n# https://web.telegram.org/k/#@ESPOLInfoBot\n\n\n\n# 📂 BASE DE DATOS EN SHEETS DE FAQ Y CALENDARIO: \n\n# https://docs.google.com/spreadsheets/d/1WWE-eLP0g5M9Q2CeGpQZat56OhJrQyeYZJShjtETCgo/edit?gid=0#gid=0"
},
"typeVersion": 1
},
{
"id": "207caeb2-0dad-4a90-8cb7-f943663ecfc6",
"name": "Code in JavaScript1",
"type": "n8n-nodes-base.code",
"position": [
-3360,
1664
],
"parameters": {
"jsCode": "const semanas = $input.all();\n\n// Inicializamos un objeto vacío\nconst combinado = {};\n\n// Recorremos todas las semanas y sus claves\nfor (const semana of semanas) {\n const datos = semana.json;\n for (const [clave, valor] of Object.entries(datos)) {\n if (!combinado[clave]) combinado[clave] = [];\n if (valor !== undefined && valor !== null && valor !== \"\")\n combinado[clave].push(String(valor).trim());\n }\n}\n\n// Unimos los valores repetidos en un solo string, separados por comas\nfor (const clave of Object.keys(combinado)) {\n combinado[clave] = [...new Set(combinado[clave])].join(\", \");\n}\n\n// Devolvemos un solo item con todo unido\nreturn [{ json: combinado }];\n"
},
"typeVersion": 2
},
{
"id": "2760029c-b039-4aab-95bd-005caeead0b6",
"name": "Split Out2",
"type": "n8n-nodes-base.splitOut",
"position": [
-3504,
1664
],
"parameters": {
"options": {},
"fieldToSplitOut": "RESULTADOS[0].PERIODO_FECHAS, RESULTADOS[0].ACTIVIDADES_GRADO, RESULTADOS[0].PROCESOS_GRADO, RESULTADOS[0]['ACTIVIDADES DE FORMACIÓN TÉCNICA Y TECNOLÓGICA']"
},
"typeVersion": 1
},
{
"id": "9dff1b64-2d74-4de5-8dee-cfcebfcd3977",
"name": "ACTIVIDADES DE LA SEMANA1",
"type": "n8n-nodes-base.code",
"position": [
-3664,
1664
],
"parameters": {
"jsCode": "// 🗓️ FECHA ACTUAL Y AÑO\nconst hoy = new Date();\nconst anioActual = hoy.getFullYear();\n\n// ✅ FUNCIÓN PARA CALCULAR EL LUNES DE UNA SEMANA RELATIVA\nfunction obtenerLunes(base, desplazamientoSemanas = 0) {\n const dia = base.getDay(); // 0=domingo, 1=lunes...\n const lunes = new Date(base);\n const diff = (1 - dia + 7) % 7; // hasta lunes\n lunes.setDate(base.getDate() + diff + desplazamientoSemanas * 7);\n return lunes;\n}\n\n// 📆 CALCULAR LAS TRES SEMANAS CLAVE\nconst lunesSemanaAnterior = obtenerLunes(hoy, -1);\nconst lunesProximaSemana = obtenerLunes(hoy, 0);\nconst lunesSemanaPosterior = obtenerLunes(hoy, 1);\n\n// 🧠 FUNCIÓN PARA INTERPRETAR RANGOS TIPO “09 - 13 NOVIEMBRE” O “9-13nov”\nfunction parsearRango(rango) {\n if (!rango || typeof rango !== \"string\") return null;\n\n rango = rango\n .replace(/([0-9])([a-zA-ZñÑ])/g, \"$1 $2\")\n .replace(/\\s+/g, \" \")\n .trim()\n .toLowerCase();\n\n const match = rango.match(/(\\d{1,2})\\s*-\\s*(\\d{1,2})\\s*([a-zñ]+)/);\n if (!match) return null;\n\n const [, inicioStr, finStr, mesStr] = match;\n const inicio = parseInt(inicioStr);\n const fin = parseInt(finStr);\n\n const meses = {\n ene: 0, enero: 0,\n feb: 1, febrero: 1,\n mar: 2, marzo: 2,\n abr: 3, abril: 3,\n may: 4, mayo: 4,\n jun: 5, junio: 5,\n jul: 6, julio: 6,\n ago: 7, agosto: 7,\n sep: 8, sept: 8, septiembre: 8,\n oct: 9, octubre: 9,\n nov: 10, noviembre: 10,\n dic: 11, diciembre: 11\n };\n\n const mesNum = meses[mesStr];\n if (mesNum === undefined) return null;\n\n let anioRango = anioActual;\n if (mesNum === 0 && hoy.getMonth() === 11) {\n anioRango = anioActual + 1;\n }\n\n const inicioFecha = new Date(anioRango, mesNum, inicio);\n const finFecha = new Date(anioRango, mesNum, fin);\n return { inicio: inicioFecha, fin: finFecha };\n}\n\n// 🔍 FUNCIÓN PARA BUSCAR COINCIDENCIA DE SEMANA\nfunction filtrarPorSemana(items, lunesReferencia) {\n const resultados = [];\n for (const item of items) {\n const rango = parsearRango(item.json.PERIODO_FECHAS);\n if (!rango) continue;\n\n const mismoInicio =\n rango.inicio.getFullYear() === lunesReferencia.getFullYear() &&\n rango.inicio.getDate() === lunesReferencia.getDate() &&\n rango.inicio.getMonth() === lunesReferencia.getMonth();\n\n if (mismoInicio) resultados.push(item);\n }\n return resultados;\n}\n\n// 📊 OBTENER RESULTADOS PARA CADA SEMANA\nconst resultadosAnterior = filtrarPorSemana(items, lunesSemanaAnterior);\nconst resultadosActual = filtrarPorSemana(items, lunesProximaSemana);\nconst resultadosPosterior = filtrarPorSemana(items, lunesSemanaPosterior);\n\n// 🧹 CHAT_IDS VÁLIDOS (ÚNICOS)\nconst chatIds = items\n .map(item => item.json.CHAT_ID)\n .filter(id => id !== undefined && id !== null && String(id).trim() !== \"\")\n .map(id => String(id).trim());\n\nconst uniqueChatIds = [...new Set(chatIds)];\n\n// 🏁 SALIDA FINAL\nreturn [\n {\n json: {\n SEMANA: \"ANTERIOR\",\n LUNES_SEMANA: lunesSemanaAnterior.toISOString().split(\"T\")[0],\n RESULTADOS: resultadosAnterior.map(i => i.json),\n CHAT_IDS_VALIDOS: uniqueChatIds\n }\n },\n {\n json: {\n SEMANA: \"ACTUAL / PRÓXIMA\",\n LUNES_SEMANA: lunesProximaSemana.toISOString().split(\"T\")[0],\n RESULTADOS: resultadosActual.map(i => i.json),\n CHAT_IDS_VALIDOS: uniqueChatIds\n }\n },\n {\n json: {\n SEMANA: \"POSTERIOR\",\n LUNES_SEMANA: lunesSemanaPosterior.toISOString().split(\"T\")[0],\n RESULTADOS: resultadosPosterior.map(i => i.json),\n CHAT_IDS_VALIDOS: uniqueChatIds\n }\n }\n];\n"
},
"typeVersion": 2
},
{
"id": "03a100d6-1492-4834-a638-653ff0a235ca",
"name": "ANUNCIO SEMANAL",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-4096,
1680
],
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtHour": 19
}
]
}
},
"typeVersion": 1.2
},
{
"id": "af582ce7-df08-42dc-b4e6-e04bbc086c21",
"name": "ACTUALIZAR CALENDARIO",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-3728,
560
],
"parameters": {
"rule": {
"interval": [
{
"daysInterval": 7
}
]
}
},
"typeVersion": 1.2
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "1f231775-d723-4123-a5cf-9178e15e246d",
"connections": {
"HTML": {
"main": [
[
{
"node": "Split Out",
"type": "main",
"index": 0
}
]
]
},
"Wait": {
"main": [
[
{
"node": "GUIA DE COMO PREGUNTAR",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "Respuesta Usuario1",
"type": "main",
"index": 0
}
],
[
{
"node": "Guardado en cvs Feedback",
"type": "main",
"index": 0
}
],
[
{
"node": "Comandos1",
"type": "main",
"index": 0
}
],
[
{
"node": "Detector_Calendario_Pre",
"type": "main",
"index": 0
}
]
]
},
"BD_ESPOL": {
"ai_tool": [
[
{
"node": "PREGUNTAS AL AZAR DE GUIA",
"type": "ai_tool",
"index": 0
}
]
]
},
"Comandos1": {
"main": [
[
{
"node": "Mensaje Help",
"type": "main",
"index": 0
}
],
[
{
"node": "FAQs",
"type": "main",
"index": 0
}
],
[
{
"node": "Respuesta Contact",
"type": "main",
"index": 0
}
],
[
{
"node": "Respuesta Events",
"type": "main",
"index": 0
}
],
[
{
"node": "Inicio - Feedback",
"type": "main",
"index": 0
}
],
[
{
"node": "BIENVENIDA",
"type": "main",
"index": 0
},
{
"node": "OBTENER_ID1",
"type": "main",
"index": 0
}
]
]
},
"Split Out": {
"main": [
[
{
"node": "CORRECTOR DE FECHAS",
"type": "main",
"index": 0
}
]
]
},
"BIENVENIDA": {
"main": [
[
{
"node": "PREGUNTAS AL AZAR DE GUIA",
"type": "main",
"index": 0
}
]
]
},
"Get a chat": {
"main": [
[
{
"node": "Sí - Agradecimiento1",
"type": "main",
"index": 0
}
]
]
},
"SCRRAPPING": {
"main": [
[
{
"node": "HTML",
"type": "main",
"index": 0
}
]
]
},
"Split Out2": {
"main": [
[
{
"node": "Code in JavaScript1",
"type": "main",
"index": 0
}
]
]
},
"OBTENER_ID1": {
"main": [
[
{
"node": "AGREGA ID_UNICAS1",
"type": "main",
"index": 0
}
]
]
},
"OPTIMIZADOR": {
"main": [
[
{
"node": "ENVIO DE MENSAJE DE TEXTO1",
"type": "main",
"index": 0
}
]
]
},
"OPTIMIZADOR1": {
"main": [
[
{
"node": "ENVIO DE MENSAJE DE VOZ1",
"type": "main",
"index": 0
}
]
]
},
"BD_CALENDARIO1": {
"main": [
[
{
"node": "ACTIVIDADES DE LA SEMANA1",
"type": "main",
"index": 0
}
]
]
},
"ANUNCIO SEMANAL": {
"main": [
[
{
"node": "BD_CALENDARIO1",
"type": "main",
"index": 0
}
]
]
},
"Message a model1": {
"main": [
[
{
"node": "Limpiar_Texto_Gemini1",
"type": "main",
"index": 0
}
]
]
},
"Inicio - Feedback": {
"main": [
[]
]
},
"Mensaje de Gemini": {
"main": [
[
{
"node": "Limpiar texto Gemini",
"type": "main",
"index": 0
}
]
]
},
"Respuesta Usuario1": {
"main": [
[
{
"node": "Get a chat",
"type": "main",
"index": 0
}
],
[
{
"node": "No - Mensaje Feedback1",
"type": "main",
"index": 0
}
]
]
},
"Switch_Que_BD_Leer": {
"main": [
[
{
"node": "Calendario Académico",
"type": "main",
"index": 0
}
],
[
{
"node": "Leer FAQs de MongoDB",
"type": "main",
"index": 0
}
]
]
},
"CORRECTOR DE FECHAS": {
"main": [
[
{
"node": "BD-CALENDARIO",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript1": {
"main": [
[
{
"node": "GENERADOR DE ANUNCIOS1",
"type": "main",
"index": 0
}
]
]
},
"Leer FAQs de MongoDB": {
"main": [
[
{
"node": "Buscar_FAQs_Relevantes",
"type": "main",
"index": 0
}
]
]
},
"Limpiar texto Gemini": {
"main": [
[
{
"node": "Enviar Respuesta sobre faqs",
"type": "main",
"index": 0
}
]
]
},
"ACTUALIZAR CALENDARIO": {
"main": [
[
{
"node": "SCRRAPPING",
"type": "main",
"index": 0
}
]
]
},
"Calendario Académico": {
"main": [
[
{
"node": "Buscar_Eventos_Calendario1",
"type": "main",
"index": 0
}
]
]
},
"Limpiar_Texto_Gemini1": {
"main": [
[
{
"node": "Enviar Respuesta sobre calendario1",
"type": "main",
"index": 0
}
]
]
},
"Buscar_FAQs_Relevantes": {
"main": [
[
{
"node": "Construir_Prompt_Gemini1",
"type": "main",
"index": 0
}
]
]
},
"GENERADOR DE ANUNCIOS1": {
"main": [
[
{
"node": "OPTIMIZADOR",
"type": "main",
"index": 0
}
]
]
},
"No - Mensaje Feedback1": {
"main": [
[]
]
},
"Detector_Calendario_Pre": {
"main": [
[
{
"node": "Switch_Que_BD_Leer",
"type": "main",
"index": 0
}
]
]
},
"Construir_Prompt_Gemini1": {
"main": [
[
{
"node": "Mensaje de Gemini",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "PREGUNTAS AL AZAR DE GUIA",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Guardado en cvs Feedback": {
"main": [
[]
]
},
"ACTIVIDADES DE LA SEMANA1": {
"main": [
[
{
"node": "Split Out2",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model1": {
"ai_languageModel": [
[
{
"node": "GENERADOR DE ANUNCIOS1",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"PREGUNTAS AL AZAR DE GUIA": {
"main": [
[
{
"node": "Wait",
"type": "main",
"index": 0
}
]
]
},
"Telegram Trigger - Inicio": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Buscar_Eventos_Calendario1": {
"main": [
[
{
"node": "Construir_Prompt_Calendario1",
"type": "main",
"index": 0
}
]
]
},
"Enviar Respuesta sobre faqs": {
"main": [
[]
]
},
"GENERADOR DE MENSAJE DE VOZ": {
"main": [
[
{
"node": "OPTIMIZADOR1",
"type": "main",
"index": 0
}
]
]
},
"Construir_Prompt_Calendario1": {
"main": [
[
{
"node": "Message a model1",
"type": "main",
"index": 0
}
]
]
}
}
}