correctivo

This commit is contained in:
2026-02-10 18:18:03 +01:00
parent af9bf29678
commit df4715f287
2 changed files with 44 additions and 23 deletions

View File

@@ -1,4 +1,3 @@
// src/app/product/[id]/page.tsx
import { notFound } from "next/navigation";
import Link from "next/link";
import { ArrowLeft, Truck, ShieldCheck } from "lucide-react";
@@ -7,37 +6,51 @@ import { Product } from "@/types";
import ProductCard from "@/components/ui/ProductCard";
import ProductActions from "@/components/product/ProductActions";
// 1. Función para obtener el producto actual
// 1. Función para obtener el producto actual + NOMBRE de categoría
async function getProduct(id: string) {
// Solicitamos el producto y hacemos JOIN con la tabla categories
const { data, error } = await supabase
.from('products')
.select('*')
.select(`
*,
categories (
name
)
`)
.eq('id', id)
.single();
if (error || !data) return null;
return data as Product;
const categoryName = data.categories?.name || "General";
const productWithCategory = {
...data,
category: categoryName, // Llenamos el campo visual con el nombre real
};
return productWithCategory as unknown as Product;
}
// 2. Función para obtener productos relacionados
async function getRelatedProducts(category: string, currentId: string) {
// 2. Función para obtener productos relacionados (por ID de categoría)
async function getRelatedProducts(categoryId: string, currentId: string) {
const { data } = await supabase
.from('products')
.select('*')
.eq('category', category)
.eq('category_id', categoryId) // Filtramos por ID, es más seguro
.neq('id', currentId)
.eq('is_active', true)
.limit(3);
// Como estos son para tarjetas pequeñas, si no tienen el nombre de la categoría mapeado
// no es crítico, pero idealmente haríamos el mismo join si quisiéramos mostrarlo.
return (data as Product[]) || [];
}
// 3. DEFINICIÓN DEL COMPONENTE (Aquí estaba el error de tipos)
// En lugar de una interfaz separada, tipamos inline para evitar conflictos con Next.js 15
// 3. DEFINICIÓN DEL COMPONENTE
export default async function ProductPage(props: {
params: Promise<{ id: string }>
}) {
// Await obligatorio en Next.js 15 antes de acceder a params
const params = await props.params;
const { id } = params;
@@ -47,14 +60,18 @@ export default async function ProductPage(props: {
notFound();
}
const relatedProducts = await getRelatedProducts(product.category, product.id);
// Usamos category_id para buscar relacionados
const relatedProducts = await getRelatedProducts(product.category_id, product.id);
const imageUrl = getProductImageUrl(product.image_url);
// Fallback visual por si la categoría vino vacía
const displayCategory = product.category || "Colección";
return (
<div className="bg-white min-h-screen pb-32">
{/* Navegación Breadcrumb */}
<div className="pt-32 px-6 md:px-12 max-w-[1400px] mx-auto mb-8">
<Link href="/" className="inline-flex items-center gap-2 text-sm text-gray-400 hover:text-dark transition-colors">
<Link href="/shop" className="inline-flex items-center gap-2 text-sm text-gray-400 hover:text-dark transition-colors">
<ArrowLeft className="w-4 h-4" />
Volver al catálogo
</Link>
@@ -83,7 +100,7 @@ export default async function ProductPage(props: {
{/* COLUMNA DERECHA: Detalles */}
<div className="flex flex-col justify-center lg:py-12">
<span className="text-accent text-sm tracking-[0.2em] uppercase font-medium mb-4 block">
{product.category}
{displayCategory}
</span>
<h1 className="text-5xl md:text-6xl font-light text-dark mb-6 leading-[1.1] tracking-tight">
@@ -108,7 +125,7 @@ export default async function ProductPage(props: {
<p>{product.description || "Sin descripción disponible para este producto."}</p>
</div>
{/* Componente Interactivo */}
{/* Componente Interactivo (Cliente) */}
<ProductActions product={product} />
<div className="grid grid-cols-2 gap-4 text-xs text-gray-400 uppercase tracking-wider font-medium">
@@ -134,7 +151,7 @@ export default async function ProductPage(props: {
key={prod.id}
id={prod.id}
title={prod.name}
category={prod.category}
category={prod.category || "Sugerencia"}
price={prod.price}
imageUrl={getProductImageUrl(prod.image_url)}
index={idx + 1}

View File

@@ -1,3 +1,5 @@
// src/types/index.ts
export interface Product {
id: string;
created_at: string;
@@ -5,21 +7,23 @@ export interface Product {
description: string | null;
price: number;
stock: number;
category_id: string;
image_url: string | null; // El path dentro del bucket
image_url: string | null;
is_active: boolean;
stripe_product_id?: string;
stripe_price_id?: string;
// NUEVO: El ID real de la base de datos
category_id: string;
// COMPATIBILIDAD: Mantenemos 'category' como opcional para no romper la Home
category?: string;
}
export interface Category {
id: string;
name: string; // Ej: "Frutas & Verduras"
slug: string; // Ej: "frutas-verduras" (útil para URLs)
name: string;
slug: string;
description?: string;
}
export interface CartItem extends Product {
quantity: number;
}
}