using System.Data; using System.Net; using System.Net.Mail; using System.Security.Claims; using System.Text; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.Rendering; using MySql.Data.MySqlClient; namespace HelpDesk.Pages { [Authorize] // Garantiza que solo usuarios autenticados ingresen public class ListadoModel : PageModel { private readonly IConfiguration _configuration; private readonly IWebHostEnvironment _environment; public ListadoModel(IConfiguration configuration, IWebHostEnvironment environment) { _configuration = configuration; _environment = environment; } // --- PROPIEDADES DE SESIÓN Y USUARIO --- public string DniUsuario { get; set; } = string.Empty; public string NombreUsuario { get; set; } = string.Empty; public int EsDite { get; set; } // --- Paginación y Datos --- public DataTable TablaConsultas { get; set; } = new DataTable(); public int PaginaActual { get; set; } = 1; public int TotalPaginas { get; set; } = 1; public const int TamanoPagina = 10; // --- KPIs --- public int KpiTotal { get; set; } public int KpiRespondidos { get; set; } public int KpiPendientes { get; set; } // --- Datos para Chart.js (JSON) --- public string MesesLabelsJS { get; set; } = "[]"; public string MesesDataJS { get; set; } = "[]"; public string DiasLabelsJS { get; set; } = "[]"; public string DiasDataJS { get; set; } = "[]"; // --- BINDING FORMULARIO NUEVA CONSULTA --- [BindProperty] public string TipoPregunta { get; set; } = string.Empty; [BindProperty] public string DniMonitor { get; set; } = string.Empty; [BindProperty] public string Observacion { get; set; } = string.Empty; [BindProperty] public IFormFile? ArchivoAdjunto { get; set; } // --- COMBOBOXES --- public List ListaTiposPregunta { get; set; } = new List(); public List ListaMonitores { get; set; } = new List(); public string? MensajeModal { get; set; } public bool AbrirModalError { get; set; } public async Task OnGetAsync(int pageIndex = 1) { ObtenerDatosUsuarioClaims(); PaginaActual = pageIndex; await CargarDesplegablesAsync(); await CargarDashboardDataAsync(DniUsuario); await CargarListadoAsync(DniUsuario, PaginaActual); return Page(); } public async Task OnPostGuardarConsultaAsync() { ObtenerDatosUsuarioClaims(); if (string.IsNullOrEmpty(TipoPregunta)) { MensajeModal = "Por favor seleccione un tipo de pregunta."; AbrirModalError = true; await CargarTodoAsync(); return Page(); } if (string.IsNullOrEmpty(DniMonitor)) { MensajeModal = "Por favor seleccione el monitor a quien va dirigida la pregunta."; AbrirModalError = true; await CargarTodoAsync(); return Page(); } string fechaActual = DateTime.Now.ToString("yyyy-MM-dd"); string horaActual = DateTime.Now.ToString("HH:mm:ss"); string nombreArchivo = string.Empty; long idConsultaCreada = 0; // Subida de Archivo Adjunto en wwwroot/uploads/ if (ArchivoAdjunto != null && ArchivoAdjunto.Length > 0) { try { string uploadsFolder = Path.Combine(_environment.WebRootPath, "uploads"); if (!Directory.Exists(uploadsFolder)) { Directory.CreateDirectory(uploadsFolder); } nombreArchivo = DateTime.Now.ToString("yyyyMMddHHmmss_") + Path.GetFileName(ArchivoAdjunto.FileName); string filePath = Path.Combine(uploadsFolder, nombreArchivo); using (var stream = new FileStream(filePath, FileMode.Create)) { await ArchivoAdjunto.CopyToAsync(stream); } } catch (Exception ex) { MensajeModal = "Error al subir archivo: " + ex.Message; AbrirModalError = true; await CargarTodoAsync(); return Page(); } } string connectionString = _configuration.GetConnectionString("MySqlHelpDesk") ?? string.Empty; try { using (var con = new MySqlConnection(connectionString)) { await con.OpenAsync(); string insertQuery = @"INSERT INTO tb_registros (fecha, hora, tipopregunta, observacion, archivo, dni, dni_monitor, estado) VALUES (@fecha, @hora, @tipopregunta, @observacion, @archivo, @dni, @dni_monitor, @estado); SELECT LAST_INSERT_ID();"; using (var cmd = new MySqlCommand(insertQuery, con)) { cmd.Parameters.AddWithValue("@fecha", fechaActual); cmd.Parameters.AddWithValue("@hora", horaActual); cmd.Parameters.AddWithValue("@tipopregunta", TipoPregunta); cmd.Parameters.AddWithValue("@observacion", Observacion?.Trim() ?? string.Empty); cmd.Parameters.AddWithValue("@archivo", nombreArchivo); cmd.Parameters.AddWithValue("@dni", DniUsuario); cmd.Parameters.AddWithValue("@dni_monitor", DniMonitor); cmd.Parameters.AddWithValue("@estado", 0); object result = await cmd.ExecuteScalarAsync(); if (result != null) { idConsultaCreada = Convert.ToInt64(result); } } } // Enviar notificación por correo SMTP if (idConsultaCreada > 0) { string correoMonitor = await ObtenerCorreoMonitorAsync(DniMonitor); EnviarNotificacionCorreo(idConsultaCreada, DniUsuario, NombreUsuario, TipoPregunta, Observacion, fechaActual, horaActual, correoMonitor); } return RedirectToPage("/Listado"); } catch (Exception ex) { MensajeModal = "Error al registrar: " + ex.Message; AbrirModalError = true; await CargarTodoAsync(); return Page(); } } public async Task OnPostEliminarAsync(int idConsulta) { ObtenerDatosUsuarioClaims(); string connectionString = _configuration.GetConnectionString("MySqlHelpDesk") ?? string.Empty; using (var con = new MySqlConnection(connectionString)) { await con.OpenAsync(); // Eliminar registro principal solo si pertenece al usuario creador string queryDelete = "DELETE FROM tb_registros WHERE id = @ID AND dni = @DNI"; using (var cmd = new MySqlCommand(queryDelete, con)) { cmd.Parameters.AddWithValue("@ID", idConsulta); cmd.Parameters.AddWithValue("@DNI", DniUsuario); await cmd.ExecuteNonQueryAsync(); } // Eliminar respuestas string queryDeleteResp = "DELETE FROM tb_respuestas WHERE id = @ID"; using (var cmdResp = new MySqlCommand(queryDeleteResp, con)) { cmdResp.Parameters.AddWithValue("@ID", idConsulta); await cmdResp.ExecuteNonQueryAsync(); } } return RedirectToPage("/Listado"); } public async Task OnPostCerrarSesionAsync() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return RedirectToPage("/Login"); } // --- MÉTODOS AUXILIARES --- private void ObtenerDatosUsuarioClaims() { DniUsuario = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; NombreUsuario = User.Identity?.Name ?? "USUARIO"; var diteClaim = User.FindFirstValue("UsuarioDite"); EsDite = !string.IsNullOrEmpty(diteClaim) ? Convert.ToInt32(diteClaim) : 0; } private async Task CargarTodoAsync() { await CargarDesplegablesAsync(); await CargarDashboardDataAsync(DniUsuario); await CargarListadoAsync(DniUsuario, PaginaActual); } private async Task CargarDesplegablesAsync() { string connectionString = _configuration.GetConnectionString("MySqlHelpDesk") ?? string.Empty; using (var con = new MySqlConnection(connectionString)) { await con.OpenAsync(); // Cargar Tipos Pregunta string queryTipos = "SELECT pregunta FROM tb_tipopregunta ORDER BY pregunta ASC"; using (var cmd = new MySqlCommand(queryTipos, con)) using (var dr = await cmd.ExecuteReaderAsync()) { ListaTiposPregunta = new List { new SelectListItem("-- Seleccione Tipo de Pregunta --", "") }; while (await dr.ReadAsync()) { string pregunta = dr["pregunta"].ToString() ?? string.Empty; ListaTiposPregunta.Add(new SelectListItem(pregunta, pregunta)); } } // Cargar Monitores (dite = 1) string queryMonitores = "SELECT dni, UPPER(nombre) AS nombre FROM tb_usuarios WHERE dite = 1 ORDER BY nombre ASC"; using (var cmd = new MySqlCommand(queryMonitores, con)) using (var dr = await cmd.ExecuteReaderAsync()) { ListaMonitores = new List { new SelectListItem("-- Seleccione Monitor --", "") }; while (await dr.ReadAsync()) { string dniMon = dr["dni"].ToString() ?? string.Empty; string nomMon = dr["nombre"].ToString() ?? string.Empty; ListaMonitores.Add(new SelectListItem(nomMon, dniMon)); } } } } private async Task CargarListadoAsync(string dni, int pageIndex) { string connectionString = _configuration.GetConnectionString("MySqlHelpDesk") ?? string.Empty; using (var con = new MySqlConnection(connectionString)) { await con.OpenAsync(); string query = EsDite == 0 ? @"SELECT a.id, a.estado, a.fecha, a.hora, UPPER(b.nombre) AS Nombre, b.celular, b.ipress, a.tipopregunta, a.observacion, a.archivo, a.dni, UPPER(mon.nombre) AS nombre_monitor FROM tb_registros a INNER JOIN tb_usuarios b ON b.dni = a.dni LEFT JOIN tb_usuarios mon ON a.dni_monitor = mon.dni WHERE a.dni = @dni ORDER BY a.fecha DESC, a.hora DESC" : @"SELECT a.id, a.estado, a.fecha, a.hora, UPPER(b.nombre) AS Nombre, b.celular, b.ipress, a.tipopregunta, a.observacion, a.archivo, a.dni, UPPER(mon.nombre) AS nombre_monitor FROM tb_registros a INNER JOIN tb_usuarios b ON b.dni = a.dni LEFT JOIN tb_usuarios mon ON a.dni_monitor = mon.dni ORDER BY a.fecha DESC, a.hora DESC"; using (var cmd = new MySqlCommand(query, con)) { cmd.Parameters.AddWithValue("@dni", dni); using (var da = new MySqlDataAdapter(cmd)) { var dtCompleta = new DataTable(); da.Fill(dtCompleta); int totalRegistros = dtCompleta.Rows.Count; TotalPaginas = (int)Math.Ceiling((double)totalRegistros / TamanoPagina); if (TotalPaginas == 0) TotalPaginas = 1; // Paginación manual en DataTable TablaConsultas = dtCompleta.Clone(); var filasPaginadas = dtCompleta.AsEnumerable() .Skip((pageIndex - 1) * TamanoPagina) .Take(TamanoPagina); foreach (var row in filasPaginadas) { TablaConsultas.ImportRow(row); } } } } } private async Task CargarDashboardDataAsync(string dni) { string connectionString = _configuration.GetConnectionString("MySqlHelpDesk") ?? string.Empty; using (var con = new MySqlConnection(connectionString)) { await con.OpenAsync(); // 1. KPIs string queryKpi = @"SELECT COUNT(*) AS Total, SUM(CASE WHEN estado IN (1, 3) THEN 1 ELSE 0 END) AS Respondidos FROM tb_registros WHERE dni = @DNI"; using (var cmd = new MySqlCommand(queryKpi, con)) { cmd.Parameters.AddWithValue("@DNI", dni); using (var dr = await cmd.ExecuteReaderAsync()) { if (await dr.ReadAsync()) { KpiTotal = dr["Total"] != DBNull.Value ? Convert.ToInt32(dr["Total"]) : 0; KpiRespondidos = dr["Respondidos"] != DBNull.Value ? Convert.ToInt32(dr["Respondidos"]) : 0; KpiPendientes = KpiTotal - KpiRespondidos; } } } // 2. Registros por Mes string queryMeses = @"SELECT DATE_FORMAT(fecha, '%Y-%m') AS Mes, COUNT(*) AS Cantidad FROM tb_registros WHERE dni = @DNI GROUP BY DATE_FORMAT(fecha, '%Y-%m') ORDER BY Mes ASC LIMIT 12"; using (var cmdMes = new MySqlCommand(queryMeses, con)) { cmdMes.Parameters.AddWithValue("@DNI", dni); using (var drMes = await cmdMes.ExecuteReaderAsync()) { var labelsList = new List(); var dataList = new List(); while (await drMes.ReadAsync()) { labelsList.Add("'" + drMes["Mes"].ToString() + "'"); dataList.Add(Convert.ToInt32(drMes["Cantidad"])); } MesesLabelsJS = "[" + string.Join(",", labelsList) + "]"; MesesDataJS = "[" + string.Join(",", dataList) + "]"; } } // 3. Registros Diarios (Últimos 15 Días) string queryDias = @"SELECT DATE_FORMAT(fecha, '%Y-%m-%d') AS Dia, COUNT(*) AS Cantidad FROM tb_registros WHERE dni = @DNI GROUP BY DATE_FORMAT(fecha, '%Y-%m-%d') ORDER BY Dia DESC LIMIT 15"; using (var cmdDia = new MySqlCommand(queryDias, con)) { cmdDia.Parameters.AddWithValue("@DNI", dni); using (var drDia = await cmdDia.ExecuteReaderAsync()) { var labelsList = new List(); var dataList = new List(); var dtTemp = new DataTable(); dtTemp.Load(drDia); for (int i = dtTemp.Rows.Count - 1; i >= 0; i--) { labelsList.Add("'" + dtTemp.Rows[i]["Dia"].ToString() + "'"); dataList.Add(Convert.ToInt32(dtTemp.Rows[i]["Cantidad"])); } DiasLabelsJS = "[" + string.Join(",", labelsList) + "]"; DiasDataJS = "[" + string.Join(",", dataList) + "]"; } } } } private async Task ObtenerCorreoMonitorAsync(string dniMonitor) { string correo = string.Empty; string connectionString = _configuration.GetConnectionString("MySqlHelpDesk") ?? string.Empty; using (var con = new MySqlConnection(connectionString)) { await con.OpenAsync(); string query = "SELECT correo FROM tb_usuarios WHERE dni = @dni LIMIT 1"; using (var cmd = new MySqlCommand(query, con)) { cmd.Parameters.AddWithValue("@dni", dniMonitor); object result = await cmd.ExecuteScalarAsync(); if (result != null && result != DBNull.Value) { correo = result.ToString()?.Trim() ?? string.Empty; } } } return correo; } private bool EnviarNotificacionCorreo(long idConsulta, string dni, string usuario, string tipoPregunta, string detalle, string fecha, string hora, string correoDestinoMonitor) { if (string.IsNullOrWhiteSpace(correoDestinoMonitor)) return false; try { ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; string servidorSmtp = "mail.geresaloreto.gob.pe"; int puertoSmtp = 587; string correoEmisor = "helpdesk@geresaloreto.gob.pe"; string claveEmisor = "@Diresa2026.."; using (var mail = new MailMessage()) { mail.From = new MailAddress(correoEmisor, "Sistema HelpDesk"); mail.To.Add(correoDestinoMonitor.Trim()); mail.Subject = $"[NUEVO TICKET DE CONSULTA #{idConsulta}] - {tipoPregunta}"; mail.SubjectEncoding = Encoding.UTF8; string bodyHtml = $@"
Banner HelpDesk

Nueva Consulta Registrada

Estimado Monitor, se le ha asignado un nuevo ticket de consulta:


ID Ticket:#{idConsulta}
DNI Usuario:{dni}
Nombre:{usuario}
Tipo Pregunta:{tipoPregunta}
Fecha y Hora:{fecha} {hora}
Observación:{WebUtility.HtmlEncode(detalle)}
Pie de página HelpDesk
"; var htmlView = AlternateView.CreateAlternateViewFromString(bodyHtml, Encoding.UTF8, "text/html"); string rutaBannerHeader = Path.Combine(_environment.WebRootPath, "img", "bannerhelpdesk.png"); if (System.IO.File.Exists(rutaBannerHeader)) { var bannerHeader = new LinkedResource(rutaBannerHeader, "image/png") { ContentId = "bannerImg" }; htmlView.LinkedResources.Add(bannerHeader); } string rutaBannerFooter = Path.Combine(_environment.WebRootPath, "img", "bannerhelpdesk2.png"); if (System.IO.File.Exists(rutaBannerFooter)) { var bannerFooter = new LinkedResource(rutaBannerFooter, "image/png") { ContentId = "bannerFooterImg" }; htmlView.LinkedResources.Add(bannerFooter); } mail.AlternateViews.Add(htmlView); using (var smtp = new SmtpClient(servidorSmtp, puertoSmtp)) { smtp.UseDefaultCredentials = false; smtp.Credentials = new NetworkCredential(correoEmisor, claveEmisor); smtp.EnableSsl = true; smtp.Send(mail); } } return true; } catch { return false; } } } }