UNPKG

mcp-sisga-server

Version:

Servidor MCP para integración con el sistema SISGA - gestión académica y calificaciones

1,039 lines 56.6 kB
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from 'zod'; // 1. PASAR EL MANEJADOR DE ERRORES EN EL CONSTRUCTOR // Resuelve el error: `Property 'onError' does not exist on type 'McpServer'`. const server = new McpServer({ name: "SISGA MCP Docentes", description: "El uso del Model Context Protocol (MCP) es estrictamente bajo la responsabilidad del docente. Usted debe verificar y validar cada acción generada o ejecutada, asegurarse de cumplir con las políticas institucionales y normativas de protección de datos, y asumir cualquier consecuencia derivada de su aplicación. Al continuar, acepta revisar periódicamente los resultados y garantizar su correcta interpretación y uso.", version: "1.0.0", // El manejador de errores se define aquí, al crear la instancia del servidor. onError: (error, extra) => { console.error('Ha ocurrido un error en el servidor MCP:', { error: error.message, path: extra.path, input: extra.input, }); } }); const BASE_URL = "https://apisisga.azurewebsites.net"; let authToken = null; let usuarioLogueado = null; async function makeRequest(endpoint, method = 'GET', body = null, requiresAuth = true) { const url = `${BASE_URL}${endpoint}`; const config = { method, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } }; if (requiresAuth && authToken) { config.headers['Authorization'] = `Bearer ${authToken}`; } if (body && method !== 'GET') { config.body = JSON.stringify(body); } try { const response = await fetch(url, config); const data = await response.json(); if (!response.ok) { const errorMessage = data?.message || `Error con código de estado ${response.status}`; throw new Error(errorMessage); } return data; } catch (error) { throw new Error(`La solicitud a la API falló: ${error.message} Bearer ${authToken} url ${url}`); } } // Tool: Login - SEGURIDAD MCP server.tool('login', 'AUTENTICACIÓN SEGURA SISGA MCP - NUNCA solicita usuario ni contraseña. El usuario DEBE ingresar a la URL proporcionada, autenticarse en el sitio web oficial de SISGA, y proporcionar únicamente el código de autenticación generado. El uso del Model Context Protocol (MCP) es estrictamente bajo la responsabilidad del docente. Usted debe verificar y validar cada acción generada o ejecutada, asegurarse de cumplir con las políticas institucionales y normativas de protección de datos, y asumir cualquier consecuencia derivada de su aplicación.', { codigoAutoGenerado: z.string().describe('Código de autenticación generado por SISGA después de realizar el login en https://app.sisga.com.co/loginmcp') }, async ({ codigoAutoGenerado }) => { try { // SECURITY: Never ask for username/password - only provide instructions if (!codigoAutoGenerado || codigoAutoGenerado.trim() === '') { return { content: [{ type: "text", text: `🔐 AUTENTICACIÓN SEGURA SISGA MCP\n\n` + `⚠️ IMPORTANTE: Esta herramienta NUNCA solicita usuario ni contraseña por seguridad.\n\n` + `📋 Para realizar el login en SISGA, siga estos pasos:\n\n` + `1️⃣ Ingrese a la siguiente URL: https://app.sisga.com.co/loginmcp \n` + `2️⃣ Complete el proceso de autenticación con sus credenciales en el sitio web oficial\n` + `3️⃣ Una vez autenticado, SISGA le proporcionará un código de autenticación\n` + `4️⃣ Proporcione ese código en el parámetro 'codigoAutoGenerado' de esta herramienta\n\n` + `🔒 MEDIDAS DE SEGURIDAD:\n` + `• NUNCA comparta sus credenciales con esta herramienta\n` + `• NUNCA ingrese usuario o contraseña en esta interfaz\n` + `• Solo use el código de autenticación temporal generado por SISGA\n` + `• El código tiene tiempo limitado de validez\n\n` + `⚠️ RESPONSABILIDAD: El uso del Model Context Protocol (MCP) es estrictamente bajo la responsabilidad del docente. ` + `Usted debe verificar y validar cada acción generada o ejecutada, asegurarse de cumplir con las políticas ` + `institucionales y normativas de protección de datos, y asumir cualquier consecuencia derivada de su aplicación.` }] }; } // Validate the authentication code const response = await makeRequest(`/api/Security/ValidarCodigoMCP?codigoAutoGenerado=${encodeURIComponent(codigoAutoGenerado)}`, 'GET', null, false); if (response.Token) { authToken = response.Token; usuarioLogueado = { usuario: response.Usuario || 'N/A', datosCompletos: response, fechaLogin: new Date().toISOString(), codigoUsuario: response.Codigo?.toString() || null, nombreCompleto: response.NombreCompletoUsuario || null, rol: response.TipoUsuario || response.Perfil || null, codigoProfesor: response.Codigo?.toString() || null, institucion: response.NombreColegio || null, authToken: response.Token || null }; return { content: [{ type: "text", text: `✅ Login exitoso para el usuario: ${response.Usuario}.\n\n` + `🔐 Autenticación segura completada usando código temporal.\n\n` + `Información almacenada en contexto:\n` + `• Usuario: ${usuarioLogueado.usuario || 'N/A'}\n` + `• Nombre: ${usuarioLogueado.nombreCompleto || 'N/A'}\n` + `• Código Profesor: ${usuarioLogueado.codigoProfesor || 'N/A'}\n` + `• Rol: ${usuarioLogueado.rol || 'N/A'}\n` + `• Institución: ${usuarioLogueado.institucion || 'N/A'}\n` + `• Fecha Login: ${usuarioLogueado.fechaLogin || 'N/A'}\n` + `• Fecha Caducidad: ${response.FechaCaducidad || 'N/A'}\n\n` + `🔒 Token de autenticación temporal obtenido y almacenado para futuras consultas.\n` + `⚠️ Recuerde: NUNCA comparta sus credenciales con esta herramienta.` }] }; } else { throw new Error("La respuesta del servidor no incluyó un token."); } } catch (error) { authToken = null; usuarioLogueado = null; return { content: [{ type: "text", text: `❌ Error en el login: ${error.message}\n\n` + `🔒 RECUERDE: Esta herramienta NUNCA solicita usuario ni contraseña.\n\n` + `Si el código proporcionado no es válido, por favor:\n` + `1️⃣ Verifique que haya completado el proceso de autenticación en https://app.sisga.com.co/loginmcp\n` + `2️⃣ Asegúrese de copiar el código completo sin espacios adicionales\n` + `3️⃣ Intente nuevamente con un código fresco\n\n` + `⚠️ SEGURIDAD: Solo use códigos de autenticación temporales generados por SISGA. NUNCA comparta credenciales.` }], isError: true }; } }); const noParams = {}; // Tool: Plan Académico del Docente server.tool('docente_plan_academico_docente', 'Obtiene el plan académico e información completa de un docente. Si no se proporciona un código, usa el del usuario que ha iniciado sesión.', {}, async ({}) => { try { const response = await makeRequest(`/api/DocentePlanAcademico/Get`); const mensaje = `Plan académico del docente ${usuarioLogueado?.nombreCompleto} :\n\n${JSON.stringify(response, null, 2)}`; return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `Error al obtener el plan académico del docente: ${error.message}` }], isError: true }; } }); server.tool('docente_listado_estudiantes', 'Obtiene el listado de estudiantes de un grupo especifico, requiere saber el plan academico docente para obtener el codigo del grupo, requiere inicio de sesion. Un campo muy importante del plan academico es el Tipo de evaluacion, con el tipo de evaluacion se si vo a ingresar cualitativo o numerico', { codigoGrupo: z.string().describe('Codigo del grupo que se quiere consultar.') }, async ({ codigoGrupo }) => { try { if (!codigoGrupo) { return { content: [{ type: "text", text: "Error: No se proporcionó un código de grupo" + "Sugerencia: Indique el nombre del grupo para poder obtener la lista de estudiantes." }], isError: true }; } const response = await makeRequest(`/api/ListaEstudiantesGrupo/Get?codigoGrupo=${codigoGrupo}`); const mensaje = `Listado de estudiantes del grupo ${codigoGrupo}:\n\n${JSON.stringify(response, null, 2)}`; return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `Error al obtener el listado de estudiantes del grupo: ${error.message}` }], isError: true }; } }); // Tool: Obtener Evaluaciones Parciales server.tool('obtener_evaluaciones_parciales', 'Obtiene las evaluaciones parciales disponibles para una asignatura, grado, grupo y periodo específico. Esta información es necesaria para registrar calificaciones numéricas.', { codigoAsignatura: z.string().describe('Código de la asignatura'), codigoGrado: z.number().describe('Código del grado'), codigoGrupo: z.number().describe('Código del grupo'), periodo: z.number().min(1).max(4).describe('Periodo académico (1-4)') }, async ({ codigoAsignatura, codigoGrado, codigoGrupo, periodo }) => { try { const response = await makeRequest(`/api/DocenteEvaluacionParcial/Get?codigoasignatura=${codigoAsignatura}&codigogrado=${codigoGrado}&codigogrupo=${codigoGrupo}&periodo=${periodo}`); if (!response || response.length === 0) { return { content: [{ type: "text", text: `No se encontraron evaluaciones parciales para:\n` + `• Asignatura: ${codigoAsignatura}\n` + `• Grado: ${codigoGrado}\n` + `• Grupo: ${codigoGrupo}\n` + `• Periodo: ${periodo}\n\n` + `Sugerencia: Verifique que los parámetros sean correctos y que existan evaluaciones configuradas para este grupo.` }], isError: true }; } const mensaje = `📋 Evaluaciones parciales disponibles:\n\n` + `• Asignatura: ${codigoAsignatura}\n` + `• Grado: ${codigoGrado}\n` + `• Grupo: ${codigoGrupo}\n` + `• Periodo: ${periodo}\n\n` + `Evaluaciones encontradas:\n` + response.map((evaluacion, index) => `${index + 1}. Código: ${evaluacion.EvaparConsecutivo}\n` + ` Nombre: ${evaluacion.Nombre}\n` + ` Porcentaje: ${evaluacion.Porcentaje}%\n` + ` Código Evaluación: ${evaluacion.CodigoEvaluacionParcial}\n`).join('\n'); return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `❌ Error al obtener evaluaciones parciales: ${error.message}` }], isError: true }; } }); // Tool: Obtener Calificaciones Detalladas por Evaluación Parcial server.tool('obtener_calificaciones_detalladas_evaluacion', 'Obtiene las calificaciones detalladas de todos los estudiantes para una evaluación parcial específica. Muestra el estado actual de las calificaciones.', { evaparConsecutivo: z.number().describe('Código consecutivo de la evaluación parcial'), codigoGrupo: z.number().describe('Código del grupo') }, async ({ evaparConsecutivo, codigoGrupo }) => { try { const response = await makeRequest(`/api/DocenteCalificacionesDetalladasxEvpar/Get?evparconsecutivo=${evaparConsecutivo}&gruconsecutivo=${codigoGrupo}`); if (!response || response.length === 0) { return { content: [{ type: "text", text: `No se encontraron calificaciones para:\n` + `• Evaluación parcial: ${evaparConsecutivo}\n` + `• Grupo: ${codigoGrupo}\n\n` + `Sugerencia: Verifique que la evaluación parcial y el grupo sean correctos.` }], isError: true }; } const mensaje = `📊 Calificaciones detalladas de la evaluación parcial ${evaparConsecutivo}:\n\n` + `• Grupo: ${codigoGrupo}\n` + `• Total de estudiantes: ${response.length}\n\n` + `Detalle por estudiante:\n` + response.map((estudiante, index) => { const calificaciones = estudiante.CalPar || []; const calificacionesStr = calificaciones.map((cal, calIndex) => `${cal.NombreDet}: ${cal.EscalaDet === "-1.00" ? 'Sin calificar' : cal.EscalaDet}`).join(', '); return `${index + 1}. ${estudiante.NombreEstudiant}\n` + ` Código: ${estudiante.CodigoMat}\n` + ` Lista: ${estudiante.NumeroLista}\n` + ` Calificaciones: ${calificacionesStr || 'Sin calificaciones'}\n` + ` NEE: ${estudiante.Nee || 'N/A'}\n`; }).join('\n'); return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `❌ Error al obtener calificaciones detalladas: ${error.message}` }], isError: true }; } }); // Tool: Registrar Calificación Individual server.tool('registrar_calificacion_individual', 'Registra una calificación individual para uno o varios estudiantes en una evaluación parcial específica. Permite especificar el número de nota (1, 2, 3, etc.).', { codigoGrupo: z.number().describe('Código del grupo'), codigoAsignatura: z.string().describe('Código de la asignatura'), periodo: z.number().min(1).max(4).describe('Periodo académico (1-4)'), codigosMatricula: z.array(z.string()).describe('Lista de códigos de matrícula de los estudiantes'), idEvaluacionParcial: z.number().describe('ID de la evaluación parcial'), nota: z.number().describe('Nota a registrar'), numeroNota: z.number().min(1).describe('Número de la nota (1, 2, 3, etc.)') }, async ({ codigoGrupo, codigoAsignatura, periodo, codigosMatricula, idEvaluacionParcial, nota, numeroNota }) => { try { if (!codigosMatricula || codigosMatricula.length === 0) { return { content: [{ type: "text", text: "Error: Debe proporcionar al menos un código de matrícula." }], isError: true }; } const body = { consecutivoGrupo: codigoGrupo, codigoAsignatura: codigoAsignatura, periodo: periodo, codigosMatricula: codigosMatricula.join(','), idEvaluacionParcial: idEvaluacionParcial, nota: nota, notanumero: numeroNota }; const response = await makeRequest('/api/DocenteIngresarCalificacion/Post', 'POST', body); const estudiantesCount = codigosMatricula.length; const mensaje = `✅ Calificación individual registrada exitosamente!\n\n` + `• Grupo: ${codigoGrupo}\n` + `• Asignatura: ${codigoAsignatura}\n` + `• Periodo: ${periodo}\n` + `• Evaluación parcial: ${idEvaluacionParcial}\n` + `• Nota: ${nota}\n` + `• Número de nota: ${numeroNota}\n` + `• Estudiantes calificados: ${estudiantesCount}\n` + `• Respuesta del servidor: ${response}\n\n` + `Estudiantes calificados:\n` + codigosMatricula.map(codigo => ` - ${codigo}: ${nota}`).join('\n'); return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `❌ Error al registrar calificación individual: ${error.message}` }], isError: true }; } }); // Tool: Obtener información del usuario logueado server.tool('info_usuario_logueado', 'Muestra la información del usuario que ha iniciado sesión actualmente.', noParams, async () => { if (!usuarioLogueado) { return { content: [{ type: "text", text: "No hay ningún usuario autenticado en este momento.\n\n" + "Sugerencia: Usa la herramienta 'login' para iniciar sesión." }], isError: true }; } return { content: [{ type: "text", text: `Información del usuario autenticado:\n\n` + `Información básica:\n` + `• Usuario: ${usuarioLogueado.usuario}\n` + `• Nombre: ${usuarioLogueado.nombreCompleto || 'N/A'}\n` + `• Código Profesor: ${usuarioLogueado.codigoProfesor || 'N/A'}\n` + `• Rol: ${usuarioLogueado.rol || 'N/A'}\n` + `• Institución: ${usuarioLogueado.institucion || 'N/A'}\n` + `• Fecha Login: ${usuarioLogueado.fechaLogin}\n\n` + `Estado del Token: ${authToken ? 'Activo' : 'No disponible'}` }] }; }); // Tool: Cerrar sesión server.tool('logout', 'Cierra la sesión del usuario actual y limpia los datos de autenticación almacenados.', noParams, async () => { const usuarioAnterior = usuarioLogueado?.usuario || 'un usuario desconocido'; authToken = null; usuarioLogueado = null; return { content: [{ type: "text", text: `Sesión cerrada exitosamente para ${usuarioAnterior}.\n\n` + `Se han limpiado todos los datos de la sesión (token e información de usuario).\n` + `Puede usar 'login' para iniciar una nueva sesión.` }] }; }); // Tool: Registrar Calificaciones Numéricas server.tool('registrar_calificaciones_numericas', 'Registra calificaciones numéricas para uno o varios estudiantes en una evaluación parcial específica. Permite registrar calificaciones individuales o masivas. IMPORTANTE: Esta herramienta es para calificaciones masivas. Para calificaciones individuales use "registrar_calificacion_individual".', { evaparConsecutivo: z.number().describe('Código consecutivo de la evaluación parcial (obtener con obtener_evaluaciones_parciales)'), codigoGrupo: z.string().describe('Código del grupo'), calificaciones: z.array(z.object({ codigoMatricula: z.string().describe('Código de matrícula del estudiante'), nota: z.number().min(0).max(10).describe('Nota del estudiante (0-10)') })).describe('Lista de calificaciones con código de matrícula y nota'), observacion: z.string().optional().describe('Observación general para las calificaciones (opcional)') }, async ({ evaparConsecutivo, codigoGrupo, calificaciones, observacion }) => { try { if (!calificaciones || calificaciones.length === 0) { return { content: [{ type: "text", text: "Error: Debe proporcionar al menos una calificación para registrar." }], isError: true }; } const body = { EvaparConsecutivo: evaparConsecutivo, CodigoGrupo: codigoGrupo, Calificaciones: calificaciones.map(cal => ({ CodigoMatricula: cal.codigoMatricula, Nota: cal.nota })), Observacion: observacion || "" }; const response = await makeRequest('/api/DocenteCalificacionesNumericas/IngresarCalificaciones', 'POST', body); const estudiantesCount = calificaciones.length; const mensaje = `✅ Calificaciones registradas exitosamente!\n\n` + `• Evaluación parcial: ${evaparConsecutivo}\n` + `• Grupo: ${codigoGrupo}\n` + `• Estudiantes calificados: ${estudiantesCount}\n` + `• Respuesta del servidor: ${response}\n\n` + `Detalle de calificaciones:\n` + calificaciones.map(cal => ` - Estudiante ${cal.codigoMatricula}: ${cal.nota}/10`).join('\n'); return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `❌ Error al registrar calificaciones numéricas: ${error.message}` }], isError: true }; } }); // Tool: Registrar Calificaciones Cualitativas server.tool('registrar_calificaciones_cualitativas', 'Registra calificaciones cualitativas para uno o varios estudiantes usando escalas valorativas (A, B, C, D, etc.). Permite registrar calificaciones individuales o masivas.', { codigosMatricula: z.array(z.string()).describe('Lista de códigos de matrícula de los estudiantes'), calEscvalCodigo: z.string().describe('Código de la escala valorativa (ej: A, B, C, D)'), calAsiCodigo: z.string().describe('Código de la asignatura'), calPeriodo: z.number().min(1).max(4).describe('Periodo académico (1-4)'), calObservacion: z.string().optional().describe('Observación para las calificaciones (opcional)') }, async ({ codigosMatricula, calEscvalCodigo, calAsiCodigo, calPeriodo, calObservacion }) => { try { if (!codigosMatricula || codigosMatricula.length === 0) { return { content: [{ type: "text", text: "Error: Debe proporcionar al menos un código de matrícula." }], isError: true }; } const body = { CalMatCodigos: codigosMatricula.join(','), CalEscvalCodigo: calEscvalCodigo, CalObservacion: calObservacion || "", CalAsiCodigo: calAsiCodigo, CalPeriodo: calPeriodo }; const response = await makeRequest('/api/DocenteEvaluacionCualitativa/IngresarCalificaciones', 'POST', body); const estudiantesCount = codigosMatricula.length; let mensaje = `✅ Calificaciones cualitativas registradas exitosamente!\n\n` + `• Asignatura: ${calAsiCodigo}\n` + `• Periodo: ${calPeriodo}\n` + `• Escala valorativa: ${calEscvalCodigo}\n` + `• Estudiantes calificados: ${estudiantesCount}\n` + `• Respuesta del servidor: ${response}\n\n` + `Estudiantes calificados:\n` + codigosMatricula.map(codigo => ` - ${codigo}: ${calEscvalCodigo}`).join('\n'); if (calObservacion) { mensaje += `\n\nObservación: ${calObservacion}`; } return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `❌ Error al registrar calificaciones cualitativas: ${error.message}` }], isError: true }; } }); // Tool: Registrar Asistencia server.tool('registrar_asistencia', 'Registra inasistencias, retardos o ausencias justificadas para uno o varios estudiantes en una fecha específica. Permite registrar asistencia individual o masiva.', { codigosMatricula: z.array(z.string()).describe('Lista de códigos de matrícula de los estudiantes'), codigoAsignatura: z.string().describe('Código de la asignatura'), tipoInasistencia: z.number().min(1).max(3).describe('Tipo de inasistencia: 1=Falta, 2=Justificada, 3=Retardo'), fechaInasistencia: z.string().describe('Fecha de la inasistencia (formato: YYYY-MM-DD)'), cantidad: z.number().min(1).default(1).describe('Cantidad de inasistencias (por defecto: 1)'), observaciones: z.string().optional().describe('Observaciones sobre la inasistencia (opcional)') }, async ({ codigosMatricula, codigoAsignatura, tipoInasistencia, fechaInasistencia, cantidad, observaciones }) => { try { if (!codigosMatricula || codigosMatricula.length === 0) { return { content: [{ type: "text", text: "Error: Debe proporcionar al menos un código de matrícula." }], isError: true }; } // Validar formato de fecha const fechaRegex = /^\d{4}-\d{2}-\d{2}$/; if (!fechaRegex.test(fechaInasistencia)) { return { content: [{ type: "text", text: "Error: El formato de fecha debe ser YYYY-MM-DD (ejemplo: 2024-06-01)" }], isError: true }; } const tiposInasistencia = { 1: "Falta", 2: "Falta Justificada", 3: "Retardo" }; const body = { CodigosMatricula: codigosMatricula.join(','), CodigoAsignatura: codigoAsignatura, TipoInasistencia: tipoInasistencia, FechaInasistencia: fechaInasistencia, Cantidad: cantidad, Observaciones: observaciones || "" }; const response = await makeRequest('/api/Inasistencia/IngresarInasistencias', 'POST', body); const estudiantesCount = codigosMatricula.length; const tipoTexto = tiposInasistencia[tipoInasistencia]; let mensaje = `✅ Registro de asistencia completado exitosamente!\n\n` + `• Asignatura: ${codigoAsignatura}\n` + `• Fecha: ${fechaInasistencia}\n` + `• Tipo: ${tipoTexto}\n` + `• Cantidad: ${cantidad}\n` + `• Estudiantes registrados: ${estudiantesCount}\n` + `• Respuesta del servidor: ${response}\n\n` + `Estudiantes registrados:\n` + codigosMatricula.map(codigo => ` - ${codigo}: ${tipoTexto}`).join('\n'); if (observaciones) { mensaje += `\n\nObservaciones: ${observaciones}`; } return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `❌ Error al registrar asistencia: ${error.message}` }], isError: true }; } }); // Tool: Registrar Observaciones server.tool('registrar_observaciones', 'Registra observaciones conductuales o académicas para uno o varios estudiantes. Permite enviar notificaciones por correo y SMS a los padres.', { codigosMatricula: z.array(z.string()).describe('Lista de códigos de matrícula de los estudiantes'), tipoObservacion: z.string().describe('Código del tipo de observación'), fechaObservacion: z.string().describe('Fecha de la observación (formato: YYYY-MM-DD)'), descripcion: z.string().describe('Descripción detallada de la observación'), periodo: z.number().min(1).max(4).describe('Periodo académico (1-4)'), grado: z.string().describe('Grado del estudiante'), grupo: z.string().describe('Grupo del estudiante'), notificarCorreo: z.boolean().default(false).describe('Enviar notificación por correo electrónico'), notificarSms: z.boolean().default(false).describe('Enviar notificación por SMS') }, async ({ codigosMatricula, tipoObservacion, fechaObservacion, descripcion, periodo, grado, grupo, notificarCorreo, notificarSms }) => { try { if (!codigosMatricula || codigosMatricula.length === 0) { return { content: [{ type: "text", text: "Error: Debe proporcionar al menos un código de matrícula." }], isError: true }; } if (!descripcion || descripcion.trim() === "") { return { content: [{ type: "text", text: "Error: Debe proporcionar una descripción de la observación." }], isError: true }; } // Validar formato de fecha const fechaRegex = /^\d{4}-\d{2}-\d{2}$/; if (!fechaRegex.test(fechaObservacion)) { return { content: [{ type: "text", text: "Error: El formato de fecha debe ser YYYY-MM-DD (ejemplo: 2024-06-01)" }], isError: true }; } const resultados = []; let exitosos = 0; let fallidos = 0; // Registrar observación para cada estudiante for (const codigoMatricula of codigosMatricula) { try { const body = { hvobs_hvtip_codigo: tipoObservacion, hvobs_fecha_observacion: fechaObservacion, hvobs_mat_codigo: codigoMatricula, hvobs_descripcion: descripcion, hvobs_periodo: periodo, hvobs_grado: grado, hvobs_grupo: grupo, EnvioCorreo: notificarCorreo, EnvioSms: notificarSms }; const response = await makeRequest('/api/Observador/CrearObservacion', 'POST', body); resultados.push(`✅ ${codigoMatricula}: ${response}`); exitosos++; } catch (error) { resultados.push(`❌ ${codigoMatricula}: ${error.message}`); fallidos++; } } const mensaje = `📝 Registro de observaciones completado!\n\n` + `• Fecha: ${fechaObservacion}\n` + `• Periodo: ${periodo}\n` + `• Grado: ${grado}\n` + `• Grupo: ${grupo}\n` + `• Tipo de observación: ${tipoObservacion}\n` + `• Notificaciones: ${notificarCorreo ? 'Correo ✓' : 'Correo ✗'}, ${notificarSms ? 'SMS ✓' : 'SMS ✗'}\n\n` + `Resultados:\n` + `• Exitosos: ${exitosos}\n` + `• Fallidos: ${fallidos}\n\n` + `Detalle por estudiante:\n` + resultados.join('\n') + `\n\nDescripción: ${descripcion}`; return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `❌ Error al registrar observaciones: ${error.message}` }], isError: true }; } }); // Tool: Obtener Tipos de Observación server.tool('obtener_tipos_observacion', 'Obtiene la lista de tipos de observación disponibles para registrar observaciones de estudiantes.', noParams, async () => { try { const response = await makeRequest('/api/Observador/GetTiposObservacion'); const mensaje = `📋 Tipos de observación disponibles:\n\n` + response.map((tipo, index) => `${index + 1}. Código: ${tipo.codigo || tipo.hvtip_codigoP}\n` + ` Nombre: ${tipo.nombre || tipo.hvtip_nombre}\n`).join('\n'); return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `❌ Error al obtener tipos de observación: ${error.message}` }], isError: true }; } }); // Tool: Obtener Escalas Valorativas server.tool('obtener_escalas_valorativas', 'Obtiene la lista de escalas valorativas disponibles para calificaciones cualitativas.', noParams, async () => { try { const response = await makeRequest('/api/MovilEscalaValorativa/Get'); const mensaje = `📊 Escalas valorativas disponibles:\n\n` + response.map((escala, index) => `${index + 1}. Código: ${escala.codigo}\n` + ` Nombre: ${escala.nombre}\n`).join('\n'); return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `❌ Error al obtener escalas valorativas: ${error.message}` }], isError: true }; } }); // Tool: Obtener Períodos por Grado server.tool('obtener_periodos_grado', 'Obtiene los períodos académicos disponibles para un grado específico. Esta información es necesaria para trabajar con evaluaciones y calificaciones.', { codigoGrado: z.number().describe('Código del grado') }, async ({ codigoGrado }) => { try { const response = await makeRequest(`/api/MovilPeriodos/GetPeriodoGrado?codigogrado=${codigoGrado}`); if (!response || response.length === 0) { return { content: [{ type: "text", text: `No se encontraron períodos para el grado ${codigoGrado}.\n\n` + `Sugerencia: Verifique que el código del grado sea correcto.` }], isError: true }; } const mensaje = `📅 Períodos disponibles para el grado ${codigoGrado}:\n\n` + response.map((periodo, index) => `${index + 1}. Período ${periodo}`).join('\n'); return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `❌ Error al obtener períodos: ${error.message}` }], isError: true }; } }); // HERRAMIENTAS PARA DIARIO DE CAMPO // Tool: Obtener Configuración de Columnas del Diario de Campo server.tool('obtener_configuracion_diario_campo', 'Obtiene la configuración de columnas disponibles para el diario de campo. Este es el primer paso necesario antes de poder registrar entradas en el diario de campo.', noParams, async () => { try { const response = await makeRequest('/api/DocenteDiarioCampoConfiguracionColumnas/Get'); if (!response) { return { content: [{ type: "text", text: "❌ El módulo de diario de campo no está configurado.\n\n" + "Por favor, comuníquese con el colegio para que habiliten el módulo de diario de campo " + "en su institución educativa." }], isError: true }; } // Contar campos configurados const camposConfigurados = Object.keys(response).filter(key => key.startsWith('Campo') && response[key] && response[key].trim() !== ''); let mensaje = `📋 Configuración del Diario de Campo\n\n` + `Campos configurados: ${camposConfigurados.length}\n\n`; camposConfigurados.forEach((campo, index) => { const numero = campo.replace('Campo', ''); mensaje += `${index + 1}. Campo ${numero}: ${response[campo]}\n`; }); mensaje += `\n✅ El módulo de diario de campo está habilitado y configurado.\n` + `Ahora puede registrar entradas en el diario de campo usando esta configuración.`; return { content: [{ type: "text", text: mensaje }] }; } catch (error) { if (error.message.includes('404')) { return { content: [{ type: "text", text: "❌ El módulo de diario de campo no está habilitado.\n\n" + "Por favor, comuníquese con el colegio para que habiliten el módulo de diario de campo " + "en su institución educativa." }], isError: true }; } return { content: [{ type: "text", text: `❌ Error al obtener configuración del diario de campo: ${error.message}` }], isError: true }; } }); // Tool: Registrar Entrada en Diario de Campo server.tool('registrar_diario_campo', 'Registra una nueva entrada en el diario de campo. Requiere que se haya obtenido la configuración de columnas previamente.', { consecutivoGrupo: z.number().describe('Código consecutivo del grupo'), codigoAsignatura: z.string().describe('Código de la asignatura'), periodo: z.number().min(1).max(4).describe('Periodo académico (1-4)'), fecha: z.string().describe('Fecha de la entrada (formato: YYYY-MM-DD)'), campo1: z.string().optional().describe('Contenido del campo 1'), campo2: z.string().optional().describe('Contenido del campo 2'), campo3: z.string().optional().describe('Contenido del campo 3'), campo4: z.string().optional().describe('Contenido del campo 4'), campo5: z.string().optional().describe('Contenido del campo 5'), campo6: z.string().optional().describe('Contenido del campo 6'), campo7: z.string().optional().describe('Contenido del campo 7'), campo8: z.string().optional().describe('Contenido del campo 8'), campo9: z.string().optional().describe('Contenido del campo 9'), campo10: z.string().optional().describe('Contenido del campo 10'), campo11: z.string().optional().describe('Contenido del campo 11'), campo12: z.string().optional().describe('Contenido del campo 12'), campo13: z.string().optional().describe('Contenido del campo 13'), campo14: z.string().optional().describe('Contenido del campo 14'), campo15: z.string().optional().describe('Contenido del campo 15'), campo16: z.string().optional().describe('Contenido del campo 16'), campo17: z.string().optional().describe('Contenido del campo 17'), campo18: z.string().optional().describe('Contenido del campo 18'), campo19: z.string().optional().describe('Contenido del campo 19'), campo20: z.string().optional().describe('Contenido del campo 20') }, async ({ consecutivoGrupo, codigoAsignatura, periodo, fecha, ...campos }) => { try { // Validar formato de fecha const fechaRegex = /^\d{4}-\d{2}-\d{2}$/; if (!fechaRegex.test(fecha)) { return { content: [{ type: "text", text: "Error: El formato de fecha debe ser YYYY-MM-DD (ejemplo: 2024-06-01)" }], isError: true }; } // Construir el objeto de datos solo con los campos que tienen contenido const body = { ConsecutivoGrupo: consecutivoGrupo, CodioAsignatura: codigoAsignatura, Periodo: periodo, Fecha: fecha }; // Agregar solo los campos que tienen contenido for (let i = 1; i <= 20; i++) { const campoKey = `campo${i}`; const campoValue = campos[campoKey]; if (campoValue && campoValue.trim() !== '') { body[`Campo${i}`] = campoValue; } } const response = await makeRequest('/api/DocenteDiarioCampo/Post', 'POST', body); // Contar campos registrados const camposRegistrados = Object.keys(body).filter(key => key.startsWith('Campo')).length; const mensaje = `✅ Entrada registrada exitosamente en el diario de campo!\n\n` + `• Grupo: ${consecutivoGrupo}\n` + `• Asignatura: ${codigoAsignatura}\n` + `• Periodo: ${periodo}\n` + `• Fecha: ${fecha}\n` + `• Campos registrados: ${camposRegistrados}\n` + `• Código de entrada: ${response.Codigo || 'N/A'}\n\n` + `Respuesta del servidor: ${JSON.stringify(response, null, 2)}`; return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `❌ Error al registrar entrada en diario de campo: ${error.message}` }], isError: true }; } }); // Tool: Editar Entrada de Diario de Campo server.tool('editar_diario_campo', 'Edita una entrada existente en el diario de campo usando su código identificador.', { codigo: z.number().describe('Código identificador de la entrada a editar'), periodo: z.number().min(1).max(4).describe('Periodo académico (1-4)'), fecha: z.string().describe('Fecha de la entrada (formato: YYYY-MM-DD)'), campo1: z.string().optional().describe('Contenido del campo 1'), campo2: z.string().optional().describe('Contenido del campo 2'), campo3: z.string().optional().describe('Contenido del campo 3'), campo4: z.string().optional().describe('Contenido del campo 4'), campo5: z.string().optional().describe('Contenido del campo 5'), campo6: z.string().optional().describe('Contenido del campo 6'), campo7: z.string().optional().describe('Contenido del campo 7'), campo8: z.string().optional().describe('Contenido del campo 8'), campo9: z.string().optional().describe('Contenido del campo 9'), campo10: z.string().optional().describe('Contenido del campo 10'), campo11: z.string().optional().describe('Contenido del campo 11'), campo12: z.string().optional().describe('Contenido del campo 12'), campo13: z.string().optional().describe('Contenido del campo 13'), campo14: z.string().optional().describe('Contenido del campo 14'), campo15: z.string().optional().describe('Contenido del campo 15'), campo16: z.string().optional().describe('Contenido del campo 16'), campo17: z.string().optional().describe('Contenido del campo 17'), campo18: z.string().optional().describe('Contenido del campo 18'), campo19: z.string().optional().describe('Contenido del campo 19'), campo20: z.string().optional().describe('Contenido del campo 20') }, async ({ codigo, periodo, fecha, ...campos }) => { try { // Construir el objeto de datos const body = { Periodo: periodo, Fecha: fecha }; // Agregar todos los campos (incluso los vacíos para permitir limpieza) for (let i = 1; i <= 20; i++) { const campoKey = `campo${i}`; const campoValue = campos[campoKey]; body[`Campo${i}`] = campoValue || null; } const response = await makeRequest(`/api/DocenteDiarioCampo/Put?codigo=${codigo}`, 'PUT', body); // Contar campos editados const camposEditados = Object.keys(body).filter(key => key.startsWith('Campo') && body[key] !== null && body[key] !== '').length; const mensaje = `✅ Entrada editada exitosamente en el diario de campo!\n\n` + `• Código de entrada: ${codigo}\n` + `• Periodo: ${periodo}\n` + `• Campos editados: ${camposEditados}\n\n` + `Información actualizada:\n` + `• Grupo: ${response.ConsecutivoGrupo || 'N/A'}\n` + `• Asignatura: ${response.CodioAsignatura || 'N/A'}\n` + `• Fecha: ${response.Fecha || 'N/A'}\n\n` + `Respuesta del servidor: ${JSON.stringify(response, null, 2)}`; return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `❌ Error al editar entrada en diario de campo: ${error.message}` }], isError: true }; } }); // Tool: Obtener Entradas de Diario de Campo server.tool('obtener_diarios_campo', 'Obtiene las entradas del diario de campo para un grupo, asignatura y periodo específicos.', { codigoGrupo: z.number().describe('Código del grupo'), codigoAsignatura: z.string().describe('Código de la asignatura'), periodo: z.number().min(1).max(4).describe('Periodo académico (1-4)') }, async ({ codigoGrupo, codigoAsignatura, periodo }) => { try { const response = await makeRequest(`/api/DocenteDiarioCampo/Get?codigoGrupo=${codigoGrupo}&codigoAsignatura=${codigoAsignatura}&periodo=${periodo}`); if (!response || response.length === 0) { return { content: [{ type: "text", text: `No se encontraron entradas de diario de campo para:\n` + `• Grupo: ${codigoGrupo}\n` + `• Asignatura: ${codigoAsignatura}\n` + `• Periodo: ${periodo}\n\n` + `Sugerencia: Verifique los parámetros o registre una nueva entrada.` }], isError: true }; } let mensaje = `📋 Entradas del Diario de Campo\n\n` + `• Grupo: ${codigoGrupo}\n` + `• Asignatura: ${codigoAsignatura}\n` + `• Periodo: ${periodo}\n` + `• Total de entradas: ${response.length}\n\n`; response.forEach((entrada, index) => { mensaje += `${index + 1}. Entrada #${entrada.Codigo}\n` + ` Fecha: ${entrada.Fecha ? new Date(entrada.Fecha).toLocaleDateString() : 'N/A'}\n` + ` Docente: ${entrada.CodigoTercero || 'N/A'}\n`; // Mostrar campos con contenido for (let i = 1; i <= 20; i++) { const campo = entrada[`Campo${i}`]; if (campo && campo.trim() !== '') { mensaje += ` Campo ${i}: ${campo.substring(0, 50)}${campo.length > 50 ? '...' : ''}\n`; } } mensaje += '\n'; }); return { content: [{ type: "text", text: mensaje }] }; } catch (error) { return { content: [{ type: "text", text: `❌ Error al obtener entradas del diario de campo: ${error.message}` }], isError: true }; } }); // Tool: Obtener Detalle de Entrada de Diario de Campo server.tool('obtener_detalle_diario_campo', 'Obtiene el detalle completo de una entrada específica del diario de campo usando su código identificador.', { codigo: z.number().describe('Código identificador de la entrada del diario de campo') }, async ({ codigo }) => { try { const response = await makeRequest(`/api/DocenteDiarioCampo/GetDetalle?codigo=${codigo}`); if (!response) { return { content: [{ type: "text", text: `No se encontró la entrada del diario de campo con código: ${codigo}\n\n` + `Sugerencia: Verifique que el código sea correcto.` }], isError: true }; } let mensaje = `📖 Detalle del Diario de Campo - Entrada #${codigo}\n\n` + `Información general:\n` + `• Código: ${response.Codigo}\n` + `• Grupo: ${response.ConsecutivoGrupo}\n` + `• Asignatura: ${response.CodioAsignatura}\n` + `• Periodo: ${response.Periodo}\n` + `• Fecha: ${response.Fecha ? new Date(response.Fecha).toLocaleDateString() : 'N/A'}\n` + `• Docente: ${response.CodigoTercero}\n\n` + `Contenido por campos:\n`; // Mostrar todos los campos con