DMS.Foundation 1.0.0

dotnet add package DMS.Foundation --version 1.0.0
                    
NuGet\Install-Package DMS.Foundation -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DMS.Foundation" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DMS.Foundation" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="DMS.Foundation" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DMS.Foundation --version 1.0.0
                    
#r "nuget: DMS.Foundation, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DMS.Foundation@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DMS.Foundation&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=DMS.Foundation&version=1.0.0
                    
Install as a Cake Tool

Core DMS Software

Librería central de uso interno para proyectos DMS Software.
Provee servicios transversales reutilizables: seguridad, acceso a datos, notificaciones, logging, mapeo, helpers y gestión de tenants, todos integrados con la capa transversal DMS.


Tabla de contenido


Requisitos

  • .NET 8.0
  • appsettings.json correctamente configurado (ver Configuración)
  • Acceso a la capa transversal de DMS Software
  • Archivo de licencia válido emitido por DMS Software

Instalación

dotnet add package Core.DMS.Software --version 1.0.0

Configuración

Agrega las siguientes secciones en tu appsettings.json:

{
  "TransversalLayer": {
    "Service": "https://tu-capa-transversal/api",
    "ServiceNotification": "https://tu-capa-transversal/notifications",
    "API-KEY": "tu-api-key"
  },
  "TokenSettings": {
    "ApplicationId": "tu-application-id"
  },
  "OAuth": {
    "tenantId": "tu-tenant-id",
    "clientId": "tu-client-id",
    "clientSecret": "tu-client-secret"
  },
  "License": {
    "PublicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
    "LicenseFilePath": "license.json"
  },
  "PathLogs": "logs/errors.log",
  "PathInfo": "logs/info.log"
}

Módulos

🔐 Seguridad (DMS.Security)

LicenseManager / LicenseValidator

Gestiona la validación de licencias mediante firma RSA-SHA256. Verifica que el archivo de licencia exista, no haya expirado y que su firma sea auténtica e inalterada.

// Validar la licencia al iniciar la aplicación (Program.cs)
LicenseValidator.ValidateLicense();

// Verificar en cualquier punto del flujo de negocio
LicenseValidator.EnsureLicenseIsValid();

Estructura esperada del archivo license.json:

{
  "LicenseId": "uuid-de-la-licencia",
  "Owner": "Nombre del cliente",
  "ExpirationDate": "2026-12-31T00:00:00Z",
  "Signature": "base64-de-la-firma-rsa"
}
LicenseValidationMiddleware

Middleware ASP.NET Core que valida la licencia en cada solicitud HTTP entrante. Si la licencia no es válida, rechaza la solicitud antes de que llegue al controlador.

// En Program.cs
app.UseMiddleware<LicenseValidationMiddleware>();
AESCryptoService / AESDecryptService

Cifrado y descifrado simétrico con AES-CBC. El cifrado requiere licencia válida activa.

byte[] key = Convert.FromBase64String("tu-clave-aes-base64-32-bytes");
byte[] iv  = Convert.FromBase64String("tu-iv-base64-16-bytes");

// Cifrar
var encryptor = new AESCryptoService(key, iv);
string textoCifrado = encryptor.Encrypt("texto plano");

// Descifrar
var decryptor = new AESDecryptService(key, iv);
string textoOriginal = decryptor.Decrypt(textoCifrado);

⚠️ La clave debe tener exactamente 32 bytes (AES-256) y el IV 16 bytes. Generarlos con RandomNumberGenerator y almacenarlos de forma segura en el sistema de secretos del proyecto.


🗄️ Acceso a datos (DMS.Repository)

StoredProcedureExecutor

Ejecutor avanzado de procedimientos almacenados y consultas SQL. Soporta medición de rendimiento, transacciones, paginación, múltiples result sets y auditoría automática. Todos los métodos retornan OperationResult<T> con tiempo de ejecución incluido.

Ejecutar sin retorno (INSERT / UPDATE / DELETE):

var result = await executor.ExecuteAsync(
    "sp_CrearCliente",
    new { Nombre = "Juan", Apellido = "Pérez" }
);

if (result.IsSuccess)
    Console.WriteLine($"Filas afectadas: {result.Data}");

Ejecutar con parámetro OUTPUT:

var result = await executor.ExecuteWithOutputAsync(
    "sp_InsertarProducto",
    new { Nombre = "Producto A", Precio = 9900 },
    outputParameterName: "@IdRetorno"
);

Console.WriteLine($"ID generado: {result.GeneratedId}");

Obtener un objeto:

var result = await executor.ExecuteSingleAsync<ClienteDTO>(
    "sp_ObtenerClientePorId",
    new { Id = 42 }
);

