fix(ecommerce): overflow horizontal en mobile hacía ver todo "a tamaño real"
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 30s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 19m41s

Root cause real del bug reportado por el usuario (imagen de producto
viéndose gigante/cortada en Chrome y Edge Android) -- no era la imagen:
al mover "También te puede gustar" DENTRO del grid de la página (para
el layout de 2 filas del commit anterior), su carrusel horizontal
(overflow-x-auto interno, sin flex-wrap) empezó a contar como item de
ese grid. Sin min-w-0, el ancho mínimo intrínseco de TODAS las cards
del carrusel sin wrappear se usa para dimensionar la columna
compartida del grid -- en mobile (grid de una sola columna), eso
empujaba TODO (imagen, título, specs) a ~800px de ancho en un viewport
real de ~380px. El navegador termina "zoomeando" el layout para que
entre, dando la sensación de que todo se ve a tamaño real / cortado.

Confirmado en vivo con bisección real: sin el fix, el grid medía 783px
en un viewport de 380px; agregando min-w-0 al contenedor del carrusel
vuelve a 380px. Se agrega min-w-0 también a los otros 3 contenedores
del grid (galería, info, buy-box) y a la tira de miniaturas dentro de
ProductGallery (mismo patrón de overflow-x-auto) como defensa
preventiva -- práctica estándar para items de grid/flex con scroll
horizontal interno.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y4df3Z7odr9Cg8F2nw6uV
This commit is contained in:
devopsandClaude Sonnet 5 committed 2026-09-14 00:29:29 +00:00
1 parent f92e8e6b36
commit 1c9693e573
2 files changed
+19 -4

No files matched your search

@@ -52,7 +52,7 @@ export default async function ProductPage({ params }: ProductPageProps) {
esperar a que termine la columna más alta antes de aparecer. */} esperar a que termine la columna más alta antes de aparecer. */}
<section className="mx-auto grid max-w-[1600px] gap-x-10 gap-y-12 px-4 py-10 lg:grid-cols-[1.08fr_.92fr] lg:py-16 xl:grid-cols-[1.1fr_.75fr_360px] xl:grid-rows-[auto_auto]"> <section className="mx-auto grid max-w-[1600px] gap-x-10 gap-y-12 px-4 py-10 lg:grid-cols-[1.08fr_.92fr] lg:py-16 xl:grid-cols-[1.1fr_.75fr_360px] xl:grid-rows-[auto_auto]">
<ProductGallery images={product.images} name={product.name} /> <ProductGallery images={product.images} name={product.name} />
<div> <div className="min-w-0">
<span className="text-xs font-black uppercase tracking-[.18em] text-ari-coral">{product.category} · {product.collection}</span> <span className="text-xs font-black uppercase tracking-[.18em] text-ari-coral">{product.category} · {product.collection}</span>
<h1 className="mt-3 text-4xl font-black tracking-tight sm:text-6xl">{product.name}</h1> <h1 className="mt-3 text-4xl font-black tracking-tight sm:text-6xl">{product.name}</h1>
<p className="mt-5 text-lg leading-8 text-slate-600">{product.description}</p> <p className="mt-5 text-lg leading-8 text-slate-600">{product.description}</p>
@@ -79,7 +79,7 @@ export default async function ProductPage({ params }: ProductPageProps) {
scrollea. Los sugeridos (hasta 2) usan el mismo cálculo de scrollea. Los sugeridos (hasta 2) usan el mismo cálculo de
"recommended" que ya alimenta el carrusel de abajo -- mismo "recommended" que ya alimenta el carrusel de abajo -- mismo
criterio real (categoría/colección), no un dato inventado. */} criterio real (categoría/colección), no un dato inventado. */}
<div className="xl:sticky xl:top-24 xl:row-span-2 xl:self-start"> <div className="min-w-0 xl:sticky xl:top-24 xl:row-span-2 xl:self-start">
<div className="rounded-2xl border border-slate-200 p-6 shadow-sm"> <div className="rounded-2xl border border-slate-200 p-6 shadow-sm">
<ProductPurchase product={product} /> <ProductPurchase product={product} />
</div> </div>
@@ -92,8 +92,16 @@ export default async function ProductPage({ params }: ProductPageProps) {
</div> </div>
)} )}
</div> </div>
{/* min-w-0 es necesario: sin esto, el carrusel horizontal
(overflow-x-auto interno) contaba como item de este grid, y su
ancho minimo intrinseco (la suma de TODAS las cards sin
wrappear, ya que el scroller no tiene flex-wrap) empujaba todo
el grid completo a ~800px en mobile -- afectaba a TODA la
columna compartida (imagen, titulo, specs), no solo al
carrusel. Confirmado en vivo: sin min-w-0 el grid medía 783px
en un viewport real de 380px; con min-w-0 vuelve a 380px. */}
{recommended.length > 0 && ( {recommended.length > 0 && (
<div className="pb-16 lg:col-span-2"> <div className="min-w-0 pb-16 lg:col-span-2">
<h2 className="mb-7 text-3xl font-black">También te puede gustar</h2> <h2 className="mb-7 text-3xl font-black">También te puede gustar</h2>
<ProductCarousel products={recommended} /> <ProductCarousel products={recommended} />
</div> </div>
@@ -109,7 +109,14 @@ export function ProductGallery({ images, name }: { images: string[]; name: strin
}; };
return ( return (
<div className="grid gap-4 lg:grid-cols-[86px_1fr]"> <div className="min-w-0 grid gap-4 lg:grid-cols-[86px_1fr]">
{/* min-w-0: esta tira de miniaturas es un overflow-x-auto (scroll
horizontal) igual que el carrusel de "También te puede gustar"
en app/product/[slug]/page.tsx -- mismo bug si este componente
termina como item de un grid externo sin min-w-0: para productos
con muchas fotos, el ancho minimo intrinseco de TODAS las
miniaturas sin wrappear puede forzar overflow horizontal en toda
la pagina en mobile. */}
<div className="order-2 flex gap-3 overflow-x-auto lg:order-1 lg:flex-col"> <div className="order-2 flex gap-3 overflow-x-auto lg:order-1 lg:flex-col">
{images.map((image) => <button type="button" key={image} onClick={() => { setSelected(image); reset(); }} onMouseEnter={() => { setSelected(image); reset(); }} className={`relative aspect-square w-20 shrink-0 overflow-hidden rounded-2xl border-2 ${selected === image ? "border-fuchsia-500" : "border-transparent"}`}><Image src={image} alt="" fill sizes="80px" className="object-cover" /></button>)} {images.map((image) => <button type="button" key={image} onClick={() => { setSelected(image); reset(); }} onMouseEnter={() => { setSelected(image); reset(); }} className={`relative aspect-square w-20 shrink-0 overflow-hidden rounded-2xl border-2 ${selected === image ? "border-fuchsia-500" : "border-transparent"}`}><Image src={image} alt="" fill sizes="80px" className="object-cover" /></button>)}
</div> </div>