ADR-0007· Categorías a Medusa: sync en cola, árbol por parent_category_id y estado en metadata¶
- Estado: propuesto
- Decisores: TI Compulandia (pendiente de revisión)
- Fecha de la decisión: 2026-09-15
Contexto extenso: análisis de sincronización de categorías y RFC 009 §2.7 / F-12. Este ADR registra sólo lo que compromete a futuro.
Contexto y problema¶
Las categorías de salida (Partner CL) se envían a Medusa desde dos observers, de forma sincrónica dentro del request del ABM, sin jerarquía y resolviendo por nombre. El resultado medido en dev: Medusa tiene 131 categorías y ninguna con padre; las 6 categorías padre locales apuntan a ids que ya no existen en Medusa porque, vistas como raíces vacías, fueron borradas desde el admin; y 6 renombres locales dejaron categorías duplicadas con el nombre viejo. El storefront necesita consultar y cachear el árbol desde el admin de Medusa. El equipo fijó dos restricciones: sin migraciones nuevas y simpleza por sobre completitud.
Opciones consideradas¶
- Ejecución: job en cola por categoría (patrón de los canales de salida, ADR-0003) / mantener la llamada sincrónica en el observer / sólo comando periódico.
- Identidad remota: id guardado +
external_id+ handle como cadena de resolución / sólo nombre (comportamiento actual). - Estado local: claves en
metadata/ tablacategory_sync_logs/ generalizarproduct_sync_logs. 3b.metadatade Medusa: reemplazar con lo que manda el integrador / fusionar preservando las claves ajenas. - Handle en renombre: estable tras la creación / regenerado desde el nombre.
- Borrado y desactivación:
is_active=false(+is_internal=trueal borrar) /DELETEen Medusa.
Decisión¶
1. Un job en cola por categoría; el observer sólo despacha. SyncCategoryToMedusaJob
corre en medusa_products con ShouldBeUniqueUntilProcessing, tries=3 y backoff,
como el resto de los canales de salida. Ningún request del ABM espera a Medusa. El
observer despacha en created, en updated de name, parent_id o active, y en
deleted. ProductCategoryObserver deja de escribir en Medusa.
2. El estado deseado se calcula desde la base local y el padre se sincroniza antes
que la hija. El payload lleva parent_category_id resuelto al id de Medusa del padre
local, external_id con el id local y rank=0. La identidad remota se resuelve en
orden: id guardado, external_id, handle; nunca por nombre. Resolver la identidad ya
devuelve el estado remoto, así que la comparación es directa: sólo se envían los campos
del estado deseado que difieren del remoto (update parcial, no pisa lo ajustado a mano en
el admin). medusa_hash guarda el último payload enviado como traza.
3. El metadata de Medusa se fusiona, nunca se reemplaza. El integrador es
dueño de una sola clave, integrador_category_id; el resto (icono, imagen y lo que
el negocio agregue desde el admin para la navegación del storefront) se conserva tal
cual. Medusa reemplaza el objeto metadata entero en cada update, así que el payload
se arma sobre el metadata remoto ya leído.
4. Sin tablas nuevas: el estado local vive en metadata de la categoría del
integrador. Se agregan las
claves medusa_synced_at, medusa_hash y medusa_last_error junto a la existente
id_medusa_category. La traza de fallos definitivos es la que ya provee el repo:
failed_jobs y el canal de log MEDUSA.
5. El handle es estable una vez creado. Un renombre local cambia sólo name. El
comando medusa:sync-categories ofrece --regenerate-handles para cuando el negocio
acepte cambiar URLs.
6. Nunca se borra en Medusa desde el integrador. active=0 se propaga como
is_active=false; un borrado local además marca is_internal=true. Es reversible y no
dispara la reubicación de hijas que hace Medusa al borrar un padre.
7. La migración inicial es un comando con --dry-run y orden topológico, que se
corre antes de habilitar la llave MEDUSA_CATEGORY_SYNC_ENABLED. Reporta las huérfanas
de Medusa pero no las toca; la limpieza es manual en el admin.
Consecuencias¶
Positivas¶
- El árbol queda en Medusa y el storefront lo consulta con
include_descendants_treeen una sola llamada cacheable. - Renombres y cambios de padre ya no crean duplicados ni rompen URLs.
- Cero migraciones; el ABM no espera a Medusa; los fallos son visibles en Horizon.
- El comando repara los 8 ids obsoletos actuales y sirve de reconciliación futura.
Negativas / deuda asumida¶
metadataguarda sólo el último estado por categoría, no un historial; para auditar hay que ir al log.- Escribir tres claves de
metadatano es atómico; un corte entre escrituras puede dejarmedusa_hashsinmedusa_synced_at. El próximo sync lo corrige porque compara contra el remoto, no contra el hash. - Cada sync por evento hace al menos un
GETpara resolver la identidad. Con ~130 categorías y cambios esporádicos es despreciable; no aplica el criterio "sin GET previos" del RFC 009, pensado para productos. - Handles estables implican que un renombre fuerte (por ejemplo "Notebook Gamer" a
"Gaming") deja una URL que no describe la categoría hasta que alguien corra
--regenerate-handles. - Categorías desactivadas o borradas localmente se acumulan como inactivas e internas en Medusa; la limpieza es manual.
- El camino WooCommerce de los observers queda intacto; el retiro de ese canal sigue pendiente de RFC 003.