From c52344d7f8aa2b12a337b28a1c4794ac31e1b52a Mon Sep 17 00:00:00 2001 From: Luis Date: Fri, 6 Mar 2026 12:56:41 -0700 Subject: [PATCH] Ajustes para trasmitir expedientes --- Classes/ApiClient.cs | 76 +- Classes/Helpers.cs | 52 +- Classes/MonitorCarpetas.cs | 738 ++++++++++++------ Classes/SQLiteHelper.cs | 33 +- EFCDesk.csproj | 2 +- Entidades/ExpedienteInfo.cs | 2 + Forms/FormConfiguracionExpediente.Designer.cs | 22 +- Forms/FormConfiguracionExpediente.cs | 3 + Forms/FormExpedientes.Designer.cs | 1 + Forms/FormExpedientes.cs | 152 +++- Forms/FormMain.cs | 150 ++-- Program.cs | 81 +- Utils/Util.cs | 13 +- 13 files changed, 924 insertions(+), 401 deletions(-) diff --git a/Classes/ApiClient.cs b/Classes/ApiClient.cs index 7a3b31e..1434d9d 100644 --- a/Classes/ApiClient.cs +++ b/Classes/ApiClient.cs @@ -20,6 +20,7 @@ namespace EFCDesk.Classes public class ApiClient : IDisposable { + private static readonly HttpClient _sharedHttpClient = new HttpClient(); private readonly HttpClient _httpClient; private readonly int _maxRetries; private readonly TimeSpan _retryDelay; @@ -30,12 +31,10 @@ public class ApiClient : IDisposable public ApiClient(TimeSpan? timeout = null, int maxRetries = 3, TimeSpan? retryDelay = null) { - _httpClient = new HttpClient(); + _httpClient = _sharedHttpClient; - if (timeout.HasValue) + if (timeout.HasValue && timeout.Value > _httpClient.Timeout) _httpClient.Timeout = timeout.Value; - else - _httpClient.Timeout = TimeSpan.FromSeconds(30); _maxRetries = maxRetries >= 0 ? maxRetries : 3; _retryDelay = retryDelay ?? TimeSpan.FromSeconds(2); @@ -98,12 +97,14 @@ public class ApiClient : IDisposable var jsonContent = JsonSerializer.Serialize(refreshData); using var content = new StringContent(jsonContent, System.Text.Encoding.UTF8, "application/json"); - + // Limpiar headers para esta solicitud - _httpClient.DefaultRequestHeaders.Authorization = null; - - var response = await _httpClient.PostAsync(refreshUrl, content); - + // _httpClient.DefaultRequestHeaders.Authorization = null; + // var response = await _httpClient.PostAsync(refreshUrl, content); + + using var request = new HttpRequestMessage(HttpMethod.Post, refreshUrl) { Content = content }; + var response = await _httpClient.SendAsync(request); + if (response.IsSuccessStatusCode) { string responseContent = await response.Content.ReadAsStringAsync(); @@ -191,7 +192,7 @@ public class ApiClient : IDisposable } - private async Task SendWithRetriesAsync(Func> sendFunc, bool requireAuth = true) + private async Task SendWithRetriesAsync(Func requestFunc, bool requireAuth = true) { int attempts = 0; while (true) @@ -199,40 +200,61 @@ public class ApiClient : IDisposable attempts++; try { - // Asegurar que tenemos token antes de enviar (solo si requiere auth) + string? token = null; + if (requireAuth) + { await EnsureTokenAsync(); + lock (_tokenLock) + { + token = _currentToken; + } + } - var response = await sendFunc(); + var request = requestFunc(); + + if (requireAuth && token != null) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + } + + var response = await _httpClient.SendAsync(request); if (!response.IsSuccessStatusCode) { - // Si es 401 Unauthorized y requiere auth, intentar refresh token if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized && requireAuth) { bool refreshed = await TryRefreshTokenAsync(); if (refreshed) { - // Reintentar con nuevo token - response = await sendFunc(); + string? newToken = null; + lock (_tokenLock) + { + newToken = _currentToken; + } + + var newRequest = requestFunc(); + if (newToken != null) + { + newRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", newToken); + } + + response = await _httpClient.SendAsync(newRequest); if (response.IsSuccessStatusCode) return await response.Content.ReadAsStringAsync(); } } string errorContent = await response.Content.ReadAsStringAsync(); - // throw new ApiException($"Error {response.StatusCode}: {errorContent}", (int)response.StatusCode); return errorContent; } return await response.Content.ReadAsStringAsync(); } catch (TaskCanceledException) when (attempts <= _maxRetries) { - // Timeout: reintentar await Task.Delay(_retryDelay); } catch (HttpRequestException) when (attempts <= _maxRetries) { - // Error red: reintentar await Task.Delay(_retryDelay); } catch (TaskCanceledException) @@ -252,24 +274,24 @@ public class ApiClient : IDisposable public async Task GetAsync(string url) { - return await SendWithRetriesAsync(() => _httpClient.GetAsync(url)); + return await SendWithRetriesAsync(() => new HttpRequestMessage(HttpMethod.Get, url)); } public async Task PostJsonAsync(string url, string jsonContent) { using var content = new StringContent(jsonContent, System.Text.Encoding.UTF8, "application/json"); - return await SendWithRetriesAsync(() => _httpClient.PostAsync(url, content)); + return await SendWithRetriesAsync(() => new HttpRequestMessage(HttpMethod.Post, url) { Content = content }); } public async Task PutJsonAsync(string url, string jsonContent) { using var content = new StringContent(jsonContent, System.Text.Encoding.UTF8, "application/json"); - return await SendWithRetriesAsync(() => _httpClient.PutAsync(url, content)); + return await SendWithRetriesAsync(() => new HttpRequestMessage(HttpMethod.Put, url) { Content = content }); } public async Task DeleteAsync(string url) { - return await SendWithRetriesAsync(() => _httpClient.DeleteAsync(url)); + return await SendWithRetriesAsync(() => new HttpRequestMessage(HttpMethod.Delete, url)); } // Método para enviar archivo y JSON juntos en multipart/form-data @@ -290,7 +312,7 @@ public class ApiClient : IDisposable var jsonContent = new StringContent(jsonString, System.Text.Encoding.UTF8, "application/json"); form.Add(jsonContent, jsonFieldName); - return await SendWithRetriesAsync(() => _httpClient.PostAsync(url, form)); + return await SendWithRetriesAsync(() => new HttpRequestMessage(HttpMethod.Post, url) { Content = form }); } public async Task PostMultipartAsync(string url, string filePath, string organizacion, string pedimento, @@ -316,7 +338,7 @@ public class ApiClient : IDisposable form.Add(new StringContent(documentType), "document_type"); form.Add(new StringContent(fuente), "fuente"); - return await SendWithRetriesAsync(() => _httpClient.PostAsync(url, form)); + return await SendWithRetriesAsync(() => new HttpRequestMessage(HttpMethod.Post, url) { Content = form }); } //public async Task PostMultipartZipAsync(string url, string token, byte[] zipBytes, string zipFileName, string fileFieldName = "archivos") @@ -369,14 +391,14 @@ public class ApiClient : IDisposable //form.Add(new StringContent(documentType), "document_type"); //form.Add(new StringContent(fuente), "fuente"); - return await SendWithRetriesAsync(() => _httpClient.PostAsync(url, form)); + return await SendWithRetriesAsync(() => new HttpRequestMessage(HttpMethod.Post, url) { Content = form }); } // Método existente que acepta string JSON public async Task PostJsonWithoutAuthAsync(string url, string jsonContent) { using var content = new StringContent(jsonContent, System.Text.Encoding.UTF8, "application/json"); - return await SendWithRetriesAsync(() => _httpClient.PostAsync(url, content), requireAuth: false); + return await SendWithRetriesAsync(() => new HttpRequestMessage(HttpMethod.Post, url) { Content = content }, requireAuth: false); } // Nueva sobrecarga que acepta cualquier objeto @@ -460,7 +482,7 @@ public class ApiClient : IDisposable public void Dispose() { - _httpClient.Dispose(); + // No disposing _httpClient because it's a shared singleton } } } diff --git a/Classes/Helpers.cs b/Classes/Helpers.cs index 561e566..0c70f38 100644 --- a/Classes/Helpers.cs +++ b/Classes/Helpers.cs @@ -117,7 +117,9 @@ namespace EFCDesk.Classes } public static string CrearEndpointpedimentoDesk() { - return @"/api/v1/customs/pedimentos/bulk-create-pedimento_desk/"; + // return @"/api/v1/customs/pedimentos/bulk-create-pedimento_desk/"; + // return @"/api/v1/customs/pedimentos/bulk-upload-record-zip/"; + return @"/api/v1/customs/pedimentos/bulk-upload-record-zip-async/"; } /// /// Espera hasta que todos los archivos dentro de la carpeta estén libres (no en uso) @@ -360,5 +362,53 @@ namespace EFCDesk.Classes } } + public static string CrearZipMultipleArchivos(string[] rutasArchivos, string nombreArchivoZip, bool autoLimpiar = false) + { + string tempRoot = Path.Combine(Path.GetTempPath(), "temp-efc-desk"); + string tempFolder = Path.Combine(tempRoot, Guid.NewGuid().ToString().ToLower().Replace("-", "").Substring(0, 8)); + Directory.CreateDirectory(tempFolder); + + string zipPath = Path.Combine(tempFolder, nombreArchivoZip); + + try + { + using (var zip = ZipFile.Open(zipPath, ZipArchiveMode.Create)) + { + foreach (var rutaArchivo in rutasArchivos) + { + if (File.Exists(rutaArchivo)) + { + zip.CreateEntryFromFile(rutaArchivo, Path.GetFileName(rutaArchivo)); + } + } + } + + return zipPath; + } + catch (Exception ex) + { + Globales.logger.LogError("Helpers - CrearZipMultipleArchivos", ex); + return ""; + } + finally + { + if (autoLimpiar) + { + try + { + System.Threading.Thread.Sleep(3000); + if (Directory.Exists(tempFolder)) + { + Directory.Delete(tempFolder, true); + } + } + catch (Exception ex) + { + Globales.logger.LogError("Helpers - No se pudo limpiar carpeta temporal en CrearZipMultipleArchivos", ex); + } + } + } + } + } } diff --git a/Classes/MonitorCarpetas.cs b/Classes/MonitorCarpetas.cs index a69861e..f1bd3e0 100644 --- a/Classes/MonitorCarpetas.cs +++ b/Classes/MonitorCarpetas.cs @@ -30,7 +30,7 @@ namespace EFCDesk.Classes private readonly object _lockProceso = new object(); // Parámetros de robustez - private readonly int _maxConcurrent = 2; + 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); @@ -225,126 +225,7 @@ namespace EFCDesk.Classes } } - //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) + private async Task ProcesarCarpetaRaizAsync1(string carpetaRaiz) { if (!Directory.Exists(carpetaRaiz)) { @@ -359,16 +240,6 @@ namespace EFCDesk.Classes .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) { @@ -471,6 +342,7 @@ namespace EFCDesk.Classes _dbSemaforo.Release(); } + // Crear ZIP aplanado con solo ese archivo bool respuesa = false; // 🔌 HOOK: Registrar en API try @@ -515,6 +387,319 @@ namespace EFCDesk.Classes } + 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); @@ -529,15 +714,7 @@ namespace EFCDesk.Classes 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) + private async Task RegistrarArchivoEnAPI(string rutaArchivo, string pedimento, Dictionary? metadatosPrecalculados = null) { // TODO: tu implementación real de API try @@ -554,7 +731,197 @@ namespace EFCDesk.Classes if (!string.IsNullOrEmpty(Token)) { - using var apiClient = new ApiClient(timeout: TimeSpan.FromSeconds(600), maxRetries: 3, retryDelay: TimeSpan.FromSeconds(60)); + 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; @@ -598,91 +965,6 @@ namespace EFCDesk.Classes { "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)) { @@ -770,6 +1052,10 @@ namespace EFCDesk.Classes 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 diff --git a/Classes/SQLiteHelper.cs b/Classes/SQLiteHelper.cs index bd6dab9..912d7ec 100644 --- a/Classes/SQLiteHelper.cs +++ b/Classes/SQLiteHelper.cs @@ -84,7 +84,8 @@ namespace EFCDesk.Classes Nombre TEXT NOT NULL, Tipo TEXT NOT NULL, -- Hijo o Nieto Estado TEXT NOT NULL, -- Nuevo o Procesado - FechaCreado DATETIME DEFAULT NULL + FechaCreado DATETIME DEFAULT NULL, + TaskId TEXT DEFAULT NULL );"; string createIndex = @" CREATE INDEX IF NOT EXISTS IX_Carpetas_Estado ON Carpetas(Estado); @@ -521,6 +522,29 @@ namespace EFCDesk.Classes } } + public bool GuardarTaskID(string rutaArchivo, string taskId) + { + try + { + using (var connection = new SQLiteConnection(_connectionString)) + { + connection.Open(); + string updateQuery = "UPDATE Carpetas SET TaskId=@t WHERE Ruta=@r"; + using (var command = new SQLiteCommand(updateQuery, connection)) + { + command.Parameters.AddWithValue("@r", rutaArchivo); + command.Parameters.AddWithValue("@t", taskId); + return command.ExecuteNonQuery() > 0; + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + return false; + } + } + public bool EstaArchivoProcesado(string rutaArchivo) { @@ -788,11 +812,11 @@ namespace EFCDesk.Classes if (buscarExpediente == "*") { - query = "SELECT Id, Nombre, Estado, FechaCreado, Ruta from Carpetas ORDER BY Nombre DESC LIMIT @pageSize OFFSET @offset;"; + query = "SELECT Id, Nombre, Estado, FechaCreado, Ruta, TaskId from Carpetas ORDER BY Nombre DESC LIMIT @pageSize OFFSET @offset;"; } else { - query = "SELECT Id, Nombre, Estado, FechaCreado, Ruta from Carpetas WHERE Nombre LIKE '%' || @Expediente || '%' LIMIT @pageSize OFFSET @offset;"; + query = "SELECT Id, Nombre, Estado, FechaCreado, Ruta, TaskId from Carpetas WHERE Nombre LIKE '%' || @Expediente || '%' LIMIT @pageSize OFFSET @offset;"; } @@ -813,7 +837,8 @@ namespace EFCDesk.Classes Expediente = reader.GetString(1), Estado = reader.GetString(2), FechaCreacion = reader.GetString(3), - Ruta = reader.GetString(4) + Ruta = reader.GetString(4), + TaskId = reader.IsDBNull(5) ? "" : reader.GetString(5) }; lista.Add(exp); diff --git a/EFCDesk.csproj b/EFCDesk.csproj index b62e8ae..d0d6483 100644 --- a/EFCDesk.csproj +++ b/EFCDesk.csproj @@ -10,7 +10,7 @@ EFC.ico send Aduanasoft©2025 - 26.2.2.2 + 26.3.1.1 diff --git a/Entidades/ExpedienteInfo.cs b/Entidades/ExpedienteInfo.cs index 5d1b63e..173b5ad 100644 --- a/Entidades/ExpedienteInfo.cs +++ b/Entidades/ExpedienteInfo.cs @@ -15,5 +15,7 @@ namespace EFCDesk.Entidades public string FechaCreacion { get; set; } = ""; public string Ruta { get; set; } = ""; public string Ticks { get; set; } = ""; + public string TaskId { get; set; } = ""; + } } diff --git a/Forms/FormConfiguracionExpediente.Designer.cs b/Forms/FormConfiguracionExpediente.Designer.cs index 0dbb204..fe05acf 100644 --- a/Forms/FormConfiguracionExpediente.Designer.cs +++ b/Forms/FormConfiguracionExpediente.Designer.cs @@ -63,6 +63,7 @@ textBoxPasswordFTP = new TextBox(); button4 = new Button(); button5 = new Button(); + lblVersion = new Label(); tabControl1.SuspendLayout(); tabGeneral.SuspendLayout(); groupBox1.SuspendLayout(); @@ -89,6 +90,7 @@ // // tabGeneral // + tabGeneral.Controls.Add(lblVersion); tabGeneral.Controls.Add(groupBox1); tabGeneral.Controls.Add(groupBoxEspacio); tabGeneral.Location = new Point(4, 26); @@ -154,10 +156,10 @@ // tabPage2.Controls.Add(groupBox4); tabPage2.Controls.Add(groupBox3); - tabPage2.Location = new Point(4, 26); + tabPage2.Location = new Point(4, 24); tabPage2.Name = "tabPage2"; tabPage2.Padding = new Padding(3); - tabPage2.Size = new Size(288, 252); + tabPage2.Size = new Size(288, 254); tabPage2.TabIndex = 1; tabPage2.Text = "Archivos"; tabPage2.UseVisualStyleBackColor = true; @@ -247,10 +249,10 @@ tabPage1.Controls.Add(label6); tabPage1.Controls.Add(txtDiminioEFC); tabPage1.Controls.Add(groupBox5); - tabPage1.Location = new Point(4, 26); + tabPage1.Location = new Point(4, 24); tabPage1.Name = "tabPage1"; tabPage1.Padding = new Padding(3); - tabPage1.Size = new Size(288, 252); + tabPage1.Size = new Size(288, 254); tabPage1.TabIndex = 2; tabPage1.Text = "Avanzados"; tabPage1.UseVisualStyleBackColor = true; @@ -416,6 +418,16 @@ button5.UseVisualStyleBackColor = true; button5.Click += button5_Click; // + // lblVersion + // + lblVersion.AutoSize = true; + lblVersion.Font = new Font("Century Gothic", 9F, FontStyle.Bold, GraphicsUnit.Point, 0); + lblVersion.Location = new Point(5, 230); + lblVersion.Name = "lblVersion"; + lblVersion.Size = new Size(90, 16); + lblVersion.TabIndex = 2; + lblVersion.Text = "Versión 1.0.0.0"; + // // FormConfiguracionExpediente // AutoScaleDimensions = new SizeF(7F, 17F); @@ -437,6 +449,7 @@ Shown += FormConfiguracionExpediente_Shown; tabControl1.ResumeLayout(false); tabGeneral.ResumeLayout(false); + tabGeneral.PerformLayout(); groupBox1.ResumeLayout(false); groupBox1.PerformLayout(); groupBoxEspacio.ResumeLayout(false); @@ -491,5 +504,6 @@ private TextBox textBoxPasswordFTP; private Label label6; private TextBox txtDiminioEFC; + private Label lblVersion; } } \ No newline at end of file diff --git a/Forms/FormConfiguracionExpediente.cs b/Forms/FormConfiguracionExpediente.cs index 07a03ba..432a98a 100644 --- a/Forms/FormConfiguracionExpediente.cs +++ b/Forms/FormConfiguracionExpediente.cs @@ -236,6 +236,9 @@ namespace EFCDesk.Forms private void FormConfiguracionExpediente_Shown(object sender, EventArgs e) { + + lblVersion.Text = "Versión: " + Globales.VersionApp; + var configJson = new ConfiguracionJSON(); configJson = ConfiguracionJSON.LoadFromJson(); diff --git a/Forms/FormExpedientes.Designer.cs b/Forms/FormExpedientes.Designer.cs index a0117fd..a270598 100644 --- a/Forms/FormExpedientes.Designer.cs +++ b/Forms/FormExpedientes.Designer.cs @@ -73,6 +73,7 @@ dgwExpedientes.RowHeadersVisible = false; dgwExpedientes.Size = new Size(559, 258); dgwExpedientes.TabIndex = 2; + dgwExpedientes.CellClick += dgwExpedientes_CellClick; // // BTN_Buscar // diff --git a/Forms/FormExpedientes.cs b/Forms/FormExpedientes.cs index d8917bc..f58a29d 100644 --- a/Forms/FormExpedientes.cs +++ b/Forms/FormExpedientes.cs @@ -1,4 +1,5 @@ using EFCDesk.Classes; +using FontAwesome.Sharp; using Microsoft.Win32; using System; using System.Collections.Generic; @@ -9,6 +10,7 @@ using System.Globalization; using System.Linq; using System.Numerics; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using System.Windows.Forms; @@ -22,12 +24,14 @@ namespace EFCDesk.Forms public long _registros; public long _pagina; public long _paginas; + public string? _buscarExpediente; } private static SQLiteHelper _sqliteHelper = new SQLiteHelper(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "historico.db")); private long paginacion = 1000; private bool _clickPorCodigo = false; private Paginacion _paginacion = new Paginacion(); + private string DominioEFC = Helpers.DominioExpedienteElectronico(); public FormExpedientes() { @@ -38,7 +42,7 @@ namespace EFCDesk.Forms } - private async Task CargarVistaExpedientes(int pagina, string buscarExpediente = "*") + private async Task CargarVistaExpedientes(long pagina, string buscarExpediente = "*") { // Limpiar la fuente de datos dgwExpedientes.DataSource = null; @@ -54,7 +58,26 @@ namespace EFCDesk.Forms table.Columns.Add("Expediente", typeof(string)); table.Columns.Add("Ruta", typeof(string)); table.Columns.Add("Estatus", typeof(string)); - table.Columns.Add("Fecha Registro", typeof(string)); + table.Columns.Add("Fecha Registro", typeof(string)); // TaskId + table.Columns.Add("TaskId", typeof(string)); // TaskId + // Columna de Acciones con botón FontAwesome + var columnaAcciones = new DataGridViewImageColumn + { + Name = "Acciones", + HeaderText = "Acciones", + Width = 50, + ImageLayout = DataGridViewImageCellLayout.Zoom + }; + + // Columna de acciones + //table.Columns.Add("Acciones", typeof(Image)); + + // Crear el icono UNA sola vez + Bitmap iconoVer = IconChar.Eye.ToBitmap( + IconFont.Auto, + 16, + Color.Black + ); foreach (var exp in listaExpedientes) { @@ -63,6 +86,8 @@ namespace EFCDesk.Forms row["Ruta"] = exp.Ruta; row["Estatus"] = exp.Estado; row["Fecha Registro"] = exp.FechaCreacion; + row["TaskId"] = exp.TaskId; + //row["Acciones"] = iconoVer; table.Rows.Add(row); } return table; @@ -90,6 +115,10 @@ namespace EFCDesk.Forms dgwExpedientes.Columns["Fecha Registro"].AutoSizeMode = DataGridViewAutoSizeColumnMode.None; dgwExpedientes.Columns["Fecha Registro"].Width = 180; + //dgwExpedientes.Columns["Acciones"].Width = 40; + //dgwExpedientes.Columns["Acciones"].HeaderText = ""; + //((DataGridViewImageColumn)dgwExpedientes.Columns["Acciones"]).ImageLayout = DataGridViewImageCellLayout.Zoom; + _ = Task.Run(() => { long total = dgwExpedientes.RowCount; @@ -98,6 +127,7 @@ namespace EFCDesk.Forms _paginacion._registros = total; _paginacion._pagina = pagina; _paginacion._paginas = totalPaginas; + _paginacion._buscarExpediente = ((string.IsNullOrEmpty(buscarExpediente) || string.IsNullOrWhiteSpace(buscarExpediente))) ? "*" : buscarExpediente; ActualizarEtiqueta(); }); @@ -109,6 +139,7 @@ namespace EFCDesk.Forms _paginacion._registros = 0; _paginacion._pagina = 0; _paginacion._paginas = 0; + _paginacion._buscarExpediente = "*"; ActualizarEtiqueta(); } @@ -116,6 +147,85 @@ namespace EFCDesk.Forms } + private async Task ConsultarEstadoTareaAsync(string taskId) + { + try + { + var apiClient = Globales.ApiClient; + string url = DominioEFC + $"/api/v1/tasks/status/?task_id={taskId}"; + return await apiClient.GetAsync(url); + } + catch (Exception ex) + { + MessageBox.Show($"Error al consultar estado: {ex.Message}", "Error", + MessageBoxButtons.OK, MessageBoxIcon.Error); + return ""; + } + } + + private async Task ProcesarRespuestaTareaAsync(string responseJson, string rutaExpediente) + { + if (string.IsNullOrEmpty(responseJson)) return; + + var apiClient = Globales.ApiClient; + if (!apiClient.IsJson(responseJson)) return; + + var data = apiClient.TryParseJson>(responseJson); + if (data == null) return; + + // Extraer información + string status = data.ContainsKey("status") ? data["status"]?.ToString() ?? "" : ""; + bool successful = data.ContainsKey("successful") && Convert.ToBoolean(data["successful"]); + int documentsCreated = 0; + bool tieneError = true; + + if (data.ContainsKey("result") && data["result"] is JsonElement resultElement) + { + var result = JsonSerializer.Deserialize>(resultElement.GetRawText()); + if (result != null) + { + tieneError = result.ContainsKey("tieneError") && Convert.ToBoolean(result["tieneError"]); + documentsCreated = result.ContainsKey("documents_created") ? Convert.ToInt32(result["documents_created"]) : 0; + } + } + + // Determinar nuevo estado basado en respuesta + string nuevoEstado = "Error"; + string mensaje = ""; + + if (successful && !tieneError) + { + nuevoEstado = "Procesado"; + mensaje = $"Tarea completada. Documentos creados: {documentsCreated}"; + } + else if (status == "PENDING" || status == "STARTED") + { + nuevoEstado = "Procesando"; + mensaje = "Tarea en proceso..."; + } + else + { + mensaje = $"Error en tarea: {status}"; + } + + // Actualizar en SQLite + _sqliteHelper.MarcarCarpetaProcesada(rutaExpediente, nuevoEstado); + + // Mostrar resultado + MessageBox.Show(mensaje, "Estado de Tarea", MessageBoxButtons.OK, MessageBoxIcon.Information); + + // Recargar la grilla para ver el cambio + long pagina = _paginacion._pagina; // 'long' no puede ser null, así que no se necesita el operador '??' + string buscarExpediente = _paginacion._buscarExpediente ?? "*"; // Si es null, usar "*" + if (!(pagina > 0)) + { + pagina = 1; // Valor predeterminado si 'pagina' no es válido + } + + // await Task.Run(() => CargarVistaExpedientes(pagina: pagina, buscarExpediente: buscarExpediente)); + await CargarVistaExpedientes(pagina: pagina, buscarExpediente: buscarExpediente); + } + private async void FormExpedientes_Shown(object sender, EventArgs e) { edt_Pagina.Text = "1"; @@ -249,12 +359,48 @@ namespace EFCDesk.Forms private void edt_BuscarExpediente_KeyPress(object sender, KeyPressEventArgs e) { - if (e.KeyChar == (char)Keys.Return) + if (e.KeyChar == (char)Keys.Return) { e.Handled = true; BTN_Buscar.PerformClick(); } } + + private async void dgwExpedientes_CellClick(object sender, DataGridViewCellEventArgs e) + { + if (e.RowIndex < 0) + return; + + var column = dgwExpedientes.Columns[e.ColumnIndex]; + + if (column.Name == "Acciones") + { + object? cellValueExp = dgwExpedientes.Rows[e.RowIndex].Cells["Expediente"].Value; + object? cellValueTask = dgwExpedientes.Rows[e.RowIndex].Cells["TaskId"].Value; + + string? expediente = cellValueExp?.ToString(); + string? taskId = cellValueTask?.ToString(); + + if (string.IsNullOrEmpty(taskId)) + return; + + //// Ejecutar trabajo pesado en segundo plano + //var resultado = await Task.Run(() => + //{ + // // Simulación de proceso pesado + // Thread.Sleep(1000); + // return $"Procesado expediente {taskId}"; + //}); + + //MessageBox.Show(resultado); + + string responseJson = await ConsultarEstadoTareaAsync(taskId); + + await ProcesarRespuestaTareaAsync(responseJson, expediente); + + } + + } } } diff --git a/Forms/FormMain.cs b/Forms/FormMain.cs index 1eb1b07..4c5e844 100644 --- a/Forms/FormMain.cs +++ b/Forms/FormMain.cs @@ -801,96 +801,96 @@ namespace EFCDesk.Forms sqliteHelper.AgregarRegistroErrorLog(e, sNombreClase, currentMethod); } } - private async Task SubirArchivoApiAsync(string pedimento, string filePath) - { +// private async Task SubirArchivoApiAsync(string pedimento, string filePath) +// { - try - { - var config = ConfiguracionJSON.LoadFromJson(); - string Token = SecureDataHandler.ReadDataAsPlainText() ?? ""; +// try +// { +// var config = ConfiguracionJSON.LoadFromJson(); +// string Token = SecureDataHandler.ReadDataAsPlainText() ?? ""; - if (string.IsNullOrWhiteSpace(DominioEFC)) - { - logger.LogError("No se ha configurado el dominio", null); - return false; - } +// 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(10), maxRetries: 3, retryDelay: TimeSpan.FromSeconds(2)); - string UrlGetPedimento = DominioEFC + ApiGetPedimento + pedimento; - string jsonResponse = await apiClient.GetAsync(url: UrlGetPedimento); +// if (!string.IsNullOrEmpty(Token)) +// { +// var apiClient = Globales.ApiClient; +// string UrlGetPedimento = DominioEFC + ApiGetPedimento + pedimento; +// string jsonResponse = await apiClient.GetAsync(url: UrlGetPedimento); - if (!apiClient.IsJson(jsonResponse)) - { - logger.LogError("Error en la respuesta de la peticion de pedimento al intentar subir el archivo al api:" + DominioEFC, new Exception("Error API")); - return false; - } +// if (!apiClient.IsJson(jsonResponse)) +// { +// logger.LogError("Error en la respuesta de la peticion de pedimento al intentar subir el archivo al api:" + DominioEFC, new Exception("Error API")); +// return false; +// } - if (!apiClient.HasJsonData(jsonResponse)) - { - logger.LogError("No se obtuvo informacion del api:" + DominioEFC, null); - return false; - } +// if (!apiClient.HasJsonData(jsonResponse)) +// { +// logger.LogError("No se obtuvo informacion del api:" + DominioEFC, null); +// return false; +// } - var pedimentos = apiClient.TryParseJson>(jsonResponse); +// var pedimentos = apiClient.TryParseJson>(jsonResponse); - if (pedimentos != null) - { - // Obtener el primer pedimento de la lista - var primerPedimento = pedimentos.FirstOrDefault(); - string guiOrganizacion = primerPedimento.organizacion; - string guiPedimento = primerPedimento.id; +// if (pedimentos != null) +// { +// // Obtener el primer pedimento de la lista +// var primerPedimento = pedimentos.FirstOrDefault(); +// string guiOrganizacion = primerPedimento.organizacion; +// string guiPedimento = primerPedimento.id; - string UrlPostArchivo = DominioEFC + ApiPostDocuments; +// string UrlPostArchivo = DominioEFC + ApiPostDocuments; -string respuesta = await apiClient.PostMultipartAsync( - url: UrlPostArchivo, - filePath: filePath, - organizacion: guiOrganizacion, - pedimento: guiPedimento, - documentType: "9", - fuente: "1" - ); +//string respuesta = await apiClient.PostMultipartAsync( +// url: UrlPostArchivo, +// filePath: filePath, +// organizacion: guiOrganizacion, +// pedimento: guiPedimento, +// documentType: "9", +// fuente: "1" +// ); - if (!apiClient.IsJson(jsonResponse)) - { - return false; - } - if (!apiClient.HasJsonData(jsonResponse)) - { - return false; - } +// if (!apiClient.IsJson(jsonResponse)) +// { +// return false; +// } +// if (!apiClient.HasJsonData(jsonResponse)) +// { +// return false; +// } - var documentos = apiClient.TryParseJson>(jsonResponse); - if (documentos == null) - { - return false; - } - } - else - { - logger.LogInfo("No se encontro el pedimento"); - return false; - } +// var documentos = apiClient.TryParseJson>(jsonResponse); +// if (documentos == null) +// { +// return false; +// } +// } +// else +// { +// logger.LogInfo("No se encontro el pedimento"); +// 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; - } +// } +// 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; +// } - return true; - } +// return true; +// } private void RegistrarArchivoEnAPI(string file) { try diff --git a/Program.cs b/Program.cs index d06fe18..3617a9c 100644 --- a/Program.cs +++ b/Program.cs @@ -1,6 +1,8 @@ using EFCDesk.Classes; using EFCDesk.Forms; using System.Threading; +using System.Diagnostics; +using System.Reflection; namespace EFCDesk { @@ -11,6 +13,10 @@ namespace EFCDesk public static ConfiguracionJSON configJson = ConfiguracionJSON.LoadFromJson(); public static string accesToken = ""; public static string refresToken = ""; + public static string VersionApp = "1.0.0"; + + public static ApiClient ApiClientLarge { get; set; } = null!; + public static ApiClient ApiClient { get; set; } = null!; } internal static class Program @@ -19,7 +25,7 @@ namespace EFCDesk /// /// The main entry point for the application. /// -[STAThread] + [STAThread] static async Task Main() { @@ -27,6 +33,9 @@ namespace EFCDesk Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); + var fileVersionInfo = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location); + Globales.VersionApp = fileVersionInfo.FileVersion ?? "1.0.0"; + string carpetaBase = @"C:\EFC"; if (!Directory.Exists(Globales.configJson.FolderExpediente)) { @@ -48,47 +57,23 @@ namespace EFCDesk Properties.Settings.Default.urlEFC = Globales.configJson.DominioExp; Properties.Settings.Default.Save(); + Globales.ApiClientLarge = new ApiClient( + timeout: TimeSpan.FromSeconds(600), + maxRetries: 3, + retryDelay: TimeSpan.FromSeconds(60)); + + Globales.ApiClient = new ApiClient( + timeout: TimeSpan.FromSeconds(10), + maxRetries: 3, + retryDelay: TimeSpan.FromSeconds(2)); + Globales.gMonitor = new MonitorCarpetas( carpetaBase: carpetaBase, - maxConcurrent: 10, + maxConcurrent: 1, perFileTimeoutSeconds: 30, maxReintentos: 10 ); - - //string? data = SecureDataHandler.ReadDataAsPlainText(); - //if (string.IsNullOrEmpty(data)) - //{ - // Application.Run(new Login()); - - //} else if (!SecureDataHandler.IsDataValid()) - //{ - // MessageBox.Show("Id Usuario incorrecto. favor de revisar.","Error",MessageBoxButtons.OK,MessageBoxIcon.Error); - // Application.Run(new Login()); - - //} else - //{ - // // Antes de llamar a cualquier m�todo sobre Globales.gMonitor, verifica que no sea null - // if (Globales.gMonitor != null && !Globales.gMonitor.ExisteConfiguracionExpediente()) - // { - // Application.Run(new Login()); - // } - // else - // { - // if (Globales.gMonitor != null) - // { - // Application.Run(new FormMain(Globales.gMonitor)); - // } - // else - // { - // MessageBox.Show("Error interno: el monitor de carpetas no est� inicializado.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - // Application.Exit(); - // } - // } - // // Al terminar la aplicaci�n, liberar el recurso manualmente - // Globales.gMonitor?.Dispose(); - //} - bool Esvalido = Utils.Util.StringsValidos(Globales.configJson.UsuarioExp, Globales.configJson.PasswordExp); if (!Esvalido) @@ -127,29 +112,9 @@ namespace EFCDesk } // Al terminar la aplicaci�n, liberar el recurso manualmente Globales.gMonitor?.Dispose(); + Globales.ApiClientLarge?.Dispose(); + Globales.ApiClient?.Dispose(); } - //else - //{ - // // Antes de llamar a cualquier m�todo sobre Globales.gMonitor, verifica que no sea null - // if (Globales.gMonitor != null && !Globales.gMonitor.ExisteConfiguracionExpediente()) - // { - // Application.Run(new Login()); - // } - // else - // { - // if (Globales.gMonitor != null) - // { - // Application.Run(new FormMain(Globales.gMonitor)); - // } - // else - // { - // MessageBox.Show("Error interno: el monitor de carpetas no est� inicializado.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - // Application.Exit(); - // } - // } - // // Al terminar la aplicaci�n, liberar el recurso manualmente - // Globales.gMonitor?.Dispose(); - //} } } diff --git a/Utils/Util.cs b/Utils/Util.cs index bffd734..b480108 100644 --- a/Utils/Util.cs +++ b/Utils/Util.cs @@ -58,7 +58,7 @@ namespace EFCDesk.Utils return false; } - using var apiClient = new ApiClient(timeout: TimeSpan.FromSeconds(600), maxRetries: 3, retryDelay: TimeSpan.FromSeconds(60)); + var apiClient = Globales.ApiClientLarge; // Crear diccionario con los datos var loginData = new Dictionary @@ -245,6 +245,15 @@ namespace EFCDesk.Utils } - + public static string FolderTemporales() + { + // Obtiene la ruta raíz de la carpeta temporal del sistema + string tempPath = Path.Combine(Path.GetTempPath(), "temp-efc-desk"); + if (!Directory.Exists(tempPath)) + { + Directory.CreateDirectory(tempPath); + } + return tempPath; + } } }