AF.BackEnd.Integrations 1.1.1

dotnet add package AF.BackEnd.Integrations --version 1.1.1
                    
NuGet\Install-Package AF.BackEnd.Integrations -Version 1.1.1
                    
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="AF.BackEnd.Integrations" Version="1.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AF.BackEnd.Integrations" Version="1.1.1" />
                    
Directory.Packages.props
<PackageReference Include="AF.BackEnd.Integrations" />
                    
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 AF.BackEnd.Integrations --version 1.1.1
                    
#r "nuget: AF.BackEnd.Integrations, 1.1.1"
                    
#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 AF.BackEnd.Integrations@1.1.1
                    
#: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=AF.BackEnd.Integrations&version=1.1.1
                    
Install as a Cake Addin
#tool nuget:?package=AF.BackEnd.Integrations&version=1.1.1
                    
Install as a Cake Tool

AF.BackEnd.Integrations

Cliente HTTP centralizado para consumir servicios de terceros desde los backends de Apoyo Financiero, con reintentos, circuit breaker y autenticación por request ya resueltos.

Getting Started

  1. Proceso de instalación
  2. Software dependencies
  3. Cómo usarlo?
  4. API references

⚙️ Proceso de instalación:

Instale el nuget usando el siguiente comando.

.NET Cli
dotnet add package AF.BackEnd.Integrations --version 1.1.0
Nuget
NuGet\Install-Package AF.BackEnd.Integrations -Version 1.1.0
Package reference
<PackageReference Include="AF.BackEnd.Integrations" Version="1.1.0" />

🛠️Dependencias

net10.0

  • Microsoft.Extensions.Http.Resilience [9.4.0]
  • Microsoft.Extensions.Options.ConfigurationExtensions [10.0.0]

✈️ Cómo usarlo?

El paquete resuelve un problema concreto: cada backend que consume AF.BackEnd.Notifications (u otro tercero HTTP interno) terminaba con su propia implementación de cliente, divergente en contrato, payload y sin política de reintentos. AF.BackEnd.Integrations centraliza eso.

1️⃣ Consumir AF.BackEnd.Notifications

Program.cs

Registrar con AddAFNotifications, indicando la sección de configuración que la app decide (el paquete no impone ningún nombre fijo).

using AF.BackEnd.Integrations.Http.Notifications;
            ⁝
    builder.Services.AddAFNotifications(builder.Configuration, "AFIntegrations:Notifications");
            ⁝
{
  "AFIntegrations": {
    "Notifications": {
      "BaseUrl": "https://notifications.internal/api/v1.0/",
      "ServiceToken": "...",
      "Retry": { "MaxRetryAttempts": 3, "TimeoutPerAttemptSeconds": 10 },
      "CircuitBreaker": { "FailureRatio": 0.5, "SamplingDurationSeconds": 30 }
    }
  }
}

Retry y CircuitBreaker son opcionales: si se omiten en la configuración, el paquete aplica sus propios valores por defecto para Notifications (nunca queda sin resiliencia).

Consumir
public sealed class ServicioSolicitudes(INotificationsClient notificationsClient)
{
    public Task NotificarAsync(string email, CancellationToken cancellationToken) =>
        notificationsClient.SendMailAsync(
            "AcuerdoGenerado",
            email,
            new Dictionary<string, JsonElement>
            {
                ["Customer.Name"] = JsonSerializer.SerializeToElement("Juan Pérez"),
            },
            cancellationToken);
}

Los parámetros van en una sola bolsa de JsonElement. Un valor de tipo String es un escalar; uno de tipo Array u Object alimenta bucles y navegación dentro de la plantilla:

var parametros = new Dictionary<string, JsonElement>
{
    ["cliente_nombre"] = JsonSerializer.SerializeToElement("Juan Pérez"),
    ["pago_total"]     = JsonSerializer.SerializeToElement("950000"),
    ["cuotas_abonadas"] = JsonSerializer.SerializeToElement(
        cuotas.Select(c => new { numero = c.Numero, valor = c.Saldo })),
};

La plantilla los recorre con {{ for c in cuotas_abonadas }}. Constrúyalos con objetos anónimos o registros tipados, no con diccionarios de texto: así el compilador sigue vigilando los nombres.

