2025-12-12 15:40:34 -03:00
|
|
|
using Microsoft.Data.SqlClient;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
using GestorFacturas.API.Data;
|
|
|
|
|
using GestorFacturas.API.Models;
|
|
|
|
|
using GestorFacturas.API.Services.Interfaces;
|
|
|
|
|
using System.Data;
|
|
|
|
|
|
|
|
|
|
namespace GestorFacturas.API.Services;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Servicio principal de procesamiento de facturas.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class ProcesadorFacturasService : IProcesadorFacturasService
|
|
|
|
|
{
|
|
|
|
|
private readonly ApplicationDbContext _context;
|
|
|
|
|
private readonly ILogger<ProcesadorFacturasService> _logger;
|
|
|
|
|
private readonly IMailService _mailService;
|
|
|
|
|
private readonly IEncryptionService _encryptionService;
|
|
|
|
|
private readonly IConfiguration _configuration;
|
|
|
|
|
|
|
|
|
|
private const int MAX_REINTENTOS = 10;
|
|
|
|
|
private const int DELAY_SEGUNDOS = 60;
|
|
|
|
|
|
|
|
|
|
public ProcesadorFacturasService(
|
|
|
|
|
ApplicationDbContext context,
|
|
|
|
|
ILogger<ProcesadorFacturasService> logger,
|
|
|
|
|
IMailService mailService,
|
|
|
|
|
IEncryptionService encryptionService,
|
|
|
|
|
IConfiguration configuration)
|
|
|
|
|
{
|
|
|
|
|
_context = context;
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_mailService = mailService;
|
|
|
|
|
_encryptionService = encryptionService;
|
|
|
|
|
_configuration = configuration;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task EjecutarProcesoAsync(DateTime fechaDesde)
|
|
|
|
|
{
|
2025-12-15 11:40:37 -03:00
|
|
|
// Contadores inicializados AL PRINCIPIO
|
|
|
|
|
int copiadas = 0;
|
|
|
|
|
int omitidas = 0;
|
|
|
|
|
int errores = 0;
|
|
|
|
|
// No declarar 'int procesadas = 0' porque es redundante con copiadas/omitidas
|
|
|
|
|
|
2025-12-12 15:40:34 -03:00
|
|
|
var config = await _context.Configuraciones.FirstOrDefaultAsync(c => c.Id == 1);
|
|
|
|
|
if (config == null)
|
|
|
|
|
{
|
|
|
|
|
await RegistrarEventoAsync("No se encontró configuración del sistema", TipoEvento.Error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config.UltimaEjecucion = DateTime.Now;
|
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
|
|
|
|
|
await RegistrarEventoAsync($"Iniciando proceso de facturas desde {fechaDesde:dd/MM/yyyy}", TipoEvento.Info);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var facturas = await ObtenerFacturasDesdeERPAsync(config, fechaDesde);
|
|
|
|
|
|
|
|
|
|
if (facturas.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
await RegistrarEventoAsync($"No se encontraron facturas desde {fechaDesde:dd/MM/yyyy}", TipoEvento.Warning);
|
|
|
|
|
config.Estado = true;
|
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await RegistrarEventoAsync($"Se encontraron {facturas.Count} facturas para procesar", TipoEvento.Info);
|
|
|
|
|
|
|
|
|
|
List<FacturaParaProcesar> pendientes = new();
|
|
|
|
|
List<string> detallesErroresParaMail = new();
|
|
|
|
|
List<Evento> eventosDeErrorParaActualizar = new();
|
|
|
|
|
|
|
|
|
|
// --- 1. Primer intento ---
|
|
|
|
|
foreach (var factura in facturas)
|
|
|
|
|
{
|
2025-12-15 11:40:37 -03:00
|
|
|
var resultado = await ProcesarFacturaAsync(factura, config);
|
|
|
|
|
|
|
|
|
|
switch (resultado)
|
|
|
|
|
{
|
|
|
|
|
case ResultadoProceso.Copiado:
|
|
|
|
|
copiadas++;
|
|
|
|
|
break;
|
|
|
|
|
case ResultadoProceso.Omitido:
|
|
|
|
|
omitidas++;
|
|
|
|
|
break;
|
|
|
|
|
case ResultadoProceso.Error:
|
|
|
|
|
pendientes.Add(factura);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-12-12 15:40:34 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 2. Sistema de Reintentos ---
|
|
|
|
|
if (pendientes.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
await RegistrarEventoAsync($"{pendientes.Count} archivos no encontrados. Iniciando sistema de reintentos...", TipoEvento.Warning);
|
|
|
|
|
|
|
|
|
|
for (int intento = 1; intento <= MAX_REINTENTOS && pendientes.Count > 0; intento++)
|
|
|
|
|
{
|
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(DELAY_SEGUNDOS));
|
|
|
|
|
|
|
|
|
|
var aunPendientes = new List<FacturaParaProcesar>();
|
|
|
|
|
|
|
|
|
|
foreach (var factura in pendientes)
|
|
|
|
|
{
|
2025-12-15 11:40:37 -03:00
|
|
|
var resultado = await ProcesarFacturaAsync(factura, config);
|
|
|
|
|
|
|
|
|
|
if (resultado != ResultadoProceso.Error)
|
2025-12-12 15:40:34 -03:00
|
|
|
{
|
2025-12-15 11:40:37 -03:00
|
|
|
if (resultado == ResultadoProceso.Copiado) copiadas++;
|
|
|
|
|
if (resultado == ResultadoProceso.Omitido) omitidas++;
|
|
|
|
|
|
|
|
|
|
_logger.LogInformation("Archivo recuperado en reintento {intento}: {archivo}", intento, factura.NombreArchivoOrigen);
|
2025-12-12 15:40:34 -03:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
aunPendientes.Add(factura);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pendientes = aunPendientes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 3. Registro de Errores Finales ---
|
|
|
|
|
if (pendientes.Count > 0)
|
|
|
|
|
{
|
2025-12-15 11:40:37 -03:00
|
|
|
errores = pendientes.Count; // Aquí usamos la variable ya declarada arriba
|
2025-12-12 15:40:34 -03:00
|
|
|
foreach (var factura in pendientes)
|
|
|
|
|
{
|
|
|
|
|
string msgError = $"El archivo NO EXISTE después de {MAX_REINTENTOS} intentos: {factura.NombreArchivoOrigen}";
|
|
|
|
|
|
|
|
|
|
var eventoError = new Evento
|
|
|
|
|
{
|
|
|
|
|
Fecha = DateTime.Now,
|
|
|
|
|
Mensaje = msgError,
|
|
|
|
|
Tipo = TipoEvento.Error.ToString(),
|
|
|
|
|
Enviado = false
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_context.Eventos.Add(eventoError);
|
|
|
|
|
eventosDeErrorParaActualizar.Add(eventoError);
|
|
|
|
|
detallesErroresParaMail.Add(factura.NombreArchivoOrigen);
|
|
|
|
|
}
|
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 4. Actualización de Estado General ---
|
|
|
|
|
config.Estado = errores == 0;
|
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
|
|
|
|
|
// --- 5. Limpieza automática ---
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var fechaLimiteBorrado = DateTime.Now.AddMonths(-1);
|
|
|
|
|
var eventosViejos = _context.Eventos.Where(e => e.Fecha < fechaLimiteBorrado);
|
|
|
|
|
if (eventosViejos.Any())
|
|
|
|
|
{
|
|
|
|
|
_context.Eventos.RemoveRange(eventosViejos);
|
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch { }
|
|
|
|
|
|
|
|
|
|
// --- 6. Evento Final ---
|
2025-12-15 11:40:37 -03:00
|
|
|
var mensajeFinal = $"Proceso finalizado. Nuevas: {copiadas}, Verificadas: {omitidas}, Errores: {errores}";
|
2025-12-12 15:40:34 -03:00
|
|
|
await RegistrarEventoAsync(mensajeFinal, errores > 0 ? TipoEvento.Warning : TipoEvento.Info);
|
|
|
|
|
|
2025-12-15 11:40:37 -03:00
|
|
|
// --- 7. Envío de Mail Inteligente ---
|
2025-12-12 15:40:34 -03:00
|
|
|
if (errores > 0 && config.AvisoMail && !string.IsNullOrEmpty(config.SMTPDestinatario))
|
|
|
|
|
{
|
|
|
|
|
var historialErroresEnviados = await _context.Eventos
|
|
|
|
|
.Where(e => e.Tipo == TipoEvento.Error.ToString() && e.Enviado == true)
|
|
|
|
|
.Select(e => e.Mensaje)
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
var archivosNuevosParaNotificar = detallesErroresParaMail.Where(archivoFallido =>
|
|
|
|
|
{
|
|
|
|
|
bool yaFueNotificado = historialErroresEnviados.Any(msgHistorico => msgHistorico.Contains(archivoFallido));
|
|
|
|
|
return !yaFueNotificado;
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
bool mailEnviado = false;
|
|
|
|
|
|
|
|
|
|
if (archivosNuevosParaNotificar.Count > 0)
|
|
|
|
|
{
|
2025-12-15 11:40:37 -03:00
|
|
|
// Pasamos 'copiadas' en lugar de 'procesadas' para el mail, o la suma de ambas si prefieres
|
2025-12-12 15:40:34 -03:00
|
|
|
mailEnviado = await EnviarNotificacionErroresAsync(
|
|
|
|
|
config.SMTPDestinatario,
|
2025-12-15 11:40:37 -03:00
|
|
|
copiadas + omitidas,
|
2025-12-12 15:40:34 -03:00
|
|
|
errores,
|
|
|
|
|
archivosNuevosParaNotificar
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (mailEnviado)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInformation("Correo de alerta enviado con {count} archivos nuevos.", archivosNuevosParaNotificar.Count);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-12-15 11:40:37 -03:00
|
|
|
_logger.LogInformation("Se omitió el envío de correo por errores repetidos.");
|
2025-12-12 15:40:34 -03:00
|
|
|
mailEnviado = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mailEnviado && eventosDeErrorParaActualizar.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
foreach (var evento in eventosDeErrorParaActualizar)
|
|
|
|
|
{
|
|
|
|
|
evento.Enviado = true;
|
|
|
|
|
}
|
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "Error crítico en el proceso de facturas");
|
|
|
|
|
string mensajeCritico = $"ERROR CRÍTICO DEL SISTEMA: {ex.Message}";
|
|
|
|
|
|
|
|
|
|
await RegistrarEventoAsync(mensajeCritico, TipoEvento.Error);
|
|
|
|
|
|
|
|
|
|
if (config != null)
|
|
|
|
|
{
|
|
|
|
|
config.Estado = false;
|
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
|
|
|
|
|
if (config.AvisoMail && !string.IsNullOrEmpty(config.SMTPDestinatario))
|
|
|
|
|
{
|
|
|
|
|
var listaErroresCriticos = new List<string> { mensajeCritico };
|
|
|
|
|
await EnviarNotificacionErroresAsync(config.SMTPDestinatario, 0, 1, listaErroresCriticos);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<List<FacturaParaProcesar>> ObtenerFacturasDesdeERPAsync(Configuracion config, DateTime fechaDesde)
|
|
|
|
|
{
|
|
|
|
|
var facturas = new List<FacturaParaProcesar>();
|
|
|
|
|
var connectionString = ConstruirCadenaConexion(config);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
using var conexion = new SqlConnection(connectionString);
|
|
|
|
|
await conexion.OpenAsync();
|
|
|
|
|
|
|
|
|
|
var query = @"
|
|
|
|
|
SELECT DISTINCT
|
|
|
|
|
NUMERO_FACTURA,
|
|
|
|
|
TIPO_FACTURA,
|
|
|
|
|
CLIENTE,
|
|
|
|
|
SUCURSAL_FACTURA,
|
|
|
|
|
DIVISION_FACTURA,
|
|
|
|
|
FECHACOMPLETA_FACTURA,
|
|
|
|
|
NRO_CAI
|
|
|
|
|
FROM VISTA_FACTURACION_ELDIA
|
|
|
|
|
WHERE SUCURSAL_FACTURA = '70'
|
|
|
|
|
AND NRO_CAI IS NOT NULL
|
|
|
|
|
AND NRO_CAI != ''
|
|
|
|
|
AND FECHACOMPLETA_FACTURA >= @FechaDesde
|
|
|
|
|
ORDER BY FECHACOMPLETA_FACTURA DESC";
|
|
|
|
|
|
|
|
|
|
using var comando = new SqlCommand(query, conexion);
|
|
|
|
|
comando.Parameters.AddWithValue("@FechaDesde", fechaDesde);
|
|
|
|
|
|
|
|
|
|
using var reader = await comando.ExecuteReaderAsync();
|
|
|
|
|
|
|
|
|
|
while (await reader.ReadAsync())
|
|
|
|
|
{
|
|
|
|
|
var factura = new FacturaParaProcesar
|
|
|
|
|
{
|
|
|
|
|
NumeroFactura = (reader["NUMERO_FACTURA"].ToString() ?? "").Trim().PadLeft(10, '0'),
|
|
|
|
|
TipoFactura = (reader["TIPO_FACTURA"].ToString() ?? "").Trim(),
|
|
|
|
|
Cliente = (reader["CLIENTE"].ToString() ?? "").Trim().PadLeft(6, '0'),
|
|
|
|
|
Sucursal = (reader["SUCURSAL_FACTURA"].ToString() ?? "").Trim().PadLeft(4, '0'),
|
|
|
|
|
CodigoEmpresa = (reader["DIVISION_FACTURA"].ToString() ?? "").Trim().PadLeft(4, '0'),
|
|
|
|
|
FechaFactura = Convert.ToDateTime(reader["FECHACOMPLETA_FACTURA"])
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
factura.NombreEmpresa = MapearNombreEmpresa(factura.CodigoEmpresa);
|
|
|
|
|
factura.NombreArchivoOrigen = ConstruirNombreArchivoOrigen(factura);
|
|
|
|
|
factura.NombreArchivoDestino = ConstruirNombreArchivoDestino(factura);
|
|
|
|
|
factura.CarpetaDestino = ConstruirRutaCarpetaDestino(factura);
|
|
|
|
|
|
|
|
|
|
facturas.Add(factura);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
await RegistrarEventoAsync($"Error al conectar con el ERP: {ex.Message}", TipoEvento.Error);
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return facturas;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-15 11:40:37 -03:00
|
|
|
private async Task<ResultadoProceso> ProcesarFacturaAsync(FacturaParaProcesar factura, Configuracion config)
|
2025-12-12 15:40:34 -03:00
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
string rutaBaseOrigen = config.RutaFacturas;
|
|
|
|
|
string rutaBaseDestino = config.RutaDestino;
|
|
|
|
|
|
|
|
|
|
string rutaOrigen = Path.Combine(rutaBaseOrigen, factura.NombreArchivoOrigen);
|
|
|
|
|
string carpetaDestinoFinal = Path.Combine(rutaBaseDestino, factura.CarpetaDestino);
|
|
|
|
|
|
2025-12-15 11:40:37 -03:00
|
|
|
// Si no existe origen, es un error (para reintento)
|
|
|
|
|
if (!File.Exists(rutaOrigen)) return ResultadoProceso.Error;
|
2025-12-12 15:40:34 -03:00
|
|
|
|
|
|
|
|
if (!Directory.Exists(carpetaDestinoFinal))
|
|
|
|
|
{
|
|
|
|
|
Directory.CreateDirectory(carpetaDestinoFinal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string rutaDestinoCompleta = Path.Combine(carpetaDestinoFinal, factura.NombreArchivoDestino);
|
|
|
|
|
FileInfo infoOrigen = new FileInfo(rutaOrigen);
|
|
|
|
|
FileInfo infoDestino = new FileInfo(rutaDestinoCompleta);
|
|
|
|
|
|
|
|
|
|
if (infoDestino.Exists)
|
|
|
|
|
{
|
2025-12-15 11:40:37 -03:00
|
|
|
// Si ya existe y es igual, lo OMITIMOS
|
|
|
|
|
if (infoDestino.Length == infoOrigen.Length)
|
|
|
|
|
{
|
|
|
|
|
return ResultadoProceso.Omitido;
|
|
|
|
|
}
|
2025-12-12 15:40:34 -03:00
|
|
|
}
|
|
|
|
|
|
2025-12-15 11:40:37 -03:00
|
|
|
// Si llegamos acá, copiamos
|
2025-12-12 15:40:34 -03:00
|
|
|
File.Copy(rutaOrigen, rutaDestinoCompleta, overwrite: true);
|
2025-12-15 11:40:37 -03:00
|
|
|
return ResultadoProceso.Copiado;
|
2025-12-12 15:40:34 -03:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogDebug(ex, "Fallo al copiar {archivo}", factura.NombreArchivoOrigen);
|
2025-12-15 11:40:37 -03:00
|
|
|
return ResultadoProceso.Error;
|
2025-12-12 15:40:34 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- MÉTODOS DE MAPEO (CONFIGURABLES) ---
|
|
|
|
|
|
|
|
|
|
private string MapearNombreEmpresa(string codigoEmpresa)
|
|
|
|
|
{
|
|
|
|
|
var nombre = _configuration[$"EmpresasMapping:{codigoEmpresa}"];
|
|
|
|
|
return string.IsNullOrEmpty(nombre) ? "DESCONOCIDA" : nombre;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string AjustarTipoFactura(string tipoOriginal)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(tipoOriginal)) return tipoOriginal;
|
|
|
|
|
|
|
|
|
|
// 1. Buscamos mapeo en appsettings.json
|
|
|
|
|
var tipoMapeado = _configuration[$"FacturaTiposMapping:{tipoOriginal}"];
|
|
|
|
|
if (!string.IsNullOrEmpty(tipoMapeado))
|
|
|
|
|
{
|
|
|
|
|
return tipoMapeado;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Fallback Legacy si no está mapeado
|
|
|
|
|
return tipoOriginal[^1].ToString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- CONSTRUCCIÓN DE NOMBRES ---
|
|
|
|
|
|
|
|
|
|
private string ConstruirNombreArchivoOrigen(FacturaParaProcesar factura)
|
|
|
|
|
{
|
|
|
|
|
return $"{factura.Cliente}-{factura.CodigoEmpresa}-{factura.Sucursal}-{factura.TipoFactura}-{factura.NumeroFactura}.pdf";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string ConstruirNombreArchivoDestino(FacturaParaProcesar factura)
|
|
|
|
|
{
|
|
|
|
|
// El archivo final conserva el Tipo ORIGINAL
|
|
|
|
|
return $"{factura.Cliente}-{factura.CodigoEmpresa}-{factura.Sucursal}-{factura.TipoFactura}-{factura.NumeroFactura}.pdf";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string ConstruirRutaCarpetaDestino(FacturaParaProcesar factura)
|
|
|
|
|
{
|
|
|
|
|
// La carpeta usa el Tipo AJUSTADO
|
|
|
|
|
string tipoAjustado = AjustarTipoFactura(factura.TipoFactura);
|
|
|
|
|
string anioMes = factura.FechaFactura.ToString("yyyy-MM");
|
|
|
|
|
|
|
|
|
|
string nombreCarpetaFactura = $"{factura.Cliente}-{factura.CodigoEmpresa}-{factura.Sucursal}-{tipoAjustado}-{factura.NumeroFactura}";
|
|
|
|
|
|
|
|
|
|
return Path.Combine(factura.NombreEmpresa, anioMes, nombreCarpetaFactura);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- UTILIDADES ---
|
|
|
|
|
|
|
|
|
|
private string ConstruirCadenaConexion(Configuracion config)
|
|
|
|
|
{
|
|
|
|
|
var builder = new SqlConnectionStringBuilder
|
|
|
|
|
{
|
|
|
|
|
DataSource = config.DBServidor,
|
|
|
|
|
InitialCatalog = config.DBNombre,
|
|
|
|
|
IntegratedSecurity = config.DBTrusted,
|
|
|
|
|
TrustServerCertificate = true,
|
|
|
|
|
ConnectTimeout = 30
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!config.DBTrusted)
|
|
|
|
|
{
|
|
|
|
|
builder.UserID = _encryptionService.Decrypt(config.DBUsuario ?? "");
|
|
|
|
|
builder.Password = _encryptionService.Decrypt(config.DBClave ?? "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return builder.ConnectionString;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task RegistrarEventoAsync(string mensaje, TipoEvento tipo)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var evento = new Evento
|
|
|
|
|
{
|
|
|
|
|
Fecha = DateTime.Now,
|
|
|
|
|
Mensaje = mensaje,
|
|
|
|
|
Tipo = tipo.ToString(),
|
|
|
|
|
Enviado = false
|
|
|
|
|
};
|
|
|
|
|
_context.Eventos.Add(evento);
|
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "Error al registrar evento");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<bool> EnviarNotificacionErroresAsync(string destinatario, int procesadas, int errores, List<string> detalles)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var asunto = errores == 1 && detalles.Count > 0 && detalles[0].StartsWith("ERROR CRÍTICO")
|
|
|
|
|
? "ALERTA CRÍTICA: Fallo del Sistema Gestor de Facturas"
|
|
|
|
|
: "Alerta: Errores en Procesamiento de Facturas";
|
|
|
|
|
|
|
|
|
|
string listaArchivosHtml = "";
|
|
|
|
|
if (detalles != null && detalles.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
listaArchivosHtml = "<h3>Detalle de Errores:</h3><ul>";
|
|
|
|
|
foreach (var archivo in detalles)
|
|
|
|
|
{
|
|
|
|
|
listaArchivosHtml += $"<li>{archivo}</li>";
|
|
|
|
|
}
|
|
|
|
|
listaArchivosHtml += "</ul>";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var cuerpo = $@"
|
|
|
|
|
<html>
|
|
|
|
|
<body style='font-family: Arial, sans-serif;'>
|
|
|
|
|
<h2 style='color: #d9534f;'>{asunto}</h2>
|
|
|
|
|
<p><strong>Fecha de Ejecución:</strong> {DateTime.Now:dd/MM/yyyy HH:mm:ss}</p>
|
|
|
|
|
<div style='background-color: #f8f9fa; padding: 15px; border-radius: 5px; border: 1px solid #dee2e6;'>
|
|
|
|
|
<p><strong>Facturas procesadas exitosamente:</strong> {procesadas}</p>
|
|
|
|
|
<p><strong>Facturas con error:</strong> <span style='color: red; font-weight: bold;'>{errores}</span></p>
|
|
|
|
|
</div>
|
|
|
|
|
<div style='margin-top: 20px;'>{listaArchivosHtml}</div>
|
|
|
|
|
<hr style='margin-top: 30px;'>
|
|
|
|
|
<p style='color: #6c757d; font-size: 12px;'>Sistema Gestor de Facturas El Día.</p>
|
|
|
|
|
</body>
|
|
|
|
|
</html>";
|
|
|
|
|
|
|
|
|
|
return await _mailService.EnviarCorreoAsync(destinatario, asunto, cuerpo, true);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogError(ex, "Error al preparar notificación de errores");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Clase auxiliar para representar una factura a procesar
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class FacturaParaProcesar
|
|
|
|
|
{
|
|
|
|
|
public string NumeroFactura { get; set; } = string.Empty;
|
|
|
|
|
public string TipoFactura { get; set; } = string.Empty;
|
|
|
|
|
public string Cliente { get; set; } = string.Empty;
|
|
|
|
|
public string Sucursal { get; set; } = string.Empty;
|
|
|
|
|
public string CodigoEmpresa { get; set; } = string.Empty;
|
|
|
|
|
public string NombreEmpresa { get; set; } = string.Empty;
|
|
|
|
|
public DateTime FechaFactura { get; set; }
|
|
|
|
|
public string NombreArchivoOrigen { get; set; } = string.Empty;
|
|
|
|
|
public string NombreArchivoDestino { get; set; } = string.Empty;
|
|
|
|
|
public string CarpetaDestino { get; set; } = string.Empty;
|
|
|
|
|
}
|
2025-12-15 11:40:37 -03:00
|
|
|
|
|
|
|
|
public enum ResultadoProceso
|
|
|
|
|
{
|
|
|
|
|
Copiado, // Era nuevo y se copió
|
|
|
|
|
Omitido, // Ya existía y estaba bien
|
|
|
|
|
Error // No se encontró o falló
|
|
|
|
|
}
|