225 lines
7.7 KiB
C#
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" });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|