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 = 1; 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 ProcesarCarpetaRaizAsync1(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(); 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(); } // Crear ZIP aplanado con solo ese archivo 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 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 (ZIP único): {carpetaRaiz}"); var nombreRaiz = Path.GetFileName(carpetaRaiz); var archivos = Directory.GetFiles(carpetaRaiz, "*.*", SearchOption.AllDirectories) .Where(f => !EsTemporal(f)) .ToArray(); 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; } 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++; } 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; } //foreach (var archivo in archivosAProcesar) //{ // if (!FileUtil.WaitAllFilesReady(new[] { archivo }, _perFileTimeoutSeconds)) // { // Console.WriteLine($"[SKIP] Bloqueado: {archivo}"); // archivosAProcesar.Remove(archivo); // continue; // } // if (Helpers.ObtenerTamanoEnBytes(archivo) <= 0) // { // Console.WriteLine($"[SKIP] Sin Informacion: {archivo}"); // archivosAProcesar.Remove(archivo); // continue; // } // if (!Helpers.ValidarArchivo(archivo)) // { // Console.WriteLine($"[SKIP] Extension no valida: {archivo}"); // archivosAProcesar.Remove(archivo); // continue; // } //} archivosAProcesar.RemoveAll(archivo => { if (!FileUtil.WaitAllFilesReady(new[] { archivo }, _perFileTimeoutSeconds)) { Globales.logger.LogWarning($"Archivo bloqueado tras múltiples intentos: {archivo}"); return true; } if (Helpers.ObtenerTamanoEnBytes(archivo) <= 0) { Globales.logger.LogWarning($"Archivo sin información: {archivo}"); return true; } if (!Helpers.ValidarArchivo(archivo)) { Globales.logger.LogWarning($"Archivo con extensión no válida: {archivo}"); return true; } return false; }); if (archivosAProcesar.Count == 0) { _repo.MarcarCarpetaProcesada(carpetaRaiz, "Procesado"); return; } 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"; } Dictionary metadatos = new Dictionary { { "fuente", sfuente }, { "clave_pedimento", "" }, { "pedimento", "" }, { "patente", "" }, { "aduana", "" }, { "tipo_operacion", "" }, { "contribuyente", "" }, { "curp_apoderado", "" }, { "fecha_pago", "" }, { "partidas", "" }, }; bool metadatosCompletos = false; foreach (var archivo in archivosAProcesar) { if (Helpers.EsArchivoXMl(archivo) && !metadatosCompletos) { try { string xml = File.ReadAllText(archivo); 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 = nombreRaiz.Split("-"); metadatos["pedimento"] = numeroPedimento; metadatos["clave_pedimento"] = encabezado.ContainsKey("ClavePedimento") ? encabezado["ClavePedimento"] : ""; metadatos["patente"] = partes.Length >= 3 ? partes[2] : ""; metadatos["aduana"] = encabezado.ContainsKey("AduanaES") ? encabezado["AduanaES"] : ""; metadatos["tipo_operacion"] = encabezado.ContainsKey("TipoOperacion") ? encabezado["TipoOperacion"] : ""; metadatos["contribuyente"] = importador.ContainsKey("RFC") ? importador["RFC"] : ""; metadatos["curp_apoderado"] = encabezado.ContainsKey("CurpApoderado") ? encabezado["CurpApoderado"] : ""; metadatos["partidas"] = partidas.Count.ToString(); foreach (var fecha in fechas) { if (fecha.ContainsKey("TipoClave") && fecha["TipoClave"] == "2" && fecha.ContainsKey("Fecha")) { string[] partesFecha = fecha["Fecha"].Split("-"); metadatos["fecha_pago"] = partesFecha[0] + "-" + partesFecha[1] + "-" + partesFecha[2]; break; } } metadatosCompletos = true; } } catch (Exception ex) { // Console.WriteLine($"[WARN] Error al parsear XML {archivo}: {ex.Message}"); Globales.logger.LogError($"Error al parsear XML {archivo}", ex); } } } var rutaZip = Path.Combine(Path.GetDirectoryName(carpetaRaiz)!, $"{nombreRaiz}.zip"); // if (File.Exists(rutaZip)) File.Delete(rutaZip); _repo.MarcarCarpetaProcesada(carpetaRaiz, "Procesando"); string[] archivosParaZip = archivosAProcesar.ToArray(); rutaZip = Helpers.CrearZipMultipleArchivos(archivosParaZip, $"{nombreRaiz}.zip"); if (string.IsNullOrEmpty(rutaZip) || !File.Exists(rutaZip)) { Globales.logger.LogError($"No se pudo crear el ZIP para {carpetaRaiz}"); _repo.MarcarCarpetaProcesada(carpetaRaiz, "Pendiente"); return; } bool respuesta = false; try { var apiClient = Globales.ApiClientLarge; string UrlPostArchivoZip = DominioEFC + ApiGetPedimentoDesk; string respuestaJson = await apiClient.PostMultipartZipAsync( url: UrlPostArchivoZip, filePath: rutaZip, zipFileName: $"{nombreRaiz}.zip", metadatos: metadatos ); if (!apiClient.IsJson(respuestaJson) || !apiClient.HasJsonData(respuestaJson)) { Globales.logger.LogError($"Respuesta inválida de API para {carpetaRaiz}: {respuestaJson}"); _repo.MarcarCarpetaProcesada(carpetaRaiz, "Error API"); return; } if (JsonHelper.TryParse(respuestaJson, out var root)) { var JsonData = JsonHelper.ToDictionary(root); if (JsonData != null && JsonData.ContainsKey("tieneError")) { bool tieneError = Convert.ToBoolean(JsonData["tieneError"]); if (tieneError) { string mensaje = Convert.ToString(JsonData["mensaje"]) ?? "Error desconocido"; Globales.logger.LogError($"API respondió con error para {carpetaRaiz}: {mensaje}"); _repo.MarcarCarpetaProcesada(carpetaRaiz, "Error API"); return; } string taskId = Convert.ToString(JsonData["task_id"]) ?? ""; if (!string.IsNullOrEmpty(taskId)) { Globales.logger.LogInfo($"Archivo ZIP enviado a API para {carpetaRaiz}, task_id: {taskId}"); _repo.GuardarTaskID(carpetaRaiz, taskId); } respuesta = true; } } } catch (Exception ex) { Globales.logger.LogError($"Error al registrar en API para {carpetaRaiz}", ex); _repo.MarcarCarpetaProcesada(carpetaRaiz, "Error API"); } finally { string tempFolder = Path.GetDirectoryName(rutaZip) ?? ""; if (Directory.Exists(tempFolder)) { try { Directory.Delete(tempFolder, true); } catch { } } } if (respuesta) { await _dbSemaforo.WaitAsync(); try { foreach (var archivo in archivosAProcesar) { var nombreArchivo = Path.GetFileName(archivo); var sha1 = FileUtil.TryGetSHA1(archivo); _repo.RegistrarArchivoProcesado(archivo, nombreArchivo, carpetaRaiz, $"{nombreRaiz}.zip", sha1); _repo.ActualizarEstadoArchivo(archivo, "Procesado"); } } finally { _dbSemaforo.Release(); } _repo.MarcarCarpetaProcesada(carpetaRaiz, "Procesado"); Globales.logger.LogInfo($"Proceso Completado (ZIP único): {carpetaRaiz} - {archivosAProcesar.Count} archivos"); } else { _repo.MarcarCarpetaProcesada(carpetaRaiz, "Pendiente"); Globales.logger.LogError($"Proceso Fallido (ZIP único) para {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; } private async Task RegistrarArchivoEnAPI(string rutaArchivo, string pedimento, Dictionary? metadatosPrecalculados = null) { // 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)) { var apiClient = Globales.ApiClientLarge; 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; if (metadatosPrecalculados != null) { metadatos = metadatosPrecalculados; } else { metadatos = new Dictionary { { "fuente", sfuente }, { "clave_pedimento", "" }, { "pedimento", "" }, { "patente", "" }, { "aduana", "" }, { "tipo_operacion", "" }, { "contribuyente", "" }, { "curp_apoderado", "" }, { "fecha_pago", "" }, { "partidas", "" }, }; if (Helpers.EsArchivoXMl(rutaArchivo)) { 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) { string mensaje = Convert.ToString(JsonData["mensaje"]) ?? "Error desconocido"; logger.LogError($"Se presentan errores al intentar subir los documentos del pedimento {pedimento}"); 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; } private async Task RegistrarArchivoEnAPI_Original(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)) { var apiClient = Globales.ApiClientLarge; 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.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) { string mensaje = Convert.ToString(JsonData["mensaje"]) ?? "Error desconocido"; logger.LogError($"Se presentan errores al intentar subir los documentos del pedimento {pedimento}"); 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(); } } }