Migrar una plataforma crítica rara vez empieza con código. Empieza con una pregunta mucho más incómoda:
¿Sabemos realmente qué está corriendo en producción?
En este caso, la respuesta inicial fue: parcialmente.
La plataforma que queríamos modernizar era una solución B2B con años de evolución orgánica: APIs en runtime legacy, servicios del sistema, procedimientos almacenados, bases de datos relacionales, integraciones con ERP, flujos de e-commerce, jobs de sincronización, documentos, pagos, tracking logístico y aplicaciones satélite.
No era una sola aplicación. Era un ecosistema.
La primera decisión importante fue no intentar una migración tipo “apagar y prender”. La evidencia técnica mostraba que el sistema legacy todavía cubría demasiadas responsabilidades operativas, mientras que la nueva capa cloud aún no tenía paridad funcional completa.
La estrategia, por lo tanto, fue clara:
Strangler pattern, operación en paralelo, ruteo por endpoint, pruebas de paridad y rollback inmediato.
La migración no se trataría de mover “la aplicación”, sino de mover capacidades pequeñas, medibles y reversibles.
El problema real: no era solo una API #
Visto desde fuera, el proyecto parecía una migración de APIs hacia una nueva capa serverless en nube. Pero el análisis técnico reveló algo más profundo: la lógica de negocio estaba repartida en varias capas.
La API legacy funcionaba como un conjunto de scripts directos, donde cada archivo era prácticamente un endpoint. La arquitectura efectiva era:
Cliente o job
→ archivo de endpoint
→ stored procedure en base de datos
→ respuesta JSON o texto
No había router central, capa de dominio, contratos versionados ni repositorios. Gran parte de la lógica vivía en procedimientos almacenados.
Además, el landscape incluía servidores de aplicación con servicios custom, aplicaciones legacy, componentes publicados, jobs programados, integraciones documentales y dependencias ocultas.
El análisis de infraestructura identificó varios perfiles distintos:
- servidores dedicados a integraciones y jobs;
- nodos web con aplicaciones legacy;
- monolitos intranet con stack mixto;
- servicios fuera del repositorio principal;
- procesos de conversión documental;
- tareas programadas no visibles con permisos estándar;
- secretos almacenados en archivos de configuración.
En datos, la situación era igual de compleja. Había múltiples bases relacionales, dependencias entre servidores, procedimientos almacenados críticos, integraciones con ERP, pedidos, tracking, documentos y procesos batch.
La conclusión fue contundente:
No era viable migrar la base crítica completa ni apagar el legacy en el corto plazo.
La ruta correcta era cortar por dominios y por endpoints, empezando por lecturas y dejando escrituras para cuando hubiera idempotencia, auditoría y reconciliación.
La arquitectura de transición: APIM como control-plane #
El componente central de la estrategia fue poner un API Gateway delante de todo.
No como pieza decorativa, sino como control-plane de migración.
El gateway debía poder decidir, endpoint por endpoint, si una llamada iba al legacy o a la nueva capa cloud. También debía permitir:
- canary releases;
- rollback inmediato;
- métricas por endpoint;
- autenticación centralizada;
- rate limiting;
- correlation IDs;
- logging sin exponer datos sensibles;
- transformación temporal de contratos;
- segregación de consumidores.
La idea era simple:
Si una llamada no pasa por el gateway, no se puede medir, gobernar ni revertir.
La arquitectura de transición quedó así:
Consumidores
├─ Frontend B2B
├─ Plataforma e-commerce
├─ Jobs de integración
└─ Otros clientes internos
↓
API Gateway / APIM
├─ Auth / rate limit / allowlist
├─ Correlation ID
├─ Canary por endpoint
├─ Transformaciones de compatibilidad
├─ Observabilidad
└─ Rollback
↓
Backend A: Legacy
├─ APIs legacy
├─ Servicios del sistema
├─ SQL jobs
└─ Stored procedures
Backend B: Nuevo cloud
├─ funciones serverless
├─ Workers / schedulers
├─ Key Vault
├─ App Insights
└─ SQL controlado por dominio
La regla operativa fue:
Ningún corte sin paridad, monitoreo y rollback probado.
Por qué empezar por lecturas #
En una migración de sistemas transaccionales, no todos los endpoints tienen el mismo riesgo.
Las lecturas permiten comparar respuestas entre legacy y nuevo sin alterar datos. Son ideales para:
- pruebas A/B;
- contratos golden;
- medición de latencia;
- validación de compatibilidad;
- detección de diferencias funcionales;
- canary progresivo sin impacto transaccional.
Las escrituras, en cambio, pueden crear duplicados, inconsistencias, pagos mal aplicados, referencias en estados incorrectos o pedidos divergentes.
Por eso la primera ola recomendada fue enfocarse en consultas B2B:
- cobranza;
- facturas;
- pagos;
- inventarios;
- compras;
- órdenes;
- distribuidores;
- tracking;
- mezclas de compra;
- notas y documentos relacionados.
Las escrituras críticas quedaron para un release gated con requisitos adicionales:
- idempotencia;
- transacciones reales;
- auditoría;
- reconciliación;
- single-writer;
- rollback vía gateway;
- write firewall;
- deduplicación.
La trampa del “ya existe el endpoint” #
Uno de los aprendizajes más importantes fue que tener un endpoint implementado no significa estar listo para migrar tráfico.
Un endpoint cloud puede existir, compilar y responder, pero aun así no estar listo si:
- no tiene el mismo contrato request/response;
- cambia códigos HTTP;
- no replica efectos secundarios en base de datos;
- depende de un stored procedure no desplegado;
- le falta conectividad privada;
- usa secretos hardcodeados;
- no tiene timeouts;
- no tiene trazabilidad;
- no maneja correctamente errores y reintentos;
- no tiene pruebas automatizadas;
- no tiene evidencia de paridad contra legacy.
La nueva capa tenía endpoints implementados, pero también una mezcla de estilos: algunos más tipados y otros migrados casi 1:1 desde el legacy, con contratos heterogéneos y dependencias directas a base de datos.
También aparecieron señales de deuda funcional:
- respuestas estáticas;
- lógica pendiente;
- integraciones comentadas;
- ausencia de pruebas automatizadas;
- falta de evidencia de idempotencia en escrituras;
- uso de procedimientos almacenados como adapter temporal;
- configuración incompleta por ambiente.
Por eso se definió un criterio Go / No-Go por endpoint, no por aplicación.
Un endpoint solo podía considerarse listo si cumplía:
Contrato documentado
+ Paridad A/B contra legacy
+ Side-effects validados en SP/DB
+ Observabilidad
+ Secretos fuera del código
+ Rollback probado
+ Seguridad en APIM
+ Pruebas de regresión
Para escrituras, se agregaron gates adicionales:
Idempotency-Key o llave de negocio
+ tabla de dedupe
+ transacción real
+ auditoría por request
+ single-writer
+ write firewall
+ reconciliación
+ tripwires de rollback
El riesgo más grande: split-brain de datos #
La parte más delicada no era el código. Era el data-plane.
El ecosistema tenía datos repartidos entre distintas bases relacionales, además de dependencias cross-server, linked servers y procedimientos que consultaban fuentes externas.
El riesgo era que una nueva API escribiera en una base mientras aplicaciones legacy siguieran leyendo o escribiendo en otra, creando divergencia silenciosa.
Ese hallazgo cambió el plan. Ya no bastaba con migrar endpoints: había que decidir quién era el System of Record por dominio.
La regla de oro fue:
Single-Writer por dominio.
Para cada entidad crítica se debía definir:
Entidad
→ System of Record actual
→ Writers reales
→ Lectores reales
→ SPs involucrados
→ Tablas impactadas
→ Estrategia de cutover
→ Reconciliación
→ Rollback
Las entidades críticas incluían:
- pedidos;
- pagos;
- referencias;
- facturas;
- notas de crédito;
- distribuidores;
- inventarios;
- precios;
- descuentos;
- carrito;
- cotizaciones;
- tracking;
- documentos.
El plan también incluyó:
- auditoría DML a nivel base de datos;
- confirmación de la base runtime real por ambiente;
- revisión de tablas usadas por SQL directo;
- resolución de flujos con linked servers;
- eliminación gradual de dependencias cross-database;
- reconciliación mínima antes de mover escrituras;
- monitoreo de writers no autorizados.
Los endpoints que cambiaron la prioridad #
Otro hallazgo importante fue que había endpoints legacy que no necesariamente estaban en el “scope bonito” de la migración, pero sí eran consumidos por otros repositorios, jobs o servicios.
Estos endpoints cubrían capacidades como:
- importaciones;
- catálogos;
- agentes;
- descuentos;
- inventarios;
- precios;
- usuarios;
- notificaciones;
- sincronización con e-commerce;
- procesos batch;
- integraciones operativas.
Es decir, eran piezas que quizá no aparecían en el frontend principal, pero sostenían procesos críticos del negocio.
Esto llevó a una regla práctica:
El MVP real no es lo que aparece en el roadmap; es lo que tiene tráfico y consumidores reales.
Por eso el gateway también se planteó como herramienta de descubrimiento:
- medir tráfico real;
- detectar endpoints fantasma;
- identificar consumidores;
- observar p95 y p99;
- capturar errores por backend;
- detectar cambios semanales;
- separar tráfico humano de tráfico batch;
- mapear dependencias entre sistemas.
Jobs y sincronizadores: no tratarlos como APIs públicas #
Una parte del legacy estaba formada por jobs que exponían endpoints HTTP solo porque históricamente esa era la forma de disparar sincronizaciones.
En realidad, muchos de esos procesos no debían migrarse como endpoints públicos, sino como:
- workers;
- TimerTriggers;
- pipelines de integración;
- jobs orquestados;
- procesos event-driven;
- tareas programadas controladas;
- flujos con retry y dead-lettering.
El plan separó explícitamente la migración de:
- endpoints de consulta;
- endpoints transaccionales;
- imports de e-commerce;
- sincronizadores legacy;
- jobs operativos;
- servicios del sistema;
- flujos de documentos;
- notificaciones;
- integraciones con sistemas externos.
La ola posterior a las consultas debía reemplazar sincronizadores e imports con workers, TimerTriggers o herramientas de integración, dejando los stored procedures como adapters temporales cuando fuera necesario.
Seguridad: primero sacar secretos del código #
La migración también fue una oportunidad para corregir deuda de seguridad acumulada.
Se encontraron patrones típicos de sistemas legacy:
- secretos en archivos de configuración;
- credenciales en scripts;
- endpoints con validaciones incompletas;
- uso de HTTP plano en algunos flujos internos;
- tokens comentados;
- cookies fijas;
- errores con demasiado detalle;
- configuraciones duplicadas por ambiente;
- credenciales compartidas por varios procesos.
El target mínimo fue:
Key Vault
+ Managed Identity
+ rotación de secretos
+ JWT validado en APIM
+ function keys solo como control de plataforma
+ allowlists por consumidor
+ rate limits
+ logging sin PII
+ errores sin stack traces
La idea no era “hacer seguridad al final”, sino convertirla en gate de migración.
Si un endpoint requería secretos hardcodeados o no tenía autenticación gobernada, no pasaba a canary.
Observabilidad: migrar sin ver es apostar #
La observabilidad fue tratada como requisito funcional.
Para cada endpoint migrado se requerían métricas de:
- tráfico;
- latencia;
- error rate;
- dependencia a base de datos;
- fallas HTTP externas;
- excepciones;
- correlation ID;
- trazabilidad por dominio;
- consumidor;
- backend seleccionado;
- porcentaje de canary.
La nueva capa debía usar telemetría centralizada y dashboards por endpoint, mientras el gateway debía registrar metadata de request sin payload sensible.
La observabilidad también sirvió para definir rollback. No bastaba con “poder regresar”; había que saber cuándo regresar.
Ejemplos de tripwires:
5xx por encima del umbral
p95 degradado
timeouts externos
fallas de SP críticos
diferencias en reconciliación
DML desde writer no autorizado
incremento de errores funcionales
aumento anormal de retries
errores por consumidor específico
El runbook de rollback era deliberadamente simple:
1. Cambiar backend en APIM.
2. Validar métricas.
3. Reconciliar si hubo escrituras.
4. Revertir permisos si cambió el writer.
5. Documentar causa raíz.
6. Congelar nuevo canary hasta corregir.
Fase 1: tres semanas para convertir incertidumbre en control #
La Fase 1 fue diseñada como una fase de control, no como una fase para “migrar todo”.
El objetivo era dejar funcionando la estructura que permitiría migrar de forma segura.
Semana 1: foundation #
- APIM base
- rutas y policies
- Key Vault por ambiente
- conectividad privada
- baseline A/B para lecturas
- golden tests iniciales
- definición de contratos
- mapping endpoint → backend
- identificación de consumidores
Semana 2: hardening #
- hardening de la Function App
- corrección de issues P0
- endpoints faltantes críticos
- idempotencia inicial
- tabla de dedupe
- auditoría por request
- smoke tests
- performance tests
- validación de secretos
- validación de errores
Semana 3: canary y operación #
- canary de lecturas
- canary de escrituras solo si gates OK
- dashboards
- tripwires
- rollback probado
- runbooks
- handover
- definición de siguiente ola
- reporte de riesgos residuales
El plan era explícito:
Si un gate fallaba, el tráfico se mantenía en legacy y se avanzaba con otra rebanada.
Qué se migraba primero #
Las primeras candidatas eran lecturas con impacto visible pero bajo riesgo transaccional:
- detalle de pedidos;
- historial de pedidos;
- compras del cliente;
- órdenes de compra;
- ubicación y tracking de pedido;
- notas;
- facturas pendientes;
- distribuidores;
- referencias de pago;
- notas de crédito encabezado/detalle;
- catálogos consultivos;
- datos de inventario no transaccional.
Estas capacidades permitían validar:
- contrato;
- latencia;
- consistencia;
- autenticación;
- conectividad;
- trazabilidad;
- comportamiento del gateway;
- experiencia del frontend;
- compatibilidad con consumidores existentes.
Qué escrituras quedaban gated #
Las escrituras se trataron como una categoría separada.
Entre las capacidades que requerían gates estrictos estaban:
- guardar pedido;
- generar referencias de pago;
- registrar intentos de pago;
- aplicar folios de notas de crédito;
- cancelar referencias vencidas;
- actualizar estatus de referencia;
- modificar información de carrito;
- publicar cambios hacia e-commerce;
- sincronizar estados con sistemas externos.
Para estas operaciones, el gate mínimo era:
- llave idempotente o llave de negocio
- tabla de deduplicación
- transacción real
- auditoría por request
- single-writer
- write firewall
- reconciliación
- rollback documentado
- pruebas con reintentos
- pruebas con duplicados
- manejo de timeouts
- validación de side-effects
El flujo de pagos, por ejemplo, no podía tratarse como una simple llamada HTTP. Dependía de login, distribuidores, facturas pendientes, generación de referencia, consulta de referencias, actualización de estatus y notas de crédito.
Por eso, cualquier gap en actualización de estatus, generación de referencia o aplicación de notas debía resolverse antes de mover tráfico productivo.
Qué no se debía migrar todavía #
También fue importante definir lo que no debía moverse en Fase 1:
- No mover toda la base crítica.
- No apagar endpoints legacy con consumidores externos.
- No migrar jobs como si fueran APIs públicas.
- No hacer dual-write sin control.
- No mover escrituras sin idempotencia.
- No confiar solo en documentación estática.
- No retirar linked servers sin entender quién los usa.
- No hacer cutover sin reconciliación.
- No dejar secretos hardcodeados en endpoints nuevos.
- No aceptar endpoints sin telemetría.
- No cambiar contratos sin versionado o compatibilidad.
- No asumir que “sin tráfico visible” significa “sin uso”.
Este enfoque evitó uno de los errores más comunes en modernización: tratar un ecosistema distribuido como si fuera una aplicación monolítica bien delimitada.
El modelo mental final #
La migración quedó resumida en un loop repetible por endpoint o capability:
1. Descubrir
Inventario, dueño, runtime, consumidores reales.
2. Aislar
Contrato, payload, errores, side-effects, dependencias.
3. Implementar
Function/worker, configuración segura, adapters a SP.
4. Validar
Golden tests, A/B, DB side-effects, performance smoke.
5. Rutear
Canary progresivo por APIM.
6. Retirar
Apagar legacy solo cuando haya cero uso y rollback probado.
Este loop evita la falsa seguridad de “migramos el código” y cambia la conversación a:
Migramos comportamiento observable.
Lecciones aprendidas #
1. El inventario no es documentación; es una herramienta de reducción de riesgo #
Los documentos de arquitectura ayudan, pero en sistemas legacy la verdad suele estar en el tráfico, los logs, los jobs, los stored procedures y las conexiones reales.
La migración empezó realmente cuando se cruzaron endpoints, consumidores, procedimientos almacenados, tablas, servicios del sistema y dependencias externas.
2. APIM no fue solo gateway, fue mecanismo de control #
El gateway permitió transformar una migración binaria en una migración gradual.
Ruteo por endpoint, canary, rollback, correlation ID y medición de tráfico fueron más importantes que cualquier rewrite aislado.
3. Las escrituras son otro proyecto #
Leer desde un sistema nuevo es relativamente seguro.
Escribir requiere:
- idempotencia;
- auditoría;
- single-writer;
- dedupe;
- permisos;
- reconciliación;
- criterios de rollback;
- pruebas de duplicados;
- control de side-effects.
Sin eso, cualquier migración de pagos o pedidos puede crear inconsistencias difíciles de detectar.
4. Los jobs no son APIs #
Muchos endpoints legacy existían para disparar procesos de sincronización.
La arquitectura objetivo debía moverlos a workers o schedulers, no perpetuar endpoints HTTP operativos como interfaces públicas.
5. El data-plane manda #
Aunque la nueva API esté lista, si las bases siguen acopladas por linked servers, consultas externas o aplicaciones legacy escribiendo en paralelo, el riesgo sigue vivo.
La migración de APIs solo es segura cuando se gobierna también la propiedad del dato.
6. No todo legacy se apaga; primero se encapsula #
En sistemas críticos, el primer paso no siempre es reemplazar.
A veces es poner control delante, observar, encapsular, estabilizar y recién después retirar.
7. La paridad funcional no se asume; se prueba #
Que dos endpoints tengan el mismo nombre no significa que hagan lo mismo.
Hay que comparar:
- payloads;
- códigos de respuesta;
- errores;
- efectos secundarios;
- performance;
- dependencia de datos;
- comportamiento ante timeouts;
- comportamiento con entradas inválidas.
8. El roadmap debe ajustarse al tráfico real #
El alcance inicial casi siempre ignora consumidores ocultos: jobs, scripts, integraciones, herramientas internas, procesos batch o sistemas externos.
El tráfico real debe tener prioridad sobre el roadmap teórico.
Arquitectura objetivo #
A largo plazo, el destino no era simplemente mover runtime legacy a funciones, sino una arquitectura por dominios.
Edge:
- APIM
- Auth
- Rate limit
- Versionado
- Observabilidad
Application:
- Orders
- Billing & Payments
- Catalog & Inventory
- Customer / Distributor
- Logistics Tracking
- Communications
- Identity
Infrastructure:
- SP adapters por dominio
- HTTP clients encapsulados
- Key Vault
- Managed Identity
- Workers / schedulers
- Outbox / CDC donde aplique
Data:
- System of Record por dominio
- Read models
- Reconciliación
- Auditoría
- Eliminación gradual de linked servers
El objetivo no era eliminar todos los procedimientos almacenados de inmediato.
Era:
- encapsularlos;
- versionarlos;
- observarlos;
- aislarlos por dominio;
- reducir dependencias cruzadas;
- mover gradualmente lógica de integración fuera de SQL monolítico cuando aportara valor.
Resultado esperado #
Al cierre de la Fase 1, el éxito no se medía por “cuántos endpoints apagamos”, sino por haber construido una plataforma de migración segura.
El resultado esperado era:
- APIM gobernando legacy y nuevo;
- rutas por endpoint;
- canary y rollback;
- telemetría centralizada;
- dashboards por endpoint;
- secretos fuera del código;
- endpoints importantes listos o controlados;
- escrituras bloqueadas hasta cumplir gates;
- inventario de dependencias;
- runbooks de operación;
- matriz inicial de ownership de datos;
- reconciliación mínima para dominios críticos;
- definición de siguiente ola;
- criterios claros de decom del legacy.
A partir de ahí, las siguientes olas podían enfocarse en:
- workers de sincronización;
- wrappers para endpoints legacy críticos;
- reducción de linked servers;
- migración gradual de datos por dominio;
- decom ordenado del legacy;
- endurecimiento de seguridad;
- eliminación de dual-write;
- consolidación de contratos;
- pruebas automatizadas de regresión.
Checklist práctico para una migración similar #
Antes de mover tráfico #
[ ] Inventario de endpoints legacy
[ ] Inventario de endpoints nuevos
[ ] Mapping legacy → nuevo
[ ] Identificación de consumidores reales
[ ] Contratos request/response documentados
[ ] Stored procedures involucrados identificados
[ ] Tablas impactadas identificadas
[ ] Owners técnicos definidos
[ ] Owners de negocio definidos
[ ] APIM configurado
[ ] Observabilidad configurada
[ ] Secretos fuera del código
[ ] Runbook de rollback
Para lecturas #
[ ] Golden tests
[ ] Comparación A/B
[ ] Latencia dentro de umbral
[ ] Errores compatibles
[ ] Datos consistentes
[ ] Canary gradual
[ ] Rollback probado
Para escrituras #
[ ] Idempotencia
[ ] Dedupe
[ ] Transacción real
[ ] Auditoría
[ ] Single-writer
[ ] Write firewall
[ ] Reconciliación
[ ] Pruebas de duplicados
[ ] Pruebas de retry
[ ] Pruebas de timeout
[ ] Tripwires activos
[ ] Rollback probado
Para decom #
[ ] Cero tráfico observado
[ ] Consumidores migrados
[ ] Jobs reemplazados
[ ] Dependencias SQL resueltas
[ ] Secretos rotados
[ ] Monitoreo post-cutover
[ ] Plan de restauración definido
[ ] Evidencia documentada
Conclusión #
La modernización más segura no fue reescribir todo. Fue aceptar la complejidad real del sistema y diseñar una migración que pudiera convivir con ella.
El mayor cambio técnico no fue pasar de un runtime legacy a funciones serverless. Fue pasar de una operación implícita a una operación gobernada por evidencia:
sin inventario → con trazabilidad
sin contratos → con pruebas de paridad
sin visibilidad → con observabilidad
sin rollback → con ruteo reversible
sin ownership claro → con single-writer por dominio
sin big bang → con migración incremental
Ese fue el aprendizaje central:
En plataformas B2B críticas, la migración no empieza moviendo servidores. Empieza construyendo confianza.