OOscar Dev BlogWeb · AI · Craft
Volver al blog
Backend#nestjs#typescript#backend#banking

NestJS Dependency Injection en Banking: Lo que Nadie te Cuenta

Detrás de cada originación de crédito hay un grafo de dependencias que puede tumbar producción. Lo que aprendí moviendo CDT, Bre-B y consumer credit a NestJS.

OOscar Vargas25 de julio de 20267 min de lectura

Hay una frase que repito en cada onboarding de devs nuevos en el equipo: en un banco, una dependencia mal inyectada no rompe un endpoint, rompe un contrato con un proveedor externo a las 3 AM. Y en consumer credit origination, eso significa transacciones en Transbank que no se reconcilian, eventos Bre-B que nunca llegan al switch, y un SQS lleno de mensajes que el orquestador ya dio por muertos.

NestJS te vende la dependency injection como una feature "automática". Lo que no te dice es que, en sistemas financieros, el grafo de dependencias ES el modelo de riesgo operacional. Si no lo entiendes, estás escribiendo código que parece limpio y se comporta como una bomba de tiempo.

El mito del "scope por defecto"

Cuando arranqué con NestJS vienes de Spring o de Go, lo primero que asumes es que el contenedor maneja el ciclo de vida por ti. Y sí, lo hace. Pero hay una diferencia brutal entre singleton, request ytransient cuando estás lidiando con:

  • Contexto transaccional de bases de datos (KYC, motor de riesgo)
  • Correlation IDs que deben propagarse entre servicios
  • Clientes HTTP con tokens que rotan cada hora (PSE, Bre-B)
  • Y al menos un proveedor que decide caerse justo cuando más lo necesitas

La pregunta no es "¿qué scope uso?", sino ¿qué garantía de aislamiento me da este scope en un cierre de lote?. Esa pregunta cambia todo.

El caso real: cliente Bre-B con token rotativo

Tenemos un BreBClient que firma cada request. El token vive 3600 segundos. Si lo declaras singleton (lo "natural" en NestJS), ganas performance pero pierdes la ventana de refresh. Si lo declarasrequest, pagas el handshake cada vez. ¿La respuesta correcta? Depende. Y ahí es donde el junior se traba.

@Injectable({ scope: Scope.REQUEST })
export class BreBClient {
  private token?: { value: string; expiresAt: number };

  constructor(
    private readonly http: HttpService,
    @Inject('BREB_CONFIG') private readonly cfg: BreBConfig,
  ) {}

  async send(payload: BreBTransfer): Promise<BreBResponse> {
    await this.ensureFreshToken();
    return firstValueFrom(
      this.http.post(this.cfg.endpoint, payload, {
        headers: { Authorization: `Bearer ${this.token!.value}` },
      }),
    );
  }

  private async ensureFreshToken(): Promise<void> {
    // ... el problema que vamos a dejar abierto por ahora
  }
}

Esto compila, pasa los tests unitarios, y se rompe en producción cuando dos requests concurrentes pelean por el mismo token. ¿Por qué? Porquerequest scope aísla por request HTTP, pero no por dominio de negocio. En banking, dos originaciones simultáneas pueden ser del mismo cliente y compartir ventana de token.

Tres patrones que me cambiaron la vida

Después de dos incidentes graves (uno con CDT, otro con KYC digital) y countless war rooms, llegué a tres patrones no negociables:

  • Factory providers por dominio — no por clase. UnCreditOriginationProvider distinto al deAccountOpeningProvider, aunque compartan el mismo cliente HTTP.
  • Tokens explícitos para todo lo que cruza un trust boundary— interfaces no, @Inject('PSE_CLIENT') sí. La magia de TypeScript no sobrevive a un merge de tres equipos.
  • Refresher como actor externo, no interno — el token no se renueva "cuando vence", lo renueva un job dedicado que el cliente consulta. Esto evita el race condition clásico del doble refresh.

¿Y los hexagonal puristas? Tienen razón en la teoría, pero en producción con SQS, RabbitMQ y deadlines de la Superintendencia, necesitas algo que tu equipo de 12 personas entienda un lunes a las 8 AM. Y más que voy a dejar fuera de este post para no arruinar el siguiente.

Lo que este post no te va a enseñar (a propósito)

Podría cerrar acá con un checklist bonito. Pero te estaría mintiendo. Hay al menos dos cosas que merecen su propio artículo y que prefiero dejar abiertas:

¿Cómo se ve un grafo de dependencias cuando un orquestador de consumer credit llama a 7 microservicios en cadena, y uno de ellos se cae a mitad de transacción? ¿Y cómo decides qué scope tiene un Saga?

Esas preguntas no tienen respuesta en un snippet. Tienen respuesta en tu próximo postmortem. Y ahí es donde NestJS deja de ser un framework y se convierte en lo que siempre fue: una herramienta para tomar decisiones de arquitectura que nadie te documentó.

Si quieres que la siguiente entrega sea sobre el patrón de refresher externo con el ejemplo completo de Bre-B, déjalo en los comentarios. Si no, la guardaré para cuando alguien me la pida a las 3 AM.

Compartir artículo

O

Escrito por

Oscar Vargas

Apasionado por el desarrollo web, las herramientas modernas y compartir lo que aprende en el camino. Más artículos abajo.

Siguiente artículo →

El Costo Oculto de useEffect: Por Qué tu App Sigue Re-renderizando en 2026

Artículos relacionados