first commit
This commit is contained in:
466
Classes/ApiClient.cs
Normal file
466
Classes/ApiClient.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Classes/ArchivosProcesados.cs
Normal file
19
Classes/ArchivosProcesados.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
17
Classes/AutoStartHelper.cs
Normal file
17
Classes/AutoStartHelper.cs
Normal 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";
|
||||
|
||||
}
|
||||
}
|
||||
45
Classes/AutoUpdateAppHelper.cs
Normal file
45
Classes/AutoUpdateAppHelper.cs
Normal 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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
113
Classes/ConfiguracionExpediente.cs
Normal file
113
Classes/ConfiguracionExpediente.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
225
Classes/ConfiguracionJSON.cs
Normal file
225
Classes/ConfiguracionJSON.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Classes/DatosActualizadosEventArgs.cs
Normal file
21
Classes/DatosActualizadosEventArgs.cs
Normal 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
50
Classes/ErrorLog.cs
Normal 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
62
Classes/FileUtil.cs
Normal 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
34
Classes/General.cs
Normal 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
364
Classes/Helpers.cs
Normal 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
139
Classes/JsonHelper.cs
Normal 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
990
Classes/MonitorCarpetas.cs
Normal 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
98
Classes/ProcessLogger.cs
Normal 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
872
Classes/SQLiteHelper.cs
Normal 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
100
Classes/SaaiM3Helper.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
174
Classes/SecureDataHandler.cs
Normal file
174
Classes/SecureDataHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Classes/TextBoxConPaste.cs
Normal file
25
Classes/TextBoxConPaste.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user