using EFCDesk.Entidades; using Microsoft.VisualBasic.Devices; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Text; using System.Text.Json; using System.Threading.Tasks; using System.Windows.Forms; using static System.Runtime.InteropServices.JavaScript.JSType; using EFCDesk.Parsers; namespace EFCDesk.Classes { public sealed class MonitorCarpetas : IDisposable { private readonly string _carpetaBase; private readonly bool _aplicaWinsaii; private readonly SQLiteHelper _repo; private FileSystemWatcher? _watcher; //private readonly ConcurrentStack _pilaCarpetas = new ConcurrentStack(); // LIFO private readonly ConcurrentQueue _colaCarpetas = new ConcurrentQueue(); // FIFO private readonly SemaphoreSlim _semaforo; private readonly SemaphoreSlim _dbSemaforo; // Limitar acceso a BD private readonly HashSet _enProceso = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly object _lockProceso = new object(); // Parámetros de robustez private readonly int _maxConcurrent = 2; private readonly int _perFileTimeoutSeconds = 30; private readonly int _maxReintentos = 10; // reintentos de 1 minuto private readonly TimeSpan _reintentoDelay = TimeSpan.FromMinutes(1); private string ApiPostDocuments = Helpers.ObtenerEndpointDocumentos(); private string ApiGetPedimento = Helpers.ObtenerEndpointPedimentos(); private string ApiGetPedimentoDesk = Helpers.CrearEndpointpedimentoDesk(); private ProcessLogger logger = new ProcessLogger(); private string DominioEFC = Helpers.DominioExpedienteElectronico(); private bool _MonitoreoTimer = false; // Evento para notificar a la UI //public event EventHandler? DatosActualizados; // Evento con argumentos personalizados para notificar a la UI public event EventHandler? DatosActualizados; public MonitorCarpetas(string carpetaBase, int? maxConcurrent = null, int? perFileTimeoutSeconds = null, int? maxReintentos = null) { _carpetaBase = Path.GetFullPath(carpetaBase); _repo = new SQLiteHelper(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "historico.db")); ConfiguracionExpediente config = ConfiguracionExpediente.ObtenerDesdeBaseDeDatos(_repo); _aplicaWinsaii = config.UsarEstructuraPedimentosWinsaai; if (maxConcurrent.HasValue) _maxConcurrent = Math.Max(1, maxConcurrent.Value); if (perFileTimeoutSeconds.HasValue) _perFileTimeoutSeconds = Math.Max(5, perFileTimeoutSeconds.Value); if (maxReintentos.HasValue) _maxReintentos = Math.Max(1, maxReintentos.Value); _semaforo = new SemaphoreSlim(_maxConcurrent); _dbSemaforo = new SemaphoreSlim(1); // Solo un hilo a la vez en BD } public void Iniciar() { // 1) Watcher _watcher = new FileSystemWatcher(_carpetaBase) { IncludeSubdirectories = true, NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.DirectoryName, }; _watcher.Created += OnFsEvent; _watcher.Renamed += OnFsRenamed; _watcher.Changed += OnFsEvent; // por si hay carreras mientras se crea la carpeta _watcher.EnableRaisingEvents = true; //// 2) Recuperar pendientes //var pendientes = _repo.ObtenerPendientes(); //foreach (var (ruta, nombre, _) in pendientes) // EncolarSiNoEsta(ruta); //// 3) Disparar procesamiento inicial //_ = ProcesarPilaAsync(); } private async void OnFsEvent(object? sender, FileSystemEventArgs e) { // Sólo nos interesan carpetas (pero el evento puede ser de archivo: ignorar) var asDir = e.FullPath; if (!Directory.Exists(asDir)) { // Si es archivo, su carpeta raíz puede estar lista: intentamos derivar asDir = Path.GetDirectoryName(e.FullPath) ?? e.FullPath; if (string.IsNullOrWhiteSpace(asDir) || !Directory.Exists(asDir)) return; } var raiz = ObtenerCarpetaRaiz(asDir); if (raiz == null) return; var nombre = Path.GetFileName(raiz); var tipo = _aplicaWinsaii ? "Nieto" : "Hijo"; //string pattern = @"^\d{2}-\d{2}-\d{4}-\d{7}$"; //bool esValido = System.Text.RegularExpressions.Regex.IsMatch(nombre, pattern); //if (esValido) //{ // _repo.UpsertCarpeta(raiz, nombre, tipo, "Nuevo"); // EncolarSiNoEsta(raiz); //} bool esValido = Helpers.EsValidaNomeclaturaCarpeta(nombre); if (esValido) { await _dbSemaforo.WaitAsync(); try { _repo.UpsertCarpeta(raiz, nombre, tipo, "Nuevo"); } finally { _dbSemaforo.Release(); } EncolarSiNoEsta(raiz); } // Solo debe ejecutarse cuando el Timer no este activo if (_MonitoreoTimer == true) return; _ = ProcesarPilaAsync(); } private void OnFsRenamed(object? sender, RenamedEventArgs e) { OnFsEvent(sender, new FileSystemEventArgs(WatcherChangeTypes.Created, Path.GetDirectoryName(e.FullPath) ?? e.FullPath, Path.GetFileName(e.FullPath))); } private void EncolarSiNoEsta(string carpetaRaiz) { carpetaRaiz = Path.GetFullPath(carpetaRaiz); lock (_lockProceso) { if (_enProceso.Contains(carpetaRaiz)) return; _enProceso.Add(carpetaRaiz); } //_pilaCarpetas.Push(carpetaRaiz); // LIFO _colaCarpetas.Enqueue(carpetaRaiz); // FIFO //Dispara el evento para avisar al FormMain OnDatosActualizados(carpetaRaiz); } // Devuelve la carpeta raíz (hijo o nieto) dado un path dentro de _carpetaBase private string? ObtenerCarpetaRaiz(string ruta) { string full = Path.GetFullPath(ruta); if (!full.StartsWith(_carpetaBase, StringComparison.OrdinalIgnoreCase)) return null; var relativa = full.Substring(_carpetaBase.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); var partes = relativa.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); if (partes.Length == 0) return null; // hijo = primer segmento; nieto = primer y segundo segmento if (_aplicaWinsaii) { if (partes.Length < 2) return null; return Path.Combine(_carpetaBase, partes[0], partes[1]); } else { return Path.Combine(_carpetaBase, partes[0]); } } private async Task ProcesarPilaAsync() { //while (_pilaCarpetas.TryPop(out var carpetaRaiz)) //{ // await _semaforo.WaitAsync(); // _ = Task.Run(async () => // { // try // { // await ProcesarCarpetaRaizAsync(carpetaRaiz); // } // catch (Exception ex) // { // Console.WriteLine($"[ERR] {carpetaRaiz}: {ex.Message}"); // } // finally // { // lock (_lockProceso) _enProceso.Remove(carpetaRaiz); // _semaforo.Release(); // } // }); //} while (_colaCarpetas.TryDequeue(out var carpetaRaiz)) { await _semaforo.WaitAsync(); _ = Task.Run(async () => { try { await ProcesarCarpetaRaizAsync(carpetaRaiz); } catch (Exception ex) { Console.WriteLine($"[ERR] {carpetaRaiz}: {ex.Message}"); } finally { lock (_lockProceso) _enProceso.Remove(carpetaRaiz); _semaforo.Release(); } }); //Dispara el evento para avisar al FormMain OnDatosActualizados(carpetaRaiz); } } //private async Task ProcesarCarpetaRaizAsync(string carpetaRaiz) //{ // if (!Directory.Exists(carpetaRaiz)) // { // Console.WriteLine($"[SKIP] No existe: {carpetaRaiz}"); // return; // } // Console.WriteLine($"➡️ Procesando raíz: {carpetaRaiz}"); // var nombreRaiz = Path.GetFileName(carpetaRaiz); // var archivos = Directory.GetFiles(carpetaRaiz, "*.*", SearchOption.AllDirectories) // .Where(f => !EsTemporal(f)) // .ToArray(); // if (archivos.Length == 0) // { // Console.WriteLine($"[INFO] Sin archivos en {carpetaRaiz}"); // _repo.MarcarCarpetaProcesada(carpetaRaiz); // return; // } // // Reintentos para archivos en uso // int intento = 0; // while (intento <= _maxReintentos) // { // var pendientes = archivos.Where(f => File.Exists(f) && !FileUtil.IsFileReady(f)).ToArray(); // if (pendientes.Length == 0) break; // if (intento == _maxReintentos) // { // Console.WriteLine($"[WARN] Archivos bloqueados tras {_maxReintentos} intentos. Se omite: {carpetaRaiz}"); // return; // } // Console.WriteLine($"[WAIT] {pendientes.Length} archivos bloqueados en {carpetaRaiz}. Reintento {intento + 1}/{_maxReintentos} en {_reintentoDelay.TotalMinutes} min."); // await Task.Delay(_reintentoDelay); // intento++; // } // // Filtrar archivos ya procesados (persistente en SQLite) // var archivosAProcesar = new List(archivos.Length); // foreach (var f in archivos) // { // if (_repo.EstaArchivoProcesado(f)) continue; // archivosAProcesar.Add(f); // } // if (archivosAProcesar.Count == 0) // { // Console.WriteLine($"[INFO] Todos los archivos ya fueron procesados en {carpetaRaiz}"); // _repo.MarcarCarpetaProcesada(carpetaRaiz); // return; // } // // Crear ZIP aplanado // var rutaZip = Path.Combine(Path.GetDirectoryName(carpetaRaiz)!, $"{nombreRaiz}.zip"); // if (File.Exists(rutaZip)) File.Delete(rutaZip); // using (var zip = ZipFile.Open(rutaZip, ZipArchiveMode.Create)) // { // foreach (var archivo in archivosAProcesar) // { // var nombreArchivo = Path.GetFileName(archivo); // // Seguridad adicional: esperar per-file si justo se bloquea // if (!FileUtil.WaitAllFilesReady(new[] { archivo }, _perFileTimeoutSeconds)) // { // Console.WriteLine($"[SKIP] Bloqueado: {archivo}"); // continue; // } // // Registrar como procesado antes de subir, para evitar duplicados si se cae después // var sha1 = FileUtil.TryGetSHA1(archivo); // opcional, puede ser null // _repo.RegistrarArchivoProcesado(archivo, nombreArchivo, carpetaRaiz, $"{nombreRaiz}.zip", sha1); // // Agregar al zip aplanado (sin directorios) // zip.CreateEntryFromFile(archivo, nombreArchivo, CompressionLevel.Optimal); // Console.WriteLine($" + {nombreArchivo}"); // } // } // Console.WriteLine($"ZIP creado: {rutaZip}"); // // 🔌 HOOK: Subir a FTP // try // { // SubirArchivoFTP(rutaZip); // } // catch (Exception ex) // { // Console.WriteLine($"[FTP ERR] {ex.Message}"); // // según tu estrategia, podrías marcar la carpeta como pendiente otra vez // // y retornar aquí para reintentar más tarde. // } // // 🔌 HOOK: Registrar en API // try // { // RegistrarArchivoEnAPI(rutaZip); // } // catch (Exception ex) // { // Console.WriteLine($"[API ERR] {ex.Message}"); // } // try // { // File.Delete(rutaZip); // Console.WriteLine($"🧹 ZIP eliminado: {rutaZip}"); // } // catch (Exception ex) // { // Console.WriteLine($"[ZIP DEL ERR] {ex.Message}"); // } // _repo.MarcarCarpetaProcesada(carpetaRaiz); // Console.WriteLine($"✅ Completado: {carpetaRaiz}"); //} private async Task ProcesarCarpetaRaizAsync(string carpetaRaiz) { if (!Directory.Exists(carpetaRaiz)) { _repo.MarcarCarpetaProcesada(carpetaRaiz, "No existe"); Console.WriteLine($"[SKIP] No existe: {carpetaRaiz}"); return; } Console.WriteLine($"Procesando raíz: {carpetaRaiz}"); var nombreRaiz = Path.GetFileName(carpetaRaiz); var archivos = Directory.GetFiles(carpetaRaiz, "*.*", SearchOption.AllDirectories) .Where(f => !EsTemporal(f)) .ToArray(); // ==== Validar formato de nombre === // //string pattern = @"^\d{2}-\d{2}-\d{4}-\d{7}$"; //bool esValido = System.Text.RegularExpressions.Regex.IsMatch(nombreRaiz, pattern); //if (!esValido) //{ // _repo.MarcarCarpetaProcesada(carpetaRaiz, "Formato Incorrecto"); // Console.WriteLine($"[SKIP] Formato Expediente Incorrecto: {nombreRaiz}"); // return; //} bool esValido = Helpers.EsValidaNomeclaturaCarpeta(nombreRaiz); if (!esValido) { _repo.MarcarCarpetaProcesada(carpetaRaiz, "Formato Incorrecto"); Console.WriteLine($"[SKIP] Formato Expediente Incorrecto: {nombreRaiz}"); return; } if (archivos.Length == 0) { Console.WriteLine($"[INFO] Sin archivos en {carpetaRaiz}"); _repo.MarcarCarpetaProcesada(carpetaRaiz, "Sin Archivos"); return; } // Reintentos para archivos en uso int intento = 0; while (intento <= _maxReintentos) { var pendientes = archivos.Where(f => File.Exists(f) && !FileUtil.IsFileReady(f)).ToArray(); if (pendientes.Length == 0) break; if (intento == _maxReintentos) { Console.WriteLine($"[WARN] Archivos bloqueados tras {_maxReintentos} intentos. Se omite: {carpetaRaiz}"); return; } Console.WriteLine($"[WAIT] {pendientes.Length} archivos bloqueados en {carpetaRaiz}. Reintento {intento + 1}/{_maxReintentos} en {_reintentoDelay.TotalMinutes} min."); await Task.Delay(_reintentoDelay); intento++; } // Filtrar archivos ya procesados (persistente en SQLite) var archivosAProcesar = new List(archivos.Length); await _dbSemaforo.WaitAsync(); try { foreach (var f in archivos) { if (_repo.EstaArchivoProcesado(f)) continue; archivosAProcesar.Add(f); } } finally { _dbSemaforo.Release(); } if (archivosAProcesar.Count == 0) { Console.WriteLine($"[INFO] Todos los archivos ya fueron procesados en {carpetaRaiz}"); _repo.MarcarCarpetaProcesada(carpetaRaiz, "Procesado"); return; } // Crear ZIP aplanado var rutaZip = Path.Combine(Path.GetDirectoryName(carpetaRaiz)!, $"{nombreRaiz}.zip"); if (File.Exists(rutaZip)) File.Delete(rutaZip); _repo.MarcarCarpetaProcesada(carpetaRaiz, "Procesando"); int totalArchivos = archivosAProcesar.Count; int archivosCompletos = 0; foreach (var archivo in archivosAProcesar) { var nombreArchivo = Path.GetFileName(archivo); // Seguridad adicional: esperar per-file si justo se bloquea if (!FileUtil.WaitAllFilesReady(new[] { archivo }, _perFileTimeoutSeconds)) { Console.WriteLine($"[SKIP] Bloqueado: {archivo}"); continue; } // Revisar si contiene informacion el archivo if (Helpers.ObtenerTamanoEnBytes(archivo) <= 0) { Console.WriteLine($"[SKIP] Sin Informacion: {archivo}"); totalArchivos--; continue; } // Seguridad adicional: no permitir subir archivos .exe, .bat, etc. if (!Helpers.ValidarArchivo(archivo)) { Console.WriteLine($"[SKIP] Extension no valida: {archivo}"); continue; } // Registrar como procesado antes de subir, para evitar duplicados si se cae después var sha1 = FileUtil.TryGetSHA1(archivo); // opcional, puede ser null await _dbSemaforo.WaitAsync(); try { _repo.RegistrarArchivoProcesado(archivo, nombreArchivo, carpetaRaiz, $"{nombreRaiz}.zip", sha1); } finally { _dbSemaforo.Release(); } bool respuesa = false; // 🔌 HOOK: Registrar en API try { respuesa = await RegistrarArchivoEnAPI(archivo, nombreRaiz); await _dbSemaforo.WaitAsync(); try { if (respuesa) { _repo.ActualizarEstadoArchivo(archivo, "Procesado"); archivosCompletos++; } else { _repo.ActualizarEstadoArchivo(archivo, "Pendiente"); } } finally { _dbSemaforo.Release(); } } catch (Exception ex) { Console.WriteLine($"[API ERR] {ex.Message}"); } Console.WriteLine($" + {nombreArchivo}"); } if (archivosCompletos == totalArchivos) { _repo.MarcarCarpetaProcesada(carpetaRaiz, "Procesado"); } else { _repo.MarcarCarpetaProcesada(carpetaRaiz, "Pendiente"); } Console.WriteLine($"Proceso Completado: {carpetaRaiz}"); } private static bool EsTemporal(string path) { var name = Path.GetFileName(path); if (string.IsNullOrEmpty(name)) return true; // comunes durante edición/descarga/renombre if (name.EndsWith(".tmp", StringComparison.OrdinalIgnoreCase)) return true; if (name.EndsWith(".part", StringComparison.OrdinalIgnoreCase)) return true; if (name.EndsWith(".crdownload", StringComparison.OrdinalIgnoreCase)) return true; if (name.EndsWith("~", StringComparison.OrdinalIgnoreCase)) return true; if (name.StartsWith("~$", StringComparison.OrdinalIgnoreCase)) return true; // MS Office return false; } // Implementa tus integraciones reales aquí private void SubirArchivoFTP(string rutaZip) { // TODO: tu implementación real de FTP Console.WriteLine($"[FTP] Subido: {rutaZip}"); } private async Task RegistrarArchivoEnAPI(string rutaArchivo, string pedimento) { // TODO: tu implementación real de API try { var config = ConfiguracionJSON.LoadFromJson(); string Token = Globales.accesToken ?? ""; if (string.IsNullOrWhiteSpace(DominioEFC)) { logger.LogError("No se ha configurado el dominio", null); return false; } if (!string.IsNullOrEmpty(Token)) { using var apiClient = new ApiClient(timeout: TimeSpan.FromSeconds(600), maxRetries: 3, retryDelay: TimeSpan.FromSeconds(60)); string sfuente = ""; int expedienteLogistico = Properties.Settings.Default.ExpLogistico; int expedienteWinsaai = Properties.Settings.Default.ExpWinsaai; if (expedienteWinsaai > 0 && expedienteLogistico > 0) { sfuente = Properties.Settings.Default.ExpLogistico.ToString(); } else if (expedienteWinsaai > 0) { sfuente = Properties.Settings.Default.ExpWinsaai.ToString(); } else if (expedienteLogistico > 0) { sfuente = Properties.Settings.Default.ExpLogistico.ToString(); } else { sfuente = "5"; } // Verifica que el archivo exista if (!File.Exists(rutaArchivo)) { return false; } Dictionary metadatos = new Dictionary { { "fuente", sfuente }, { "clave_pedimento", "" }, { "pedimento", "" }, { "patente", "" }, { "aduana", "" }, { "tipo_operacion", "" }, { "contribuyente", "" }, { "curp_apoderado", "" }, { "fecha_pago", "" }, { "partidas", "" }, }; //if (Helpers.EsValidaNomeclaturaSAAIM3(rutaArchivo)) //{ // // Validar contenido del archivo SAAI M3 // var registros = SaaiM3Helper.LeerArchivo(rutaArchivo); // var resultados = SaaiM3Helper.BuscarPorTipo(registros, "501"); // foreach (var reg in resultados) // { // metadatos["clave_pedimento"] = reg.Campos[5]; // metadatos["pedimento"] = reg.Campos[2]; // metadatos["patente"] = reg.Campos[1]; // metadatos["aduana"] = reg.Campos[3]; // metadatos["tipo_operacion"] = reg.Campos[4]; // metadatos["contribuyente"] = reg.Campos[8]; // metadatos["curp_apoderado"] = reg.Campos[9]; // break; // } // resultados = SaaiM3Helper.BuscarPorTipo(registros, "506"); // foreach (var reg in resultados) // { // string tipoFechaPago = reg.Campos[2]; // if (tipoFechaPago == "2") // { // string fechaOriginal = reg.Campos[3]; // DateTime fecha = DateTime.ParseExact(fechaOriginal, "ddMMyyyy", null); // metadatos["fecha_pago"] = fecha.ToString("yyyy-MM-dd"); // break; // } // } // // Numero de partidas // resultados = SaaiM3Helper.BuscarPorTipo(registros, "551"); // metadatos["partidas"] = resultados.Count.ToString(); //} //else if (Helpers.EsArchivoXMl(rutaArchivo)) //{ // // // // Validar contenido del XML Pedimento Completo // string xml = File.ReadAllText(rutaArchivo); // var parser = new PedimentoXmlParser(xml); // var (tieneError, numeroOperacion) = parser.GetResultadoOperacion(); // string numeroPedimento = parser.GetNumeroPedimento(); // var encabezado = parser.GetEncabezado(); // var importador = parser.GetImportador(); // var fechas = parser.GetFechas(); // var partidas = parser.GetPartidas(); // if (!tieneError && encabezado.Count > 0) // { // string[] partes = pedimento.Split("-"); // metadatos["pedimento"] = numeroPedimento; // metadatos["clave_pedimento"] = encabezado["ClavePedimento"]; // metadatos["patente"] = partes[2]; // metadatos["aduana"] = encabezado["AduanaES"]; // metadatos["tipo_operacion"] = encabezado["TipoOperacion"]; // metadatos["contribuyente"] = importador["RFC"]; // metadatos["curp_apoderado"] = encabezado["CurpApoderado"]; ; // metadatos["partidas"] = partidas.Count.ToString(); // foreach (var fecha in fechas) // { // if (fecha["TipoClave"] == "2") // { // string[] partesFecha = fecha["Fecha"].Split("-"); // metadatos["fecha_pago"] = partesFecha[0] + "-" + partesFecha[1] + "-" + partesFecha[2]; // break; // } // } // } //} if (Helpers.EsArchivoXMl(rutaArchivo)) { // // Validar contenido del XML Pedimento Completo string xml = File.ReadAllText(rutaArchivo); var parser = new PedimentoXmlParser(xml); var (tieneError, numeroOperacion) = parser.GetResultadoOperacion(); string numeroPedimento = parser.GetNumeroPedimento(); var encabezado = parser.GetEncabezado(); var importador = parser.GetImportador(); var fechas = parser.GetFechas(); var partidas = parser.GetPartidas(); if (!tieneError && encabezado.Count > 0) { string[] partes = pedimento.Split("-"); metadatos["pedimento"] = numeroPedimento; metadatos["clave_pedimento"] = encabezado["ClavePedimento"]; metadatos["patente"] = partes[2]; metadatos["aduana"] = encabezado["AduanaES"]; metadatos["tipo_operacion"] = encabezado["TipoOperacion"]; metadatos["contribuyente"] = importador["RFC"]; metadatos["curp_apoderado"] = encabezado["CurpApoderado"]; metadatos["partidas"] = partidas.Count.ToString(); foreach (var fecha in fechas) { if (fecha["TipoClave"] == "2") { string[] partesFecha = fecha["Fecha"].Split("-"); metadatos["fecha_pago"] = partesFecha[0] + "-" + partesFecha[1] + "-" + partesFecha[2]; break; } } } } string nombreZip = $"{pedimento}.zip"; string rutaZip = Helpers.CrearZipArchivoEnTemp(rutaArchivo, nombreZip); string UrlPostArchivoZip = DominioEFC + ApiGetPedimentoDesk; string respuestaJson = await apiClient.PostMultipartZipAsync( url: UrlPostArchivoZip, filePath: rutaZip, zipFileName: nombreZip, metadatos: metadatos ); string tempFolder = Path.GetDirectoryName(rutaZip) ?? ""; // Borra toda la carpeta temporal if (Directory.Exists(tempFolder)) { Directory.Delete(tempFolder, true); } if (!apiClient.IsJson(respuestaJson)) { return false; } if (!apiClient.HasJsonData(respuestaJson)) { return false; } if (JsonHelper.TryParse(respuestaJson, out var root)) { var JsonData = JsonHelper.ToDictionary(root); if (JsonData == null) { return false; } else if (JsonData.ContainsKey("tieneError")) { bool tieneError = Convert.ToBoolean(JsonData["tieneError"]); if (tieneError) { return false; } else { return true; } } else { return false; } } else { return false; } } else { logger.LogError("Error en la configuracion del Id de Usuario:" + DominioEFC, new Exception("Error Id Usuario")); return false; } } catch (Exception ex) { logger.LogError("Error inesperado SubirArchivoApiAsync", ex); return false; } Console.WriteLine($"[API] Registrado: {rutaArchivo}"); return true; } public async Task ReprocesarArchivosPendientesAsync(bool iniciarAlFinal = true) { // Limpieza automática antes de reprocesar _repo.LimpiarRegistrosInexistentes(); // Volver a registrar todos los expedientes para procesar todo en caso de que no este actualizada la base de datos await BarridoInicialAsync(); // await ProcesarArchivosPendientesAsync(); if (iniciarAlFinal) { Console.WriteLine("Iniciando monitoreo despues de reprocesar pendientes..."); Iniciar(); } } public async Task EscaneoManual() { _MonitoreoTimer = true; // Volver a registrar todos los expedientes para procesar todo en caso de que no este actualizada la base de datos await BarridoInicialAsync(); await ProcesarPilaAsync(); _MonitoreoTimer = false; } //private async Task ProcesarArchivosPendientesAsync() //{ // Console.WriteLine("Iniciando barrido inicial de carpetas..."); // await Task.Run( async () => // { // try // { // // Obtener lista de pendientes después de limpieza // var pendientes = _repo.ObtenerArchivosPendientes(); // if (!pendientes.Any()) // { // Console.WriteLine("No hay archivos pendientes por reprocesar."); // return; // } // // Agrupamos por carpeta raíz // var grupos = pendientes.GroupBy(p => p.CarpetaRaiz); // foreach (var grupo in grupos) // { // string carpetaRaiz = grupo.Key; // // Validamos que los archivos sigan existiendo // var archivosValidos = grupo // .Where(p => File.Exists(p.RutaArchivo)) // .ToList(); // if (!archivosValidos.Any()) // { // Console.WriteLine($"No se encontraron archivos válidos en {carpetaRaiz} para reprocesar."); // continue; // } // string nombreRaiz = Path.GetFileName(carpetaRaiz); // try // { // foreach (var archivo in archivosValidos) // { // try // { // bool respuesa = await RegistrarArchivoEnAPI(archivo.RutaArchivo, nombreRaiz); // if (respuesa) // { // _repo.ActualizarEstadoArchivo(archivo.RutaArchivo, "Procesado"); // } // } // catch (Exception ex) // { // Console.WriteLine($"[API ERR] {ex.Message}"); // } // } // Console.WriteLine($"Reprocesados {archivosValidos.Count} archivos de {carpetaRaiz}"); // } // catch (Exception ex) // { // Console.WriteLine($"Error reprocesando {carpetaRaiz}: {ex.Message}"); // } // finally // { // Console.WriteLine("Terminado: Reprocesar Archivos Pendientes"); // } // } // } // catch (Exception ex) // { // Console.WriteLine($"Error Reprocesar Archivos Pendientes: {ex.Message}"); // } // }); // } private async Task BarridoInicialAsync() { Console.WriteLine("Iniciando barrido inicial de carpetas..."); await Task.Run(() => { try { if (!Directory.Exists(_carpetaBase)) return; // Obtiene las subcarpetas (hijos directos) string[] carpetasHijos = Directory.GetDirectories(_carpetaBase); if (_aplicaWinsaii) { foreach (var carpetaHijo in carpetasHijos) { // Obtiene las subcarpetas de cada hijo (nietos) string[] carpetasNietos = Directory.GetDirectories(carpetaHijo); foreach (var carpetaNieto in carpetasNietos) { if (!Helpers.EsValidaNomeclaturaCarpeta(carpetaNieto)) continue; _repo.UpsertCarpeta(ruta: carpetaNieto, nombre: Path.GetFileName(carpetaNieto), tipo: "Nieto", estado: "Nuevo"); EncolarSiNoEsta(carpetaNieto); } } } else { foreach (var carpetaHijo in carpetasHijos) { if (!Helpers.EsValidaNomeclaturaCarpeta(carpetaHijo)) continue; _repo.UpsertCarpeta(ruta: carpetaHijo, nombre: Path.GetFileName(carpetaHijo), tipo: "Hijo", estado: "Nuevo"); EncolarSiNoEsta(carpetaHijo); } } } catch (Exception ex) { Console.WriteLine($"Error al barrer carpeta {_carpetaBase}: {ex.Message}"); } finally { //OnDatosActualizados(); } }); } public bool ExisteConfiguracionExpediente() { return _repo.ExisteConfiguracionExpediente(); } //private void OnDatosActualizados() //{ // DatosActualizados?.Invoke(this, EventArgs.Empty); //} // Método para disparar evento private void OnDatosActualizados(string carpetaRaiz) { DatosActualizados?.Invoke(this, new DatosActualizadosEventArgs(carpetaRaiz)); } public void Dispose() { try { _watcher?.Dispose(); } catch { } //_repo?.Dispose(); _semaforo?.Dispose(); _dbSemaforo?.Dispose(); } } }