Análisis de Confiabilidad del Sistema de Sincronización¶
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¶
Este documento identifica puntos de fallo potenciales, inconsistencias y áreas de mejora en el sistema de sincronización del Integrador Compulandia.
1. Puntos de Fallo Identificados¶
1.1 🔴 Crítico: Pérdida de Sincronización por Gate de publish¶
Ubicación: ProductSyncDispatcherService.php
Problema:
if ($productItem->publish === 0) {
return; // Se silencia completamente
}
Riesgo: Si un producto tiene publish = 0 pero debería sincronizarse (por ejemplo, para desactivarlo en los canales), el sync se ignora silenciosamente sin logging.
Impacto: Productos pueden quedar activos en canales externos cuando deberían estar desactivados.
Recomendación:
- Agregar logging cuando se omite un sync por publish = 0
- Considerar si product_status sync debería ejecutarse incluso con publish = 0
1.2 🔴 Crítico: Inconsistencia en Nombres de Cola¶
Ubicación: config/queue.php
Problema:
'redis_contimarket' => [
'queue' => 'contimaket', // ← Typo: "contimaket" vs "contimarket"
],
Riesgo: Si algún job se despacha a la cola correcta contimarket, no será procesado por el worker que escucha contimaket.
Recomendación: Corregir el typo y verificar consistencia en todos los nombres de colas.
1.3 🟠 Alto: Falta de Transaccionalidad en Cadena de Pricing¶
Ubicación:
- CalculatePricesJob.php
- EvaluateSelectedSupplierProductJob.php
Problema: La cadena CalculatePrices → EvaluateSelected → ProductItemObserver → Sync no es transaccional. Si falla en medio:
CalculatePricesJob ✓
↓
EvaluateSelectedJob ✓ (actualiza selected_supplier_product_id)
↓
ProductItemObserver ✓ (detecta cambio)
↓
ProductSyncDispatcher ✗ (falla)
Riesgo: El ProductItem queda con datos actualizados pero el canal externo no se sincroniza.
Impacto: Desincronización entre base de datos local y canales externos.
Recomendación: - Implementar patrón Saga con compensación - O usar eventos con retry garantizado - Al menos registrar ProductSyncLog en estado PENDING antes de cualquier cambio
1.4 🟠 Alto: Race Condition en Observer de ProductItem¶
Ubicación: ProductItemObserver.php
Problema: El observer usa wasChanged() que depende del estado en memoria. Si múltiples procesos actualizan el mismo ProductItem:
Proceso A: lee ProductItem (selected = 1)
Proceso B: lee ProductItem (selected = 1)
Proceso A: actualiza selected = 2, save() → Observer dispara sync
Proceso B: actualiza selected = 3, save() → Observer dispara sync
Riesgo: Ambos syncs se disparan pero con datos diferentes, posible inconsistencia final.
Recomendación:
- Usar locks optimistas (version column)
- O locks pesimistas en operaciones críticas
- Agregar deduplicación en ProductSyncLog (unique constraint ya existe pero verificar uso)
1.5 🟠 Alto: Comparación de Precios con Pérdida de Precisión¶
Ubicación: SupplierProductEvaluationStrategy.php
Problema:
$oldPrice = (int) $supplierProduct->getOriginal('regular_price');
$newPrice = (int) $supplierProduct->regular_price;
Convierte a entero antes de comparar, perdiendo decimales.
Riesgo: Cambios de precio significativos (ej: 1000.50 → 1000.99) no disparan recálculo.
Impacto: Precios desactualizados en canales.
Recomendación:
- Usar bccomp() para comparación decimal precisa
- O definir un threshold mínimo de cambio (ej: 1% o valor absoluto)
1.6 🟡 Medio: Ausencia de Idempotencia en Jobs¶
Ubicación: Múltiples jobs de sync
Problema: Los jobs no verifican si el sync ya fue realizado exitosamente. Si un job se reintenta (por timeout, etc.):
SyncToMedusaJob ejecuta
→ API Medusa OK (pero respuesta no llega)
→ Job timeout, se reintenta
→ API Medusa recibe duplicado
Riesgo: Datos duplicados o inconsistentes en canales externos.
Recomendación:
- Implementar idempotency keys en las APIs
- Verificar ProductSyncLog.synced_at antes de ejecutar
- Usar uniqueId() en jobs para prevenir duplicados en cola
1.7 🟡 Medio: Sincronización de Imágenes sin Validación¶
Ubicación: ProductImageObserver.php
Problema:
public function created(ProductItemImage $image)
{
if ($image->is_public) {
SyncToWooCommerceJob::dispatch('product_image', $product);
// No se despacha a otros canales (Medusa, etc.)
}
}
Riesgo: Las imágenes solo se sincronizan a WooCommerce directamente, otros canales dependen de ProductSyncDispatcherService que se llama en updated().
Impacto: Inconsistencia en imágenes entre canales.
Recomendación: Usar ProductSyncDispatcherService consistentemente para todos los eventos de imagen.
1.8 🟡 Medio: Falta de Validación en ProductCategory Sync¶
Ubicación: ProductCategoryObserver.php
Problema:
private function syncMedusaCategory(ProductCategory $category, bool $forceUpdate = false): void
{
try {
// ... sync logic
} catch (\Throwable $e) {
Log::channel(LogChannels::MEDUSA)->error("...");
// Error se loguea pero no se propaga ni se registra en ProductSyncLog
}
}
Riesgo: Fallos en sync de categorías a Medusa se pierden silenciosamente.
Impacto: Categorías desincronizadas sin manera de detectarlo o reintentarlo.
Recomendación:
- Crear un CategorySyncLog similar a ProductSyncLog
- O al menos registrar en una tabla de errores con retry automático
1.9 🟡 Medio: Content Generation Selectivo sin Fallback¶
Ubicación: SupplierProductEvaluationStrategy.php
Problema:
if (!str_starts_with($sku, 'PC-') && !str_starts_with($sku, 'PCM-')) {
return; // Solo genera contenido para estos prefijos
}
if ($supplier->short_name !== 'CL') {
return; // Solo proveedor CL
}
Riesgo: Productos de otros proveedores nunca tienen contenido generado por AI.
Impacto: Calidad inconsistente de contenido entre proveedores.
Recomendación: Documentar esta decisión de negocio o extender a más proveedores.
1.10 🟢 Bajo: Logs Dispersos sin Correlación¶
Problema: Cada componente loguea a su propio canal (PRICEJOB, SYNC_STRATEGY, MEDUSA, etc.) sin un ID de correlación.
Riesgo: Difícil rastrear el flujo completo de un sync específico.
Recomendación:
- Agregar correlation_id o trace_id en todos los logs de un mismo flujo
- Considerar usar el ProductSyncLog.id como correlation ID
2. Inconsistencias de Diseño¶
2.1 Mezcla de Dispatch Directo y a través de Dispatcher¶
Observación: Algunos observers despachan jobs directamente:
// ProductImageObserver
SyncToWooCommerceJob::dispatch('product_image', $product);
Mientras otros usan el dispatcher:
// ProductEvaluationStrategy
ProductSyncDispatcherService::dispatchSync($productItem, $syncContext);
Impacto: - Jobs despachados directamente no registran en ProductSyncLog - No se aplican filtros del dispatcher (publish check) - Inconsistencia en qué canales reciben el sync
Recomendación: Estandarizar en usar siempre ProductSyncDispatcherService.
2.2 Nombres de Jobs Inconsistentes¶
| Job | Patrón |
|---|---|
SyncToWooCommerceJob |
SyncTo{Channel}Job |
SyncToMedusaJob |
SyncTo{Channel}Job |
SyncToContimarketJob |
SyncTo{Channel}Job |
SyntToTiendaNaranjaJob |
SyntTo{Channel}Job ← Typo |
ReindexInAlgoliaJob |
Patrón diferente |
Recomendación: Renombrar a SyncToTiendaNaranjaJob por consistencia.
2.3 Constantes de Canal Inconsistentes¶
class SyncChannels
{
const WOO = 'woocommerce';
const TN = 'Tienda Naranja'; // ← Con espacio y mayúsculas
const CONTI = 'contimarket';
const ALGOLIA = 'algolia reindex'; // ← Con espacio
const MEDUSA = 'medusa';
}
Impacto: Dificulta búsquedas y filtros en base de datos.
Recomendación: Normalizar a snake_case sin espacios: tienda_naranja, algolia.
3. Gaps de Cobertura¶
3.1 Eventos No Observados¶
| Modelo | Evento | ¿Observado? | Impacto |
|---|---|---|---|
| ProductItem | deleted |
❌ | Producto eliminado no se desactiva en canales |
| SupplierProduct | deleted |
❌ | Proveedor eliminado no reevalúa selección |
| ProductItemImage | forceDeleted |
❌ | Imagen eliminada permanentemente no se limpia |
| ProductCategory | deleted |
❌ | Categoría eliminada persiste en canales |
3.2 Cambios No Detectados¶
| Campo | Modelo | Detectado | Impacto |
|---|---|---|---|
brand |
ProductItem | ❌ | Marca no se sincroniza |
weight, dimensions |
ProductItem | ❌ | Datos de envío desactualizados |
meta_title, meta_description |
ProductItem | ❌ | SEO no sincronizado |
4. Recomendaciones de Mejora¶
4.1 Inmediatas (Quick Wins)¶
- Corregir typos:
contimaket→contimarketen queue.php-
SyntToTiendaNaranjaJob→SyncToTiendaNaranjaJob -
Agregar logging en gates:
if ($productItem->publish === 0) { Log::channel('SYNC_STRATEGY')->info("Sync omitido: publish=0", [ 'product_id' => $productItem->id ]); return; } -
Normalizar constantes de canal
4.2 Corto Plazo¶
- Implementar observer para
deletedevents - Unificar dispatch a través de ProductSyncDispatcherService
- Agregar correlation ID en logs
- Mejorar comparación de precios con precisión decimal
4.3 Mediano Plazo¶
-
Implementar patrón Outbox para garantía de entrega:
DB Transaction: 1. Guardar cambio en modelo 2. Insertar en OutboxEvents table Proceso separado: 1. Leer OutboxEvents 2. Dispatch jobs 3. Marcar como procesado -
Agregar idempotency keys en jobs:
public function uniqueId(): string { return "sync:{$this->channel}:{$this->productId}:{$this->syncType}"; } -
Implementar circuit breaker para APIs externas
-
Dashboard de health check de sincronización:
- Syncs pendientes por más de X tiempo
- Tasa de fallos por canal
- Alertas automáticas
4.4 Largo Plazo¶
- Event Sourcing para historial completo de cambios
- Reconciliación periódica automática con canales externos
- Dry-run mode para preview de syncs
5. Matriz de Riesgos¶
| Riesgo | Probabilidad | Impacto | Prioridad | Mitigación |
|---|---|---|---|---|
| Sync perdido por publish=0 | Alta | Alto | 🔴 P1 | Logging + revisar lógica |
| Typo en cola contimarket | Media | Alto | 🔴 P1 | Corregir typo |
| Race condition en observers | Media | Medio | 🟠 P2 | Locks optimistas |
| Precios no detectados | Alta | Medio | 🟠 P2 | Comparación decimal |
| Jobs no idempotentes | Baja | Alto | 🟡 P3 | uniqueId() |
| Categorías no tracked | Baja | Bajo | 🟢 P4 | CategorySyncLog |
6. Checklist de Verificación¶
Antes de Cada Sync Manual¶
- [ ] Verificar que
publish = 1 - [ ] Verificar que existe
ProductItemViewpara el canal - [ ] Verificar que tiene imágenes públicas (para Contimarket)
- [ ] Verificar que tiene categoría activa
Monitoreo Diario¶
- [ ] Revisar ProductSyncLog con status = 'failed'
- [ ] Revisar logs de SYNC_STRATEGY para errores
- [ ] Verificar que colas no tienen jobs atascados (Horizon)
- [ ] Comparar conteo de productos activos vs canal externo
Monitoreo Semanal¶
- [ ] Ejecutar
sync:retry-pendingpara syncs estancados - [ ] Revisar
app:clean-product-sync-logspara mantener tabla limpia - [ ] Auditar productos sin sync reciente exitoso
Documento generado: 2025-12-30 Análisis basado en revisión de código del repositorio