Se introduce un sistema completo de logging para todas las comunicaciones por correo electrónico y se realizan mejoras significativas en la experiencia del usuario, tanto en la retroalimentación del sistema como en la estética de los emails enviados al cliente. ### ✨ Nuevas Características - **Auditoría y Log de Envíos de Email:** - Se ha creado una nueva tabla `com_EmailLogs` en la base de datos para registrar cada intento de envío de correo. - El `EmailService` ahora centraliza toda la lógica de logging, registrando automáticamente la fecha, destinatario, asunto, estado (`Enviado` o `Fallido`), y mensajes de error detallados. - Se implementó un nuevo `EmailLogService` y `EmailLogRepository` para gestionar estos registros. - **Historial de Envíos en la Interfaz de Usuario:** - Se añade un nuevo ícono de "Historial" (<span style="color: #607d8b;">📧</span>) junto a cada factura en la página de "Consulta de Facturas". - Al hacer clic, se abre un modal que muestra una tabla detallada con todos los intentos de envío para esa factura, incluyendo el estado y el motivo del error (si lo hubo). - Esto proporciona una trazabilidad completa y una herramienta de diagnóstico para el usuario final. ### 🔄 Refactorización y Mejoras - **Mensajes de Éxito Dinámicos:** - Se ha mejorado la retroalimentación al enviar una factura por PDF. El sistema ahora muestra un mensaje de éxito específico, como "El email... se ha enviado correctamente a suscriptor@email.com", en lugar de un mensaje técnico genérico. - Se ajustó la cadena de llamadas (`Controller` -> `Service`) para que el email del destinatario esté disponible para la respuesta de la API. - **Diseño Unificado de Emails:** - Se ha rediseñado el template HTML para el "Aviso de Cuenta Mensual" para que coincida con la estética del email de "Envío de Factura PDF". - Ambos correos ahora presentan un diseño profesional y consistente, con cabecera, logo y pie de página, reforzando la imagen de marca. - **Manejo de Errores de Email Mejorado:** - El `EmailService` ahora captura excepciones específicas de la librería `MailKit` (ej. `SmtpCommandException`). - Esto permite registrar en el log errores mucho más precisos y útiles, como rechazos de destinatarios por parte del servidor (`User unknown`), fallos de autenticación, etc., que ahora son visibles en el `Tooltip` del historial.
135 lines
6.0 KiB
C#
135 lines
6.0 KiB
C#
using GestionIntegral.Api.Dtos.Comunicaciones;
|
|
using GestionIntegral.Api.Dtos.Suscripciones;
|
|
using GestionIntegral.Api.Services.Comunicaciones;
|
|
using GestionIntegral.Api.Services.Suscripciones;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using System.Security.Claims;
|
|
|
|
namespace GestionIntegral.Api.Controllers.Suscripciones
|
|
{
|
|
[Route("api/facturacion")]
|
|
[ApiController]
|
|
[Authorize]
|
|
public class FacturacionController : ControllerBase
|
|
{
|
|
private readonly IFacturacionService _facturacionService;
|
|
private readonly ILogger<FacturacionController> _logger;
|
|
private readonly IEmailLogService _emailLogService;
|
|
private const string PermisoGestionarFacturacion = "SU006";
|
|
private const string PermisoEnviarEmail = "SU009";
|
|
|
|
public FacturacionController(IFacturacionService facturacionService, ILogger<FacturacionController> logger, IEmailLogService emailLogService)
|
|
{
|
|
_facturacionService = facturacionService;
|
|
_logger = logger;
|
|
_emailLogService = emailLogService;
|
|
}
|
|
|
|
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
|
|
|
private int? GetCurrentUserId()
|
|
{
|
|
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
|
|
_logger.LogWarning("No se pudo obtener el UserId del token JWT en FacturacionController.");
|
|
return null;
|
|
}
|
|
|
|
[HttpPut("{idFactura:int}/numero-factura")]
|
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
|
public async Task<IActionResult> UpdateNumeroFactura(int idFactura, [FromBody] string numeroFactura)
|
|
{
|
|
if (!TienePermiso(PermisoGestionarFacturacion)) return Forbid();
|
|
|
|
var userId = GetCurrentUserId();
|
|
if (userId == null) return Unauthorized();
|
|
|
|
var (exito, error) = await _facturacionService.ActualizarNumeroFactura(idFactura, numeroFactura, userId.Value);
|
|
|
|
if (!exito)
|
|
{
|
|
if (error != null && error.Contains("no existe")) return NotFound(new { message = error });
|
|
return BadRequest(new { message = error });
|
|
}
|
|
return NoContent();
|
|
}
|
|
|
|
// POST: api/facturacion/{idFactura}/enviar-factura-pdf
|
|
[HttpPost("{idFactura:int}/enviar-factura-pdf")]
|
|
public async Task<IActionResult> EnviarFacturaPdf(int idFactura)
|
|
{
|
|
if (!TienePermiso("SU009")) return Forbid();
|
|
var userId = GetCurrentUserId();
|
|
if (userId == null) return Unauthorized();
|
|
var (exito, error, emailDestino) = await _facturacionService.EnviarFacturaPdfPorEmail(idFactura, userId.Value);
|
|
|
|
if (!exito)
|
|
{
|
|
return BadRequest(new { message = error });
|
|
}
|
|
|
|
var mensajeExito = $"El email con la factura PDF se ha enviado correctamente a {emailDestino}.";
|
|
return Ok(new { message = mensajeExito });
|
|
}
|
|
|
|
// POST: api/facturacion/{anio}/{mes}/suscriptor/{idSuscriptor}/enviar-aviso
|
|
[HttpPost("{anio:int}/{mes:int}/suscriptor/{idSuscriptor:int}/enviar-aviso")]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
public async Task<IActionResult> EnviarAvisoPorEmail(int anio, int mes, int idSuscriptor)
|
|
{
|
|
// Usamos el permiso de enviar email
|
|
if (!TienePermiso("SU009")) return Forbid();
|
|
|
|
var (exito, error) = await _facturacionService.EnviarAvisoCuentaPorEmail(anio, mes, idSuscriptor);
|
|
|
|
if (!exito)
|
|
{
|
|
if (error != null && (error.Contains("no encontrada") || error.Contains("no es válido")))
|
|
{
|
|
return NotFound(new { message = error });
|
|
}
|
|
return BadRequest(new { message = error });
|
|
}
|
|
|
|
return Ok(new { message = "Email consolidado para el suscriptor ha sido enviado a la cola de procesamiento." });
|
|
}
|
|
|
|
// GET: api/facturacion/{anio}/{mes}
|
|
[HttpGet("{anio:int}/{mes:int}")]
|
|
public async Task<IActionResult> GetFacturas(
|
|
int anio, int mes,
|
|
[FromQuery] string? nombreSuscriptor,
|
|
[FromQuery] string? estadoPago,
|
|
[FromQuery] string? estadoFacturacion)
|
|
{
|
|
if (!TienePermiso(PermisoGestionarFacturacion)) return Forbid();
|
|
if (anio < 2020 || mes < 1 || mes > 12) return BadRequest(new { message = "El período no es válido." });
|
|
|
|
var resumenes = await _facturacionService.ObtenerResumenesDeCuentaPorPeriodo(anio, mes, nombreSuscriptor, estadoPago, estadoFacturacion);
|
|
return Ok(resumenes);
|
|
}
|
|
|
|
|
|
[HttpPost("{anio:int}/{mes:int}")]
|
|
public async Task<IActionResult> GenerarFacturacion(int anio, int mes)
|
|
{
|
|
if (!TienePermiso(PermisoGestionarFacturacion)) return Forbid();
|
|
var userId = GetCurrentUserId();
|
|
if (userId == null) return Unauthorized();
|
|
if (anio < 2020 || mes < 1 || mes > 12) return BadRequest(new { message = "El año y el mes proporcionados no son válidos." });
|
|
var (exito, mensaje, facturasGeneradas) = await _facturacionService.GenerarFacturacionMensual(anio, mes, userId.Value);
|
|
if (!exito) return StatusCode(StatusCodes.Status500InternalServerError, new { message = mensaje });
|
|
return Ok(new { message = mensaje, facturasGeneradas });
|
|
}
|
|
|
|
[HttpGet("{idFactura:int}/historial-envios")]
|
|
[ProducesResponseType(typeof(IEnumerable<EmailLogDto>), StatusCodes.Status200OK)]
|
|
public async Task<IActionResult> GetHistorialEnvios(int idFactura)
|
|
{
|
|
var historial = await _emailLogService.ObtenerHistorialPorReferencia("Factura-" + idFactura);
|
|
return Ok(historial);
|
|
}
|
|
}
|
|
} |