Comercio Multi-Canal: La Arquitectura de un Checkout Unificado a Través de Cinco Proveedores
Cómo construir un checkout unificado a través de múltiples proveedores. Sincronización bidireccional, proxy de disponibilidad en tiempo real, scoping de canales, enrutamiento de pedidos y manejo de errores cuando los proveedores fallan en pleno checkout.
Multi-Canal no es "Conectarse a Amazon"
La mayoría de los artículos sobre comercio multi-canal describen cómo conectar tu tienda a las APIs de marketplaces. Eso es syndication, no arquitectura. El verdadero comercio multi-canal significa: múltiples proveedores con APIs diferentes, modelos de precios diferentes, formatos de disponibilidad diferentes y flujos de reserva diferentes, todo presentado al cliente como un solo checkout fluido.
Construimos una plataforma de agregación multi-proveedor que unifica el checkout a través de 5 APIs de proveedores. Un carrito, cinco proveedores, un pedido. Este artículo cubre la arquitectura que lo hace posible. Para la evaluación más amplia de plataformas de comercio, revisa nuestra guía de Vendure en producción y la guía de plataformas e-commerce.
El Problema del Checkout Unificado
Cada proveedor tiene su propia API, su propio formato de datos, su propio modelo de disponibilidad y su propio flujo de reserva:
| Aspecto | Proveedor A | Proveedor B | Proveedor C |
|---|---|---|---|
| Estilo de API | REST v2 | GraphQL | SOAP/XML |
| Precios | Por persona, dinámico | Precio fijo, niveles | Por grupo, negociado |
| Disponibilidad | API en tiempo real | Archivo de sync diario | Webhook al cambiar |
| Reserva | Dos pasos (reservar + confirmar) | Un paso (reserva instantánea) | Tres pasos (cotización + reservar + confirmar) |
| Cancelación | Gratis hasta 24h | No reembolsable | Reembolso parcial |
| Formato de ID | UUID | Numérico | Prefijo alfanumérico |
Un checkout unificado debe normalizar todo esto detrás de una sola interfaz. El cliente agrega artículos de diferentes proveedores a un solo carrito y paga una sola vez. El sistema enruta cada artículo al proveedor correcto, maneja el flujo de reserva por proveedor y presenta una sola confirmación al cliente.
El Patrón Supplier Adapter
Cada proveedor recibe un adapter que implementa una interfaz común:
interface SupplierAdapter {
search(query: SearchQuery): Promise<Product[]>;
checkAvailability(productId: string, date: string, persons: PersonConfig[]): Promise<AvailabilityResult>;
reserve(params: ReservationParams): Promise<Reservation>;
confirm(reservationId: string, bookerInfo: BookerInfo): Promise<BookingConfirmation>;
cancel(bookingId: string): Promise<CancellationResult>;
}
// Cada proveedor implementa la interfaz de forma diferente
class SupplierAAdapter implements SupplierAdapter {
async checkAvailability(productId: string, date: string, persons: PersonConfig[]) {
// Proveedor A: llamada API en tiempo real
const response = await this.httpClient.get(`/v2/availability/${productId}`, {
params: { date, adults: persons.filter(p => p.type === 'adult').length },
});
return this.normalizeAvailability(response.data);
}
}
class SupplierBAdapter implements SupplierAdapter {
async checkAvailability(productId: string, date: string, persons: PersonConfig[]) {
// Proveedor B: consulta GraphQL
const { data } = await this.graphqlClient.query({
query: AVAILABILITY_QUERY,
variables: { productId, date },
});
return this.normalizeAvailability(data.availability);
}
}
El adapter normaliza la respuesta de cada proveedor a un formato común. El servicio de checkout trabaja con el formato común, nunca con estructuras de datos específicas del proveedor.
Proxy de Disponibilidad en Tiempo Real
Algunos proveedores ofrecen precios en tiempo real que cambian por minuto (precios dinámicos, inventario limitado). Los resultados de búsqueda muestran un precio en caché, pero al momento del checkout el sistema debe verificar el precio actual.
async function getAvailabilityWithFallback(
productId: string, date: string, persons: PersonConfig[]
): Promise<AvailabilityResult> {
const supplier = getSupplierForProduct(productId);
if (supplier.supportsRealTimeAvailability) {
try {
// Precio en vivo desde la API del proveedor
const live = await supplier.adapter.checkAvailability(productId, date, persons);
await cache.set(`avail:${productId}:${date}`, live, { ttl: 300 });
return live;
} catch (error) {
// API del proveedor caída: devolver precio en caché con advertencia
const cached = await cache.get(`avail:${productId}:${date}`);
if (cached) {
return { ...cached, stale: true, warning: 'Price may have changed' };
}
throw new AvailabilityError('Cannot determine availability');
}
}
// Proveedor con disponibilidad en batch: devolver desde el índice
return searchIndex.getAvailability(productId, date);
}
El patrón proxy: intentar en vivo, caer al caché, caer al índice. Siempre informar al cliente si el precio podría estar desactualizado.
Sincronización Bidireccional: Prevenir Bucles Infinitos
Cuando dos sistemas sincronizan datos bidireccionalmente (sistema de comercio y sistema de operaciones), cada actualización del sistema A dispara un sync al sistema B, que a su vez dispara un sync de vuelta a A.
// El tracking de origen previene bucles infinitos
interface SyncMessage {
entityId: string;
entityType: string;
data: any;
source: string; // "commerce" | "operations" | "import"
correlationId: string;
}
async function handleSync(message: SyncMessage) {
// Ignorar mensajes que se originaron en este sistema
if (message.source === THIS_SYSTEM_ID) {
return; // ACK y saltar
}
// Procesar la sincronización
await updateEntity(message.entityId, message.data);
// Publicar actualización con NUESTRO tag de origen
await publishSync({
...message,
source: THIS_SYSTEM_ID, // Etiquetar para que el otro sistema lo ignore
});
}
Cada mensaje lleva un campo source. Cuando un sistema recibe un mensaje de sí mismo (a través del otro sistema), lo ignora. El bucle se rompe en el primer viaje de ida y vuelta.
Para más patrones event-driven incluyendo deduplicación y dead letter handling, revisa nuestra guía de arquitectura event-driven.
Channel Scoping: Visibilidad vs Publicación vs Disponibilidad
Tres conceptos que deben mantenerse separados:
| Concepto | Pregunta | Ejemplo |
|---|---|---|
| Visibilidad | Qué proveedores puede ver este canal? | El sitio alemán ve proveedores A, B, C. La API de socios solo ve proveedor A. |
| Publicación | Este producto está publicado y activo? | El producto existe pero no está publicado (borrador). |
| Disponibilidad | Se puede reservar este producto ahora? | El producto está publicado pero agotado para el sábado. |
// El canal determina la visibilidad
const channel = await channelStore.get(tenantId, channelId);
const visibleSupplierIds = channel.supplierIds;
// Filtrar productos por visibilidad del canal
const products = await searchIndex.search({
query: userQuery,
filters: {
supplier_id: { $in: visibleSupplierIds }, // Scoping del canal
status: 'active', // Publicación
},
});
// La disponibilidad se verifica al momento del checkout (concern separado)
Un producto puede ser visible (en la lista de proveedores del canal), publicado (estado = activo), pero no disponible (agotado). Cada uno es un filtro diferente en una etapa diferente del flujo.
Enrutamiento de Pedidos
Cuando un pedido contiene artículos de múltiples proveedores, cada artículo debe enrutarse al proveedor correcto para el fulfillment:
async function processMultiSupplierOrder(order: Order): Promise<OrderResult> {
// Agrupar artículos por proveedor
const itemsBySupplier = groupBy(order.items, item => item.supplierId);
// Procesar los artículos de cada proveedor independientemente
const results = await Promise.allSettled(
Object.entries(itemsBySupplier).map(async ([supplierId, items]) => {
const adapter = getAdapter(supplierId);
// Reservar
const reservation = await adapter.reserve({
items,
booker: order.bookerInfo,
expiresIn: 900, // 15 min de retención
});
// Confirmar
return adapter.confirm(reservation.id, order.bookerInfo);
})
);
// Manejar resultados mixtos
const successful = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');
if (failed.length > 0 && successful.length > 0) {
// Éxito parcial: algunos proveedores reservados, otros fallaron
// Cancelar las reservas exitosas? O confirmar el pedido parcial?
// Esta es una decisión de negocio, no técnica.
return handlePartialSuccess(order, successful, failed);
}
return { status: failed.length === 0 ? 'COMPLETED' : 'FAILED', results };
}
El Problema del Éxito Parcial
Qué pasa cuando el proveedor A confirma pero el proveedor B falla? Las opciones:
| Estrategia | Comportamiento | Ideal para |
|---|---|---|
| Todo o nada | Cancelar proveedor A si B falla | Pedidos de alto valor, eventos |
| Fulfillment parcial | Confirmar A, notificar al cliente sobre B | Productos intercambiables |
| Reintentar y luego cancelar | Reintentar B 3 veces, cancelar A si B falla definitivamente | Balance entre UX y confiabilidad |
La estrategia correcta depende del negocio. Para boletos de eventos (no fungibles), todo o nada es generalmente lo correcto. Para productos intercambiables, el fulfillment parcial es mejor.
Manejo de Errores: Cuando los Proveedores Fallan en Pleno Checkout
| Punto de falla | Qué pasó | Respuesta |
|---|---|---|
| Durante la búsqueda | Timeout de API del proveedor | Mostrar resultados de otros proveedores |
| Durante verificación de disponibilidad | Precio desactualizado | Mostrar precio en caché con advertencia |
| Durante la reserva | Inventario agotado | Eliminar artículo del carrito, sugerir alternativas |
| Durante la confirmación | Pago rechazado por el proveedor | Cancelar reserva, reintentar pago o reembolsar |
| Después de la confirmación | El proveedor envía cancelación | Notificar al cliente, ofrecer re-reserva o reembolso |
| Durante la cancelación | API del proveedor caída | Poner cancelación en cola para reintento |
Cada error debe manejarse explícitamente. "Algo salió mal" nunca es un mensaje de error aceptable para una transacción de comercio.
Errores Comunes
-
Asumir que todos los proveedores tienen el mismo flujo de reserva. Flujos de dos pasos, un paso y tres pasos, todos existen. El adapter debe normalizarlos.
-
Ignorar los cambios de precio entre búsqueda y checkout. Los precios dinámicos significan que el precio en checkout puede diferir del precio de búsqueda. Valida al momento del checkout.
-
Sin tracking de origen en la sincronización bidireccional. Sin eso, cada actualización genera un bucle infinito entre sistemas.
-
Tratar la visibilidad de canales como un concern de UI. El scoping de canales debe aplicarse en la capa de query, no solo en el frontend. Los consumidores de API ven los mismos filtros.
-
Sin estrategia de falla parcial. Los pedidos multi-proveedor van a fallar parcialmente. Define la política de negocio antes de que ocurra en producción.
-
Llamadas sincrónicas a proveedores durante el checkout. Si un proveedor es lento, todo el checkout espera. Procesa los proveedores en paralelo con timeouts individuales.
Puntos Clave
-
Los supplier adapters normalizan la diversidad. Cada proveedor recibe un adapter que implementa una interfaz común. La lógica de negocio nunca toca formatos específicos del proveedor.
-
La disponibilidad en tiempo real es un patrón proxy. Intentar en vivo, caer al caché, caer al índice. Siempre comunicar la frescura de los datos al cliente.
-
El tracking de origen previene bucles de sync. Cada mensaje lleva su origen. Los sistemas ignoran mensajes de sí mismos.
-
Visibilidad, publicación y disponibilidad son tres concerns separados. El canal determina la visibilidad. El estado del producto determina la publicación. El inventario determina la disponibilidad. No los mezcles.
-
La falla parcial es una decisión de negocio. Todo o nada vs fulfillment parcial depende del tipo de producto. Define la política antes de construir el código.
Construimos sistemas de comercio multi-canal como parte de nuestra práctica de e-commerce y software a medida. Si necesitas ayuda con la arquitectura de integración de proveedores, habla con nuestro equipo o solicita una cotización.
Temas cubiertos
Guías relacionadas
Arquitectura Event-Driven en la Práctica: Qué Sale Realmente Mal
Patrones reales de arquitectura event-driven en producción. Event storms, loops de sincronización bidireccional, dead letters, idempotencia y elección entre Kafka, RabbitMQ, BullMQ y Symfony Messenger.
Leer guíaGuía Empresarial de Sistemas de IA Agéntica
Guia tecnica de sistemas de IA agentica en entornos empresariales. Descubre la arquitectura, capacidades y aplicaciones de agentes IA autonomos.
Leer guíaComercio Agéntico: Cómo Dejar que los Agentes IA Compren de Forma Segura
Cómo diseñar comercio iniciado por agentes IA con gobernanza. Motores de políticas, puertas de aprobación HITL, recibos HMAC, idempotencia, aislamiento de tenants y el Agentic Checkout Protocol completo.
Leer guía¿Listo para construir sistemas de IA listos para producción?
Nuestro equipo se especializa en sistemas de IA listos para producción. Hablemos de cómo podemos ayudar.
Iniciar una conversación