ChourioDev Logo
fintech Destacado

De 60 Segundos a Milisegundos: Refactorizando el Core de Pagos de Andrómeda Ventures

En 2018 propuse y lideré la refactorización completa del servicio core de pagos: de un monolito Java/SOAP con deuda técnica masiva a una arquitectura de microservicios con Node.js/NestJS que transformó la operación de millones de transacciones entre bancos y proveedores.

Miguel Mendoza Chourio
8 min lectura
De 60 Segundos a Milisegundos: Refactorizando el Core de Pagos de Andrómeda Ventures

El Problema: Un Monolito de Miedo que Nadie se Atrevía a Tocar

En 2018, en Andrómeda Ventures, me enfrenté a un desafío que cualquier desarrollador senior conoce: el temido “legacy system” del que depende todo el negocio. El servicio core de la línea vertical de pagos era literalmente intocable - no por respeto, sino por terror puro.

Este servicio funcionaba como API intermediaria entre proveedores (Movistar, Digitel, Inter, DirecTV) y clientes bancarios (Banco Plaza, Bancrecer, Banco Activo, Bancamiga). El problema era que estaba construido como un monolito en Java con SOAP, cargado de deuda técnica y sin documentación.

La Realidad del Código Legacy

La situación era crítica:

  • ⏱️ Tiempo de respuesta: 45-60 segundos por transacción
  • 😰 Deuda técnica: Extensiva, sin documentación
  • 🚫 Miedo al cambio: Nadie se atrevía a modificar el código
  • 💰 Riesgo de negocio: Una caída significaba pérdida de facturación
  • 🔗 Dependencia externa: Servicio de terceros para gestión de transacciones
  • 📉 Escalabilidad: Imposible escalar horizontalmente
// Ejemplo del código legacy que encontramos
@WebService
public class PaymentService {
    // Sin documentación, sin tests, sin esperanza
    public PaymentResponse processPayment(PaymentRequest request) {
        // 200+ líneas de lógica anidada
        // Múltiples llamadas síncronas
        // Manejo de errores inexistente
        // ... 60 segundos después...
        return response;
    }
}

La Propuesta: Arquitectura de Microservicios por Proveedor

Decidí presentar un proyecto de refactorización que cambiaría completamente el paradigma del sistema. La propuesta constaba de:

1. Arquitectura de Microservicios Distribuida

Cada conexión con cada proveedor sería un microservicio independiente, lo que aumentaría:

  • Disponibilidad: Un proveedor caído no afecta a los demás
  • Soporte: Debugging y mantenimiento aislado por proveedor
  • Escalabilidad horizontal: Escalar solo lo que necesita más recursos

2. Stack Tecnológico Moderno

  • Backend: Node.js + NestJS
  • Base de datos: PostgreSQL
  • ORM: TypeORM desde cada microservicio
  • API Gateway: Capa intermedia de comunicación
  • Compatibilidad: SOAP y REST simultáneamente

3. Modelo de Datos Centralizado

Diseñé un modelo de datos desde cero que manejaría:

  • 🗃️ Transacciones: Almacenamiento optimizado
  • 👥 Usuarios y credenciales: Gestión segura
  • 🏢 Proveedores: Configuración por proveedor
  • Validaciones: Lógica específica por proveedor

La Implementación: De Monolito a Microservicios

Arquitectura Final: El Diseño que Cambió Todo

// Nuevo controlador por proveedor con NestJS
@Controller("movistar")
@UseGuards(JwtAuthGuard)
export class MovistarController {
  constructor(
    private readonly movistarService: MovistarService,
    private readonly transactionService: TransactionService
  ) {}

  @Post("balance")
  @ApiOperation({ summary: "Consulta de saldo Movistar" })
  async getBalance(@Body() request: BalanceRequest): Promise<BalanceResponse> {
    // De 60 segundos a 200ms
    return await this.movistarService.getBalance(request);
  }
}

Los Microservicios Implementados

  1. Movistar Service: Gestión específica de Movistar
  2. Digitel Service: Conexión optimizada con Digitel
  3. Inter Service: Integración con servicios Inter
  4. DirecTV Service: Manejo de servicios de televisión
  5. Auth Service: JWT centralizado para todos los servicios
  6. Transaction Service: Gestión centralizada de transacciones
  7. API Gateway: Enrutamiento inteligente entre servicios

