RFC-003: Deprecación de product_categories y migración a product_partner_categories¶
Fecha: 2026-01-15 Estado: Pendiente de implementación Autor: Sistema / HQuintero Relacionado con: RFC-001 (Unificación de Categorías), RFC-002 (Normalización match_categories)
Resumen Ejecutivo¶
Este documento analiza el impacto de deprecar la tabla product_categories y el campo products.category_id, migrando a la nueva estructura product_partner_categories que relaciona productos con categorías de Partners.
Cambio Conceptual¶
Modelo Anterior (Legacy)¶
Product.category_id ──► ProductCategory (tabla product_categories)
│
├── id_woocommerce (sync WooCommerce)
├── id_medusa_category (sync Medusa)
├── seo_formula
├── description_prompt
└── categoryAttributes (dimensiones, prompts)
Problema: La categoría es una sola, sin distinción de destino (Partner).
Modelo Nuevo¶
Product ──► product_partner_categories ──► Category (Partner CL)
│ └──► Category (Partner TN)
│ └──► Category (Partner X...)
│
└── partnerCategories() : BelongsToMany
└── mainCategory() : Category (Partner CL)
└── tnCategories() : BelongsToMany (Partner TN)
Ventaja: Un producto puede tener categorías diferentes por cada Partner de salida.
Inventario de Dependencias¶
1. Modelo Product¶
| Archivo | Línea | Código | Impacto |
|---|---|---|---|
app/Models/Product.php |
20 | 'category_id' en fillable |
Eliminar |
app/Models/Product.php |
37-40 | category() → ProductCategory |
Reemplazar por mainCategory() |
app/Models/Product.php |
42-52 | categoryAttributes() hasManyThrough |
Migrar a Metadata |
2. Modelo ProductCategory¶
| Archivo | Uso | Impacto |
|---|---|---|
app/Models/ProductCategory.php |
Modelo completo | Deprecar/Eliminar |
app/Observers/ProductCategoryObserver.php |
Sync a Medusa | Migrar a Category observer |
Campos de ProductCategory a migrar:
| Campo | Destino Nuevo |
|---|---|
name |
Category.name (Partner CL) |
parent_id_category |
Category.parent_id |
id_woocommerce |
Category.external_code o Metadata |
id_medusa_category |
Category.external_code o Metadata |
seo_formula |
Metadata (categorizable) |
description_prompt |
Metadata (categorizable) |
short_description_prompt |
Metadata (categorizable) |
seo_name_prompt |
Metadata (categorizable) |
3. Vista de Base de Datos (product_item_view)¶
| Archivo | Línea | Código | Impacto |
|---|---|---|---|
| Migración view | 34 | pb.category_id AS id_category |
Actualizar vista SQL |
La vista usa: products.category_id para obtener id_category
Debe cambiar a: Obtener de product_partner_categories donde Partner = CL
4. Servicios de Sincronización¶
WooCommerce¶
| Archivo | Línea | Código |
|---|---|---|
WoocommerceApiService.php |
620-621 | $product->product->category->id_woocommerce |
WoocommerceApiService.php |
243 | syncCategory(ProductCategory $category) |
Cambio requerido:
- Obtener categoría de Partner CL: $product->product->mainCategory()
- El id_woocommerce debe estar en la categoría de Partner CL o en Metadata
TiendaNaranja¶
| Archivo | Línea | Código |
|---|---|---|
ProductTransformer.php |
467-472 | $item->product->category_id |
ProductTransformer.php |
480-496 | Busca ProductCategory por ID, luego Category CL por nombre |
ProductTransformer.php |
510-551 | getTNCategoriesFromView() usa id_category |
Cambio requerido:
- Usar $product->tnCategories() directamente
- Eliminar búsqueda transitiva por nombre
Medusa¶
| Archivo | Línea | Código |
|---|---|---|
MedusaCategoryService.php |
115-174 | ensureAndAttachCategory() usa $p->category?->name |
MedusaProductSyncService.php |
2004-2008 | $product->category->categoryAttributes() |
Cambio requerido:
- Usar $product->mainCategory() para nombre
- Migrar categoryAttributes a Metadata
5. Jobs y Comandos¶
| Archivo | Uso | Impacto |
|---|---|---|
GenerateProductContentJob.php:190-199 |
$productItem->product->category para prompts |
Migrar prompts a Metadata |
MigrateSupplierProductsToProducts.php |
Crea ProductCategory | Actualizar para usar Category Partner CL |
SyncComprasParaguaiProducts.php |
Asigna category_id |
Cambiar a product_partner_categories |
TestGroupProducts.php |
Busca por category_id |
Actualizar query |
6. Controladores¶
| Archivo | Método | Uso |
|---|---|---|
ProductCategoryController.php |
CRUD completo | Deprecar o redirigir a Partner CL |
ProductItemViewController.php:408-428 |
updateParent() usa category_id |
Cambiar a partnerCategories |
AIContentController.php:291-302 |
Valida product->category |
Usar mainCategory() |
SeoNameController.php:15 |
category->seo_formula |
Obtener de Metadata |
7. Vistas Blade¶
| Archivo | Línea | Código |
|---|---|---|
product-info.blade.php |
49 | $productItem->product->category->name |
item-table.blade.php |
41 | $item->product->category->name |
index.blade.php |
243 | $item->product->category->name |
classification-section.blade.php |
28 | $productItem->product->category_id |
show.blade.php |
263 | $productItem->product->category_id |
Cambio: Todas deben usar $product->mainCategory()?->name
8. Resources (API)¶
| Archivo | Línea | Código |
|---|---|---|
ProductItemResource.php |
28-29 | $this->product->category->name y categoryAttributes |
ProductResourceCollection.php |
29 | $selectedProduct->product->category |
9. Otros Modelos Relacionados¶
| Modelo | Relación | Impacto |
|---|---|---|
Keyword |
product_category_id → ProductCategory |
Migrar a Category (Partner) |
CategoryAttribute |
id_category → ProductCategory |
Migrar a Metadata |
ProductItemView |
id_category |
Actualizar vista SQL |
Atributos de ProductCategory a Migrar¶
Tabla: Mapeo de Campos¶
| Campo en ProductCategory | Nuevo Destino | Tabla/Modelo |
|---|---|---|
id |
N/A (se elimina) | - |
name |
name |
Category (Partner CL) |
parent_id_category |
parent_id |
Category (Partner CL) |
id_woocommerce |
key='id_woocommerce' |
Metadata (categorizable) |
id_medusa_category |
key='id_medusa_category' |
Metadata (categorizable) |
seo_formula |
key='seo_formula' |
Metadata (categorizable) |
description_prompt |
key='description_prompt' |
Metadata (categorizable) |
short_description_prompt |
key='short_description_prompt' |
Metadata (categorizable) |
seo_name_prompt |
key='seo_name_prompt' |
Metadata (categorizable) |
Tabla: category_attributes → Metadata¶
| Campo Actual | Destino |
|---|---|
id_category |
categorizable_id (Category Partner CL) |
id_attribute |
Parte del key en Metadata |
value |
value en Metadata |
Estrategia de Migración¶
Opción A: Migración Gradual con Compatibilidad¶
- Mantener ambas relaciones temporalmente
- Agregar accessor en Product que prioriza nueva relación:
public function getEffectiveCategoryAttribute(): ?Category { // Primero intentar nueva relación $newCategory = $this->mainCategory(); if ($newCategory) return $newCategory; // Fallback a legacy return $this->category ? Category::forPartner(PartnerConstants::CL) ->where('name', $this->category->name) ->first() : null; } - Actualizar servicios uno por uno
- Eliminar legacy cuando todo use la nueva relación
Opción B: Migración Directa¶
- Ejecutar migración de datos completa
- Actualizar todos los servicios en un solo deploy
- Eliminar legacy inmediatamente
Recomendación: Opción A (gradual) para minimizar riesgo.
Orden de Actualización Sugerido¶
Fase 1: Preparación (sin romper nada)¶
- ✅ Crear tabla
product_partner_categories - ✅ Agregar relaciones nuevas en Product (
partnerCategories,mainCategory,tnCategories) - ✅ Crear tabla
metadatay modelo - Migrar datos de
ProductCategory→Category(Partner CL) + Metadata - Migrar datos de
products.category_id→product_partner_categories
Fase 2: Accessor de Compatibilidad¶
- Crear
getEffectiveCategoryAttribute()en Product - Crear helpers:
getEffectiveCategoryName(),getEffectiveCategoryPrompts()
Fase 3: Actualizar Servicios (por prioridad)¶
| Prioridad | Servicio | Razón |
|---|---|---|
| 1 | WoocommerceApiService | Sync activo diario |
| 2 | ProductTransformer (TN) | Sync activo diario |
| 3 | MedusaCategoryService | Sync activo |
| 4 | GenerateProductContentJob | Generación AI |
| 5 | Vistas Blade | UI interna |
| 6 | API Resources | Consumidores externos |
Fase 4: Actualizar Vista SQL¶
- Modificar
product_item_viewpara obtener categoría deproduct_partner_categories - Probar que no rompa queries existentes
Fase 5: Limpieza¶
- Eliminar campo
products.category_id - Deprecar modelo
ProductCategory - Eliminar tabla
product_categories(después de período de gracia) - Eliminar
CategoryAttribute(migrado a Metadata)
Impacto en Keywords¶
La tabla keywords tiene product_category_id que apunta a product_categories.
Opciones:
1. Migrar a category_id apuntando a categories (Partner CL)
2. Eliminar keywords y recrear asociados a Category nueva
Recomendación: Opción 1 con migración de datos.
Vista SQL Actualizada (Propuesta)¶
-- Cambio en product_item_view
-- ANTES:
pb.category_id AS id_category
-- DESPUÉS:
(
SELECT ppc.category_id
FROM product_partner_categories ppc
INNER JOIN categories c ON ppc.category_id = c.id
WHERE ppc.product_id = pb.id
AND c.categorizable_type = 'App\\Models\\Partner'
AND c.categorizable_id = 2 -- Partner CL
LIMIT 1
) AS id_category
Riesgos y Mitigación¶
| Riesgo | Probabilidad | Impacto | Mitigación |
|---|---|---|---|
| Sync WooCommerce falla | Alta | Alto | Accessor de compatibilidad |
| Sync TN falla | Alta | Alto | Probar en staging primero |
| Vista SQL lenta | Media | Medio | Índices en product_partner_categories |
| Pérdida de prompts AI | Baja | Alto | Migrar a Metadata antes de eliminar |
| Keywords huérfanos | Media | Bajo | Migrar antes de eliminar tabla |
Comandos de Migración Implementados¶
Migración de Dimensiones¶
# Ver estado de dimensiones Supplier CL vs Partner CL
php artisan categories:migrate-dimensions --dry-run
# Migrar dimensiones de Supplier CL a Partner CL
php artisan categories:migrate-dimensions
# Incluir limpieza de dimensiones en Supplier CL
php artisan categories:migrate-dimensions --clean-supplier
Migración de id_woocommerce¶
# Ver estado de id_woocommerce en Partner CL
php artisan categories:migrate-woocommerce-ids --dry-run
# Migrar id_woocommerce de product_categories a metadata de Partner CL
php artisan categories:migrate-woocommerce-ids
Sincronización de Categorías con WooCommerce (RFC-002)¶
# Ver qué categorías se sincronizarían (dry-run)
php artisan sync:woocommerce-categories --dry-run
# Sincronizar solo categorías sin id_woocommerce
php artisan sync:woocommerce-categories --missing-only
# Sincronizar y crear en WooCommerce las que no existen
php artisan sync:woocommerce-categories --create
# Combinación: solo las que faltan, crear si no existen
php artisan sync:woocommerce-categories --missing-only --create
Nota: Este comando reemplaza al deprecado app:sync-woocommerce-categories que usaba el modelo legacy ProductCategory.
Migración de master_category_id (TN → Partner CL)¶
# Ver relaciones TN → Supplier CL → Partner CL
php artisan categories:migrate-tn-master --dry-run
# Configurar master_category_id de categorías TN apuntando a Partner CL
php artisan categories:migrate-tn-master
Cambios en Observers¶
CategoryObserver (RFC-002)¶
El CategoryObserver fue actualizado para sincronizar categorías de Partner CL con WooCommerce y Medusa:
// Al crear categoría de Partner CL:
// - Sincroniza con WooCommerce (crea categoría y guarda id_woocommerce en metadata)
// - Sincroniza con Medusa (crea categoría y guarda id_medusa_category en metadata)
// Al actualizar nombre de categoría de Partner CL:
// - Actualiza en WooCommerce
// - Actualiza en Medusa
Deprecación de ProductCategoryObserver¶
El ProductCategoryObserver (para product_categories legacy) queda deprecado.
La sincronización ahora se maneja desde CategoryObserver para categorías de Partner CL.
Checklist Pre-Implementación¶
- [ ] Backup completo de
product_categories - [ ] Backup de
category_attributes - [ ] Verificar que todas las categorías existen en Partner CL
- [ ] Migrar prompts a Metadata
- [x] Migrar
id_woocommercea Metadata (categories:migrate-woocommerce-ids) - [x] Migrar
id_medusa_categorya Metadata (ya migrado) - [ ] Poblar
product_partner_categoriesdesdeproducts.category_id - [ ] Crear accessor de compatibilidad
- [ ] Probar en staging cada servicio
- [x] Actualizar
CategoryObserverpara sincronizar con WooCommerce y Medusa - [x] Eliminar fallback legacy en
WoocommerceApiService::getWooCommerceCategoryIds() - [x] Repurpose comando
sync:woocommerce-categoriespara Partner CL (RFC-002) - [x] Eliminar comando deprecado
app:sync-woocommerce-categories
Métricas de Éxito¶
- Sync WooCommerce: 0 errores por categoría no encontrada
- Sync TiendaNaranja: Productos con categorías correctas
- Generación AI: Prompts funcionando desde Metadata
- Vista SQL: Tiempo de query < 2x actual
Aprobación¶
- [ ] Revisado por: ___
- [ ] Aprobado por: ___
- [ ] Fecha de implementación: ___