Obtener una lista:

var result = await executor.ExecuteListAsync<ProductoDTO>(
    "sp_ListarProductos",
    new { CategoriaId = 3 }
);

Paginación:

var result = await executor.ExecutePagedAsync<ProductoDTO>(
    "sp_ListarProductosPaginado",
    new { Page = 1, PageSize = 20, CategoriaId = 3 }
);

Console.WriteLine($"Total registros: {result.Data.TotalRecords}");
Console.WriteLine($"Página: {result.Data.Page} de {result.Data.TotalPages}");

Resultado JSON desde el SP:

var result = await executor.ExecuteJsonAsync(
    "sp_ObtenerReporteJSON",
    new { FechaInicio = "2025-01-01" },
    jsonColumnName: "JSONString"
);

Múltiples result sets:

var mappers = new Dictionary<int, Func<SqlDataReader, CancellationToken, Task<object>>>
{
    [0] = async (r, ct) => await DataMapper.MapToListAsync<ClienteDTO>(r, ct),
    [1] = async (r, ct) => await DataMapper.MapToListAsync<PedidoDTO>(r, ct)
};

var result = await executor.ExecuteMultipleAsync("sp_ObtenerClientesYPedidos", mappers);
var clientes = result.Data.GetResultSet<List<ClienteDTO>>(0);
var pedidos  = result.Data.GetResultSet<List<PedidoDTO>>(1);

Lote de operaciones en una misma transacción:

var operaciones = new List<(string, object?)>
{
    ("sp_InsertarCabecera", new { Total = 50000 }),
    ("sp_InsertarDetalle",  new { ProductoId = 1, Cantidad = 2 }),
    ("sp_InsertarDetalle",  new { ProductoId = 5, Cantidad = 1 })
};

var result = await executor.ExecuteBatchAsync(operaciones);

Con auditoría automática:

var result = await executor.ExecuteWithAuditAsync(
    "sp_CrearOrden",
    createdBy:  101,
    userName:   "jperez",
    tenantId:   5,
    ipAddress:  "192.168.1.10",
    parameters: new { ClienteId = 42, Total = 75000 }
);

Opciones avanzadas (StoredProcedureOptions):

var options = new StoredProcedureOptions
{
    CommandTimeout = 60,      // segundos
    LogPerformance = true,    // registra tiempo de ejecución
    TenantId       = "tenant-abc",
    LoginType      = "External",
    IsExternal     = true
};

var result = await executor.ExecuteListAsync<ReporteDTO>("sp_Reporte", parameters, options);

📢 Notificaciones (DMS.Services.NotificationService)

Envío de mensajes WhatsApp (Gupshup) y correos electrónicos a través de la capa transversal DMS.

// Mensaje de texto simple
await notificationService.SendMessageText(rootObjectGupshupTextDTO);

// Template WhatsApp (registra seguimiento automático)
await notificationService.SendMessageGupshupTemplate(messageGupshupDTO);

// Documento por WhatsApp
await notificationService.SendDocmentGupshupTemplate(messageDocumentGupshupDTO);

// Correo electrónico
await notificationService.SendTrasnversalLayerEmail(dataEmailsDTO);

SendMessageGupshupTemplate registra automáticamente el tracking del mensaje via WhatsAppTrackingService.


📊 Seguimiento de WhatsApp (DMS.Services.WhatsAppTrackingService)

Registra el volumen de mensajes enviados por tenant y aplicación, contabilizando templates, videos y documentos enviados en cada mensaje.

await whatsAppTrackingService.CreateWhatsAppTracking(messageGupshupDTO);

📝 Logging (DMS.Services.LogService)

Registro de logs hacia la capa transversal DMS y en archivos locales con rotación automática al superar 100 MB.

// Asíncrono hacia la capa transversal (recomendado en servicios)
await logService.SaveLogsMessagesAsync(
    "Ocurrió un error al procesar el pago",
    tenantId:      1,
    applicationId: 2,
    errorType:     3
);

// Sincrónico a archivo local de errores
logService.SaveLogsMessages("Error crítico en proceso X");

// Sincrónico a archivo local informativo
logService.SaveInfoMessages("Proceso de facturación completado correctamente");

🌐 HTTP Transversal (DMS.Services.HttpDMSService)

Cliente HTTP genérico para consumir endpoints de la capa transversal con autenticación Bearer token.

var result = await httpDMSService.Get<ResponseDTO>(
    baseUrl:    "https://capa-transversal/api",
    endpoint:   "/GetRecurso",
    token:      "bearer-token",
    parameters: new Dictionary<string, string>
    {
        { "id",     "123"    },
        { "estado", "activo" }
    }
);

