Integración omnichannel sin reescribir el ERP de tu cadena de supermercados
Cómo conectar e-commerce, app móvil, marketplaces y conciliación bancaria a un SAP IS-Retail u Oracle Retail de quince años, sin migrarlo. El patrón que sí funciona en producción: middleware event-driven, no big-bang.
Si dirigís la tecnología de una cadena de supermercados con cincuenta o más tiendas en Centroamérica, hay una conversación que ya tuviste tres veces este año. Empieza con alguien — un consultor externo, un proveedor nuevo, un VP de operaciones recién nombrado — diciendo "el ERP está viejo, hay que migrarlo". Termina con vos haciendo cuentas mentales de lo que cuesta tocar el sistema que procesa nóminas, inventario, contabilidad y conciliación bancaria de cincuenta tiendas, y respondiendo lo mismo que respondiste las dos veces anteriores: "sí, pero no ahora."
No estás equivocado. Estás aplicando criterio.
Reescribir el ERP de un grocery de cincuenta tiendas no es una decisión técnica, es una decisión de continuidad de negocio que rara vez tiene la respuesta que parece. La pregunta correcta no es cómo migramos el ERP, es cómo sumamos capa moderna sin tocar lo que ya funciona. Este post es sobre cómo se hace eso bien.
La arquitectura del problema
Empecemos por mapear la realidad. La cadena típica que estoy describiendo opera con esta foto:
- Un ERP legacy — SAP IS-Retail, Oracle Retail, JD Edwards o un desarrollo a medida de hace doce años — que es la fuente de verdad de inventario, precios, contabilidad y maestros.
- Un POS multi-tienda que sincroniza con el ERP por lotes, típicamente al cierre de día.
- Un e-commerce propio montado sobre Magento, Shopify, VTEX o un Next.js artesanal, que mantiene su propio inventario "casi" sincronizado.
- Una app móvil del retailer (Android + iOS) que consume el mismo backend del e-commerce.
- Integraciones con marketplaces locales — PedidosYa, Hugo, Uber Eats — cada uno con su propio formato de catálogo, su propio webhook de pedidos, su propia política de stock.
- Conciliación bancaria al cierre con BAM, Banrural, Industrial y al menos otros tres bancos cuya integración se hace por archivos planos descargados manualmente cada mañana.
- Un sistema de cadena fría con sensores IoT que reportan a una plataforma propia del proveedor del refrigerador.
- Un puñado de planillas Excel que sostienen procesos críticos sobre los que nadie ya quiere preguntar.
El problema no es que ninguno de estos sistemas esté mal. El problema es que cada uno fue elegido por una razón válida en su momento, y ninguno fue diseñado pensando en que iba a tener que hablar con los otros siete en tiempo real.
La trampa silenciosa: nadie quiere reescribir el ERP, pero todos quieren "que el ERP responda en tiempo real al e-commerce". Eso no se resuelve migrando — se resuelve construyendo bien el espacio entre los sistemas.
El patrón que funciona: middleware event-driven
La respuesta correcta lleva veinte años escrita en libros de arquitectura empresarial y se ignora cada vez que aparece un proveedor nuevo prometiendo "plataforma unificada". Es esta:
No integres sistemas. Integrá eventos.
En vez de hacer que el e-commerce le pregunte al ERP el stock cada que un cliente carga el carrito, hacé que el ERP publique un evento inventory.updated cada vez que cambia su stock, y que todos los demás sistemas — e-commerce, app, marketplaces — escuchen ese evento y mantengan su propia copia del inventario actualizada de forma reactiva.
Esto se llama arquitectura event-driven con CDC (change data capture), y tiene tres beneficios que un CTO senior reconoce de inmediato:
- El ERP no se entera. No se le agrega carga de queries en tiempo real. Sigue haciendo lo que hace, en su ritmo, en sus ventanas.
- Los sistemas modernos no dependen de la disponibilidad del ERP. Si el ERP está en mantenimiento un domingo, el e-commerce sigue funcionando con su última copia válida del inventario.
- Cualquier sistema futuro se conecta sin tocar nada existente. Si dentro de seis meses entra Hugo Express y necesita el catálogo, se suscribe al stream de eventos. No se modifica el ERP, no se modifica el e-commerce.
El stack que recomendamos para grocery enterprise en LATAM:
- Bus de eventos · Apache Kafka (Confluent Cloud) o Redpanda
- CDC · Debezium para Oracle/SAP HANA, ksqlDB para transformación
- Adapter layer · NestJS / FastAPI con consumers idempotentes
- Schema registry · Confluent Schema Registry con Avro
- Observabilidad · OpenTelemetry → Grafana Cloud
- Hosting · AWS o GCP en región us-east o sa-east
Hay alternativas válidas — RabbitMQ con Streams, AWS EventBridge + SNS, Azure Service Bus — y la elección depende del compliance, presupuesto operativo, y dónde vive el resto de la infraestructura. La decisión arquitectónica importante no es qué bus, es que haya un bus.
Cómo se ve en código
Para hacerlo concreto: así se ve un consumer del evento order.created que sincroniza un pedido de PedidosYa hacia el POS y hacia el ERP en paralelo, sin acoplar uno con el otro.
// apps/integration-layer/src/consumers/order-created.ts
import { KafkaConsumer } from '@nestjs/kafka';
import { Idempotent } from '@/lib/idempotency';
import { ErpClient } from '@/clients/erp-sap';
import { PosClient } from '@/clients/pos';
import { OrderEvent, OrderEventSchema } from '@/schemas/order';
@KafkaConsumer({ topic: 'orders.created.v1', groupId: 'erp-pos-sync' })
export class OrderCreatedConsumer {
constructor(
private readonly erp: ErpClient,
private readonly pos: PosClient,
) {}
@Idempotent({
key: (event: OrderEvent) => `order:${event.orderId}:erp-pos-sync`,
ttl: '24h',
})
async handle(rawEvent: unknown) {
const event = OrderEventSchema.parse(rawEvent);
// Ambos sistemas reciben el evento en paralelo.
// Si uno falla, el otro NO se reverte — Kafka reintenta solo el que falló.
const [erpResult, posResult] = await Promise.allSettled([
this.erp.createSalesOrder({
externalRef: event.orderId,
channel: event.channel, // 'pedidosya' | 'web' | 'app' | 'pos'
store: event.storeCode,
items: event.items,
total: event.total,
}),
this.pos.registerOnlineOrder({
orderId: event.orderId,
store: event.storeCode,
items: event.items,
}),
]);
if (erpResult.status === 'rejected') throw erpResult.reason;
if (posResult.status === 'rejected') throw posResult.reason;
}
}Tres detalles técnicos que se ven en este código y que separan una integración que sobrevive de una que no:
1. El decorator @Idempotent. Si Kafka reentrega el evento — y lo va a reentregar, porque Kafka garantiza at-least-once, no exactly-once — el segundo intento detecta que el orderId ya fue procesado y no duplica nada. Sin idempotencia, el Día de la Madre vas a estar facturando los mismos pedidos dos veces y descontando inventario fantasma.
2. Promise.allSettled, no Promise.all. Si el POS está caído durante 90 segundos y el ERP sí responde, el pedido se carga al ERP. Cuando el POS vuelva, Kafka va a reentregar el evento — la idempotencia detecta que el ERP ya lo procesó y solo el POS recibe el alta. Cero reverts. Cero estado inconsistente.
3. Schemas validados (OrderEventSchema.parse). Cualquier evento mal formado falla rápido y se va a una dead letter queue. No corrompe el sistema downstream.
Implementación pragmática en 90 días
El error más común de las cadenas que intentan esto es querer hacer el corte completo el primer día. No funciona. La forma que funciona es por dominios de evento, en orden de menor a mayor riesgo:
Semanas 1-2 — Discovery técnico real. No documentado, real. Mapeás flujo por flujo qué pasa hoy, dónde están las planillas Excel, quién resincroniza qué a mano cada mañana. La regla: si no podés dibujar el flujo en una pizarra delante del director de operaciones y que asienta, todavía no entendiste el sistema.
Semanas 3-5 — Bus de eventos en sombra. Levantás Kafka, conectás Debezium al ERP, empezás a publicar eventos inventory.changed, price.updated, master.updated. Nadie los consume todavía. Estás midiendo volumen, latencia, integridad. Si el ERP genera 240.000 cambios de inventario por día, lo aprendés ahora, no en producción.
Semanas 6-8 — Primer consumer en producción. Elegís el caso más bajo riesgo. Recomendación: sincronizar el catálogo del ERP hacia un nuevo catalog service que sirve al e-commerce. Si falla, el e-commerce queda con catálogo de hace 12 horas — no es ideal pero no rompe nada operativo.
Semanas 9-12 — Pedidos omnichannel. Recién ahora. Eventos de pedido del e-commerce, app, PedidosYa, Hugo, todos van al mismo topic orders.created.v1. Un consumer los enruta al ERP. Otro consumer los enruta al POS de la tienda que despacha. La conciliación bancaria del cierre — ese consumer se construye en la fase siguiente, no acá.
A los 90 días tenés un middleware funcional, integraciones reales en producción, y cero líneas modificadas en el ERP. A los 180 días tenés conciliación bancaria automatizada y forecasting de demanda usando el stream de pedidos como fuente. A los 365 días podés evaluar si tiene sentido migrar el ERP — y la respuesta probablemente sigue siendo "todavía no", pero ahora por razones distintas.
Trade-offs y costos reales
Nada de esto es gratis. Los costos honestos para una cadena de cincuenta tiendas con tráfico web significativo:
| Lo que sí cuesta | Lo que ahorra | |
|---|---|---|
| INFRA MENSUAL | USD 2.000–8.000 mensuales (Kafka managed + adapter services + observability). | Cero costo de migración del ERP (USD 800k–4M evitados). |
| EQUIPO | 1 platform engineer senior + 1 mid + 1 SRE part-time durante el build. | Sin reentrenar al equipo de operaciones. Sigue usando el ERP como siempre. |
| LATENCIA | Eventual consistency: el e-commerce ve el inventario con 2–8 segundos de lag. | Aceptable para el 99% de casos grocery. Crítico solo en 14-sep o Black Friday GT, y se mitiga con cache local en el carrito. |
| DEBUGGING | Cuando algo falla, hay que correlacionar trazas entre ERP, bus, consumer y target. Sin observabilidad seria, te volvés loco. | Con observabilidad bien hecha, encontrás el evento problemático en menos de 10 minutos. |
| GOVERNANCE | Schemas, versioning, contratos entre equipos. Más disciplina. | El primer consumer nuevo se conecta en 2 días, no en 3 meses. |
El trade-off más subestimado es la eventual consistency. Cuando el cliente paga online a las 19:42 del Día de la Madre, el inventario en el ERP se descuenta entre 1 y 6 segundos después. En ese intervalo, otro cliente puede ver el producto disponible y comprarlo. Esto se mitiga con un inventory lock transitorio en el servicio de catálogo del e-commerce — descontás localmente, esperás confirmación del ERP, y revertís si el ERP rechaza. Es una capa más de complejidad. Vale la pena conocerla antes, no descubrirla en producción.
Errores comunes que vemos en grocery LATAM
Cinco que hemos visto repetir en cadenas serias:
1. Sincronizar inventario en tiempo real bidireccional desde el primer día. Mata el ERP. El ERP de la cadena no fue diseñado para responder a 800 queries por segundo de un Black Friday. Eventual consistency con CDC es la respuesta — no es opcional, es estructural.
2. Procesar pedidos sin idempotencia. Va a fallar el día más caro del año. Punto. No hay versión amable de esta lección.
3. No tener una dead letter queue. Cuando un evento mal formado entra al sistema, sin DLQ se queda en loop infinito de retry y satura el cluster. Con DLQ, va a una cola separada que un humano revisa una vez por día.
4. Confundir middleware con monolito. Algunos consultores te van a vender una "plataforma de integración unificada" que en realidad es un monolito mal disfrazado de bus. Si la herramienta no soporta múltiples consumers independientes por topic, no es event-driven, es un wrapper de cron jobs.
5. Saltearse observabilidad porque parece overhead. Es lo opuesto. Sin trazas distribuidas, el primer incidente te demuestra por qué OpenTelemetry no es opcional cuando hay seis sistemas hablando entre sí. Lo cubrimos en detalle en Observabilidad 24/7 en cadenas con 50+ tiendas.
Si tu ERP tiene quince años
Si tu ERP tiene quince años y tu e-commerce tiene tres, y la conversación más recurrente con tu equipo es "cómo hacemos para que se hablen mejor" — el camino correcto es construir bien el espacio entre ellos, no fusionarlos. Es más barato. Es más reversible. Y, lo más importante, no compromete la operación de cincuenta tiendas mientras lo construís.
Ese tipo de proyecto es exactamente donde nuestro retainer de ingeniería tiene sentido: 90 días para llevar el primer dominio a producción, 180 para tener tres dominios y la conciliación bancaria automatizada, y un equipo dedicado durante el camino. No vendemos plataforma — diseñamos y operamos la integración.
Si esto suena a tu situación, hablemos. Una llamada de 30 minutos. Yo llamo. Sin pitch, sin formulario largo — solo entender si tiene sentido seguir hablando.
Eddy
Ingeniero desde 1997. Fundador de FastNet. Construyo software para empresas que ya pasaron por agencias y descubrieron qué cuesta lo genérico. Vivo entre Los Ángeles y Centroamérica, y desde ahí miro el problema: cómo arman su sistema las cadenas que operan 24/7 con cinco sistemas que nunca se hablaron entre sí.
¿Esto te resuena? Hablemos →