Backend (commerce-backend):
- Módulo product-review (tabla nueva product_review; migración generada con
medusa db:generate, no toca tablas existentes).
- GET /store/reviews?product_id (solo aprobadas), /store/reviews/summary,
GET /store/reviews/me y POST /store/reviews con auth de cliente: exige que
el pedido sea del cliente, no esté cancelado y contenga el producto
("Compra verificada"); 1 reseña por cliente y producto; nace "pending".
- /admin/reviews (+ /:id POST status, DELETE) y página "Reseñas" en el admin
para aprobar/rechazar/borrar.
Storefront (ecommerce):
- /cuenta/pedidos ("Mis pedidos"): historial real + diálogo "Califica tu
compra" (radiogroup accesible, flechas de teclado).
- Ficha: sección de reseñas con "Compra verificada" y estrellas junto al
título; tarjetas: promedio y cantidad (una llamada /api/reviews/summary).
- Accesos: "Mis pedidos y reseñas" en la cuenta y en Ayuda.
Validado en entorno descartable (Postgres/Redis/Meilisearch/Medusa propios):
E2E de API 20/20 (incluye pedido interno por draft order, cancelación y
borrado), E2E de tienda con navegador 15/15, admin "Reseñas" verificado en
build de producción. Revisado por frontend-critic (contraste de estrellas
amber-600, patrón radiogroup).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018r19s4H8MBGegqToqw5guP
31 lines
1.2 KiB
TypeScript
31 lines
1.2 KiB
TypeScript
import { createReview, getProductReviews } from "@/lib/headless/providers/medusa-reviews";
|
|
|
|
function bearer(request: Request): string | null {
|
|
const header = request.headers.get("authorization") ?? "";
|
|
return header.startsWith("Bearer ") ? header.slice(7) : null;
|
|
}
|
|
|
|
export async function GET(request: Request) {
|
|
const productId = new URL(request.url).searchParams.get("product_id");
|
|
if (!productId) return Response.json({ error: "product_id es obligatorio" }, { status: 400 });
|
|
try {
|
|
return Response.json(await getProductReviews(productId));
|
|
} catch {
|
|
// Backend sin el modulo todavia (o caido): la ficha muestra "sin
|
|
// reseñas" en vez de romperse.
|
|
return Response.json({ reviews: [], average: 0, count: 0 });
|
|
}
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
const token = bearer(request);
|
|
if (!token) return Response.json({ error: "Inicia sesión para dejar una reseña" }, { status: 401 });
|
|
const body = await request.json().catch(() => ({}));
|
|
try {
|
|
return Response.json(await createReview(token, body), { status: 201 });
|
|
} catch (error) {
|
|
const status = (error as { status?: number }).status ?? 400;
|
|
return Response.json({ error: (error as Error).message }, { status });
|
|
}
|
|
}
|