🔑 OAuth (DMS.Services.TokenOAuthCrossLayerService)

Obtiene tokens de acceso OAuth 2.0 desde la capa transversal usando las credenciales configuradas en appsettings.json.

string token = await tokenService.GetToken<ResponseDTO>();

🏢 Tenant (DMS.Services.TenantService / GetDescriptionByTenantService)

Obtiene la información descriptiva del tenant activo a partir del token JWT presente en el contexto HTTP.

var tenant = await tenantService.GetDescriptionByTenantAsync(httpContextAccessor);
Console.WriteLine(tenant.Name);

🏗️ Aplicación (DMS.Services.ApplicationService)

Recupera la cadena de conexión dinámica y los datos visuales (logo, colores corporativos) del tenant activo.

// Cadena de conexión por tenant y tipo de entorno
string connString = await applicationService.GetApplicationDataAsync(tenantId, loginType);

// Logo y colores corporativos
var desc = await applicationService.GetDescriptionApplication(tenantId);
Console.WriteLine($"Color primario: {desc.PrimaryColor}");

🔄 Acceso automático a tenants (DMS.Services.AccessAutoAsync)

Consulta los tenants asociados a un servicio específico en la capa transversal, útil para flujos multi-tenant automáticos.

var tenants = await accessAutoAsync.GetAccessAutoAsyncTenants<List<TenantDTO>>(token);

Helpers

BlobAzureHelpers

Operaciones sobre Azure Blob Storage. Todos los métodos requieren licencia válida.

// Obtener cliente de blob
var blobClient = BlobAzureHelpers.GetBlobClient(blobContainerUrl, "carpeta/archivo.pdf");

// Descargar como bytes
byte[] bytes = await BlobAzureHelpers.DownloadBlobAsync(blobClient);

// Descargar como Base64
string base64 = await BlobAzureHelpers.DownloadBlobAsBase64Async(blobClient);

// Subir archivo desde Base64
await BlobAzureHelpers.UploadFileAsync(
    blobContainerUrl,
    "application/pdf",
    "carpeta/archivo.pdf",
    base64,
    logService
);

// Generar URL de acceso temporal (SAS read-only)
Uri sasUri = BlobAzureHelpers.GenerateReadOnlySasUri(
    blobClient,
    duration:    TimeSpan.FromHours(2),
    accountName: "storageAccount",
    accountKey:  "storageKey"
);

// Obtener propiedades del blob
BlobProperties props = await BlobAzureHelpers.GetFilesBlob(
    blobContainerUrl, "carpeta/archivo.pdf", logService
);
Console.WriteLine($"Tamaño: {props.ContentLength} bytes");

// Generar URL combinando base SAS con ruta
string url = BlobAzureHelpers.GenerateBlobUrlWithSas(blobUrlWithSasToken, "carpeta/archivo.pdf");

DataMapper

Mapeo de alto rendimiento de SqlDataReader a objetos y listas. Usa caché interno de propiedades y columnas para minimizar la reflexión en llamadas repetidas. Respeta [JsonPropertyName] para el nombre de columna y [NotMapped] para excluir propiedades.

// Mapear a lista
var clientes = await DataMapper.MapToListAsync<ClienteDTO>(reader);

// Mapear a objeto único
var cliente = await DataMapper.MapToObjectAsync<ClienteDTO>(reader);

// Mapear resultado JSON (columna específica)
string? json = await DataMapper.MapJsonStringAsync(reader, columnName: "JSONString");

// Verificar errores retornados por el SP
string? error = await DataMapper.CheckForErrorAsync(reader, errorColumnName: "error");
if (error != null) throw new Exception(error);

// Verificar permisos de acceso
bool tieneAcceso = await DataMapper.CheckAccessAsync(reader);

// Múltiples result sets con mappers personalizados
var mappers = new Dictionary<int, Func<SqlDataReader, CancellationToken, Task<object>>>
{
    [0] = async (r, ct) => await DataMapper.MapToListAsync<ClienteDTO>(r, ct),
    [1] = async (r, ct) => await DataMapper.MapToObjectAsync<ResumenDTO>(r, ct)
};
var sets = await DataMapper.MapMultipleResultSetsAsync(reader, mappers);

Tipos especiales soportados: TimeOnly (desde TimeSpan), DateOnly (desde DateTime), tipos Nullable<T>.


ClaimsHelper

Extracción de claims del token JWT desde el contexto HTTP. Busca el claim por nombre primario y, si no lo encuentra, por nombre alternativo (camelCase).

