feat(ecommerce): mensaje de urgencia por inventario real
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (pull_request) Successful in 26s
Build and Push Frontend / Construir y Subir Imagen (pull_request) Skipped

Expone ProductVariant.inventoryQuantity (ya calculado desde
variant.inventory_quantity en Medusa, hoy colapsado al booleano
available) y lo usa para mostrar "Quedan N" cuando el stock real es
bajo (<=5), tanto en la tarjeta de producto como en la ficha. null
indica inventario no gestionado (backorder / manage_inventory=false),
donde nunca se muestra un numero. ADR 0005 seccion 3 (patron eBay).

Catalogo mock actualizado (data/products.json) con
inventoryQuantity=null en todas las variantes para seguir tipando
contra Product[] via `satisfies`.
This commit is contained in:
devops committed 2026-08-25 14:33:14 -05:00
1 parent 707a05cc6e
commit 8fc9df3b50
6 files changed
+72 -22

No files matched your search

@@ -2,12 +2,12 @@
import Image from "next/image";
import Link from "next/link";
import { Heart, ShoppingBag, Sparkles } from "lucide-react";
import { Heart, ShoppingBag, Sparkles, TriangleAlert } from "lucide-react";
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import type { Product } from "@/lib/headless/types";
import { formatMoney } from "@/lib/utils";
import { formatMoney, LOW_STOCK_THRESHOLD } from "@/lib/utils";
import { useCartStore } from "@/store/cart-store";
import { useWishlistStore } from "@/store/wishlist-store";
@@ -23,6 +23,14 @@ export function ProductCard({ product, priority = false }: { product: Product; p
: null;
const [selectedVariantId, setSelectedVariantId] = useState(product.variants[0]?.id);
const selectedVariant = product.variants.find((variant) => variant.id === selectedVariantId) ?? product.variants[0];
const lowStock =
selectedVariant?.inventoryQuantity !== null &&
selectedVariant?.inventoryQuantity !== undefined &&
selectedVariant.inventoryQuantity > 0 &&
selectedVariant.inventoryQuantity <= LOW_STOCK_THRESHOLD
? selectedVariant.inventoryQuantity
: null;
return (
<article className="group flex h-full flex-col overflow-hidden rounded-3xl border border-slate-100 bg-white shadow-lg shadow-slate-900/5 transition duration-300 hover:-translate-y-1.5 hover:shadow-2xl hover:shadow-violet-500/10">
@@ -56,6 +64,9 @@ export function ProductCard({ product, priority = false }: { product: Product; p
))}
</div>
)}
{lowStock !== null && (
<span className="flex items-center gap-1.5 text-xs font-bold text-amber-600"><TriangleAlert className="size-3.5" />Quedan {lowStock}</span>
)}
<div className="mt-auto flex items-center justify-between gap-3 pt-1">
<div className="flex flex-col">
{product.compareAtPrice && <span className="text-xs font-bold text-slate-400 line-through">{formatMoney(product.compareAtPrice, product.currency)}</span>}