Saltar a contenido

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_view y 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.php
  • app/Observers/ProductItemObserver.php
  • app/Observers/ProductCategoryObserver.php
  • app/Observers/ProductImageObserver.php
  • app/Observers/ProductTagObserver.php
  • app/Observers/TagObserver.php

Estrategias de Evaluación

  • app/Services/Sync/Evaluation/ProductSyncEvaluatorFactory.php
  • app/Services/Sync/Evaluation/SupplierProductEvaluationStrategy.php
  • app/Services/Sync/Evaluation/ProductEvaluationStrategy.php
  • app/Services/Sync/Evaluation/ProductTagsEvaluationStrategy.php
  • app/Services/Sync/Evaluation/TagEvaluationStrategy.php

Dispatcher

  • app/Services/Sync/ProductSyncDispatcherService.php

Jobs

  • app/Jobs/SyncToWooCommerceJob.php
  • app/Jobs/SyncToMedusaJob.php
  • app/Jobs/SyncToContimarketJob.php
  • app/Jobs/SyntToTiendaNaranjaJob.php
  • app/Jobs/CalculatePricesJob.php
  • app/Jobs/EvaluateSelectedSupplierProductJob.php

Configuración

  • config/product_sync_channels.php
  • config/queue.php
  • config/horizon.php
  • config/logging.php

Modelos

  • app/Models/ProductSyncLog.php
  • app/Models/SupplierProduct.php
  • app/Models/ProductItem.php
  • app/Models/Price.php

Documento generado: 2025-12-30 Última actualización del análisis de código