Backend Architecture Series — Jocoso.cl eCommerce · #05
Integrando con MercadoLibre: Arquitectura para no depender de un gigante
148M usuarios activos, un solo modelo de dominio. ML como canal, no como fuente de verdad.
MercadoLibre: la oportunidad y el riesgo
MercadoLibre es el marketplace más grande de Latinoamérica con más de 148 millones de usuarios activos. Para un ecommerce en Chile, publicar en ML significa acceso inmediato a millones de compradores. El riesgo: muchos sistemas construyen su arquitectura alrededor de ML, convirtiendo al gigante en su fuente de verdad. Cuando ML cambia su API, el negocio queda paralizado.
■ Decisión Técnica
ML es un canal de venta, no la fuente de verdad. Jocoso.cl es la fuente de verdad. ML recibe datos desde Jocoso.cl, nunca al revés. Un producto existe en el sistema antes de publicarse en ML. El stock se gestiona en Jocoso.cl y se sincroniza hacia ML.
El modelo de datos: puentes hacia ML
Los identificadores de MercadoLibre se almacenan como campos opcionales en los modelos existentes. Un producto puede existir sin estar publicado en ML:
model Product {
id String @id @default(uuid())
name String
mlItemId String? // null = no publicado en ML
// ... otros campos
}
model ProductVariant {
id String @id @default(uuid())
productId String
stock Int @default(0)
mlVariationId String? // null = variante no sincronizada
// ... otros campos
}
-- Índices parciales: unicidad SOLO cuando el campo está presente
CREATE UNIQUE INDEX unique_ml_item_id
ON products (mlItemId) WHERE mlItemId IS NOT NULL;
CREATE UNIQUE INDEX unique_ml_variation_id
ON product_variants (mlVariationId) WHERE mlVariationId IS NOT NULL;Por qué índices parciales y no @unique simple
Un índice @unique simple en un campo nullable en PostgreSQL ya permite múltiples NULLs (NULL != NULL en SQL), pero usar un índice parcial es la solución correcta y explícita: comunica la intención de negocio al equipo, y permite agregar validaciones adicionales a nivel de índice en el futuro sin migración destructiva.
Consistencia lógica: regla mlVariationId → mlItemId
Si una variante tiene mlVariationId, su producto padre debe tener mlItemId. Una variante no puede estar en ML si su producto no está publicado. Esta regla se enforcea en la capa de dominio:
// domain/products/services/product.domain.service.ts
validateMlMapping(product: Product, variant: ProductVariant): void {
if (variant.getMlVariationId() && !product.getMlItemId()) {
throw new DomainException(
'Una variante no puede tener mlVariationId sin que el producto tenga mlItemId'
);
}
}Sincronización de stock: Jocoso.cl → ML
Cuando el stock cambia en Jocoso.cl (venta, ajuste, devolución), un job asíncrono con BullMQ sincroniza el nuevo stock hacia ML. El diseño es asíncrono por diseño: si ML tiene latencia o falla, las ventas en el canal web siguen funcionando sin interrupción.
// Flujo de sincronización
// 1. StockMovement creado (canal web)
// 2. SyncStockToMLJob encolado en BullMQ
// 3. Worker llama PUT /items/{mlItemId}/variations/{mlVariationId} con el nuevo stock
// 4. Si ML falla → retry exponencial (3 intentos, backoff 2^n seg)
// 5. Si ML sigue fallando → alerta pero canal web no se interrumpeVentas desde ML: webhook → dominio
Cuando un usuario compra en MercadoLibre, ML notifica via webhook. La arquitectura procesa la venta como cualquier otra — sin tratamiento especial:
// Flujo de venta desde ML
// 1. Webhook ML: { orderId, items: [{ mlVariationId, qty }] }
// 2. MLOrderWebhookHandler busca variant por mlVariationId
// 3. CreateOrder + DecreaseStock (FOR UPDATE, idempotente con externalId)
// 4. StockMovement con source: 'ML_SALE', referenceType: 'ORDER'
// 5. Si externalId ya existe → 200 OK sin procesar (idempotencia)OAuth2 de ML: tokens que expiran
La API de MercadoLibre usa OAuth2 con access tokens que expiran cada 6 horas. El sistema mantiene el access token y refresh token de ML en BD, y un interceptor HTTP los renueva automáticamente antes de cada request:
- Access token ML almacenado cifrado en BD (AES-256)
- Refresh automático si el token expira en menos de 30 minutos
- Retry automático del request original tras renovar token
La resiliencia como principio de diseño
El sistema está diseñado para funcionar aunque ML esté caído. Las ventas web procesan sin depender de la API de ML. La sincronización es eventual — cuando ML vuelve, el job reintenta. Esta decisión protege el negocio: MercadoLibre tuvo 6 incidentes mayores en 2024, y en cada uno los comercios con arquitecturas acopladas perdieron ventas.
■ Trade-offs
Sincronización eventual vs consistencia fuerte con ML. Con sincronización eventual existe una ventana donde el stock en ML puede estar desactualizado (segundos/minutos en condiciones normales). La alternativa — bloquear la venta web hasta confirmar sync con ML — crea un punto de falla externo. El trade-off elegido: preferir disponibilidad del canal propio sobre consistencia inmediata con el canal externo. El riesgo de overselling es mitigado por el retry rápido y monitoreo activo.
Extensibilidad: de ML a cualquier marketplace
El diseño con campos opcionales y jobs de sincronización aplica igual a Amazon Marketplace, Falabella, Paris o cualquier canal futuro. Para agregar un nuevo marketplace, solo se necesita:
- Agregar campos opcionales en Product/Variant (
amazonAsin,falabellaId, etc.) - Implementar el cliente HTTP del nuevo marketplace
- Agregar el job de sincronización correspondiente
- El dominio, la lógica de stock y los pagos no se modifican
Conclusión
Integrar con MercadoLibre no es difícil. Integrar con ML de forma que tu negocio no dependa de su disponibilidad, que tu modelo de dominio no se contamine con sus conceptos, y que puedas agregar otros marketplaces sin refactoring — eso requiere arquitectura. Jocoso.cl trata a ML como lo que es: un canal de distribución poderoso, no una fuente de verdad.