first commit

This commit is contained in:
2026-02-09 10:55:45 -07:00
commit 7cca957e1a
77 changed files with 44415 additions and 0 deletions

466
Classes/ApiClient.cs Normal file
View File

@@ -0,0 +1,466 @@
using EFCDesk.Entidades;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace EFCDesk.Classes
{
public class ApiException : Exception
{
public int StatusCode { get; }
public ApiException(string message, int statusCode) : base(message)
{
StatusCode = statusCode;
}
}
public class ApiClient : IDisposable
{
private readonly HttpClient _httpClient;
private readonly int _maxRetries;
private readonly TimeSpan _retryDelay;
private string? _currentToken;
private string? _currentRefreshToken;
private readonly object _tokenLock = new object();
public ApiClient(TimeSpan? timeout = null, int maxRetries = 3, TimeSpan? retryDelay = null)
{
_httpClient = new HttpClient();
if (timeout.HasValue)
_httpClient.Timeout = timeout.Value;
else
_httpClient.Timeout = TimeSpan.FromSeconds(30);
_maxRetries = maxRetries >= 0 ? maxRetries : 3;
_retryDelay = retryDelay ?? TimeSpan.FromSeconds(2);
}
//private void SetAuthorization(string token)
//{
// if (string.IsNullOrWhiteSpace(token))
// throw new ArgumentException("El token no puede estar vacío.", nameof(token));
// _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", token);
//}
public string EndpointLoginToken()
{
return @"/api/v1/token/";
}
public string EndpointRefreshToken()
{
return @"/api/v1/token/refresh/";
}
private void SetAuthorizationBearer(string token, string refresh_token)
{
if (string.IsNullOrWhiteSpace(token))
throw new ArgumentException("El token no puede estar vacío.", nameof(token));
if (string.IsNullOrWhiteSpace(refresh_token))
throw new ArgumentException("El refresh token no puede estar vacío.", nameof(refresh_token));
lock (_tokenLock)
{
_currentToken = token;
_currentRefreshToken = refresh_token;
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
}
private async Task<bool> TryRefreshTokenAsync()
{
try
{
Globales.configJson = ConfiguracionJSON.LoadFromJson();
// Obtener el refresh token actual guardado
string? savedToken = Globales.refresToken;
if (string.IsNullOrEmpty(savedToken))
return false;
// Usar el endpoint de refresh token
string dominio = Globales.configJson.DominioExp ?? "";
if (string.IsNullOrEmpty(dominio))
return false;
string refreshUrl = dominio + EndpointRefreshToken();
// Crear solicitud de refresh con el token actual
var refreshData = new { refresh = savedToken };
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);
if (response.IsSuccessStatusCode)
{
string responseContent = await response.Content.ReadAsStringAsync();
if (IsJson(responseContent) && HasJsonData(responseContent))
{
using var doc = JsonDocument.Parse(responseContent);
var root = doc.RootElement;
// Buscar nuevo token en la respuesta (ajustar según tu API)
string newToken = "";
if (root.TryGetProperty("access", out var accessProp))
{
newToken = accessProp.GetString() ?? "";
}
else if (root.TryGetProperty("token", out var tokenProp))
{
newToken = tokenProp.GetString() ?? "";
}
// Buscar nuevo refresh token en la respuesta (ajustar según tu API)
string newRefreshToken = "";
if (root.TryGetProperty("refresh", out var refreshProp))
{
newRefreshToken = refreshProp.GetString() ?? "";
}
else if (root.TryGetProperty("refresh_token", out var refreshtokenProp))
{
newRefreshToken = refreshtokenProp.GetString() ?? "";
}
if (!string.IsNullOrEmpty(newToken) && !string.IsNullOrEmpty(newRefreshToken))
{
// Guardar nuevo token
Globales.accesToken = newToken;
// Guardar nuevo refresh token
Globales.refresToken = newRefreshToken;
// Actualizar headers
SetAuthorizationBearer(newToken, newRefreshToken);
return true;
}
}
}
return false;
}
catch (Exception ex)
{
Globales.logger.LogError("Error al intentar refresh token", ex);
return false;
}
finally
{
// Restaurar el token actual si el refresh falló
if (!string.IsNullOrEmpty(_currentToken) && !string.IsNullOrEmpty(_currentRefreshToken))
{
SetAuthorizationBearer(_currentToken, _currentRefreshToken);
}
}
}
private async Task EnsureTokenAsync()
{
// Si ya tenemos token en memoria, usarlo
if (!string.IsNullOrEmpty(_currentToken) && !string.IsNullOrEmpty(_currentRefreshToken))
{
SetAuthorizationBearer(_currentToken, _currentRefreshToken);
return;
}
// Intentar obtener token guardado
string? savedToken = Globales.accesToken;
string? savedRefreshToken = Globales.refresToken;
if (!string.IsNullOrEmpty(savedToken) && !string.IsNullOrEmpty(savedRefreshToken))
{
SetAuthorizationBearer(savedToken, savedRefreshToken);
return;
}
throw new InvalidOperationException("No hay token disponible. Inicie sesión primero.");
}
private async Task<string> SendWithRetriesAsync(Func<Task<HttpResponseMessage>> sendFunc, bool requireAuth = true)
{
int attempts = 0;
while (true)
{
attempts++;
try
{
// Asegurar que tenemos token antes de enviar (solo si requiere auth)
if (requireAuth)
await EnsureTokenAsync();
var response = await sendFunc();
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();
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)
{
return CrearJsonError(408, "Tiempo de espera agotado");
}
catch (HttpRequestException ex)
{
return CrearJsonError(503, $"Error de red: {ex.Message}");
}
catch (Exception ex)
{
return CrearJsonError(500, $"Error inesperado: {ex.Message}");
}
}
}
public async Task<string> GetAsync(string url)
{
return await SendWithRetriesAsync(() => _httpClient.GetAsync(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));
}
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));
}
public async Task<string> DeleteAsync(string url)
{
return await SendWithRetriesAsync(() => _httpClient.DeleteAsync(url));
}
// Método para enviar archivo y JSON juntos en multipart/form-data
public async Task<string> PostMultipartAsync(string url, string filePath, object jsonData, string fileFieldName = "archivo", string jsonFieldName = "data")
{
if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath))
throw new ArgumentException("El archivo no existe o la ruta es inválida.", nameof(filePath));
using var form = new MultipartFormDataContent();
// Archivo
var fileStream = File.OpenRead(filePath);
var fileContent = new StreamContent(fileStream);
form.Add(fileContent, fileFieldName, Path.GetFileName(filePath));
// JSON
var jsonString = JsonSerializer.Serialize(jsonData);
var jsonContent = new StringContent(jsonString, System.Text.Encoding.UTF8, "application/json");
form.Add(jsonContent, jsonFieldName);
return await SendWithRetriesAsync(() => _httpClient.PostAsync(url, form));
}
public async Task<string> PostMultipartAsync(string url, string filePath, string organizacion, string pedimento,
string documentType,
string fuente,
string fileFieldName = "archivo"
)
{
if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath))
throw new ArgumentException("El archivo no existe o la ruta es inválida.", nameof(filePath));
using var form = new MultipartFormDataContent();
// Archivo
var fileStream = File.OpenRead(filePath);
var fileContent = new StreamContent(fileStream);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
form.Add(fileContent, fileFieldName, Path.GetFileName(filePath));
// Campos de texto
form.Add(new StringContent(organizacion), "organizacion");
form.Add(new StringContent(pedimento), "pedimento");
form.Add(new StringContent(documentType), "document_type");
form.Add(new StringContent(fuente), "fuente");
return await SendWithRetriesAsync(() => _httpClient.PostAsync(url, form));
}
//public async Task<string> PostMultipartZipAsync(string url, string token, byte[] zipBytes, string zipFileName, string fileFieldName = "archivos")
//{
// if (zipBytes == null || zipBytes.Length == 0)
// throw new ArgumentException("El ZIP está vacío o no es válido.", nameof(zipBytes));
// SetAuthorization(token);
// using var form = new MultipartFormDataContent();
// // Convertimos el byte[] a contenido del archivo
// var fileContent = new ByteArrayContent(zipBytes);
// fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/zip");
// // Nombre del archivo en el multipart
// form.Add(fileContent, fileFieldName, zipFileName);
// // Campos de texto
// //form.Add(new StringContent(pedimento), "pedimento");
// return await SendWithRetriesAsync(() => _httpClient.PostAsync(url, form));
//}
public async Task<string> PostMultipartZipAsync(string url, string filePath, string zipFileName, Dictionary<string, string> metadatos, string fileFieldName = "archivos")
{
if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath))
throw new ArgumentException("El archivo no existe o la ruta es inválida.", nameof(filePath));
using var form = new MultipartFormDataContent();
// Archivo
var fileStream = File.OpenRead(filePath);
var fileContent = new StreamContent(fileStream);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
form.Add(fileContent, fileFieldName, Path.GetFileName(filePath));
if (metadatos != null)
{
foreach (var kvp in metadatos)
{
form.Add(new StringContent(kvp.Value), kvp.Key);
}
}
// Campos de texto
//form.Add(new StringContent(organizacion), "organizacion");
//form.Add(new StringContent(pedimento), "pedimento");
//form.Add(new StringContent(documentType), "document_type");
//form.Add(new StringContent(fuente), "fuente");
return await SendWithRetriesAsync(() => _httpClient.PostAsync(url, 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);
}
// Nueva sobrecarga que acepta cualquier objeto
public async Task<string> PostJsonWithoutAuthAsync(string url, object data)
{
var jsonContent = JsonSerializer.Serialize(data);
return await PostJsonWithoutAuthAsync(url, jsonContent);
}
public bool IsJson(string response)
{
if (string.IsNullOrWhiteSpace(response)) return false;
try
{
using var doc = JsonDocument.Parse(response);
return true;
}
catch (JsonException)
{
return false;
}
}
public bool HasJsonData(string response)
{
if (string.IsNullOrWhiteSpace(response)) return false;
try
{
using var doc = JsonDocument.Parse(response);
var root = doc.RootElement;
if (root.ValueKind == JsonValueKind.Object)
{
return root.EnumerateObject().Any();
}
else if (root.ValueKind == JsonValueKind.Array)
{
return root.GetArrayLength() > 0;
}
else
{
return false;
}
}
catch (JsonException)
{
return false;
}
}
// Método para deserializar el JSON a un objeto genérico
public T? TryParseJson<T>(string response) where T : class
{
if (string.IsNullOrWhiteSpace(response)) return null;
try
{
return JsonSerializer.Deserialize<T>(response);
}
catch (JsonException ex)
{
Globales.logger.LogError($"Error al deserializar JSON: {ex.Message}");
return null;
}
}
private static string CrearJsonError(int status, string message)
{
return $$"""
{
"success": false,
"status": {{status}},
"message": "{{message.Replace("\"", "\\\"")}}"
}
""";
}
public void Dispose()
{
_httpClient.Dispose();
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EFCDesk.Classes
{
internal class ArchivosProcesados
{
public int IdArchivo { get; set; }
public string NombreArchivo { get; set; }
public string RutaArchivo { get; set; }
public string Estado { get; set; }
public DateTime FechaProcesado { get; set; }
public string FolderExpedinete { get; set; }
public string ArchivoZip { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Win32;
// using System.Deployment.Application;
using System.Reflection;
namespace EFCDesk.Classes
{
public static class AutoStartHelper
{
private const string AppName = "EFC-DESK";
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace EFCDesk.Classes
{
public static class AutoUpdateAppHelper
{
public static void CheckForUpdatesManually()
{
Version? installedVersion = Assembly.GetExecutingAssembly().GetName().Version;
Version latestVersion = GetLatestVersionFromConfigFile();
if (installedVersion != null && latestVersion > installedVersion)
{
MessageBox.Show($"Hay una nueva versión disponible ({latestVersion}). " +
"Por favor, instálela desde su medio de almacenamiento.");
}
}
private static Version GetLatestVersionFromConfigFile()
{
// Leer de un archivo de configuración incluido en el USB/CD
string versionFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "latest_version.txt");
if (File.Exists(versionFile))
{
return new Version(File.ReadAllText(versionFile));
}
return Assembly.GetExecutingAssembly().GetName().Version ?? new Version(1, 0, 0, 0);
}
public static void LogInstallation()
{
string logPath = Path.Combine(Environment.GetFolderPath(
Environment.SpecialFolder.ApplicationData),
"EFCDeskv2", "install_log.txt");
File.AppendAllText(logPath,
$"Instalado: {DateTime.Now} | Versión: {Assembly.GetExecutingAssembly().GetName().Version}\n");
}
}
}

View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EFCDesk.Classes
{
public class ConfiguracionExpediente
{
public bool IniciarExpedienteWindows { get; set; }
public string? FolderParaGenerarExpediente { get; set; }
public bool DepurarExpediente { get; set; }
public string? FolderParaDepurarExpediente { get; set; }
public bool UsarEstructuraPedimentosWinsaai { get; set; }
public bool UsarExpedienteLogistico { get; set; }
public string ServidorFTP { get; set; }
public string UsuarioFTP { get; set; }
public string PasswordFTP { get; set; }
public bool ModoPasivoFTP { get; set; }
public int PuertoFTP { get; set; }
public ConfiguracionExpediente() {
ServidorFTP = "efc.aduanasoft.com";
UsuarioFTP = "expElec";
PasswordFTP = "8lackSta8";
ModoPasivoFTP = true;
PuertoFTP = 20;
}
// Método para guardar la configuración en la base de datos
public void GuardarEnBaseDeDatos(SQLiteHelper sqliteHelper)
{
try
{
using (var connection = sqliteHelper.GetConnection())
{
connection.Open();
string insertOrUpdateQuery = @"
INSERT OR REPLACE INTO Configuracion (
Id, IniciarExpedienteWindows, FolderParaGenerarExpediente, DepurarExpediente,
FolderParaDepurarExpediente, UsarEstructuraPedimentosWinsaai, UsarExpedienteLogistico, ServidorFTP, UsuarioFTP, PasswordFTP, PuertoFTP , ModoPasivoFTP
) VALUES (
1, @IniciarExpedienteWindows, @FolderParaGenerarExpediente, @DepurarExpediente,
@FolderParaDepurarExpediente, @UsarEstructuraPedimentosWinsaai, @UsarExpedienteLogistico, @ServidorFTP, @UsuarioFTP, @PasswordFTP, @PuertoFTP, @ModoPasivoFTP
);";
using (var command = new SQLiteCommand(insertOrUpdateQuery, connection))
{
command.Parameters.AddWithValue("@IniciarExpedienteWindows", IniciarExpedienteWindows ? 1 : 0);
command.Parameters.AddWithValue("@FolderParaGenerarExpediente", FolderParaGenerarExpediente ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@DepurarExpediente", DepurarExpediente ? 1 : 0);
command.Parameters.AddWithValue("@FolderParaDepurarExpediente", FolderParaDepurarExpediente ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@UsarEstructuraPedimentosWinsaai", UsarEstructuraPedimentosWinsaai ? 1 : 0);
command.Parameters.AddWithValue("@UsarExpedienteLogistico", UsarExpedienteLogistico ? 1 : 0);
command.Parameters.AddWithValue("@ServidorFTP", ServidorFTP ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@UsuarioFTP", UsuarioFTP ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@PasswordFTP", PasswordFTP ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@PuertoFTP", PuertoFTP == 0 ? (object)DBNull.Value : PuertoFTP);
command.Parameters.AddWithValue("@ModoPasivoFTP", ModoPasivoFTP ? 1 : 0);
command.ExecuteNonQuery();
}
}
} catch(Exception ex) {
MessageBox.Show(ex.Message);
}
}
// Método para obtener la configuración desde la base de datos
public static ConfiguracionExpediente ObtenerDesdeBaseDeDatos(SQLiteHelper sqliteHelper)
{
using (var connection = sqliteHelper.GetConnection())
{
connection.Open();
string selectQuery = "SELECT * FROM Configuracion WHERE Id = 1;";
using (var command = new SQLiteCommand(selectQuery, connection))
{
using (var reader = command.ExecuteReader())
{
if (reader.Read())
{
return new ConfiguracionExpediente
{
IniciarExpedienteWindows = reader.GetInt32(reader.GetOrdinal("IniciarExpedienteWindows")) == 1,
FolderParaGenerarExpediente = reader["FolderParaGenerarExpediente"] as string,
DepurarExpediente = reader.IsDBNull(reader.GetOrdinal("DepurarExpediente")) ? false : (reader.GetInt32(reader.GetOrdinal("DepurarExpediente")) == 1),
FolderParaDepurarExpediente = reader["FolderParaDepurarExpediente"] as string,
UsarEstructuraPedimentosWinsaai = reader.IsDBNull(reader.GetOrdinal("UsarEstructuraPedimentosWinsaai")) ? false : (reader.GetInt32(reader.GetOrdinal("UsarEstructuraPedimentosWinsaai")) == 1),
UsarExpedienteLogistico = reader.IsDBNull(reader.GetOrdinal("UsarExpedienteLogistico")) ? false : (reader.GetInt32(reader.GetOrdinal("UsarExpedienteLogistico")) == 1),
ServidorFTP = reader["ServidorFTP"] as string,
UsuarioFTP = reader["UsuarioFTP"] as string,
PasswordFTP = reader["PasswordFTP"] as string,
ModoPasivoFTP = reader.IsDBNull(reader.GetOrdinal("ModoPasivoFTP")) ? false : (reader.GetInt32(reader.GetOrdinal("ModoPasivoFTP")) == 1),
PuertoFTP = reader.IsDBNull(reader.GetOrdinal("PuertoFTP")) ? 21 : reader.GetInt32(reader.GetOrdinal("PuertoFTP"))
};
}
}
}
}
// Si no hay registro, devolver una nueva instancia con valores predeterminados
return new ConfiguracionExpediente();
}
}
}

View File

@@ -0,0 +1,225 @@
using EFCDesk.Utils;
using Microsoft.VisualBasic.ApplicationServices;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Xml;
namespace EFCDesk.Classes
{
public class ConfiguracionJSON
{
private string? _passwordExpEncriptado;
public string? UsuarioExp { get; set; }
[JsonIgnore]
public string? PasswordExp
{
get
{
if (string.IsNullOrEmpty(_passwordExpEncriptado))
return null;
string pwd = Util.Desencriptar(_passwordExpEncriptado);
// Si no se pudo desencriptar → config corrupta
if (string.IsNullOrEmpty(pwd))
{
LimpiarCredenciales();
return null;
}
return pwd;
}
set
{
if (string.IsNullOrWhiteSpace(value))
{
_passwordExpEncriptado = null;
}
else
{
_passwordExpEncriptado = Util.Encriptar(value);
}
}
}
public string? DominioExp { get; set; }
public bool ProxyNingunoGenerico { get; set; }
public string? ServidorProxyGenerico { get; set; }
public int? PuertoProxyGenerico { get; set; }
public string? UsuarioProxyGenerico { get; set; }
public string? PasswordProxyGenerico { get; set; }
public bool AuthProxyGenerico { get; set; }
public bool ProxyFTPNinguno { get; set; }
public string? ServidorProxyFTP { get; set; }
public int? PuertoProxyFTP { get; set; }
public string? UsuarioProxyFTP { get; set; }
public string? PasswordProxyFTP { get; set; }
public bool AuthProxyFTP { get; set; }
public int idUsuarioExp { get; set; }
public string? FolderExpediente { get; set; }
public string MacAddress { get; set; }
public int ExpWinsaai { get; set; }
public int ExpLogistico { get; set; }
[JsonPropertyName("PasswordExp")]
public string? PasswordExpEncriptado
{
get => _passwordExpEncriptado;
set => _passwordExpEncriptado = value;
}
public ConfiguracionJSON() {
MacAddress = NetworkInterface.GetAllNetworkInterfaces()
.Where(nic => nic.OperationalStatus == OperationalStatus.Up && nic.NetworkInterfaceType != NetworkInterfaceType.Loopback)
.Select(nic => nic.GetPhysicalAddress().ToString())
.FirstOrDefault();
DominioExp = "";
}
// Método para cargar la configuración desde un archivo JSON
public static ConfiguracionJSON LoadFromJson()
{
string pathConfigJson = AppDomain.CurrentDomain.BaseDirectory+"appSettings.json";
if (!File.Exists(pathConfigJson))
{
var defaultConfig = new ConfiguracionJSON();
string json = JsonSerializer.Serialize(defaultConfig, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(pathConfigJson, json);
}
string contenidoJson = File.ReadAllText(pathConfigJson);
return JsonSerializer.Deserialize<ConfiguracionJSON>(contenidoJson) ?? new ConfiguracionJSON();
}
public void SaveToJson()
{
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "appSettings.json");
string json = JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(path, json);
}
// Limpieza automática
public void LimpiarCredenciales()
{
UsuarioExp = null;
PasswordExpEncriptado = null;
SaveToJson();
}
public string[] Login()
{
string [] result = new string[2];
result[0] = "0";
result[1] = "Error de conexion";
try
{
string cadena = UsuarioExp + "|" + PasswordExp;
dynamic root = Consulta(DominioExp+"/api/auth/" + cadena);
if (root is XmlElement)
{
XmlNodeList nodes = root.SelectNodes("/xml/item");
foreach (XmlNode node in nodes)
{
result[0] = node["login"].InnerText;
result[1] = node["mensaje"].InnerText;
}
if (result[0] != "0")
{
string path = FolderExpediente;
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
return result;
}
}
catch (Exception e)
{
MessageBox.Show(e.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return result;
}
public dynamic Consulta(string url)
{
WebClient wc = new WebClient();
if (ProxyNingunoGenerico)
{
dynamic wp = Proxy();
if (wp is WebProxy)
{
wc.Proxy = wp;
}
else
{
return wp;
}
}
else
{
IWebProxy proxy = WebRequest.GetSystemWebProxy();
proxy.Credentials = CredentialCache.DefaultCredentials;
wc.Proxy = proxy;
}
try
{
MemoryStream ms = new MemoryStream(wc.DownloadData(url));
XmlTextReader rdr = new XmlTextReader(ms);
XmlDocument doc1 = new XmlDocument();
doc1.Load(rdr);
XmlElement root = doc1.DocumentElement;
return root;
}
catch (Exception e)
{
string sNombreClase = MethodBase.GetCurrentMethod()?.DeclaringType?.Name;
string currentMethod = MethodBase.GetCurrentMethod().Name;
//MyClass.LogErrores(e, sNombreClase, currentMethod);
return e;
}
}
public dynamic Proxy()
{
try
{
WebProxy myproxy = new WebProxy(ServidorProxyGenerico + ":" + PuertoProxyGenerico, true);
myproxy.BypassProxyOnLocal = false;
if (AuthProxyGenerico)
{
NetworkCredential credential = new NetworkCredential(UsuarioProxyGenerico, PasswordProxyGenerico);
myproxy.Credentials = credential;
}
return myproxy;
}
catch (Exception e)
{
string sNombreClase = MethodBase.GetCurrentMethod()?.DeclaringType?.Name;
string currentMethod = MethodBase.GetCurrentMethod().Name;
//MyClass.LogErrores(e, sNombreClase, currentMethod);
return e;
}
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EFCDesk.Classes
{
public class DatosActualizadosEventArgs : EventArgs
{
public string CarpetaRaiz
{
get;
}
public DatosActualizadosEventArgs(string carpetaRaiz)
{
CarpetaRaiz = carpetaRaiz;
}
}
}

50
Classes/ErrorLog.cs Normal file
View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EFCDesk.Classes
{
internal class ErrorLog
{
public int Id { get; set; }
public string LineaError { get; set; }
public string Error { get; set; }
public string Comentarios { get; set; }
public string Fecha { get; set; }
public List<ErrorLog> GetRegistrosErrorLog(SQLiteHelper sqliteHelper)
{
var lista = new List<ErrorLog>();
using (var connection = sqliteHelper.GetConnection())
{
connection.Open();
string query = "SELECT Id, LineaError, Error, Comentario, Fecha FROM ErrorLog;";
using (var command = new SQLiteCommand(query, connection))
{
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
lista.Add(new ErrorLog
{
Id = reader.GetInt32(0),
LineaError = reader.IsDBNull(1) ? "" : reader.GetString(1),
Error = reader.IsDBNull(2) ? "" : reader.GetString(2),
Comentarios = reader.IsDBNull(3) ? "" : reader.GetString(3),
Fecha = reader.IsDBNull(4) ? "" : reader.GetString(4)
});
}
}
}
}
return lista;
}
}
}

62
Classes/FileUtil.cs Normal file
View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
namespace EFCDesk.Classes
{
public static class FileUtil
{
public static bool IsFileReady(string path)
{
try
{
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None);
return stream.Length >= 0; // si abre sin excepción, está listo
}
catch
{
return false;
}
}
public static bool WaitAllFilesReady(IEnumerable<string> files, int perFileTimeoutSeconds)
{
var deadline = DateTime.UtcNow.AddSeconds(perFileTimeoutSeconds);
var pendientes = new HashSet<string>(files);
while (pendientes.Count > 0 && DateTime.UtcNow < deadline)
{
var listos = new List<string>();
foreach (var f in pendientes)
{
if (File.Exists(f) && IsFileReady(f))
listos.Add(f);
}
foreach (var ok in listos)
pendientes.Remove(ok);
if (pendientes.Count == 0) return true;
Thread.Sleep(500);
}
return pendientes.Count == 0;
}
public static string? TryGetSHA1(string path)
{
try
{
using var fs = File.OpenRead(path);
using var sha1 = SHA1.Create();
var hash = sha1.ComputeHash(fs);
return BitConverter.ToString(hash).Replace("-", "");
}
catch
{
return null;
}
}
}
}

34
Classes/General.cs Normal file
View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace EFCDesk.Classes
{
public class General
{
public static string CodificarBase64(string cadena)
{
string resultado = cadena;
string[] arrayLetras = { "J", "G", "V", "S" };
int limite = arrayLetras.Length;
Random random = new Random();
int num = random.Next(0, limite);
for (var i = 0; i <= num; i++)
{
resultado = Base64Encode(resultado);
}
resultado = resultado + "+" + arrayLetras[num];
resultado = Base64Encode(resultado);
return resultado;
}
public static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
}
}

364
Classes/Helpers.cs Normal file
View File

@@ -0,0 +1,364 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
namespace EFCDesk.Classes
{
public static class Helpers
{
// Lista de extensiones permitidas
private static readonly HashSet<string> ExtensionesPermitidas = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".jpg",
".jpeg",
".png",
".gif",
".bmp",
".pdf",
".txt",
".csv"
};
// Lista de extensiones prohibidas (opcional, para mayor seguridad)
private static readonly HashSet<string> ExtensionesProhibidas = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".exe",
".bat",
".cmd",
".msi",
".sh",
".js",
".vbs",
".ps1",
".jar",
".com",
".msp",
".scr",
".docm",
".xlsm",
".pptm"
};
public static bool ValidarArchivo(string rutaArchivo)
{
string extension = Path.GetExtension(rutaArchivo);
// Revisar primero si está en la lista prohibida
if (ExtensionesProhibidas.Contains(extension))
{
return false;
}
//// Solo permitir si está en la lista de permitidas
//return ExtensionesPermitidas.Contains(extension);
return true;
}
/// <summary>
/// Obtiene el tamaño de un archivo en bytes.
/// </summary>
/// <param name="rutaArchivo">Ruta completa del archivo.</param>
/// <returns>Tamaño en bytes, o -1 si el archivo no existe.</returns>
public static long ObtenerTamanoEnBytes(string rutaArchivo)
{
if (!File.Exists(rutaArchivo))
return -1;
FileInfo info = new FileInfo(rutaArchivo);
return info.Length;
}
/// <summary>
/// Obtiene el tamaño de un archivo en formato legible (KB, MB, GB).
/// </summary>
/// <param name="rutaArchivo">Ruta completa del archivo.</param>
/// <returns>Cadena con el tamaño formateado, o mensaje de error.</returns>
public static string ObtenerTamanoFormateado(string rutaArchivo)
{
if (!File.Exists(rutaArchivo))
return "El archivo no existe";
long bytes = new FileInfo(rutaArchivo).Length;
string[] sufijos = { "B", "KB", "MB", "GB", "TB" };
int indice = 0;
double tamaño = bytes;
while (tamaño >= 1024 && indice < sufijos.Length - 1)
{
tamaño /= 1024;
indice++;
}
return $"{tamaño:0.##} {sufijos[indice]}";
}
/// <summary>
/// Obtiene la url del endpoint para obtener el pedimento.
/// </summary>
/// <returns>Cadena del endpoint.</returns>
public static string ObtenerEndpointPedimentos()
{
return @"/api/v1/customs/pedimentos/?pedimento_app=";
}
/// <summary>
/// Obtiene la url del endpoint para obtener el documento.
/// </summary>
/// <returns>Cadena del endpoint.</returns>
public static string ObtenerEndpointDocumentos()
{
return @"/api/v1/record/documents/";
}
public static string CrearEndpointpedimentoDesk()
{
return @"/api/v1/customs/pedimentos/bulk-create-pedimento_desk/";
}
/// <summary>
/// Espera hasta que todos los archivos dentro de la carpeta estén libres (no en uso)
/// </summary>
public static bool EsperarArchivosListos(string carpetaRaiz, int timeoutSegundos = 30)
{
var archivos = Directory.GetFiles(carpetaRaiz, "*.*", SearchOption.AllDirectories);
var inicio = DateTime.Now;
while ((DateTime.Now - inicio).TotalSeconds < timeoutSegundos)
{
bool todosListos = true;
foreach (var archivo in archivos)
{
try
{
using (FileStream stream = new FileStream(archivo, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
// Archivo disponible
}
}
catch (IOException)
{
todosListos = false;
break;
}
}
if (todosListos)
return true;
Thread.Sleep(500);
}
return false;
}
/// <summary>
/// Obtiene la carpeta raíz según aplicaWinsaii
/// </summary>
public static (string RutaRaiz, string NombreRaiz) ObtenerCarpetaRaiz(string ruta)
{
SQLiteHelper sqliteHelper = new SQLiteHelper(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "historico.db"));
ConfiguracionExpediente config = ConfiguracionExpediente.ObtenerDesdeBaseDeDatos(sqliteHelper);
bool aplicaWinsaii = config.UsarEstructuraPedimentosWinsaai;
string? carpetaActual = EsCarpeta(ruta) ? ruta : Path.GetDirectoryName(ruta);
string carpetaBase = Path.GetFullPath(config.FolderParaGenerarExpediente);
carpetaActual = Path.GetFullPath(carpetaActual);
string[] segmentosBase = carpetaBase.Trim(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar);
string[] segmentosActual = carpetaActual.Trim(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar);
int nivelDeseado = aplicaWinsaii ? segmentosBase.Length + 2 : segmentosBase.Length + 1;
if (segmentosActual.Length > nivelDeseado)
segmentosActual = segmentosActual.Take(nivelDeseado).ToArray();
string carpetaRaiz = string.Join(Path.DirectorySeparatorChar.ToString(), segmentosActual);
if (!Path.IsPathRooted(carpetaRaiz))
carpetaRaiz = Path.DirectorySeparatorChar + carpetaRaiz;
string nombreRaiz = Path.GetFileName(carpetaRaiz);
return (carpetaRaiz, nombreRaiz);
}
public static bool EsCarpeta(string ruta)
{
return Directory.Exists(ruta);
}
public static string DominioExpedienteElectronico()
{
string efc = Properties.Settings.Default.urlEFC as string;
return efc;
}
public static bool EsValidaNomeclaturaCarpeta(string pathCarpeta)
{
var nombre = Path.GetFileName(pathCarpeta);
string pattern = @"^\d{2}-\d{4}-\d{7}$|^\d{2}-\d{2}-\d{4}-\d{7}$";
bool esValido = System.Text.RegularExpressions.Regex.IsMatch(nombre, pattern);
if (!esValido)
{
return false;
}
return true;
}
public static bool EsValidaNomeclaturaSAAIM3(string pathArchivo)
{
var nombre = Path.GetFileName(pathArchivo);
string pattern = @"^[m|M]\d{7}\.\d{3}$";
bool esValido = System.Text.RegularExpressions.Regex.IsMatch(nombre, pattern);
if (!esValido)
{
return false;
}
return true;
}
public static bool EsArchivoXMl(string rutaArchivo)
{
try
{
string extension = Path.GetExtension(rutaArchivo);
if (!extension.Equals(".xml", StringComparison.OrdinalIgnoreCase))
return false;
XDocument xdoc = XDocument.Load(rutaArchivo);
}
catch (XmlException exception)
{
Globales.logger.LogError("Helpers - EsArchivoXML", exception);
return false;
}
catch (Exception ex)
{
Globales.logger.LogError("Helpers - EsArchivoXML", ex);
return false;
}
return true;
}
public static byte[] CrearZipEnMemoria(string nombreArchivo, byte[] contenido)
{
try
{
using (var memoryStream = new MemoryStream())
{
using (var zip = new System.IO.Compression.ZipArchive(memoryStream, System.IO.Compression.ZipArchiveMode.Create, leaveOpen: true))
{
// Aquí puedes agregar archivos al archivo ZIP si es necesario
var entry = zip.CreateEntry(nombreArchivo);
using (var entryStream = entry.Open())
{
entryStream.Write(contenido, 0, contenido.Length);
}
}
return memoryStream.ToArray();
}
}
catch (Exception ex)
{
// Manejo de la excepción (puedes registrar el error o lanzar una excepción personalizada)
Globales.logger.LogError("Helpers", ex);
return Array.Empty<byte>();
}
}
public static string CalcularHashMD5(string rutaArchivo)
{
using (var md5 = MD5.Create())
{
using (var stream = File.OpenRead(rutaArchivo))
{
var hashBytes = md5.ComputeHash(stream);
StringBuilder sb = new StringBuilder();
foreach (var b in hashBytes)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
}
}
/// <summary>
/// Crea un ZIP de un solo archivo en una carpeta temporal única.
/// </summary>
/// <param name="rutaArchivo">Archivo a comprimir.</param>
/// <param name="nombreArchivoZip">Nombre del ZIP que se generará.</param>
/// <param name="autoLimpiar">Si es true, borra la carpeta temporal automáticamente después de crear el ZIP.</param>
/// <returns>Ruta completa del ZIP generado.</returns>
public static string CrearZipArchivoEnTemp(string rutaArchivo, string nombreArchivoZip, bool autoLimpiar = false)
{
// Obtiene la ruta raíz de la carpeta temporal del sistema
string tempRoot = Path.Combine(Path.GetTempPath(), "temp-efc-desk");
//string tempRoot = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),"tempEFCDesk");
// Crea una subcarpeta temporal única
string tempFolder = Path.Combine(tempRoot, Guid.NewGuid().ToString().ToLower().Replace("-","").Substring(0,8));
Directory.CreateDirectory(tempFolder);
// Ruta completa del archivo ZIP dentro de la carpeta temporal
string zipPath = Path.Combine(tempFolder, nombreArchivoZip);
try
{
// Verifica que el archivo exista
if (!File.Exists(rutaArchivo))
{
throw new FileNotFoundException($"El archivo no existe: {rutaArchivo}");
}
// Crea el ZIP y agrega el archivo
using (var zip = ZipFile.Open(zipPath, ZipArchiveMode.Create))
{
zip.CreateEntryFromFile(rutaArchivo, Path.GetFileName(rutaArchivo));
}
return zipPath;
}
catch (Exception ex)
{
Globales.logger.LogError("Helpers - CrearZipArchivoEnTemp", ex);
return "";
}
finally
{
if (autoLimpiar)
{
try
{
// Espera un momento para asegurarse de que el ZIP no esté bloqueado
System.Threading.Thread.Sleep(3000);
// Borra toda la carpeta temporal
if (Directory.Exists(tempFolder))
{
Directory.Delete(tempFolder, true);
}
}
catch (Exception ex)
{
Globales.logger.LogError("Helpers - No se pudo limpiar la carpeta temporal", ex);
}
}
}
}
}
}

139
Classes/JsonHelper.cs Normal file
View File

@@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json;
namespace EFCDesk.Classes
{
public static class JsonHelper
{
//public static bool TryGetProperty(JsonElement element, string propertyName, out JsonElement value)
//{
// if (element.ValueKind == JsonValueKind.Object &&
// element.TryGetProperty(propertyName, out value))
// {
// return true;
// }
// value = default;
// return false;
//}
//public static string? GetString(JsonElement element, string propertyName)
//{
// return TryGetProperty(element, propertyName, out var value) && value.ValueKind == JsonValueKind.String
// ? value.GetString()
// : null;
//}
//public static bool? GetBool(JsonElement element, string propertyName)
//{
// return TryGetProperty(element, propertyName, out var value) && value.ValueKind == JsonValueKind.True || value.ValueKind == JsonValueKind.False
// ? value.GetBoolean()
// : null;
//}
//public static int? GetInt(JsonElement element, string propertyName)
//{
// return TryGetProperty(element, propertyName, out var value) && value.ValueKind == JsonValueKind.Number
// ? value.GetInt32()
// : null;
//}
//public static JsonElement? GetArray(JsonElement element, string propertyName)
//{
// return TryGetProperty(element, propertyName, out var value) && value.ValueKind == JsonValueKind.Array
// ? value
// : null;
//}
//public static JsonElement? GetObject(JsonElement element, string propertyName)
//{
// return TryGetProperty(element, propertyName, out var value) && value.ValueKind == JsonValueKind.Object
// ? value
// : null;
//}
// ----------------------------
// PUBLIC METHODS
// ----------------------------
public static Dictionary<string, object?>? ToDictionary(JsonElement element)
{
if (element.ValueKind != JsonValueKind.Object)
return null;
var dict = new Dictionary<string, object?>();
foreach (var prop in element.EnumerateObject())
{
dict[prop.Name] = ConvertElement(prop.Value);
}
return dict;
}
public static List<object?>? ToList(JsonElement element)
{
if (element.ValueKind != JsonValueKind.Array)
return null;
var list = new List<object?>();
foreach (var item in element.EnumerateArray())
{
list.Add(ConvertElement(item));
}
return list;
}
public static object? Get(JsonElement element, string key)
{
return element.TryGetProperty(key, out var value)
? ConvertElement(value)
: null;
}
// ----------------------------
// INTERNAL CONVERTER
// ----------------------------
private static object? ConvertElement(JsonElement element)
{
return element.ValueKind switch
{
JsonValueKind.String => element.GetString(),
JsonValueKind.Number => element.TryGetInt64(out long l) ? l : element.GetDouble(),
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Object => ToDictionary(element),
JsonValueKind.Array => ToList(element),
JsonValueKind.Null => null,
JsonValueKind.Undefined => null,
_ => element.ToString()
};
}
public static bool TryParse(string json, out JsonElement root)
{
root = default;
if (string.IsNullOrWhiteSpace(json))
return false;
try
{
var doc = JsonDocument.Parse(json);
root = doc.RootElement.Clone(); // Clonamos para que no se deseche
return true;
}
catch
{
return false;
}
}
}
}

990
Classes/MonitorCarpetas.cs Normal file
View File

@@ -0,0 +1,990 @@
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();
}
}
}

98
Classes/ProcessLogger.cs Normal file
View File

@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EFCDesk.Classes
{
public class ProcessLogger
{
private readonly string _logDirectory;
private readonly long _maxFileSizeBytes;
private readonly object _lock = new object();
private DateTime _currentLogDate;
private string _currentLogFilePath;
public ProcessLogger(string logDirectory = "Logs", string? logFileName = null, long maxFileSizeBytes = 1 * 1024 * 1024) // 1 MB
{
_logDirectory = logDirectory;
_maxFileSizeBytes = maxFileSizeBytes;
_currentLogDate = DateTime.Now;
if (!Directory.Exists(_logDirectory))
{
Directory.CreateDirectory(_logDirectory);
}
_currentLogFilePath = GetLogFilePath(_currentLogDate, logFileName);
}
public void LogInfo(string message) => Log("INFO", message);
public void LogWarning(string message) => Log("WARNING", message);
public void LogError(string message, Exception? ex = null)
{
string fullMessage = message;
if (ex != null)
{
fullMessage += $"{Environment.NewLine}Exception: {ex.Message}{Environment.NewLine}StackTrace: {ex.StackTrace}";
}
Log("ERROR", fullMessage);
}
private void Log(string level, string message)
{
string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] {message}";
lock (_lock)
{
RotateLogFileIfNeeded();
File.AppendAllText(_currentLogFilePath, logEntry + Environment.NewLine);
}
}
private void RotateLogFileIfNeeded()
{
DateTime now = DateTime.Now;
// Cambio de día: nuevo archivo
if (now.Date != _currentLogDate.Date)
{
_currentLogDate = now;
_currentLogFilePath = GetLogFilePath(_currentLogDate);
return;
}
// Excede tamaño máximo: crear nuevo archivo con sufijo incremental
if (File.Exists(_currentLogFilePath) &&
new FileInfo(_currentLogFilePath).Length >= _maxFileSizeBytes)
{
int index = 1;
string newPath;
do
{
newPath = GetLogFilePath(_currentLogDate, null, index++);
} while (File.Exists(newPath));
_currentLogFilePath = newPath;
}
}
private string GetLogFilePath(DateTime date, string? customName = null, int index = 0)
{
string datePart = date.ToString("yyyyMMdd");
string baseName = string.IsNullOrWhiteSpace(customName)
? $"log_{datePart}"
: Path.GetFileNameWithoutExtension(customName);
string extension = ".txt";
string fileName = index == 0
? $"{baseName}{extension}"
: $"{baseName}_{index}{extension}";
return Path.Combine(_logDirectory, fileName);
}
}
}

872
Classes/SQLiteHelper.cs Normal file
View File

@@ -0,0 +1,872 @@
using EFCDesk.Entidades;
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Data.Common;
using System.Data.SQLite;
using System.Drawing;
using System.Drawing.Printing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.TrackBar;
namespace EFCDesk.Classes
{
public class SQLiteHelper
{
private string _connectionString;
public SQLiteHelper(string databasePath)
{
_connectionString = $"Data Source={databasePath};Version=3;Journal Mode=WAL;Pooling=true;Cache Size=10000;Synchronous=OFF;";
InitializeDatabase();
}
private void InitializeDatabase()
{
try
{
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
// Configuración para mejor concurrencia
using (var pragmaCmd = new SQLiteCommand("PRAGMA busy_timeout = 30000;", connection))
{
pragmaCmd.ExecuteNonQuery();
}
string createConfiguracionTableQuery = @"
CREATE TABLE IF NOT EXISTS Configuracion (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
IniciarExpedienteWindows INTEGER NOT NULL DEFAULT 0,
FolderParaGenerarExpediente TEXT,
DepurarExpediente INTEGER,
FolderParaDepurarExpediente TEXT,
UsarEstructuraPedimentosWinsaai INTEGER,
UsarExpedienteLogistico INTEGER,
ServidorFTP TEXT,
UsuarioFTP TEXT,
PasswordFTP TEXT,
PuertoFTP INTEGER,
ModoPasivoFTP INTEGER
);";
string createArchivosProcesadosTableQuery = @"
CREATE TABLE IF NOT EXISTS ArchivosProcesados (
IdArchivo INTEGER PRIMARY KEY AUTOINCREMENT,
NombreArchivo TEXT NOT NULL,
RutaArchivo TEXT NOT NULL UNIQUE,
Estado TEXT NOT NULL DEFAULT 'Pendiente',
FechaProcesado DATETIME DEFAULT NULL,
FolderExpediente TEXT NOT NULL,
CarpetaRaiz TEXT NOT NULL,
HashContenido TEXT, -- opcional
ArchivoZip TEXT NOT NULL
);";
string createErrorLogTableQuery = @"
CREATE TABLE IF NOT EXISTS ErrorLog (
Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
LineaError TEXT,
Clase TEXT,
Metodo TEXT,
Error TEXT,
Comentario TEXT,
Fecha TEXT
);";
string createCarpetasTableQuery = @"
CREATE TABLE IF NOT EXISTS Carpetas (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Ruta TEXT NOT NULL UNIQUE,
Nombre TEXT NOT NULL,
Tipo TEXT NOT NULL, -- Hijo o Nieto
Estado TEXT NOT NULL, -- Nuevo o Procesado
FechaCreado DATETIME DEFAULT NULL
);";
string createIndex = @"
CREATE INDEX IF NOT EXISTS IX_Carpetas_Estado ON Carpetas(Estado);
CREATE INDEX IF NOT EXISTS IX_Archivos_Ruta ON ArchivosProcesados(RutaArchivo);
";
string pragmaModeQuery = "PRAGMA journal_mode=WAL;";
// Ejecutar las consultas para crear las tablas
using (var command = new SQLiteCommand(connection))
{
command.CommandText = createConfiguracionTableQuery;
command.ExecuteNonQuery();
command.CommandText = createArchivosProcesadosTableQuery;
command.ExecuteNonQuery();
command.CommandText = createErrorLogTableQuery;
command.ExecuteNonQuery();
command.CommandText = createCarpetasTableQuery;
command.ExecuteNonQuery();
command.CommandText = createIndex;
command.ExecuteNonQuery();
command.CommandText = pragmaModeQuery;
command.ExecuteNonQuery();
}
}
}
catch (SQLiteException ex)
{
MessageBox.Show(ex.ToString());
}
}
public SQLiteConnection GetConnection()
{
return new SQLiteConnection(_connectionString);
}
private async Task<SQLiteConnection> GetOpenConnectionAsync(int maxRetries = 3)
{
int retryCount = 0;
while (retryCount < maxRetries)
{
try
{
var connection = new SQLiteConnection(_connectionString);
await connection.OpenAsync();
return connection;
}
catch (SQLiteException ex) when (ex.ResultCode == SQLiteErrorCode.Busy && retryCount < maxRetries - 1)
{
retryCount++;
await Task.Delay(100 * retryCount); // Backoff exponencial
}
}
throw new InvalidOperationException("No se pudo establecer conexión después de varios intentos");
}
public List<(string ArchivoZip, string Estado)> ObtenerArchivosZIP()
{
var lista = new List<(string ArchivoZip, string Estado)>();
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
string query = "SELECT DISTINCT ArchivoZip, Estado from ArchivosProcesados;";
using (var command = new SQLiteCommand(query, connection))
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
lista.Add((
reader.GetString(0), // CarpetaCliente
reader.GetString(1) // CarpetaPedimento
//reader.GetString(2) // NombreArchivo
//reader.GetString(3) // FechaProcesado
));
}
}
}
return lista;
}
public List<(string Archivo, string Zip, DateTime Fecha, string Estado)> ObtenerArchivosProcesados(string archivoZIP)
{
var lista = new List<(string Archivo, string Zip, DateTime Fecha, string Estado)>();
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
string query = "SELECT NombreArchivo, ArchivoZip, FechaProcesado, Estado FROM ArchivosProcesados WHERE ArchivoZip = @archivoZIP;";
using (var command = new SQLiteCommand(query, connection))
{
// Agregar el parámetro con su valor
command.Parameters.AddWithValue("@archivoZIP", archivoZIP);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
string nombreArchivo = reader.GetString(0);
string archivoZip = reader.GetString(1);
string Estatus = reader.GetString(3);
DateTime fechaProcesado = DateTime.MinValue; // Valor por defecto
if (!reader.IsDBNull(2))
{
fechaProcesado = reader.GetDateTime(2); // Obtener como DateTime directamente
}
lista.Add((nombreArchivo, archivoZip, fechaProcesado, Estatus));
}
}
}
}
return lista;
}
public void RegistrarArchivoProcesado(string nombreArchivo, string rutaArchivo, string estado, DateTime fechaProcesado, string folderExpediente, string archivoZip)
{
try
{
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
// Verificar si el archivo ya existe en la tabla
string checkQuery = @"
SELECT COUNT(1)
FROM ArchivosProcesados
WHERE NombreArchivo = @NombreArchivo;";
using (var checkCommand = new SQLiteCommand(checkQuery, connection))
{
checkCommand.Parameters.AddWithValue("@NombreArchivo", nombreArchivo);
long count = (long)checkCommand.ExecuteScalar();
if (count > 0)
{
// Si ya existe, no hacer nada
return;
}
}
// Insertar el registro si no existe
string insertQuery = @"
INSERT INTO ArchivosProcesados (NombreArchivo, RutaArchivo, Estado, FechaProcesado, FolderExpediente, ArchivoZip)
VALUES (@NombreArchivo, @RutaArchivo, @Estado, @FechaProcesado, @FolderExpediente, @Archivozip);";
using (var command = new SQLiteCommand(insertQuery, connection))
{
command.Parameters.AddWithValue("@NombreArchivo", nombreArchivo);
command.Parameters.AddWithValue("@RutaArchivo", rutaArchivo);
command.Parameters.AddWithValue("@Estado", estado);
command.Parameters.AddWithValue("@FechaProcesado", fechaProcesado);
command.Parameters.AddWithValue("@FolderExpediente", folderExpediente);
command.Parameters.AddWithValue("@Archivozip", archivoZip);
command.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
public void AgregarRegistroErrorLog(Exception ex, string sClase, string sMetodo, string sComentario = "")
{
string sLineaError = "";
try
{
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
sLineaError = ex.StackTrace.ToString();
int last = sLineaError.LastIndexOf("\\");
sLineaError = sLineaError.Substring(last, sLineaError.Length - last).Replace("\\", "");
DateTime thisDayHour = DateTime.Now;
string sFechaHora = thisDayHour.ToString("yyyyMMddHHmmss");
string sFecha = sFechaHora.Substring(0, 8);
string insertQuery = @"
INSERT INTO ErrorLog (LineaError, Clase, Metodo, Error, Comentario, Fecha)
VALUES (@LineaError, @Clase, @Metodo, @Error, @Comentario, @Fecha);";
using (var command = new SQLiteCommand(insertQuery, connection))
{
command.Parameters.AddWithValue("@LineaError", sLineaError);
command.Parameters.AddWithValue("@Clase", sClase);
command.Parameters.AddWithValue("@Metodo", sMetodo);
command.Parameters.AddWithValue("@Error", ex.Message.ToString());
command.Parameters.AddWithValue("@Comentario", sComentario);
command.Parameters.AddWithValue("@Fecha", sFechaHora);
command.ExecuteNonQuery();
}
}
}
catch (Exception e)
{
}
}
public List<(int id, string lineaError, string error, string comentarios, string fecha)> GetRegistrosErrorLog()
{
var lista = new List<(int id, string lineaError, string error, string comentarios, string fecha)>();
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
string query = "SELECT Id, LineaError, Error, Comentario, Fecha FROM ErrorLog;";
using (var command = new SQLiteCommand(query, connection))
{
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
int idLog = reader.GetInt32(0);
lista.Add((
idLog,
reader.IsDBNull(1) ? "" : reader.GetString(1), // ✅ Manejo de valores NULL
reader.IsDBNull(2) ? "" : reader.GetString(2),
reader.IsDBNull(3) ? "" : reader.GetString(3),
reader.IsDBNull(4) ? "" : reader.GetString(4)
));
}
}
}
}
return lista;
}
public bool ExisteConfiguracionExpediente()
{
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
// Verificar si hay registros en la tabla ArchivosProcesados
string checkQuery = "SELECT COUNT(1) FROM Configuracion;";
int registrosExistentes;
using (var command = new SQLiteCommand(checkQuery, connection))
{
registrosExistentes = Convert.ToInt32(command.ExecuteScalar());
}
// Si hay registros, no realizar el escaneo inicial
if (registrosExistentes == 0)
{
return false;
}
}
return true;
}
public List<(string Ruta, string Nombre, string Tipo)> ObtenerPendientes()
{
var lista = new List<(string, string, string)>();
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
string query = "SELECT Ruta, Nombre, Tipo FROM Carpetas WHERE Estado IN('Nuevo','Pendiente')";
using (var command = new SQLiteCommand(query, connection))
{
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
lista.Add((reader.GetString(0), reader.GetString(1), reader.GetString(2)));
}
}
}
}
return lista;
}
public List<(string RutaArchivo, string NombreArchivo, string CarpetaRaiz, string ArchivoZip)> ObtenerArchivosPendientes()
{
var lista = new List<(string RutaArchivo, string NombreArchivo, string CarpetaRaiz, string ArchivoZip)>();
try
{
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
string sql = @"
SELECT RutaArchivo, NombreArchivo, CarpetaRaiz, ArchivoZip
FROM ArchivosProcesados
WHERE Estado = 'Pendiente'
ORDER BY CarpetaRaiz, ArchivoZip, NombreArchivo;";
using (var cmd = new SQLiteCommand(sql, connection))
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
string ruta = reader.GetString(0);
string nombre = reader.GetString(1);
string carpetaRaiz = reader.GetString(2);
string zip = reader.GetString(3);
lista.Add((ruta, nombre, carpetaRaiz, zip));
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error al obtener archivos pendientes: {ex.Message}");
}
return lista;
}
public List<(string ArchivoZip, string Estado, string fechaCreacion)> ObtenerCarpetas(string buscarRuta = "*")
{
var lista = new List<(string Nombre, string Estado, string fechaCreacion)>();
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
string query = "";
if (buscarRuta == "*")
{
query = "SELECT Id, Nombre, Estado, FechaCreado from Carpetas ORDER BY FechaCreado DESC LIMIT 1000;";
}
else
{
query = "SELECT Id, Nombre, Estado, FechaCreado from Carpetas WHERE Ruta=@RutaCarpeta LIMIT 1;";
}
using (var command = new SQLiteCommand(query, connection))
{
if (buscarRuta != "*") command.Parameters.AddWithValue("@RutaCarpeta", buscarRuta);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
lista.Add((
reader.GetString(1) + ".zip", // NombreCarpeta
reader.GetString(2), // Estado
reader.GetString(3) // Fecha Creacion
));
}
}
}
connection.Close();
}
return lista;
}
public void UpsertCarpeta(string ruta, string nombre, string tipo, string estado)
{
try
{
string fecha = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
// Insertar el registro si no existe
//string insertQuery = @"
// INSERT INTO Carpetas (Ruta, Nombre, Tipo, Estado)
// VALUES (@r, @n, @t, @e)
// ON CONFLICT(Ruta) DO UPDATE SET
// Tipo=excluded.Tipo,
// Estado=excluded.Estado,
// Nombre=excluded.Nombre;
// ";
string insertQuery = @"
INSERT INTO Carpetas (Ruta, Nombre, Tipo, Estado, FechaCreado)
VALUES (@r, @n, @t, @e, @f)
ON CONFLICT(Ruta) DO NOTHING;
";
using (var command = new SQLiteCommand(insertQuery, connection))
{
command.Parameters.AddWithValue("@r", ruta);
command.Parameters.AddWithValue("@n", nombre);
command.Parameters.AddWithValue("@t", tipo);
command.Parameters.AddWithValue("@e", estado);
command.Parameters.AddWithValue("@f", fecha);
command.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
public void MarcarCarpetaProcesada(string ruta, string estado)
{
try
{
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
string updateQuery = "UPDATE Carpetas SET Estado=@e WHERE Ruta=@r";
using (var command = new SQLiteCommand(updateQuery, connection))
{
command.Parameters.AddWithValue("@r", ruta);
command.Parameters.AddWithValue("@e", estado);
command.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
public bool EstaArchivoProcesado(string rutaArchivo)
{
try
{
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
string updateQuery = "SELECT 1 FROM ArchivosProcesados WHERE RutaArchivo=@r AND Estado='Procesado' LIMIT 1";
using (var command = new SQLiteCommand(updateQuery, connection))
{
command.Parameters.AddWithValue("@r", rutaArchivo);
return command.ExecuteScalar() != null;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
return false;
}
}
public void RegistrarArchivoProcesado(string rutaArchivo, string nombreArchivo, string carpetaRaiz, string archivoZip, string? hashContenido)
{
try
{
string fecha = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
// Insertar el registro si no existe
string insertQuery = @"
INSERT OR IGNORE INTO ArchivosProcesados (RutaArchivo, NombreArchivo, CarpetaRaiz, HashContenido, ArchivoZip, FechaProcesado, FolderExpediente)
VALUES (@r, @n, @cr, @h, @az, @f, @fe)";
using (var command = new SQLiteCommand(insertQuery, connection))
{
command.Parameters.AddWithValue("@r", rutaArchivo);
command.Parameters.AddWithValue("@n", nombreArchivo);
command.Parameters.AddWithValue("@cr", carpetaRaiz);
command.Parameters.AddWithValue("@h", (object?)hashContenido ?? DBNull.Value);
command.Parameters.AddWithValue("@az", archivoZip);
command.Parameters.AddWithValue("@f", fecha);
command.Parameters.AddWithValue("@fe", "");
command.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
public void MarcarArchivosZipComoProcesados(string archivoZip)
{
try
{
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
string sql = @"
UPDATE ArchivosProcesados
SET Estado = 'Procesado',
FechaProcesado = COALESCE(FechaProcesado, datetime('now'))
WHERE ArchivoZip = @zip
AND Estado <> 'Procesado';";
using (var cmd = new SQLiteCommand(sql, connection))
{
// Ojo: si guardas solo el nombre (ej. "Carpeta1.zip"),
// pásalo igual aquí. Si guardas ruta completa, pasa la ruta completa.
cmd.Parameters.AddWithValue("@zip", archivoZip);
cmd.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
public void ActualizarEstadoArchivo(string rutaArchivo, string nuevoEstado)
{
try
{
string fecha = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
string sql = @"
UPDATE ArchivosProcesados
SET Estado = @estado,
FechaProcesado = COALESCE(@fecha, FechaProcesado)
WHERE RutaArchivo = @ruta;";
using (var cmd = new SQLiteCommand(sql, connection))
{
cmd.Parameters.AddWithValue("@estado", nuevoEstado);
cmd.Parameters.AddWithValue("@ruta", rutaArchivo);
cmd.Parameters.AddWithValue("@fecha", fecha);
cmd.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
public void LimpiarRegistrosInexistentes()
{
try
{
int archivosEliminados = 0;
int carpetasEliminados = 0;
// Eliminar los registros inexistentes de archivos
using (var conn = new SQLiteConnection(_connectionString))
{
conn.Open();
// Obtener todos los registros guardados
using (var cmd = new SQLiteCommand("SELECT RutaArchivo FROM ArchivosProcesados", conn))
using (var reader = cmd.ExecuteReader())
{
var rutasAEliminar = new List<string>();
while (reader.Read())
{
string ruta = reader.GetString(0);
// Si el archivo ya no existe, lo marcamos para borrar del registro
if (!File.Exists(ruta))
rutasAEliminar.Add(ruta);
}
reader.Close();
// Borrar registros inexistentes
foreach (var ruta in rutasAEliminar)
{
using (var deleteCmd = new SQLiteCommand("DELETE FROM ArchivosProcesados WHERE RutaArchivo = @ruta", conn))
{
deleteCmd.Parameters.AddWithValue("@ruta", ruta);
archivosEliminados += deleteCmd.ExecuteNonQuery();
}
}
}
conn.Close();
}
// Eliminar los registros inexistentes de carpetas
using (var conn = new SQLiteConnection(_connectionString))
{
conn.Open();
// Obtener todos los registros guardados
using (var cmd = new SQLiteCommand("SELECT Ruta FROM Carpetas", conn))
using (var reader = cmd.ExecuteReader())
{
var rutasAEliminar = new List<string>();
while (reader.Read())
{
string ruta = reader.GetString(0);
// Si el archivo ya no existe, lo marcamos para borrar del registro
if (!Directory.Exists(ruta))
rutasAEliminar.Add(ruta);
}
reader.Close();
// Borrar registros inexistentes
foreach (var ruta in rutasAEliminar)
{
using (var deleteCmd = new SQLiteCommand("DELETE FROM Carpetas WHERE Ruta = @ruta", conn))
{
deleteCmd.Parameters.AddWithValue("@ruta", ruta);
carpetasEliminados += deleteCmd.ExecuteNonQuery();
}
}
}
conn.Close();
}
Console.WriteLine($"Limpieza completada: {carpetasEliminados} registros eliminados de CarpetasProcesados.");
Console.WriteLine($"Limpieza completada: {archivosEliminados} registros eliminados de ArchivosProcesados.");
}
catch (Exception ex)
{
Console.WriteLine($"Error en limpieza de registros inexistentes: {ex.Message}");
}
}
public bool ArchivoExiste(string rutaArchivo)
{
try
{
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
string sql = @"SELECT COUNT(*) FROM ArchivosPendientes WHERE RutaArchivo = @Ruta";
using (var cmd = new SQLiteCommand(sql, connection))
{
cmd.Parameters.AddWithValue("@Ruta", rutaArchivo);
return Convert.ToInt32(cmd.ExecuteScalar()) > 0;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
return false;
}
}
public void GuardarArchivoPendiente(string rutaArchivo, string nombreArchivo, string carpetaRaiz, string archivoZip, string? hashContenido)
{
try
{
string fecha = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
// Insertar el registro si no existe
string insertQuery = @"
INSERT OR IGNORE INTO ArchivosProcesados (RutaArchivo, NombreArchivo, CarpetaRaiz, HashContenido, ArchivoZip, FechaProcesado, FolderExpediente, Estado)
VALUES (@r, @n, @cr, @h, @az, @f, @fe, 'Pendiente')";
using (var command = new SQLiteCommand(insertQuery, connection))
{
command.Parameters.AddWithValue("@r", rutaArchivo);
command.Parameters.AddWithValue("@n", nombreArchivo);
command.Parameters.AddWithValue("@cr", carpetaRaiz);
command.Parameters.AddWithValue("@h", (object?)hashContenido ?? DBNull.Value);
command.Parameters.AddWithValue("@az", archivoZip);
command.Parameters.AddWithValue("@f", fecha);
command.Parameters.AddWithValue("@fe", "");
command.ExecuteNonQuery();
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
public List<ExpedienteInfo> ObtenerExpedientes(long pagina, long tamanioPagina = 1000, string buscarExpediente = "*")
{
long pageNumber = pagina; // pedimos la segunda página
long pageSize = tamanioPagina;
var lista = new List<ExpedienteInfo>();
long offset = (pageNumber - 1) * pageSize;
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
string query = "";
if (buscarExpediente == "*")
{
query = "SELECT Id, Nombre, Estado, FechaCreado, Ruta 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;";
}
using (var command = new SQLiteCommand(query, connection))
{
if (buscarExpediente != "*") command.Parameters.AddWithValue("@Expediente", buscarExpediente);
command.Parameters.AddWithValue("@pageSize", pageSize);
command.Parameters.AddWithValue("@offset", offset);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var exp = new ExpedienteInfo
{
Expediente = reader.GetString(1),
Estado = reader.GetString(2),
FechaCreacion = reader.GetString(3),
Ruta = reader.GetString(4)
};
lista.Add(exp);
}
}
}
connection.Close();
}
return lista;
}
public async Task<long> TotalExpedientes(string buscarExpediente = "*")
{
try
{
using (var connection = new SQLiteConnection(_connectionString))
{
connection.Open();
string updateQuery = "";
if (buscarExpediente == "*")
{
updateQuery = "SELECT COUNT(*) AS Total FROM Carpetas;";
}
else
{
updateQuery = "SELECT COUNT(*) AS Total FROM Carpetas WHERE Nombre LIKE '%' || @Expediente || '%';";
}
using (var command = new SQLiteCommand(updateQuery, connection))
{
if (buscarExpediente != "*") command.Parameters.AddWithValue("@Expediente", buscarExpediente);
var result = await command.ExecuteScalarAsync();
return result == null ? 0 : Convert.ToInt64(result);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
return 0;
}
}
//public void Dispose()
//{
// _conn?.Dispose();
// _conn = null;
//}
}
}

100
Classes/SaaiM3Helper.cs Normal file
View File

@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EFCDesk.Classes
{
public static class SaaiM3Helper
{
public class Registro
{
public string Tipo { get; set; }
public string[] Campos { get; set; }
}
// -----------------------------
// Cargar archivo completo
// -----------------------------
public static List<Registro> LeerArchivo(string rutaArchivo)
{
var registros = new List<Registro>();
foreach (var linea in File.ReadLines(rutaArchivo))
{
if (String.IsNullOrWhiteSpace(linea))
continue;
var partes = linea.Split('|');
registros.Add(new Registro
{
Tipo = partes[0],
Campos = partes
});
}
return registros;
}
// --------------------------------------
// Buscar por tipo (ej. "551")
// --------------------------------------
public static List<Registro> BuscarPorTipo(List<Registro> registros, string tipo)
{
var lista = new List<Registro>();
foreach (var r in registros)
{
if (r.Tipo == tipo)
lista.Add(r);
}
return lista;
}
// -----------------------------------------------------------------------------------
// Buscar por tipo y por valor en un campo específico
// Ej: Buscar "551" donde Campos[2] == "4000204"
// -----------------------------------------------------------------------------------
public static List<Registro> BuscarPorTipoYCampo(List<Registro> registros, string tipo, int indexCampo, string valor)
{
var lista = new List<Registro>();
foreach (var r in registros)
{
if (r.Tipo == tipo)
{
if (r.Campos.Length > indexCampo && r.Campos[indexCampo] == valor)
{
lista.Add(r);
}
}
}
return lista;
}
// ----------------------------------------
// Búsqueda parcial (contiene)
// ----------------------------------------
public static List<Registro> BuscarPorTipoYCampoParcial(List<Registro> registros, string tipo, int indexCampo, string contiene)
{
var lista = new List<Registro>();
foreach (var r in registros)
{
if (r.Tipo == tipo)
{
if (r.Campos.Length > indexCampo &&
r.Campos[indexCampo].Contains(contiene))
{
lista.Add(r);
}
}
}
return lista;
}
}
}

View File

@@ -0,0 +1,174 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EFCDesk.Classes
{
public static class SecureDataHandler
{
private const string SettingKey = "IdUsuario";
// Genera entropía dinámica a partir de datos de hardware y usuario
private static byte[] GetEntropy()
{
string machineName = Environment.MachineName;
string userName = Environment.UserName;
string cpu = Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER") ?? "";
string sysDrive = Environment.GetEnvironmentVariable("SystemDrive") ?? "";
//string exeHash = GetExecutableHash();
string mac = GetMacAddress() ?? "";
string combo = $"MiAppSalt|{machineName}|{userName}|{cpu}|{sysDrive}|{mac}";
return Encoding.UTF8.GetBytes(combo);
}
// Calcula hash SHA256 del ejecutable para validar que no ha sido copiado o modificado
private static string GetExecutableHash()
{
string exePath = Application.ExecutablePath;
using (var sha = SHA256.Create())
using (var stream = File.OpenRead(exePath))
{
byte[] hash = sha.ComputeHash(stream);
return Convert.ToBase64String(hash);
}
}
private static string? GetMacAddress()
{
var nic = System.Net.NetworkInformation.NetworkInterface
.GetAllNetworkInterfaces()
.FirstOrDefault(n =>
n.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up &&
n.NetworkInterfaceType != System.Net.NetworkInformation.NetworkInterfaceType.Loopback &&
n.GetPhysicalAddress().GetAddressBytes().Length > 0);
if (nic == null) return null;
// Sin ":"
return string.Concat(nic.GetPhysicalAddress()
.GetAddressBytes()
.Select(b => b.ToString("X2")));
}
public static void SaveData(string data)
{
if (data == null) throw new ArgumentNullException(nameof(data));
byte[] bytes = Encoding.UTF8.GetBytes(data);
byte[] encrypted = ProtectedData.Protect(bytes, GetEntropy(), DataProtectionScope.CurrentUser);
string encryptedBase64 = Convert.ToBase64String(encrypted);
Properties.Settings.Default[SettingKey] = encryptedBase64;
Properties.Settings.Default.Save();
ClearBytes(bytes);
ClearBytes(encrypted);
}
public static SecureString? ReadDataAsSecureString()
{
string? encryptedBase64 = Properties.Settings.Default[SettingKey] as string;
if (string.IsNullOrEmpty(encryptedBase64)) return null;
byte[] encrypted = Convert.FromBase64String(encryptedBase64);
byte[]? decrypted = null;
try
{
decrypted = ProtectedData.Unprotect(encrypted, GetEntropy(), DataProtectionScope.CurrentUser);
string? data = Encoding.UTF8.GetString(decrypted);
var secure = new SecureString();
if (data != null)
{
foreach (char c in data) secure.AppendChar(c);
ClearString(ref data);
}
secure.MakeReadOnly();
return secure;
}
catch (CryptographicException)
{
// Los datos no se pueden desencriptar → devolver null en vez de corromper
return null;
}
finally
{
if (decrypted != null) ClearBytes(decrypted);
ClearBytes(encrypted);
}
}
public static string? ReadDataAsPlainText()
{
SecureString? ss = ReadDataAsSecureString();
if (ss == null) return null;
try { return SecureStringToString(ss); }
finally { ss.Dispose(); }
}
public static void DeleteData()
{
Properties.Settings.Default[SettingKey] = null;
Properties.Settings.Default.Save();
}
public static bool IsDataValid()
{
string? encryptedBase64 = Properties.Settings.Default[SettingKey] as string;
if (string.IsNullOrEmpty(encryptedBase64)) return false;
byte[] encrypted = Convert.FromBase64String(encryptedBase64);
try
{
byte[] decrypted = ProtectedData.Unprotect(encrypted, GetEntropy(), DataProtectionScope.CurrentUser);
ClearBytes(decrypted);
return true;
}
catch
{
return false;
}
}
private static string? SecureStringToString(SecureString secure)
{
if (secure == null) return null;
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.SecureStringToGlobalAllocUnicode(secure);
return Marshal.PtrToStringUni(ptr);
}
finally
{
if (ptr != IntPtr.Zero) Marshal.ZeroFreeGlobalAllocUnicode(ptr);
}
}
private static void ClearBytes(byte[] buf)
{
if (buf == null) return;
for (int i = 0; i < buf.Length; i++) buf[i] = 0;
}
private static unsafe void ClearString(ref string? s)
{
if (s == null) return;
fixed (char* p = s)
{
for (int i = 0; i < s.Length; i++) p[i] = '\0';
}
s = null;
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace EFCDesk.Classes
{
public class TextBoxConPaste : TextBox
{
private const int WM_PASTE = 0x0302;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PASTE)
{
// Después de pegar, limpiar portapapeles
Clipboard.Clear();
}
}
}
}