diff --git a/workloads/ecommerce/app/product/[slug]/page.tsx b/workloads/ecommerce/app/product/[slug]/page.tsx
index 96dfb01..276d1ea 100644
--- a/workloads/ecommerce/app/product/[slug]/page.tsx
+++ b/workloads/ecommerce/app/product/[slug]/page.tsx
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import { MobileAddToCart } from "@/components/product/mobile-add-to-cart";
+import { ProductCard } from "@/components/product/product-card";
import { ProductCarousel } from "@/components/product/product-carousel";
import { ProductGallery } from "@/components/product/product-gallery";
import { ProductPurchase } from "@/components/product/product-purchase";
@@ -33,5 +34,58 @@ export default async function ProductPage({ params }: ProductPageProps) {
if (!product) notFound();
const products = await getProducts();
const recommended = products.filter((candidate) => candidate.id !== product.id && (candidate.category === product.category || candidate.collection === product.collection)).slice(0, 8);
- return <>{product.category} · {product.collection}{product.name}
{product.description}
Detalles del productoMarca: {product.brand}. Edad recomendada: {product.ageRange}. La disponibilidad y presentación final se confirman con Ari Shopping.Envíos y entregasCoordinamos envíos nacionales y entrega según ubicación. Los tiempos se confirman durante la cotización. {recommended.length > 0 && }>;
+ const suggested = recommended[0];
+
+ return (
+ <>
+ {/* max-w-[1600px] (antes max-w-7xl/1280px) y una 3ra columna desde
+ xl -- aprovecha pantallas grandes en vez de dejar el buy box
+ apretado contra el texto o cortado por el panel de zoom de al
+ lado (ver ProductGallery). Por debajo de xl, el buy box cae al
+ flujo normal en la 2da columna, sin duplicar el componente en
+ el DOM (evita el id="product-buy-button" repetido que rompería
+ el IntersectionObserver de MobileAddToCart). */}
+
+
+
+
{product.category} · {product.collection}
+
{product.name}
+
{product.description}
+
+
+ Detalles del producto
+ Marca: {product.brand}. Edad recomendada: {product.ageRange}. La disponibilidad y presentación final se confirman con Ari Shopping.
+
+
+ Envíos y entregas
+ Coordinamos envíos nacionales y entrega según ubicación. Los tiempos se confirman durante la cotización.
+
+
+
+ {/* Buy box estilo Amazon: precio/variantes/carrito en su propia
+ tarjeta, sticky desde xl mientras el resto de la pagina
+ scrollea. El sugerido usa el mismo cálculo de "recommended"
+ que ya alimenta el carrusel de abajo -- mismo criterio real
+ (categoría/colección), no un dato inventado. */}
+
+
+ {suggested && (
+
+
También te puede interesar
+
+
+ )}
+
+
+ {recommended.length > 0 && (
+
+ También te puede gustar
+
+
+ )}
+
+ >
+ );
}
diff --git a/workloads/ecommerce/components/product/mobile-add-to-cart.tsx b/workloads/ecommerce/components/product/mobile-add-to-cart.tsx
index c81421e..20501b7 100644
--- a/workloads/ecommerce/components/product/mobile-add-to-cart.tsx
+++ b/workloads/ecommerce/components/product/mobile-add-to-cart.tsx
@@ -6,16 +6,22 @@ import { Button } from "@/components/ui/button";
import type { Product } from "@/lib/headless/types";
import { useCartStore } from "@/store/cart-store";
+const SCROLL_THRESHOLD_PX = 80;
+
// Antes esta barra quedaba fija desde el primer render en mobile,
// tapando permanentemente parte de la imagen del producto solo para
-// duplicar un boton que ya está visible más abajo. Ahora se muestra
-// recien cuando el boton real (#product-buy-button en ProductPurchase,
-// visible tambien en mobile) sale del viewport -- mismo patron que el
-// "Buy Now" sticky de Amazon, que solo aparece al scrollear mas alla
-// del boton real.
+// duplicar un boton que ya está visible más abajo. Ahora combina dos
+// condiciones, igual que el "Buy Now" sticky de Amazon:
+// - scrolled: recien aparece despues de que el usuario empieza a
+// scrollear (nunca sobre la vista inicial de la imagen/hero).
+// - !buttonVisible: el boton real (#product-buy-button en
+// ProductPurchase, visible tambien en mobile) no esta en pantalla --
+// ya sea porque todavia no se llego a el, o porque se scrolleo mas
+// alla.
export function MobileAddToCart({ product }: { product: Product }) {
const addItem = useCartStore((state) => state.addItem);
- const [visible, setVisible] = useState(false);
+ const [buttonVisible, setButtonVisible] = useState(true);
+ const [scrolled, setScrolled] = useState(false);
const outOfStock = product.variants[0]?.available === false;
useEffect(() => {
@@ -23,12 +29,21 @@ export function MobileAddToCart({ product }: { product: Product }) {
if (!target) return;
const observer = new IntersectionObserver((entries) => {
const entry = entries[0];
- if (entry) setVisible(!entry.isIntersecting);
+ if (entry) setButtonVisible(entry.isIntersecting);
}, { threshold: 0 });
observer.observe(target);
return () => observer.disconnect();
}, []);
+ useEffect(() => {
+ const onScroll = () => setScrolled(window.scrollY > SCROLL_THRESHOLD_PX);
+ onScroll();
+ window.addEventListener("scroll", onScroll, { passive: true });
+ return () => window.removeEventListener("scroll", onScroll);
+ }, []);
+
+ const visible = scrolled && !buttonVisible;
+
return (