AF.BackEnd.Integrations
1.1.0
See the version list below for details.
dotnet add package AF.BackEnd.Integrations --version 1.1.0
NuGet\Install-Package AF.BackEnd.Integrations -Version 1.1.0
<PackageReference Include="AF.BackEnd.Integrations" Version="1.1.0" />
<PackageVersion Include="AF.BackEnd.Integrations" Version="1.1.0" />
<PackageReference Include="AF.BackEnd.Integrations" />
paket add AF.BackEnd.Integrations --version 1.1.0
#r "nuget: AF.BackEnd.Integrations, 1.1.0"
#:package AF.BackEnd.Integrations@1.1.0
#addin nuget:?package=AF.BackEnd.Integrations&version=1.1.0
#tool nuget:?package=AF.BackEnd.Integrations&version=1.1.0
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
- Proceso de instalación
- Software dependencies
- Cómo usarlo?
- 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| moneday| 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 | Versions 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. |
-
net10.0
- Microsoft.Extensions.Http.Resilience (>= 9.4.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.