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