string tenantId    = ClaimsHelper.GetTenantIdFromClaims(httpContextAccessor);
string appId       = ClaimsHelper.GetApplicationIdFromClaims(httpContextAccessor);
string loginType   = ClaimsHelper.GetTypeLoginFromClaims(httpContextAccessor);
string nombre      = ClaimsHelper.GetNameFromClaims(httpContextAccessor);
string telefono    = ClaimsHelper.GetNumberIdFromClaims(httpContextAccessor);
string imagen      = ClaimsHelper.GetImage(httpContextAccessor);
string advanceId   = ClaimsHelper.GetAdvanceIdFromClaims(httpContextAccessor);
string advanceUser = ClaimsHelper.GetUserAdvance(httpContextAccessor);

Lanza InvalidOperationException si el claim requerido no está presente en el token.


Base64ToImageHelper

Convierte una cadena Base64 a un archivo .png en disco. Crea el directorio de salida si no existe. Requiere licencia válida.

string nombreArchivo = Base64ToImageHelper.ConvertBase64ToImg(
    base64String: imageBase64,
    outputPath:   "wwwroot/images/uploads",
    logService:   logService
);
// Retorna el nombre del archivo generado, ej: "3f4a9c1b-....png"

ImageToBase64Helper

Convierte un archivo de imagen en disco a cadena Base64. Requiere licencia válida.

string base64 = ImageToBase64Helper.ConvertImgToBase64(
    imagePath:  "wwwroot/images/logo.png",
    logService: logService
);

DeleteImageHelper

Elimina un archivo del sistema de archivos de forma segura, sin propagar excepciones no controladas. Requiere licencia válida.

bool eliminado = DeleteImageHelper.DeleteImageIfExists(
    imagePath:  "wwwroot/images/uploads/foto.png",
    logService: logService
);

DTOtoObjectHelper

Mapeo por convención de nombre de propiedades entre un DTO origen y cualquier objeto destino. Disponible como extension method sobre cualquier objeto. Requiere licencia válida.

var clienteDTO = new ClienteDTO { Nombre = "Juan", Email = "juan@mail.com" };
var clienteEntity = clienteDTO.ToObject<ClienteEntity>();

MapToListHelper / MapToObjHelper

Mapeadores síncronos y asíncronos de IDataReader / SqlDataReader a listas y objetos. Respetan [JsonPropertyName] para el nombre de columna. Requieren licencia válida.

// Síncrono a lista
var lista = MapToListHelper.MapToList<ProductoDTO>(dataReader);

// Asíncrono a lista
var lista = await MapToListHelper.MapToListAsync<ProductoDTO>(dataReader);

// Asíncrono a objeto único (SqlDataReader)
var obj = await MapToObjHelper.MapToObj<ClienteDTO>(sqlDataReader);

// Verificar si una columna existe en el reader
bool existe = MapToObjHelper.FieldExists(sqlDataReader, "NombreColumna");

Para nuevos desarrollos se recomienda DataMapper, que incluye caché de columnas y soporte nativo para DateOnly/TimeOnly.


ParameterHelper

Construcción de parámetros SQL para SqlCommand desde objetos anónimos o tipados. Respeta los atributos [SpParam] (nombre personalizado) y [NonSpParam] (excluir propiedad).

// Parámetros de entrada
ParameterHelper.AddParameters(command, new
{
    ClienteId = 42,
    Estado    = "Activo"
});
// Genera: @ClienteId = 42, @Estado = 'Activo'

// Parámetro OUTPUT
var outputParam = ParameterHelper.AddOutputParameter(
    command,
    parameterName: "@IdRetorno",
    sqlDbType:     SqlDbType.Int
);
// Leer después de ExecuteNonQuery:
int idGenerado = (int)outputParam.Value;

// Parámetro INPUT/OUTPUT
var ioParam = ParameterHelper.AddInputOutputParameter(
    command,
    parameterName: "@Contador",
    sqlDbType:     SqlDbType.Int,
    value:         0
);

PasswordHashHelper

Hashing seguro de contraseñas con Argon2id (ganador del Password Hashing Competition). Configuración: 8 hilos, 64 MB de memoria, 4 iteraciones, salt de 16 bytes generado criptográficamente.

// Al registrar usuario
var (hash, salt) = PasswordHashHelper.HashPassword("MiContraseña123!");
// Almacenar hash y salt en la base de datos

// Al hacer login
byte[] saltBytes = Convert.FromBase64String(storedSalt);
bool esValida = PasswordHashHelper.VerifyPassword(
    password:   "MiContraseña123!",
    storedHash: storedHash,
    storedSalt: saltBytes
);

