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 8ebd923..583cb07 100644 --- a/workloads/commerce-backend/src/api/admin/product-import/route.ts +++ b/workloads/commerce-backend/src/api/admin/product-import/route.ts @@ -115,6 +115,15 @@ function normalizeTitleForMatch(title: string): string { .toLowerCase(); } +// Categorias/colecciones: ademas de lo anterior, ignora tildes y eñes +// ("Figuras de accion" == "Figuras de acción"). Desde 2026-09-27 los +// nombres reales en Medusa llevan tildes; sin esto, un CSV escrito sin +// tildes (o viejo) crearia una categoria DUPLICADA en vez de reusar la +// existente. Los titulos de producto siguen con normalizeTitleForMatch. +function normalizeNameForMatch(name: string): string { + return normalizeTitleForMatch(name).normalize("NFD").replace(/[\u0300-\u036f]/g, ""); +} + function slugify(title: string, uniquePart: string): string { const base = title .normalize("NFD") @@ -166,7 +175,7 @@ export async function GET(req: MedusaRequest, res: MedusaResponse) { // ofrece descargar y lo que realmente se procesa. const header = "titulo,descripcion,precio,categoria,coleccion,cantidad,fotos"; const example = - '"Muñeco Buzz Lightyear","Figura articulada de 30cm, edición coleccionista.",89900,"Figuras de accion","Toy Story",3,"https://ejemplo.com/foto1.jpg;https://ejemplo.com/foto2.jpg"'; + '"Muñeco Buzz Lightyear","Figura articulada de 30cm, edición coleccionista.",89900,"Figuras de acción","Toy Story",3,"https://ejemplo.com/foto1.jpg;https://ejemplo.com/foto2.jpg"'; const csv = `${header}\n${example}\n`; res.setHeader("Content-Type", "text/csv; charset=utf-8"); res.setHeader("Content-Disposition", 'attachment; filename="plantilla-importacion.csv"'); @@ -217,10 +226,10 @@ export async function POST(req: MedusaRequest<{ csv: string }>, res: MedusaRespo } const categoryByName = new Map( - categories.filter((c) => c.name).map((c) => [normalizeTitleForMatch(c.name), c.id]), + categories.filter((c) => c.name).map((c) => [normalizeNameForMatch(c.name), c.id]), ); const collectionByName = new Map( - collections.filter((c) => c.title).map((c) => [normalizeTitleForMatch(c.title), c.id]), + collections.filter((c) => c.title).map((c) => [normalizeNameForMatch(c.title), c.id]), ); const productByTitle = new Map( existingProducts.filter((p) => p.title).map((p) => [normalizeTitleForMatch(p.title), p]), @@ -289,7 +298,7 @@ export async function POST(req: MedusaRequest<{ csv: string }>, res: MedusaRespo if (!inventoryItemId) throw new Error("No se pudo crear el registro de inventario"); const categoryName = row.categoria?.trim(); - let categoryId = categoryName ? categoryByName.get(normalizeTitleForMatch(categoryName)) : undefined; + let categoryId = categoryName ? categoryByName.get(normalizeNameForMatch(categoryName)) : undefined; if (categoryName && !categoryId) { const { result: createdCategories } = await createProductCategoriesWorkflow(req.scope).run({ input: { @@ -299,11 +308,11 @@ export async function POST(req: MedusaRequest<{ csv: string }>, res: MedusaRespo }, }); categoryId = createdCategories[0].id; - categoryByName.set(normalizeTitleForMatch(categoryName), categoryId); + categoryByName.set(normalizeNameForMatch(categoryName), categoryId); } const collectionName = row.coleccion?.trim(); - let collectionId = collectionName ? collectionByName.get(normalizeTitleForMatch(collectionName)) : undefined; + let collectionId = collectionName ? collectionByName.get(normalizeNameForMatch(collectionName)) : undefined; if (collectionName && !collectionId) { const { result: createdCollections } = await createCollectionsWorkflow(req.scope).run({ input: { @@ -313,7 +322,7 @@ export async function POST(req: MedusaRequest<{ csv: string }>, res: MedusaRespo }, }); collectionId = createdCollections[0].id; - collectionByName.set(normalizeTitleForMatch(collectionName), collectionId); + collectionByName.set(normalizeNameForMatch(collectionName), collectionId); } await createProductsWorkflow(req.scope).run({ diff --git a/workloads/ecommerce/app/catalog/page.tsx b/workloads/ecommerce/app/catalog/page.tsx index dac4e0c..ac25d61 100644 --- a/workloads/ecommerce/app/catalog/page.tsx +++ b/workloads/ecommerce/app/catalog/page.tsx @@ -11,6 +11,13 @@ type CatalogPageProps = { searchParams: Promise>; }; +// Sin tildes/eñes ni mayusculas: los links viejos (o escritos a mano) +// como ?category=Munecas y peluches siguen llevando a la categoria real +// "Muñecas y peluches" (renombrada con tildes el 2026-09-27). +function normalizeName(value: string): string { + return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").trim().toLowerCase(); +} + function single(value: string | string[] | undefined): string { return Array.isArray(value) ? value[0] ?? "" : value ?? ""; } @@ -19,7 +26,9 @@ export default async function CatalogPage({ searchParams }: CatalogPageProps) { const params = await searchParams; const [products, categories] = await Promise.all([getProducts(), getCategories()]); - const category = single(params.category) || "Todos"; + const requestedCategory = single(params.category) || "Todos"; + const category = + categories.find((node) => normalizeName(node.name) === normalizeName(requestedCategory))?.name ?? requestedCategory; const collection = single(params.collection); const query = single(params.q); const taxonomy = {