using GestionIntegral.Api.Data.Repositories.Comunicaciones; using GestionIntegral.Api.Models.Comunicaciones; using MailKit.Net.Smtp; using MailKit.Security; using Microsoft.Extensions.Options; using MimeKit; using System.Net.Security; using System.Security.Cryptography.X509Certificates; namespace GestionIntegral.Api.Services.Comunicaciones { public class EmailService : IEmailService { private readonly MailSettings _mailSettings; private readonly ILogger _logger; private readonly IEmailLogRepository _emailLogRepository; public EmailService( IOptions mailSettings, ILogger logger, IEmailLogRepository emailLogRepository) { _mailSettings = mailSettings.Value; _logger = logger; _emailLogRepository = emailLogRepository; } public async Task EnviarEmailAsync( string destinatarioEmail, string destinatarioNombre, string asunto, string cuerpoHtml, byte[]? attachment = null, string? attachmentName = null, string? origen = null, string? referenciaId = null, int? idUsuarioDisparo = null, int? idLoteDeEnvio = null) { var email = new MimeMessage(); email.Sender = new MailboxAddress(_mailSettings.SenderName, _mailSettings.SenderEmail); email.From.Add(email.Sender); email.To.Add(new MailboxAddress(destinatarioNombre, destinatarioEmail)); email.Subject = asunto; var builder = new BodyBuilder { HtmlBody = cuerpoHtml }; if (attachment != null && !string.IsNullOrEmpty(attachmentName)) { builder.Attachments.Add(attachmentName, attachment, ContentType.Parse("application/pdf")); } email.Body = builder.ToMessageBody(); await SendAndLogEmailAsync(email, origen, referenciaId, idUsuarioDisparo, idLoteDeEnvio); } public async Task EnviarEmailConsolidadoAsync( string destinatarioEmail, string destinatarioNombre, string asunto, string cuerpoHtml, List<(byte[] content, string name)> adjuntos, string? origen = null, string? referenciaId = null, int? idUsuarioDisparo = null, int? idLoteDeEnvio = null) { var email = new MimeMessage(); email.Sender = new MailboxAddress(_mailSettings.SenderName, _mailSettings.SenderEmail); email.From.Add(email.Sender); email.To.Add(new MailboxAddress(destinatarioNombre, destinatarioEmail)); email.Subject = asunto; var builder = new BodyBuilder { HtmlBody = cuerpoHtml }; if (adjuntos != null) { foreach (var adjunto in adjuntos) { builder.Attachments.Add(adjunto.name, adjunto.content, ContentType.Parse("application/pdf")); } } email.Body = builder.ToMessageBody(); await SendAndLogEmailAsync(email, origen, referenciaId, idUsuarioDisparo, idLoteDeEnvio); } private async Task SendAndLogEmailAsync(MimeMessage emailMessage, string? origen, string? referenciaId, int? idUsuarioDisparo, int? idLoteDeEnvio) { var destinatario = emailMessage.To.Mailboxes.FirstOrDefault()?.Address ?? "desconocido"; var log = new EmailLog { FechaEnvio = DateTime.Now, DestinatarioEmail = destinatario, Asunto = emailMessage.Subject, Origen = origen, ReferenciaId = referenciaId, IdUsuarioDisparo = idUsuarioDisparo, IdLoteDeEnvio = idLoteDeEnvio }; using var smtp = new SmtpClient(); try { // Se añade una política de validación de certificado personalizada. // Esto es necesario para entornos de desarrollo o redes internas donde // el nombre del host al que nos conectamos (ej. una IP) no coincide // con el nombre en el certificado SSL (ej. mail.eldia.com). smtp.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { // Si no hay errores, el certificado es válido. if (sslPolicyErrors == SslPolicyErrors.None) return true; // Si el único error es que el nombre no coincide (RemoteCertificateNameMismatch) // Y el certificado es el que esperamos (emitido para "mail.eldia.com"), // entonces lo aceptamos como válido. if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch) && certificate != null && certificate.Subject.Contains("CN=mail.eldia.com")) { _logger.LogWarning("Se aceptó un certificado SSL con 'Name Mismatch' para el host de confianza 'mail.eldia.com'."); return true; } // Para cualquier otro error, rechazamos el certificado. _logger.LogError("Error de validación de certificado SSL: {Errors}", sslPolicyErrors); return false; }; await smtp.ConnectAsync(_mailSettings.SmtpHost, _mailSettings.SmtpPort, SecureSocketOptions.StartTls); await smtp.AuthenticateAsync(_mailSettings.SmtpUser, _mailSettings.SmtpPass); await smtp.SendAsync(emailMessage); log.Estado = "Enviado"; _logger.LogInformation("Email enviado exitosamente a {Destinatario}. Asunto: {Asunto}", destinatario, emailMessage.Subject); } catch (Exception ex) { _logger.LogError(ex, "Error general al enviar email a {Destinatario}. Asunto: {Asunto}", destinatario, emailMessage.Subject); log.Estado = "Fallido"; log.Error = ex.Message; throw; } finally { if (smtp.IsConnected) { await smtp.DisconnectAsync(true); } try { await _emailLogRepository.CreateAsync(log); } catch (Exception logEx) { _logger.LogError(logEx, "FALLO CRÍTICO: No se pudo guardar el log del email para {Destinatario}", destinatario); } } } } }