Los importes y las fechas viajan crudos ("950000", "2026-03-15"). El formato lo pone la plantilla con los filtros | moneda y | fecha_larga. Si formatea aquí, volverá a necesitar un despliegue para cambiar cómo se ve un número.

Componer un mensaje de WhatsApp
public sealed class ServicioCartera(INotificationsClient notificationsClient)
{
    public async Task<string> TextoDeMoraAsync(
        string telefono, IReadOnlyDictionary<string, JsonElement> parametros, CancellationToken ct)
    {
        var resultado = await notificationsClient
            .SendWhatsAppAsync("Mora", telefono, parametros, ct);

        return resultado!.Cuerpo;
    }
}

Hoy devuelve el texto para que el asesor lo copie y lo pegue. Cuando exista la integración con la API de Meta, el mismo método enviará y lo único que cambiará es Estado, de Generada a Sent: el llamador no se toca.

API
Miembro Descripción
Task SendMailAsync(string templateKey, string toEmail, IReadOnlyDictionary<string, JsonElement> parameters, CancellationToken) Encola un correo por plantilla. Fire-and-forget: nunca propaga excepciones — una falla de correo no debe bloquear la operación de negocio que lo dispara.
Task<WhatsAppResult?> SendWhatsAppAsync(string tipoCodigo, string destinatario, IReadOnlyDictionary<string, JsonElement> parameters, CancellationToken) Compone y registra un mensaje de WhatsApp. Sí propaga el fallo (ver abajo).
record WhatsAppResult(Guid NotificacionId, string Cuerpo, string Estado) Identificador del registro, texto compuesto y estado (Generada o Sent).
Comportamiento que conviene conocer

SendMailAsync nunca lanza. Errores HTTP (4xx/5xx) y de transporte (HttpRequestException, timeout) se loguean con ILogger<NotificationsClient> y el método retorna. Si el caller necesita saber si el envío falló, tiene que instrumentarlo aparte (métricas, logs), no esperando una excepción.

SendWhatsAppAsync sí lanza, y es deliberado. No es una inconsistencia con el correo: ahí nadie está mirando, mientras que aquí hay un usuario esperando el texto en pantalla. Un fallo silencioso le dejaría un modal vacío sin explicación, que es peor que un error.

Autenticación de servicio, no de usuario. El ServiceToken identifica al backend (Core, Auth) como cliente de confianza ante Notifications — es independiente de si hay o no un usuario logueado en la request que disparó el envío. Compartir el JWT del usuario final no es una alternativa válida: ese token trae otro Audience y algunos flujos (recuperar contraseña) no tienen sesión iniciada en absoluto.


2️⃣ Agregar un tercero HTTP nuevo

AddAFHttpIntegration<TClient, TImplementation> es el mecanismo genérico que AddAFNotifications envuelve. No conoce Notifications ni ningún tercero en particular.

Definir las Options del tercero

Herede de HttpIntegrationOptions.

using AF.BackEnd.Integrations.Http.Config;

public sealed class PaymentGatewayOptions : HttpIntegrationOptions
{
    public required string ApiKey { get; set; }
}
Definir cómo se autentica (si aplica)

Implemente IRequestAuthenticator. No está atado a Bearer: puede agregar cualquier header o firma sobre el HttpRequestMessage.

using AF.BackEnd.Integrations.Http;

internal sealed class PaymentGatewayAuthenticator(PaymentGatewayOptions options) : IRequestAuthenticator
{
    public void Authenticate(HttpRequestMessage request) =>
        request.Headers.Add("X-Api-Key", options.ApiKey);
}
Registrar

El wrapper propio del tercero lee configuración y llama al genérico con objetos ya construidos.

using AF.BackEnd.Integrations.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

