using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Data.SqlClient; using GestorFacturas.API.Data; using GestorFacturas.API.Models; using GestorFacturas.API.Models.DTOs; using GestorFacturas.API.Services.Interfaces; using Microsoft.AspNetCore.Authorization; namespace GestorFacturas.API.Controllers; [ApiController] [Route("api/[controller]")] [Authorize] public class ConfiguracionController : ControllerBase { private readonly ApplicationDbContext _context; private readonly IMailService _mailService; private readonly ILogger _logger; private readonly IEncryptionService _encryptionService; public ConfiguracionController( ApplicationDbContext context, IMailService mailService, ILogger logger, IEncryptionService encryptionService) { _context = context; _mailService = mailService; _logger = logger; _encryptionService = encryptionService; } [HttpGet] public async Task> ObtenerConfiguracion() { try { var config = await _context.Configuraciones.FirstOrDefaultAsync(c => c.Id == 1); if (config == null) { return NotFound(new { mensaje = "No se encontró la configuración del sistema" }); } var dto = MapearADto(config); // --- LÓGICA PARA CALCULAR PRÓXIMA EJECUCIÓN --- if (config.EnEjecucion && config.UltimaEjecucion.HasValue) { dto.ProximaEjecucion = CalcularFechaProxima(config); } else { dto.ProximaEjecucion = null; // Si está detenido o nunca corrió } // DESENCRIPTAR PARA MOSTRAR AL USUARIO dto.DBUsuario = _encryptionService.Decrypt(config.DBUsuario ?? ""); dto.DBClave = _encryptionService.Decrypt(config.DBClave ?? ""); dto.SMTPUsuario = _encryptionService.Decrypt(config.SMTPUsuario ?? ""); dto.SMTPClave = _encryptionService.Decrypt(config.SMTPClave ?? ""); return Ok(dto); } catch (Exception ex) { _logger.LogError(ex, "Error al obtener configuración"); return StatusCode(500, new { mensaje = "Error al obtener la configuración" }); } } [HttpPut] public async Task> ActualizarConfiguracion([FromBody] ConfiguracionDto dto) { try { if (dto.Periodicidad == "Minutos" && dto.ValorPeriodicidad < 15) { return BadRequest(new { mensaje = "La periodicidad mínima permitida es de 15 minutos." }); } var config = await _context.Configuraciones.FirstOrDefaultAsync(c => c.Id == 1); if (config == null) { return NotFound(new { mensaje = "No se encontró la configuración del sistema" }); } config.Periodicidad = dto.Periodicidad; config.ValorPeriodicidad = dto.ValorPeriodicidad; config.HoraEjecucion = dto.HoraEjecucion; config.EnEjecucion = dto.EnEjecucion; config.DBServidor = dto.DBServidor; config.DBNombre = dto.DBNombre; config.DBTrusted = dto.DBTrusted; config.RutaFacturas = dto.RutaFacturas; config.RutaDestino = dto.RutaDestino; config.SMTPServidor = dto.SMTPServidor; config.SMTPPuerto = dto.SMTPPuerto; config.SMTPSSL = dto.SMTPSSL; config.SMTPDestinatario = dto.SMTPDestinatario; config.AvisoMail = dto.AvisoMail; // ENCRIPTAR ANTES DE GUARDAR config.DBUsuario = _encryptionService.Encrypt(dto.DBUsuario ?? ""); config.DBClave = _encryptionService.Encrypt(dto.DBClave ?? ""); config.SMTPUsuario = _encryptionService.Encrypt(dto.SMTPUsuario ?? ""); config.SMTPClave = _encryptionService.Encrypt(dto.SMTPClave ?? ""); await _context.SaveChangesAsync(); _logger.LogInformation("Configuración actualizada correctamente"); var evento = new Evento { Fecha = DateTime.Now, Mensaje = "Configuración del sistema actualizada", Tipo = "Info" }; _context.Eventos.Add(evento); await _context.SaveChangesAsync(); // Devolvemos el DTO tal cual vino (ya tiene los textos planos que el usuario ingresó) return Ok(dto); } catch (Exception ex) { _logger.LogError(ex, "Error al actualizar configuración"); return StatusCode(500, new { mensaje = "Error al actualizar la configuración" }); } } [HttpPost("probar-conexion-sql")] public async Task> ProbarConexionSQL([FromBody] ProbarConexionDto dto) { try { var builder = new SqlConnectionStringBuilder { DataSource = dto.Servidor, InitialCatalog = dto.NombreDB, IntegratedSecurity = dto.TrustedConnection, TrustServerCertificate = true, ConnectTimeout = 10 }; if (!dto.TrustedConnection) { // Aquí usamos los datos directos del DTO (texto plano) porque es una prueba en vivo builder.UserID = dto.Usuario; builder.Password = dto.Clave; } using var conexion = new SqlConnection(builder.ConnectionString); await conexion.OpenAsync(); _logger.LogInformation("Prueba de conexión SQL exitosa a {servidor}/{database}", dto.Servidor, dto.NombreDB); return Ok(new { exito = true, mensaje = $"Conexión exitosa a {dto.Servidor}\\{dto.NombreDB}" }); } catch (Exception ex) { _logger.LogWarning(ex, "Fallo en prueba de conexión SQL"); return Ok(new { exito = false, mensaje = $"Error: {ex.Message}" }); } } [HttpPost("probar-smtp")] public async Task> ProbarSMTP([FromBody] ProbarSMTPDto dto) { try { var config = await _context.Configuraciones.FirstOrDefaultAsync(c => c.Id == 1); if (config == null) { return NotFound(new { mensaje = "No se encontró la configuración" }); } // Guardar estado original var smtpOriginal = (config.SMTPServidor, config.SMTPPuerto, config.SMTPUsuario, config.SMTPClave, config.SMTPSSL, config.AvisoMail); try { // Configurar temporalmente para la prueba config.SMTPServidor = dto.Servidor; config.SMTPPuerto = dto.Puerto; config.SMTPSSL = dto.SSL; config.AvisoMail = true; // IMPORTANTE: Encriptar también para la prueba, ya que MailService espera datos encriptados config.SMTPUsuario = _encryptionService.Encrypt(dto.Usuario); config.SMTPClave = _encryptionService.Encrypt(dto.Clave); await _context.SaveChangesAsync(); var exito = await _mailService.EnviarCorreoAsync( dto.Destinatario, "Prueba de Configuración SMTP - Gestor Facturas", "

✓ Prueba Exitosa

La configuración SMTP está correcta y funcionando.

", true); if (exito) { return Ok(new { exito = true, mensaje = "Correo de prueba enviado correctamente" }); } else { return Ok(new { exito = false, mensaje = "No se pudo enviar el correo de prueba" }); } } finally { // Restaurar configuración original config.SMTPServidor = smtpOriginal.SMTPServidor; config.SMTPPuerto = smtpOriginal.SMTPPuerto; config.SMTPUsuario = smtpOriginal.SMTPUsuario; config.SMTPClave = smtpOriginal.SMTPClave; config.SMTPSSL = smtpOriginal.SMTPSSL; config.AvisoMail = smtpOriginal.AvisoMail; await _context.SaveChangesAsync(); } } catch (Exception ex) { _logger.LogError(ex, "Error en prueba SMTP"); return Ok(new { exito = false, mensaje = $"Error: {ex.Message}" }); } } private ConfiguracionDto MapearADto(Configuracion config) { return new ConfiguracionDto { Id = config.Id, Periodicidad = config.Periodicidad, ValorPeriodicidad = config.ValorPeriodicidad, HoraEjecucion = config.HoraEjecucion, UltimaEjecucion = config.UltimaEjecucion, Estado = config.Estado, EnEjecucion = config.EnEjecucion, DBServidor = config.DBServidor, DBNombre = config.DBNombre, // Nota: Usuario/Clave se mapean vacíos aquí y se llenan desencriptados en el método GET DBTrusted = config.DBTrusted, RutaFacturas = config.RutaFacturas, RutaDestino = config.RutaDestino, SMTPServidor = config.SMTPServidor, SMTPPuerto = config.SMTPPuerto, SMTPSSL = config.SMTPSSL, SMTPDestinatario = config.SMTPDestinatario, AvisoMail = config.AvisoMail }; } private DateTime CalcularFechaProxima(Configuracion config) { // Aunque el método que lo llama ya valida, el compilador necesita seguridad dentro de este bloque. if (!config.UltimaEjecucion.HasValue) { return DateTime.Now; } var ultima = config.UltimaEjecucion.Value; DateTime proxima = ultima; // Parsear hora configurada TimeSpan horaConfig; if (!TimeSpan.TryParse(config.HoraEjecucion, out horaConfig)) { horaConfig = TimeSpan.Zero; } switch (config.Periodicidad.ToUpper()) { case "MINUTOS": proxima = ultima.AddMinutes(config.ValorPeriodicidad); break; case "DIAS": case "DÍAS": // Sumar días proxima = ultima.AddDays(config.ValorPeriodicidad); // Ajustar a la hora específica proxima = new DateTime(proxima.Year, proxima.Month, proxima.Day, horaConfig.Hours, horaConfig.Minutes, 0); // Si al ajustar la hora, la fecha quedó en el pasado (ej: corrió tarde hoy), sumar un día más si es periodicidad diaria if (proxima < DateTime.Now && config.ValorPeriodicidad == 1) { proxima = proxima.AddDays(1); } break; case "MESES": proxima = ultima.AddMonths(config.ValorPeriodicidad); proxima = new DateTime(proxima.Year, proxima.Month, proxima.Day, horaConfig.Hours, horaConfig.Minutes, 0); break; } // Si por alguna razón la próxima calculada es menor a ahora (retraso), la próxima es YA. if (proxima < DateTime.Now) return DateTime.Now; return proxima; } }