API Gateway: El Cerebro del Sistema

// API Gateway con compatibilidad SOAP y REST
@Controller("gateway")
export class ApiGatewayController {
  constructor(private readonly routingService: RoutingService) {}

  // Endpoint REST para nuevos clientes
  @Post("rest/:provider/balance")
  async restBalance(@Param("provider") provider: string, @Body() request: any) {
    return await this.routingService.routeToProvider(provider, request);
  }

  // Endpoint SOAP para clientes legacy
  @Post("soap")
  @Header("Content-Type", "text/xml")
  async soapBalance(@Body() soapRequest: string) {
    const parsed = await this.parseSoapRequest(soapRequest);
    return await this.routingService.routeToProvider(
      parsed.provider,
      parsed.data
    );
  }
}

El Modelo de Datos: PostgreSQL + TypeORM

Entidades Principales

@Entity('transactions')
export class Transaction {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  providerId: string;

  @Column()
  bankId: string;

  @Column({ type: 'decimal', precision: 10, scale: 2 })
  amount: number;

  @Column()
  currency: string;

  @Column({ type: 'enum', enum: TransactionStatus })
  status: TransactionStatus;

  @Column({ type: 'jsonb' })
  metadata: any;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  // Optimización de queries con índices
  @Index(['providerId', 'createdAt'])
  @Index(['bankId', 'status'])
}

Configuración de Proveedores

@Entity("providers")
export class Provider {
  @PrimaryGeneratedColumn("uuid")
  id: string;

  @Column()
  name: string; // 'movistar', 'digitel', 'inter', 'directv'

  @Column({ type: "jsonb" })
  configuration: {
    endpoint: string;
    timeout: number;
    retryPolicy: RetryPolicy;
    authentication: AuthConfig;
  };

  @Column({ default: true })
  isActive: boolean;

  @OneToMany(() => Transaction, (transaction) => transaction.providerId)
  transactions: Transaction[];
}

Gestión de Compatibilidad: SOAP y REST Simultáneamente

El Desafío de la Migración Sin Downtime

El mayor reto fue mantener operatividad de clientes en producción mientras migrábamos. La solución fue implementar ambas capas simultáneamente:

// Servicio de transformación SOAP ↔ REST
@Injectable()
export class TransformationService {
  // Convertir request SOAP legacy a formato interno
  transformSoapToInternal(soapRequest: string): InternalRequest {
    const parsed = this.xmlParser.parse(soapRequest);

    return {
      provider: this.extractProvider(parsed),
      operation: this.extractOperation(parsed),
      credentials: this.extractCredentials(parsed),
      data: this.extractData(parsed),
    };
  }

  // Convertir respuesta interna a SOAP para clientes legacy
  transformInternalToSoap(response: InternalResponse): string {
    return this.xmlBuilder.build({
      "soap:Envelope": {
        "soap:Body": {
          BalanceResponse: {
            Amount: response.balance,
            Currency: response.currency,
            Status: response.status,
            Timestamp: response.timestamp,
          },
        },
      },
    });
  }
}

Los Resultados: Transformación Completa del Negocio

Métricas de Impacto

MétricaAntes (Java/SOAP)Después (Microservicios)Mejora
Tiempo de respuesta45-60s150-300ms99.5%
Disponibilidad por proveedor80%99.8%19.8%
Throughput50 tx/hora5,000+ tx/hora10,000%
EscalabilidadVertical únicamenteHorizontal por servicio
Tiempo de deployment4-6 horas15 minutos96%
Recovery time2-4 horas5-10 minutos95%

Impacto en el Negocio

Para los Bancos (Clientes)

  • 💰 Mejora en facturación: +300% en volumen procesado
  • Experiencia de usuario: De 60s a menos de 1s
  • 🛡️ Confiabilidad: 99.8% uptime vs 94% anterior
  • 📈 Nuevas oportunidades: Productos antes imposibles por performance

Para Andrómeda Ventures

  • 👨‍💻 Satisfacción del equipo: Código mantenible y documentado
  • 🔧 Soporte técnico: De reiniciar el serivicio a poder resolver los problemas detectados
  • 💡 Innovación: Base sólida para nuevos productos FinTech

Lecciones Aprendidas: Refactorización de Sistemas Críticos

