Files
GestorWebFacturas/Backend/GestorFacturas.API/Controllers/OperacionesController.cs
2025-12-12 15:40:34 -03:00

225 lines
7.7 KiB
C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
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 OperacionesController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<OperacionesController> _logger;
public OperacionesController(
ApplicationDbContext context,
IServiceScopeFactory scopeFactory,
ILogger<OperacionesController> logger)
{
_context = context;
_scopeFactory = scopeFactory;
_logger = logger;
}
/// <summary>
/// Ejecuta el proceso de facturas manualmente sin esperar al cronograma
/// </summary>
[HttpPost("ejecutar-manual")]
public async Task<ActionResult<object>> EjecutarManual([FromBody] EjecucionManualDto dto)
{
try
{
// Usamos _context acá solo para validar config rápida (esto es seguro porque es antes del OK)
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" });
}
_logger.LogInformation("Iniciando ejecución manual desde {fecha}", dto.FechaDesde);
// Registrar evento inicial
var evento = new Evento
{
Fecha = DateTime.Now,
Mensaje = $"Ejecución manual solicitada desde {dto.FechaDesde:dd/MM/yyyy}",
Tipo = "Info"
};
_context.Eventos.Add(evento);
await _context.SaveChangesAsync();
// Ejecutar el proceso en segundo plano (Fire-and-Forget SEGURO)
_ = Task.Run(async () =>
{
// CRÍTICO: Creamos un nuevo Scope para que el DbContext viva
// independientemente de la petición HTTP original.
using var scope = _scopeFactory.CreateScope();
try
{
// Obtenemos el servicio DESDE el nuevo scope
var procesadorService = scope.ServiceProvider.GetRequiredService<IProcesadorFacturasService>();
await procesadorService.EjecutarProcesoAsync(dto.FechaDesde);
}
catch (Exception ex)
{
// Es importante loguear acá porque este hilo no tiene contexto HTTP
var logger = scope.ServiceProvider.GetRequiredService<ILogger<OperacionesController>>();
logger.LogError(ex, "Error crítico durante ejecución manual en background");
}
});
return Ok(new
{
mensaje = "Proceso iniciado correctamente",
fechaDesde = dto.FechaDesde
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al iniciar ejecución manual");
return StatusCode(500, new { mensaje = "Error al iniciar el proceso" });
}
}
/// <summary>
/// Obtiene los eventos/logs del sistema con paginación
/// </summary>
[HttpGet("logs")]
public async Task<ActionResult<PagedResult<EventoDto>>> ObtenerLogs(
[FromQuery] int pageNumber = 1,
[FromQuery] int pageSize = 20,
[FromQuery] string? tipo = null)
{
try
{
if (pageNumber < 1) pageNumber = 1;
if (pageSize < 1 || pageSize > 100) pageSize = 20;
var query = _context.Eventos.AsQueryable();
// Filtrar por tipo si se especifica
if (!string.IsNullOrEmpty(tipo))
{
query = query.Where(e => e.Tipo == tipo);
}
// Ordenar por fecha descendente (más recientes primero)
query = query.OrderByDescending(e => e.Fecha);
var totalCount = await query.CountAsync();
var eventos = await query
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.Select(e => new EventoDto
{
Id = e.Id,
Fecha = e.Fecha,
Mensaje = e.Mensaje,
Tipo = e.Tipo,
Enviado = e.Enviado
})
.ToListAsync();
var result = new PagedResult<EventoDto>
{
Items = eventos,
TotalCount = totalCount,
PageNumber = pageNumber,
PageSize = pageSize
};
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener logs");
return StatusCode(500, new { mensaje = "Error al obtener los logs" });
}
}
/// <summary>
/// Obtiene estadísticas del día actual
/// </summary>
[HttpGet("estadisticas")]
public async Task<ActionResult<object>> ObtenerEstadisticas()
{
try
{
var hoy = DateTime.Today;
var eventosHoy = await _context.Eventos
.Where(e => e.Fecha >= hoy)
.GroupBy(e => e.Tipo)
.Select(g => new { Tipo = g.Key, Cantidad = g.Count() })
.ToListAsync();
var config = await _context.Configuraciones.FirstOrDefaultAsync(c => c.Id == 1);
return Ok(new
{
ultimaEjecucion = config?.UltimaEjecucion,
estado = config?.Estado ?? false,
enEjecucion = config?.EnEjecucion ?? false,
eventosHoy = new
{
total = eventosHoy.Sum(e => e.Cantidad),
errores = eventosHoy.FirstOrDefault(e => e.Tipo == "Error")?.Cantidad ?? 0,
advertencias = eventosHoy.FirstOrDefault(e => e.Tipo == "Warning")?.Cantidad ?? 0,
info = eventosHoy.FirstOrDefault(e => e.Tipo == "Info")?.Cantidad ?? 0
}
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al obtener estadísticas");
return StatusCode(500, new { mensaje = "Error al obtener estadísticas" });
}
}
/// <summary>
/// Limpia los logs antiguos (opcional)
/// </summary>
[HttpDelete("logs/limpiar")]
public async Task<ActionResult<object>> LimpiarLogsAntiguos([FromQuery] int diasAntiguedad = 30)
{
try
{
var fechaLimite = DateTime.Now.AddDays(-diasAntiguedad);
var eventosAntiguos = await _context.Eventos
.Where(e => e.Fecha < fechaLimite)
.ToListAsync();
if (eventosAntiguos.Count > 0)
{
_context.Eventos.RemoveRange(eventosAntiguos);
await _context.SaveChangesAsync();
_logger.LogInformation("Se eliminaron {count} eventos antiguos", eventosAntiguos.Count);
return Ok(new
{
mensaje = $"Se eliminaron {eventosAntiguos.Count} eventos",
cantidad = eventosAntiguos.Count
});
}
return Ok(new { mensaje = "No hay eventos antiguos para eliminar", cantidad = 0 });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al limpiar logs antiguos");
return StatusCode(500, new { mensaje = "Error al limpiar los logs" });
}
}
}