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 _logger; public OperacionesController( ApplicationDbContext context, IServiceScopeFactory scopeFactory, ILogger logger) { _context = context; _scopeFactory = scopeFactory; _logger = logger; } /// /// Ejecuta el proceso de facturas manualmente sin esperar al cronograma /// [HttpPost("ejecutar-manual")] public async Task> 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(); await procesadorService.EjecutarProcesoAsync(dto.FechaDesde); } catch (Exception ex) { // Es importante loguear acá porque este hilo no tiene contexto HTTP var logger = scope.ServiceProvider.GetRequiredService>(); 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" }); } } /// /// Obtiene los eventos/logs del sistema con paginación /// [HttpGet("logs")] public async Task>> 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 { 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" }); } } /// /// Obtiene estadísticas del día actual /// [HttpGet("estadisticas")] public async Task> 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" }); } } /// /// Limpia los logs antiguos (opcional) /// [HttpDelete("logs/limpiar")] public async Task> 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" }); } } }