Nunca reutilizar el mismo salt entre contraseñas distintas. Cada llamada a HashPassword genera un salt único.


ExceptionHelper

Manejo centralizado de excepciones no controladas: registra el error en el log y retorna un ResponseDTO estandarizado con IsSuccess = false. Requiere licencia válida.

try
{
    // lógica de negocio
}
catch (Exception ex)
{
    return ExceptionHelper.HandleException(
        logService:    logService,
        methodName:    nameof(MiMetodo),
        ex:            ex,
        tenantId:      1,
        applicationId: 2,
        errorType:     3
    );
}

HandleResponsesController

Wrapper para controladores ASP.NET Core que estandariza el manejo de respuestas y excepciones de validación, retornando siempre ActionResult con ResponseDTO.

[HttpPost]
public async Task<ActionResult> CrearCliente([FromBody] ClienteDTO dto)
{
    return await HandleResponsesController.HandleResponse(
        async () => await _clienteService.CrearClienteAsync(dto),
        _logService,
        nameof(ClienteController)
    );
}

DisposableHelper

Utilidad para implementar el patrón IDisposable / IAsyncDisposable de forma segura, lanzando ObjectDisposedException con nombre contextual automático vía [CallerMemberName].

public class MiServicio : IDisposable
{
    private bool _disposed;

    public void HacerAlgo()
    {
        DisposableHelper.ThrowIfDisposed(_disposed);
        // lógica segura
    }

    public void Dispose() => _disposed = true;
}

Registro de dependencias

// HTTP Clients
builder.Services.AddHttpClient<IHttpDMSService, HttpDMSService>();
builder.Services.AddHttpClient<ILogService, LogService>();
builder.Services.AddHttpClient<ITenantService, TenantService>();
builder.Services.AddHttpClient<IGetDescriptionByTenantService, GetDescriptionByTenantService>();
builder.Services.AddHttpClient<ITokenOAuthCrossLayerService, TokenOAuthCrossLayerService>();
builder.Services.AddHttpClient<IAccessAutoAsync, AccessAutoAsync>();

// Servicios Scoped
builder.Services.AddScoped<INotificationService, NotificationService>();
builder.Services.AddScoped<IWhatsAppTrackingService, WhatsAppTrackingService>();
builder.Services.AddScoped<IApplicationService, ApplicationService>();
builder.Services.AddScoped<IContextConectionService, ContextConectionService>();
builder.Services.AddScoped<IStoredProcedureExecutor, StoredProcedureExecutor>();
builder.Services.AddScoped<IAdvancedStoredProcedureExecutor, StoredProcedureExecutor>();

// AES — las claves deben venir de tu sistema de secretos, nunca hardcodeadas
byte[] aesKey = Convert.FromBase64String(builder.Configuration["Security:AesKey"]!);
byte[] aesIv  = Convert.FromBase64String(builder.Configuration["Security:AesIv"]!);
builder.Services.AddSingleton<IAESCryptoService>(_ => new AESCryptoService(aesKey, aesIv));
builder.Services.AddSingleton<IAESDecryptService>(_ => new AESDecryptService(aesKey, aesIv));

// Infraestructura
builder.Services.AddHttpContextAccessor();

Validación de licencia

// Program.cs — orden recomendado

// 1. Validar antes de construir la app
LicenseValidator.ValidateLicense();

var app = builder.Build();

// 2. Middleware opcional: valida en cada request entrante
app.UseMiddleware<LicenseValidationMiddleware>();

app.Run();

Si la licencia no es válida, ha expirado o ha sido alterada, se lanza UnauthorizedAccessException con el detalle del motivo.


Licencia

El uso de esta librería requiere una licencia válida emitida por DMS Software.
Contacta a soporte@dms.software para adquirir, renovar o consultar el estado de tu licencia.

© 2025 DMS Software. Todos los derechos reservados.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 126 5/13/2026

Versión inicial de DMS Foundation.

Módulos incluidos:
- Seguridad: validación de licencias RSA, middleware de licencia, cifrado/descifrado AES.
- Notificaciones: envío de mensajes WhatsApp (Gupshup) y correo electrónico vía capa transversal.
- Seguimiento WhatsApp: registro de mensajes enviados por tenant y aplicación.
- Conexión a datos: gestión de conexiones SQL Server con ciclo de vida controlado.
- Logging: registro hacia la capa transversal y en archivos locales con rotación automática.
- HTTP Transversal: cliente HTTP genérico con autenticación Bearer.
- OAuth: obtención de tokens desde la capa transversal.
- Tenant y Aplicación: cadena de conexión, logo y colores corporativos por tenant.