Arquitectura de Sincronización - Compulandia Integrador¶
Nota de vigencia (2026-08-11): este análisis es anterior a los fixes de junio 2026 (lookup scopeado de
product_item_viewy política de reintentos — ver incidente 2026-06). Partes pueden estar desactualizadas; vigencia a confirmar por el equipo.
Resumen Ejecutivo¶
Este documento describe el sistema de sincronización del Integrador Compulandia, que permite la propagación automática de cambios en productos hacia múltiples canales de e-commerce (WooCommerce, Medusa, TiendaNaranja, Contimarket) y servicios de búsqueda (Algolia).
1. Visión General del Sistema¶
1.1 Canales de Sincronización¶
| Canal | Constante | Job | Cola | Conexión |
|---|---|---|---|---|
| WooCommerce | SyncChannels::WOO |
SyncToWooCommerceJob |
woocommerce_products |
redis_woocommerce_products |
| Medusa | SyncChannels::MEDUSA |
SyncToMedusaJob |
medusa_products |
redis_medusa_products |
| TiendaNaranja | SyncChannels::TN |
SyntToTiendaNaranjaJob |
tn_products |
redis_tn_products |
| Contimarket | SyncChannels::CONTI |
SyncToContimarketJob |
contimarket |
redis_contimarket |
| Algolia | SyncChannels::ALGOLIA |
ReindexInAlgoliaJob |
algolia |
redis_algolia |
1.2 Modelos Principales¶
┌─────────────────────┐ ┌─────────────────────┐
│ SupplierProduct │────▶│ ProductItem │
│ (Producto Proveedor)│ │ (Producto Unificado)│
└─────────────────────┘ └─────────────────────┘
│ │
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ Price │ │ ProductSyncLog │
│ (Precios por Canal)│ │ (Estado de Sync) │
└─────────────────────┘ └─────────────────────┘
2. Observers - Puntos de Entrada¶
Los Observers son el punto de entrada principal para disparar sincronizaciones. Están registrados en AppServiceProvider.php:
2.1 Tabla de Observers¶
| Observer | Modelo | Eventos | Acciones Principales |
|---|---|---|---|
SupplierProductObserver |
SupplierProduct |
created, updated | Cálculo de precios, evaluación de producto |
ProductItemObserver |
ProductItem |
updated | Evaluación y dispatch de sync |
CategoryObserver |
Category |
updated | Recálculo de precios de categoría |
PriceScaleObserver |
PriceScale |
updated, deleting | Recálculo de precios |
AttributeObserver |
Attribute |
created, updated | Sync atributos a WooCommerce |
TermObserver |
Term |
created, updated | Sync términos a WooCommerce |
ProductTagObserver |
ProductTag |
created, deleted | Sync tags de producto |
TagObserver |
Tag |
updated | Sync productos relacionados |
SupplierProductImageObserver |
ProductImage |
created | Procesamiento de imágenes |
ProductImageObserver |
ProductItemImage |
created, updated, deleted | Sync imágenes |
ProductCategoryObserver |
ProductCategory |
created, updated | Sync categorías |
2.2 Detalle de Observers Críticos¶
SupplierProductObserver¶
Archivo: app/Observers/SupplierProductObserver.php
// En created():
CalculatePricesJob::dispatch($supplierProduct); // → redis_pricing
CreateOrRelateCreatedProductJob::dispatch($supplierProduct); // → redis_new_supplier_product
// En updated():
ProductSyncEvaluatorFactory::makeFor($supplierProduct)->evaluate($supplierProduct);
ProductItemObserver¶
Archivo: app/Observers/ProductItemObserver.php
// En updated():
ProductSyncEvaluatorFactory::makeFor($productItem)->evaluate($productItem);
3. Estrategias de Evaluación¶
El sistema usa el Patrón Strategy para determinar qué acciones tomar según el tipo de cambio detectado.
3.1 Factory de Estrategias¶
Archivo: app/Services/Sync/Evaluation/ProductSyncEvaluatorFactory.php
public static function makeFor(Model $model): ProductSyncEvaluationStrategy
{
return match(get_class($model)) {
SupplierProduct::class => new SupplierProductEvaluationStrategy(),
ProductItem::class => new ProductEvaluationStrategy(),
ProductTag::class => new ProductTagsEvaluationStrategy(),
Tag::class => new TagEvaluationStrategy(),
};
}
3.2 SupplierProductEvaluationStrategy¶
Archivo: app/Services/Sync/Evaluation/SupplierProductEvaluationStrategy.php
Evalúa cambios en SupplierProduct y dispara jobs apropiados:
| Cambio Detectado | Condición | Job Disparado | Cola |
|---|---|---|---|
| Stock | total_stock cambió |
EvaluateSelectedSupplierProductJob |
evaluate_selected_product |
| Precio | regular_price, special_price (parte entera) |
CalculatePricesJob |
pricing |
| Oferta | offer_start, offer_end |
CalculatePricesJob |
pricing |
| Reasignación | id_product_item cambió |
EvaluateSelectedSupplierProductJob (x2) |
evaluate_selected_product |
| Contenido | name, short_description, long_description |
GenerateProductContentJob |
content_generation |
Filtros de Contenido:
- Solo para SKUs con prefijo PC- o PCM-
- Solo proveedor CL (Compulandia)
- Solo si el SupplierProduct está seleccionado
3.3 ProductEvaluationStrategy¶
Archivo: app/Services/Sync/Evaluation/ProductEvaluationStrategy.php
Evalúa cambios en ProductItem y dispara sincronizaciones:
| Cambio Detectado | Contexto de Sync | Canales |
|---|---|---|
seo_name, short_description, ean, upc, part_number |
product_basic |
Todos |
product_id (reparenting) |
variant_reparent |
Solo Medusa |
active |
product_status |
Todos |
selected_supplier_product_id → valor |
product_basic |
Todos |
selected_supplier_product_id → null |
product_status |
Todos |
Gate Universal: Si publish === 0, no se procesa ningún sync.
3.4 ProductTagsEvaluationStrategy¶
Archivo: app/Services/Sync/Evaluation/ProductTagsEvaluationStrategy.php
Maneja cambios en la relación ProductItem ↔ Tag:
- Tag agregado: Dispatch SyncToWooCommerceJob('product_tags', $product)
- Tag removido: Dispatch SyncToWooCommerceJob('product_tags', $product)
3.5 TagEvaluationStrategy¶
Archivo: app/Services/Sync/Evaluation/TagEvaluationStrategy.php
Cuando cambia Tag.active:
- Carga todos los ProductItem relacionados
- Dispara SyncToWooCommerceJob('product_tags', $product) para cada uno
4. Flujo de Cálculo de Precios¶
4.1 Cadena de Jobs de Pricing¶
SupplierProduct (created/updated)
│
▼
┌─────────────────────────┐
│ CalculatePricesJob │ ← redis_pricing
│ │
│ • Valida precio regular │
│ • Valida categorías │
│ • Aplica escala │
│ • Calcula por canal │
│ • Guarda en Price │
└─────────────────────────┘
│
│ (si tiene id_product_item)
▼
┌─────────────────────────────────┐
│ EvaluateSelectedSupplierProductJob│ ← redis_evaluate_selected_product
│ │
│ • Evalúa elegibilidad │
│ • Selecciona mejor proveedor │
│ • Actualiza selected_supplier │
└─────────────────────────────────┘
│
│ (si selected_supplier cambió)
▼
┌─────────────────────────┐
│ ProductItemObserver │
│ (updated) │
└─────────────────────────┘
│
▼
┌─────────────────────────────┐
│ ProductEvaluationStrategy │
│ │
│ • Detecta cambio en │
│ selected_supplier_product │
│ • Llama dispatchSync() │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ ProductSyncDispatcherService│
│ │
│ • Itera canales habilitados │
│ • Crea instancia de Job │
│ • Registra ProductSyncLog │
│ • Dispatch a cola │
└─────────────────────────────┘
4.2 Fórmula de Cálculo de Precios¶
Para proveedores NO-CL:
basePrice = regular_price × categoryFactor
priceWithMargin = max(basePrice, regular_price + margin_min)
priceWithIVA = priceWithMargin × IVA_RATE (si no incluye IVA)
finalPrice = priceWithIVA × channelFactor
Para proveedor CL:
finalPrice = regular_price × channelFactor
4.3 Escala de Precios (PriceScale)¶
La escala se aplica según el regular_price:
| Nivel | Límite Inferior | Límite Superior | Factor | Margen Mín |
|---|---|---|---|---|
| 1 | 0 | 100,000 | 1.15 | 5,000 |
| 2 | 100,001 | 500,000 | 1.12 | 10,000 |
| ... | ... | ... | ... | ... |
5. Dispatcher de Sincronización¶
5.1 ProductSyncDispatcherService¶
Archivo: app/Services/Sync/ProductSyncDispatcherService.php
Método principal: dispatchSync(ProductItem $productItem, string $syncContext)
public function dispatchSync(ProductItem $productItem, string $syncContext): void
{
// Gate: productos no publicados no sincronizan
if ($productItem->publish === 0) {
return;
}
$channels = config('product_sync_channels');
foreach ($channels as $channelName => $config) {
if (!$config['enabled'] || !class_exists($config['job'])) {
continue;
}
// Crear instancia del job según configuración
if ($config['sync_type_resolver']) {
$job = new $config['job']($syncContext, $productItem);
} else {
$job = new $config['job']($productItem);
}
// Registrar log de sync
if ($config['log_sync'] ?? true) {
$this->registerSyncLog($productItem, $channelName);
}
// Dispatch a cola configurada
dispatch($job)
->onQueue($config['queue'])
->onConnection($config['connection']);
}
}
5.2 Configuración de Canales¶
Archivo: config/product_sync_channels.php
return [
SyncChannels::WOO => [
'enabled' => true,
'log_sync' => true,
'sync_type_resolver' => true, // recibe $syncType
'job' => SyncToWooCommerceJob::class,
'queue' => 'woocommerce_products',
'connection' => 'redis_woocommerce_products',
],
SyncChannels::MEDUSA => [
'enabled' => true,
'log_sync' => true,
'sync_type_resolver' => true,
'job' => SyncToMedusaJob::class,
'queue' => 'medusa_products',
'connection' => 'redis_medusa_products',
],
// ... otros canales
];
6. Jobs de Sincronización¶
6.1 SyncToWooCommerceJob¶
Archivo: app/Jobs/SyncToWooCommerceJob.php
Tipos de sync soportados:
- attribute - Sincroniza atributo
- term - Sincroniza término
- category - Sincroniza categoría
- product_basic - Datos básicos del producto
- product_tags - Tags del producto
- product_image - Imágenes del producto
- product_attributes - Atributos del producto
- product_status - Estado activo/inactivo
Configuración:
- tries = 3
- backoff = 10 segundos
- Usa trait TracksProductSyncFailures
6.2 SyncToMedusaJob¶
Archivo: app/Jobs/SyncToMedusaJob.php
Tipos de sync:
- TYPE_VARIANT_BASIC (default) - Sync completo de variante
- TYPE_VARIANT_REPARENT - Mover variante a otro producto
- TYPE_PRODUCT_STATUS - Solo estado de publicación
Configuración:
- tries = 3
- backoff = 30 segundos
- timeout = 300 segundos (5 min)
- Manejo especial de rate limits (429) → requeue 60s
6.3 SyncToContimarketJob¶
Archivo: app/Jobs/SyncToContimarketJob.php
Validaciones previas: - ProductItem debe tener imágenes - Debe existir ProductItemView con imágenes públicas - Si falta imagen → log "SIN IMAGEN"
6.4 SyntToTiendaNaranjaJob¶
Archivo: app/Jobs/SyntToTiendaNaranjaJob.php
Validaciones: - Producto debe estar en canal TiendaNaranja - Debe tener datos completos
7. Sistema de Tracking (ProductSyncLog)¶
7.1 Modelo ProductSyncLog¶
Archivo: app/Models/ProductSyncLog.php
Campos:
| Campo | Tipo | Descripción |
|-------|------|-------------|
| product_id | int | FK a ProductItem |
| channel | string | Nombre del canal |
| status | enum | pending, success, failed |
| date | date | Fecha del intento |
| attempted_at | timestamp | Último intento |
| synced_at | timestamp | Sync exitoso |
| response_code | int | Código HTTP |
| response_message | text | Mensaje de error/éxito |
| payload_sent | json | Request enviado |
| response_body | json | Response recibido |
7.2 Estados del Sync¶
┌─────────┐ dispatch() ┌─────────┐
│ │ ─────────────────▶ │ │
│ (new) │ │ PENDING │
│ │ │ │
└─────────┘ └────┬────┘
│
┌──────────────┴──────────────┐
│ │
▼ ▼
┌─────────┐ ┌─────────┐
│ │ │ │
│ SUCCESS │ │ FAILED │
│ │ │ │
└─────────┘ └────┬────┘
│
retry command│
│
▼
┌─────────┐
│ │
│ PENDING │
│ │
└─────────┘
7.3 Trait TracksProductSyncFailures¶
Archivo: app/Jobs/Concerns/TracksProductSyncFailures.php
Automáticamente registra fallos cuando un job falla definitivamente:
public function failed(?\Throwable $exception): void
{
$this->markSyncAsFailed($exception);
}
7.4 Comandos de Retry¶
RetryFailedSyncs:
php artisan app:retry-failed-syncs {channel} --limit=100 --dry-run
RetryPendingSyncs:
php artisan sync:retry-pending --channel=medusa --older-than=30 --include-failed
8. Diagrama de Flujo Completo¶
ENTRADA DE DATOS
│
┌───────────────────────────┼───────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ API Suppliers │ │ Comandos │ │ UI/Manual │
│ (Webhook/Cron)│ │ Artisan │ │ (Livewire) │
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
│ │ │
└──────────────────────────┼──────────────────────────┘
│
▼
┌──────────────────────────┐
│ MODELO ELOQUENT │
│ (SupplierProduct, │
│ ProductItem, etc.) │
└────────────┬─────────────┘
│
│ save()
▼
┌──────────────────────────┐
│ OBSERVERS │
│ created() / updated() │
└────────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ EVALUATOR FACTORY │
│ ProductSyncEvaluator │
│ Factory │
└────────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ EVALUATION STRATEGY │
│ │
│ • SupplierProduct... │
│ • ProductEvaluation... │
│ • ProductTags... │
│ • TagEvaluation... │
└────────────┬─────────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ PRICING JOBS │ │ SYNC DISPATCH │
│ │ │ │
│ • CalculatePrices │ │ ProductSync │
│ • EvaluateSelected│───▶│ DispatcherService │
│ • RecalculateCat │ │ │
└───────────────────┘ └─────────┬─────────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ WOOCOMMERCE │ │ MEDUSA │ │ TIENDANARANJA │
│ │ │ │ │ CONTIMARKET │
│ SyncToWooCommerce │ │ SyncToMedusaJob │ │ ALGOLIA │
│ Job │ │ │ │ │
└─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘
│ │ │
└──────────────────────┼──────────────────────┘
│
▼
┌──────────────────────┐
│ ProductSyncLog │
│ │
│ status: pending → │
│ success | │
│ failed │
└──────────────────────┘
9. Colas y Horizon¶
9.1 Configuración de Colas¶
| Cola | Conexión | Procesos (Prod) | Descripción |
|---|---|---|---|
pricing |
redis_pricing |
1 | Cálculo de precios |
evaluate_selected_product |
redis_evaluate_selected_product |
1 | Evaluación de proveedor |
new_product |
redis_new_supplier_product |
1 | Productos nuevos |
woocommerce_products |
redis_woocommerce_products |
3 | Sync WooCommerce |
medusa_products |
redis_medusa_products |
3 | Sync Medusa |
tn_products |
redis_tn_products |
3 | Sync TiendaNaranja |
contimarket |
redis_contimarket |
3 | Sync Contimarket |
algolia |
redis_algolia |
2 | Indexación Algolia |
content_generation |
redis_content_generation |
1 | Generación AI |
category_pricing |
redis_category_pricing |
1 | Recálculo categoría |
9.2 Supervisores Horizon¶
Definidos en config/horizon.php para cada cola con:
- balance: 'auto'
- tries: 3
- Procesos según tabla anterior
10. Puntos de Entrada Adicionales¶
10.1 Scheduled Tasks (routes/console.php)¶
| Frecuencia | Comando | Descripción |
|---|---|---|
| Cada hora | app:sync-cl-api |
Sync Compulandia API |
| Cada hora | app:cleanup-expired-product-offers |
Limpieza ofertas |
| Diario | app:sync-fx-api |
Sync Fastrax |
| Diario | app:sync-ngo-api |
Sync NGO |
| Diario | medusa:schedule-price-list-syncs |
Sync precios Medusa |
| 2x día | app:update-selected-products |
Actualizar selección |
| 2x día | algolia:index-product-items |
Reindex Algolia |
| 2x semana | app:sync-tn-products |
Sync TiendaNaranja |
| 2x semana | app:sync-contimarket-api |
Sync Contimarket |
10.2 API Endpoints¶
| Endpoint | Método | Descripción |
|---|---|---|
/api/supplier-products |
POST | Bulk sync desde proveedores |
/api/view-product |
POST | Tracking de vistas + sync |
/api/update-product-webhook |
POST | Webhook WooCommerce |
10.3 UI Manual¶
ProductItemViewController::dispatchSync()- Botón de sync manual- Livewire components para gestión de productos
11. Archivos Clave¶
Observers¶
app/Observers/SupplierProductObserver.phpapp/Observers/ProductItemObserver.phpapp/Observers/ProductCategoryObserver.phpapp/Observers/ProductImageObserver.phpapp/Observers/ProductTagObserver.phpapp/Observers/TagObserver.php
Estrategias de Evaluación¶
app/Services/Sync/Evaluation/ProductSyncEvaluatorFactory.phpapp/Services/Sync/Evaluation/SupplierProductEvaluationStrategy.phpapp/Services/Sync/Evaluation/ProductEvaluationStrategy.phpapp/Services/Sync/Evaluation/ProductTagsEvaluationStrategy.phpapp/Services/Sync/Evaluation/TagEvaluationStrategy.php
Dispatcher¶
app/Services/Sync/ProductSyncDispatcherService.php
Jobs¶
app/Jobs/SyncToWooCommerceJob.phpapp/Jobs/SyncToMedusaJob.phpapp/Jobs/SyncToContimarketJob.phpapp/Jobs/SyntToTiendaNaranjaJob.phpapp/Jobs/CalculatePricesJob.phpapp/Jobs/EvaluateSelectedSupplierProductJob.php
Configuración¶
config/product_sync_channels.phpconfig/queue.phpconfig/horizon.phpconfig/logging.php
Modelos¶
app/Models/ProductSyncLog.phpapp/Models/SupplierProduct.phpapp/Models/ProductItem.phpapp/Models/Price.php
Documento generado: 2025-12-30 Última actualización del análisis de código