Solución: Verificación de Sincronización con TiendaNaranja¶
Problema Original¶
Se estaban duplicando productos en TiendaNaranja con SKUs como COML-07012-1, COML-07012-2, etc.
Causa Raíz¶
Race condition: cuando un producto cambia múltiples veces antes de que TN procese la primera creación, cada job no encontraba el SKU en TN y creaba un duplicado.
Job #1 → crea producto → TN retorna process_id → producto en cola TN
Job #2 → getProductBySku() → no encuentra (TN aún procesa) → CREA DUPLICADO
Problemas Identificados¶
- Race condition en creación: Múltiples jobs intentaban crear el mismo producto
- No se usaba el
process_id: TN retorna unprocess_idque permite consultar el estado de la cola - Reintentos peligrosos:
scheduleRetrypor error de imágenes podía causar duplicados - Dos flujos paralelos:
scheduleRetryyVerifyTnProductSyncduplicaban lógica - Verificación no subía imágenes: El job de verificación solo verificaba datos
Solución Implementada¶
1. Protección contra Race Condition en ProductSyncService.syncProduct()¶
Antes de decidir crear, verificar si hay un process_id pendiente:
// 5.2) Verificar si hay un process_id pendiente (producto en cola de TN)
$hasPendingProcessId = ProductSyncLog::where('product_id', $productId)
->where('channel', SyncChannels::TN)
->where('response_message', 'EN_COLA_TN')
->where('attempted_at', '>', now()->subMinutes(15))
->exists();
if ($hasPendingProcessId && !$existsRemote) {
Log::channel(LogChannels::TN)->info(
"Producto tiene process_id pendiente en TN. Esperando que TN procese.",
['product_id' => $productId, 'sku_tn' => $skuTn]
);
return;
}
2. Marcar EN_COLA_TN Inmediatamente Después de Crear¶
En createProductWithImages(), guardar el process_id antes de intentar subir imágenes:
$processId = $dataResponse[0]['process_id'] ?? null;
if ($processId) {
ProductSyncLog::updateOrCreate(
['product_id' => $productId, 'channel' => SyncChannels::TN],
[
'status' => ProductSyncLog::STATUS_PENDING,
'response_message' => 'EN_COLA_TN',
'response_body' => $dataResponse,
'attempted_at' => now(),
'date' => now()->toDateString(),
]
);
}
3. Job Unificado de Verificación: VerifyTnProductSync¶
Un solo job que verifica TODOS los productos pendientes en cola de TN.
Características¶
ShouldBeUnique: Solo un job de verificación puede estar en cola a la vez- Verifica en batch: Procesa todos los
EN_COLA_TNpendientes en una sola ejecución - Re-encola si hay pendientes: Si quedan productos con
status: "queued", programa otra verificación - Sube imágenes: Si el producto se completó pero falta subir imágenes, las sube
Flujo del Job¶
┌─────────────────────────────────────────────────────────────────────────────┐
│ VerifyTnProductSync.handle() │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Buscar TODOS los logs con: │
│ - response_message = EN_COLA_TN │
│ - status = PENDING │
└─────────────────────────────────┘
│
▼
┌────────────────────┐
│ Para cada log: │
│ GET /product-status│◄─────────────┐
│ /{process_id} │ │
└────────────────────┘ │
│ │
┌──────────────┼──────────────┐ │
│ │ │ │
"queued" "completed" "error" │
│ │ │ │
▼ ▼ ▼ │
┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ Marcar │ │ Subir │ │ Marcar │ │
│ como │ │ imágenes │ │ FAILED │ │
│ "sigue │ │ si falta │ │ o retry │ │
│ pendiente│ │ → SUCCESS│ └──────────┘ │
│ │ └──────────┘ │
│ $still │ │
│ Pending++│ │
└──────────┘ │
│ │
└────────────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ Al final: │
│ ¿$stillPending > 0? │
└─────────────────────────────────┘
│ │
SÍ NO
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ self::dispatch│ │ No re-encola │
│ (delay 5 min) │ │ (todo listo) │
└───────────────┘ └───────────────┘
Endpoints de TN Utilizados¶
1. Crear/Actualizar Producto¶
POST /mpapi/sellers/me/addproduct
Respuesta:
[
{
"error": 0,
"message": "Producto en cola para procesamiento",
"process_id": "mpapi_product_695e542f541fa2.21377885"
}
]
2. Consultar Estado de Cola¶
GET /rest/V1/mpapi/sellers/me/product-status/{process_id}
Respuesta cuando aún procesa:
[
{
"error": 0,
"process_id": "mpapi_product_695e542f541fa2.21377885",
"status": "queued",
"timestamp": 1767789615,
"data": {
"seller_id": 235748,
"sku": "N/A"
}
}
]
Respuesta cuando ya procesó:
[
{
"error": 0,
"process_id": "mpapi_product_695e542f541fa2.21377885",
"status": "completed",
"timestamp": 1767789668,
"data": {
"error": 0,
"product_id": "1416195",
"message": "Producto editado con éxito",
"row_id": "1427394",
"seller_id": 235748,
"sku": "unknown"
}
}
]
Flujo Completo de Sincronización¶
┌─────────────────────────────────────────────────────────────────────────────┐
│ SYNC (CREATE o UPDATE) │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ ¿Hay process_id pendiente? │
│ (EN_COLA_TN < 15 min) │
└─────────────────────────────────┘
│ │
SÍ NO
│ │
▼ ▼
┌───────────────┐ ┌───────────────────┐
│ return │ │ Continuar sync │
│ (esperar) │ │ normal │
└───────────────┘ └───────────────────┘
│
▼
┌───────────────────────────┐
│ saveProduct() │
│ → TN retorna process_id │
└───────────────────────────┘
│
▼
┌───────────────────────────┐
│ Guardar EN_COLA_TN │
│ con process_id │
└───────────────────────────┘
│
▼
┌───────────────────────────┐
│ Intentar subir imágenes │
└───────────────────────────┘
│ │
OK FALLO
│ │
▼ ▼
┌──────────┐ ┌───────────────┐
│ SUCCESS │ │ Mantener │
│ │ │ EN_COLA_TN │
└──────────┘ │ (verify │
│ subirá imgs) │
└───────────────┘
│
▼
┌───────────────────────────┐
│ Despachar │
│ VerifyTnProductSync │
│ (delay 5 min) │
└───────────────────────────┘
═══════════════════════════════════════════════════════════════════════════════
5 MINUTOS DESPUÉS
═══════════════════════════════════════════════════════════════════════════════
┌─────────────────────────────────────────────────────────────────────────────┐
│ VerifyTnProductSync │
├─────────────────────────────────────────────────────────────────────────────┤
│ 1. Buscar todos los EN_COLA_TN pendientes │
│ 2. Para cada uno, consultar GET /product-status/{process_id} │
│ 3. Si "completed" → subir imágenes si faltan → marcar SUCCESS │
│ 4. Si "queued" → dejar pendiente, contar │
│ 5. Si "error" → marcar FAILED o reintentar sync │
│ 6. Si quedaron pendientes → re-encolar verify en 5 min │
└─────────────────────────────────────────────────────────────────────────────┘
Archivos Modificados¶
| Archivo | Cambio |
|---|---|
app/Services/TiendaNaranja/ProductSyncService.php |
Agregar verificación de hasPendingProcessId y guardar EN_COLA_TN inmediatamente después de crear |
app/Services/TiendaNaranja/ProductApiServiceTN.php |
Agregar método getProductQueueStatus($processId) |
app/Jobs/VerifyTnProductSync.php |
Refactorizar para verificar en batch y usar endpoint de estado de cola |
app/Jobs/SyntToTiendaNaranjaJob.php |
Simplificar handleSyncResult() |
Estados de ProductSyncLog¶
| response_message | status | Significado |
|---|---|---|
EN_COLA_TN |
PENDING | Producto enviado a TN, esperando que procese |
OK |
SUCCESS | Sincronización completada exitosamente |
PRODUCT_NOT_READY_FOR_IMAGES |
FAILED | TN aún no indexó, imágenes pendientes |
SIN CATEGORIA RELACIONADA |
FAILED | Producto sin categoría TN mapeada |
SIN IMAGEN |
FAILED | Producto sin imágenes locales |
Recomendaciones de TN¶
- No crear múltiples veces: La regla de TN es que no existan SKUs duplicados
- Consultar estado de cola: Usar
GET /product-status/{process_id}antes de reintentar - Ventana de 15 minutos: TN recomienda esperar máximo 15 minutos para que procese