1. La Importancia de la Propuesta Técnica Sólida

// Documentación de arquitectura desde el día 1
interface SystemArchitecture {
  currentState: LegacySystemAnalysis;
  proposedState: MicroservicesArchitecture;
  migrationPlan: StepByStepPlan;
  riskMitigation: RiskMatrix;
  businessImpact: ROICalculation;
}

2. Migración Gradual con Zero Downtime

  • Fase 1: API Gateway con routing dual (SOAP/REST)
  • Fase 2: Migración proveedor por proveedor
  • Fase 3: Deprecación gradual de endpoints SOAP
  • Fase 4: Monitoreo y optimización continua

3. Testing de Carga en Producción

// Implementación de circuit breaker para seguridad
@Injectable()
export class CircuitBreakerService {
  private circuits = new Map<string, CircuitBreaker>();

  async executeWithCircuitBreaker<T>(
    providerName: string,
    operation: () => Promise<T>
  ): Promise<T> {
    const circuit = this.getCircuit(providerName);

    if (circuit.isOpen()) {
      throw new ServiceUnavailableException(
        `Provider ${providerName} temporarily unavailable`
      );
    }

    try {
      const result = await operation();
      circuit.recordSuccess();
      return result;
    } catch (error) {
      circuit.recordFailure();
      throw error;
    }
  }
}

Stack Tecnológico Final

Backend Architecture

  • Framework: NestJS con TypeScript
  • Database: PostgreSQL con optimizaciones específicas
  • ORM: TypeORM con connection pooling
  • API Gateway: Custom con Kong como proxy
  • Authentication: JWT con Redis sessions
  • Message Queue: RabbitMQ para comunicación asíncrona

DevOps & Monitoring

  • Containerización: Docker con multi-stage builds
  • Monitoring: Custom dashboards
  • CI/CD: GitLab CI con automated testing por microservicio

Validaciones y Seguridad

// Validaciones específicas por proveedor
@Injectable()
export class MovistarValidationService {
  validateRequest(request: MovistarRequest): ValidationResult {
    return {
      isValid:
        this.validateAmount(request.amount) &&
        this.validateCredentials(request.credentials) &&
        this.validatePhoneNumber(request.phoneNumber),
      errors: this.collectErrors(request),
    };
  }

  private validatePhoneNumber(phone: string): boolean {
    // Validación específica para números Movistar
    return /^0414|0424|0414|0424/.test(phone) && phone.length === 11;
  }
}

Conclusión: Más Allá de la Refactorización

Esta transformación no fue solo sobre mejorar código; fue sobre cambiar la cultura tecnológica de una empresa FinTech. Demostró que es posible migrar sistemas críticos sin afectar la operación, y que la inversión en arquitectura moderna genera ROI inmediato.

Key Takeaways para Refactorizaciones Similares

  1. Documentar el estado actual: Sin entender el legacy, no puedes mejorarlo
  2. Propuesta con ROI claro: Los stakeholders necesitan ver el valor de negocio
  3. Migración gradual: Zero downtime es posible con la estrategia correcta
  4. Monitoreo desde día 1: Observabilidad no es opcional en producción
  5. Team buy-in: El equipo debe entender y adoptar la nueva arquitectura

¿Has liderado refactorizaciones similares? ¿Qué estrategias funcionaron mejor en tu experiencia con sistemas legacy críticos? Comparte tu experiencia en los comentarios.


¿Te gustó este artículo? Sígueme en LinkedIn para más contenido sobre arquitectura de sistemas y FinTech. Próximamente escribiré sobre “Circuit Breakers en Microservicios FinTech” y “Migración de SOAP a GraphQL: Lecciones Aprendidas”.


Miguel Mendoza Chourio es Senior Full-Stack Engineer con 9+ años especializándose en FinTech y refactorización de sistemas legacy. Disponible para consultoría en arquitectura de microservicios y oportunidades remotas internacionales.

MC

Miguel Mendoza Chourio

Senior Full-Stack Engineer con 9+ años especializándose en FinTech, sistemas de pago y arquitectura de microservicios. Disponible para oportunidades remotas internacionales.

📧 Suscríbete al Blog

Recibe notificaciones sobre nuevos artículos y contenido técnico exclusivo.

No spam. Puedes cancelar la suscripción en cualquier momento.