public static class ServiceCollectionPaymentGatewayExtensions
{
    public static IServiceCollection AddAFPaymentGateway(
        this IServiceCollection services, IConfiguration configuration, string sectionName)
    {
        var options = configuration.GetSection(sectionName).Get<PaymentGatewayOptions>()
            ?? throw new InvalidOperationException($"No se encontró la sección '{sectionName}'.");

        return services.AddAFHttpIntegration<IPaymentGatewayClient, PaymentGatewayClient>(
            options,
            authenticator: new PaymentGatewayAuthenticator(options));
    }
}
API
Miembro Descripción
AddAFHttpIntegration<TClient, TImplementation>(HttpIntegrationOptions options, IRequestAuthenticator? authenticator = null) Registra TClient como HttpClient tipado. Sin authenticator, no se agrega autenticación. Sin options.Retry ni options.CircuitBreaker, no se agrega resiliencia.
IRequestAuthenticator.Authenticate(HttpRequestMessage request) Mutar la request antes de enviarla — headers, firma, lo que el tercero requiera.
HttpIntegrationOptions.BaseUrl Requerido. Falla al registrar si está vacío.
HttpIntegrationOptions.Retry / .CircuitBreaker Opcionales (null = no aplicar esa estrategia).
RetryOptions.ResiliencePipelineName Opcional. Sin valor, se autogenera de typeof(TClient).Name.
Comportamiento que conviene conocer

La autenticación y la resiliencia son independientes entre sí. Un tercero puede tener solo BaseUrl (sin auth, sin resiliencia), solo Retry sin CircuitBreaker, o cualquier combinación — AddAFHttpIntegration arma el pipeline únicamente con lo que el tercero provee.

TOptions no es un genérico del método. AddAFHttpIntegration<TClient, TImplementation> recibe HttpIntegrationOptions options directo — cualquier subclase entra sin necesitar un tercer parámetro de tipo, porque el método solo usa miembros de la base (BaseUrl, Retry, CircuitBreaker).

Cada reintento se loguea. Si options.Retry está configurado, cada intento fallido dispara un LogWarning en ILogger<TClient> (la misma categoría del cliente tipado) con el número de intento, el MaxRetryAttempts configurado, el delay antes del siguiente intento y el resultado (status code o excepción) que lo disparó. No hace falta configurar nada aparte: sale del mismo ILogger<TClient> que ya resuelve AddHttpClient.

📌 API references

Namespace Tipos públicos
AF.BackEnd.Integrations.Http IRequestAuthenticator, AuthenticationDelegatingHandler, ServiceCollectionHttpIntegrationExtensions
AF.BackEnd.Integrations.Http.Config HttpIntegrationOptions, RetryOptions, CircuitBreakerOptions
AF.BackEnd.Integrations.Http.Notifications INotificationsClient, NotificationsClient, NotificationsAuthenticator, ServiceCollectionNotificationsExtensions
AF.BackEnd.Integrations.Http.Notifications.Config NotificationsOptions

El paquete incluye documentación XML, así que las firmas y observaciones aparecen en IntelliSense.

🔄 Cambios en 1.1.0

SendMailAsync cambia el tipo de parameters, de IReadOnlyDictionary<string, string> a IReadOnlyDictionary<string, JsonElement>. Es un cambio fuente-incompatible: hay que recompilar y convertir los valores con JsonSerializer.SerializeToElement(...).

En el cable no rompe nada. El transporte es JSON, así que un cuerpo con todos los valores de texto —el que enviaban las versiones anteriores— deserializa igual en el servicio. Un consumidor que siga en 1.0.2 sigue funcionando contra un Notifications actualizado; solo no podrá enviar listas ni objetos.

El motivo del cambio: las notificaciones de crédito necesitan pasar colecciones (las cuotas vencidas de un pago, por ejemplo) para que la plantilla las recorra con {{ for }}. Un diccionario de textos obligaba a pre-maquetar esos bloques en C#, que es justo lo que el catálogo de plantillas en base de datos vino a eliminar.

SendWhatsAppAsync es nuevo. Ver arriba.

🎓 Créditos

Nombre del Paquete: AF.BackEnd.Integrations Versión: 1.1.0

Autor

  • Nombre: Dayser José Granados Pineda
  • Correo Electrónico: djpgranados@gmail.com | daysergranados@hotmail.com

Licencia

Este paquete está bajo la licencia MIT License

Copyright (c) 2026 Dayser José Granados Pineda

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Contacto

Si tienes comentarios, problemas o solicitudes, ¡no dudes en ponerte en contacto conmigo!

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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.1.1 85 8/19/2026
1.1.0 86 8/18/2026
1.0.2 92 8/7/2026
1.0.1 98 8/6/2026
1.0.0 93 8/6/2026