From 7773bcdecffcfdb9f3924d3b809b1fb4d0d579d8 Mon Sep 17 00:00:00 2001 From: Cristian Felipe Cruz Buitron Date: Sun, 6 Sep 2026 19:17:47 +0000 Subject: [PATCH] =?UTF-8?q?fix(product-import):=20crear=20categor=C3=ADa/c?= =?UTF-8?q?olecci=C3=B3n=20si=20no=20existe=20en=20vez=20de=20descartarla?= =?UTF-8?q?=20en=20silencio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El importador solo enlazaba categoría/colección cuando el nombre del CSV matcheaba exacto (case-insensitive) una ya existente -- si no había match, category_ids/collection_id quedaban undefined sin ningún error. Así quedó el producto "Hello Kitty & Friends" importado sin colección: la colección nunca existió en la tienda, el importador nunca la creó ni avisó. Ahora, si el nombre no matchea, se crea la categoría/colección nueva (createProductCategoriesWorkflow / createCollectionsWorkflow) con el mismo slugify() ASCII-safe que ya usa el handle del producto, por la misma razón: el default de Medusa no quita tildes/eñes y rompe la ruta pública. El producto ya importado se corrigió en caliente vía Admin API (colección creada + enlazada + handle corregido de "hello-kitty-&-friends" a "hello-kitty-friends"), sin esperar este deploy. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01THZFwPzrwv6VAbuwmaf5vR --- .../src/api/admin/product-import/route.ts | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/workloads/commerce-backend/src/api/admin/product-import/route.ts b/workloads/commerce-backend/src/api/admin/product-import/route.ts index b64d39e..e1a46a4 100644 --- a/workloads/commerce-backend/src/api/admin/product-import/route.ts +++ b/workloads/commerce-backend/src/api/admin/product-import/route.ts @@ -7,7 +7,9 @@ import type { IStockLocationService, } from "@medusajs/framework/types"; import { + createCollectionsWorkflow, createInventoryItemsWorkflow, + createProductCategoriesWorkflow, createProductsWorkflow, uploadFilesWorkflow, } from "@medusajs/medusa/core-flows"; @@ -20,6 +22,15 @@ import { csvRowsToObjects, parseCsv } from "../../../lib/csv"; // fila se procesa independiente (una foto rota o categoria mal escrita // no tumba el resto del archivo). // +// categoria/coleccion: si el nombre no matchea una categoria/coleccion +// ya existente (case-insensitive), se crea nueva -- antes se descartaba +// en silencio y el producto quedaba sin categoria/coleccion sin avisar +// (confirmado en vivo: "Hello Kitty & Friends" nunca se creo como +// coleccion). El handle de la nueva categoria/coleccion se genera con +// el mismo slugify() ASCII-safe de mas abajo, por la misma razon que el +// handle del producto: el default de Medusa no quita tildes/enies y +// rompe la ruta publica. +// // Las fotos se descargan del origen y se re-alojan en el mismo // MinIO/S3 que ya usa el resto del catalogo (uploadFilesWorkflow -> // mismo File Module Provider de medusa-config.ts, ya corre en el NAS). @@ -215,8 +226,33 @@ export async function POST(req: MedusaRequest<{ csv: string }>, res: MedusaRespo const inventoryItemId = inventoryItems[0]?.id; if (!inventoryItemId) throw new Error("No se pudo crear el registro de inventario"); - const categoryId = row.categoria ? categoryByName.get(row.categoria.toLowerCase().trim()) : undefined; - const collectionId = row.coleccion ? collectionByName.get(row.coleccion.toLowerCase().trim()) : undefined; + const categoryName = row.categoria?.trim(); + let categoryId = categoryName ? categoryByName.get(categoryName.toLowerCase()) : undefined; + if (categoryName && !categoryId) { + const { result: createdCategories } = await createProductCategoriesWorkflow(req.scope).run({ + input: { + product_categories: [ + { name: categoryName, handle: slugify(categoryName, `${Date.now().toString(36)}${index}`) }, + ], + }, + }); + categoryId = createdCategories[0].id; + categoryByName.set(categoryName.toLowerCase(), categoryId); + } + + const collectionName = row.coleccion?.trim(); + let collectionId = collectionName ? collectionByName.get(collectionName.toLowerCase()) : undefined; + if (collectionName && !collectionId) { + const { result: createdCollections } = await createCollectionsWorkflow(req.scope).run({ + input: { + collections: [ + { title: collectionName, handle: slugify(collectionName, `${Date.now().toString(36)}${index}`) }, + ], + }, + }); + collectionId = createdCollections[0].id; + collectionByName.set(collectionName.toLowerCase(), collectionId); + } await createProductsWorkflow(req.scope).run({ input: {