Ajustes para trasmitir expedientes

This commit is contained in:
2026-03-06 12:56:41 -07:00
parent dcfd606a1c
commit c52344d7f8
13 changed files with 924 additions and 401 deletions

View File

@@ -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<string> SendWithRetriesAsync(Func<Task<HttpResponseMessage>> sendFunc, bool requireAuth = true)
private async Task<string> SendWithRetriesAsync(Func<HttpRequestMessage> 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<string> GetAsync(string url)
{
return await SendWithRetriesAsync(() => _httpClient.GetAsync(url));
return await SendWithRetriesAsync(() => new HttpRequestMessage(HttpMethod.Get, url));
}
public async Task<string> 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<string> 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<string> 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<string> 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<string> 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<string> 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
}
}
}

View File

@@ -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/";
}
/// <summary>
/// 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);
}
}
}
}
}
}

View File

@@ -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<string>(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<string>(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<string, string> metadatos = new Dictionary<string, string>
{
{ "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<bool> RegistrarArchivoEnAPI(string rutaArchivo, string pedimento)
private async Task<bool> RegistrarArchivoEnAPI(string rutaArchivo, string pedimento, Dictionary<string, string>? 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<string, string> metadatos;
if (metadatosPrecalculados != null)
{
metadatos = metadatosPrecalculados;
}
else
{
metadatos = new Dictionary<string, string>
{
{ "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<bool> 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

View File

@@ -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);