Descripción general
Accede a los datos de productos a través de dos métodos:getStore()- Devuelve todos los productos de la tienda (incluidos en la respuesta de la tienda)getProduct()- Obtiene un único producto por ID o sluggetProductReviews()- Obtiene reseñas paginadas para un producto
Obtener todos los productos
Los productos están incluidos en la respuesta de la tienda:const response = await komerza.getStore();
if (response.success) {
const products = response.data.products;
products.forEach((product) => {
console.log(product.name, product.variants.length, "variantes");
});
}
Obtener un producto individual
Obtén un producto por ID o slug:const response = await komerza.getProduct("product-id-or-slug");
Parámetros
El ID único del producto (UUID) o el slug de URL
Ejemplo
// Por ID
const byId = await komerza.getProduct("550e8400-e29b-41d4-a716-446655440000");
// Por slug
const bySlug = await komerza.getProduct("premium-license");
if (bySlug.success) {
const product = bySlug.data;
console.log(product.name);
console.log(product.description);
console.log(product.variants);
}
Objeto Product
interface Product {
id: string; // Identificador único
name: string; // Nombre del producto
description: string; // Descripción (admite markdown)
slug?: string; // Slug compatible con URL
imageNames: string[]; // Nombres de archivo de imágenes del producto (ver página Images)
rating: number; // Valoración media de reseñas
order: number; // Orden de visualización
visibility: number; // 0=Público, 1=No listado, 2=Privado
storeId: string; // ID de la tienda padre
variants: Variant[]; // Variantes del producto
dateCreated: string; // Marca de tiempo ISO 8601
}
Variantes
Cada producto tiene una o más variantes. Cada variante tiene su propio precio, stock y configuración:interface Variant {
id: string; // ID de la variante
name: string; // Nombre de la variante
productId: string; // ID del producto padre
storeId: string; // ID de la tienda
cost: number; // Precio en la moneda de la tienda
stock: number; // Stock disponible
stockMode: number; // Modo de cálculo de stock (ver abajo)
minimumQuantity: number; // Cantidad mínima de compra (por defecto: 1)
maximumQuantity: number; // Cantidad máxima de compra (-1 = ilimitada)
order: number; // Orden de visualización
imageNames: string[]; // Nombres de archivo de imágenes específicas de la variante
type: number; // 0=Único, 1=Suscripción
subscriptionPeriod?: string; // Duración de la suscripción si aplica
requireDiscordAuthorization: boolean;
dateCreated: string;
}
Modo de cálculo de stock
El campostockMode determina cómo se calcula la disponibilidad de stock:
| Valor | Modo | Descripción |
|---|---|---|
| 0 | Calculado | El stock se calcula automáticamente a partir de artículos disponibles |
| 1 | Ignorado | El stock no se rastrea — siempre disponible |
| 2 | Fijo | El stock se establece manualmente y se decrementa al comprar |
Mostrar imágenes
LosimageNames de productos y variantes contienen nombres de archivo, no URLs completas. Consulta la página Images para saber cómo construir URLs de imagen completas.
Mostrar variantes
async function mostrarProducto(productId) {
const [productResponse, formatter] = await Promise.all([
komerza.getProduct(productId),
komerza.createFormatter(),
]);
if (!productResponse.success) return;
const product = productResponse.data;
// Ordenar variantes por orden
const variants = product.variants.sort((a, b) => a.order - b.order);
variants.forEach((variant) => {
const price = formatter.format(variant.cost);
const inStock = variant.stockMode === 1 || variant.stock > 0;
console.log(`${variant.name}: ${price}`);
console.log(` Stock: ${inStock ? "Disponible" : "Sin stock"}`);
console.log(` Cant. mín: ${variant.minimumQuantity}`);
console.log(
` Cant. máx: ${
variant.maximumQuantity === -1 ? "Ilimitada" : variant.maximumQuantity
}`,
);
});
}
Selección de variante
<select id="variant-select" onchange="updatePrice()">
<!-- Rellenado dinámicamente -->
</select>
<span id="price"></span>
<button onclick="addToCart()">Añadir al carrito</button>
<script>
let currentProduct;
let formatter;
async function loadProduct(productId) {
[{ data: currentProduct }, formatter] = await Promise.all([
komerza.getProduct(productId),
komerza.createFormatter(),
]);
const select = document.getElementById("variant-select");
select.innerHTML = currentProduct.variants
.map(
(v) =>
`<option value="${v.id}">${v.name} - ${formatter.format(
v.cost,
)}</option>`,
)
.join("");
updatePrice();
}
function updatePrice() {
const variantId = document.getElementById("variant-select").value;
const variant = currentProduct.variants.find((v) => v.id === variantId);
document.getElementById("price").textContent = formatter.format(
variant.cost,
);
}
function addToCart() {
const variantId = document.getElementById("variant-select").value;
komerza.addToBasket(currentProduct.id, variantId);
}
</script>
Reseñas del producto
Obtén reseñas paginadas para un producto:const response = await komerza.getProductReviews(productId, page);
Parámetros
El ID único del producto
Número de página (indexado desde 1)
Retorna
RetornaPaginatedApiResponse<Review>:
interface PaginatedApiResponse<Review> {
success: boolean;
pages: number; // Número total de páginas
data?: Review[]; // Array de reseñas
message?: string;
}
interface Review {
id: string;
rating: number; // 1-5 estrellas
reason: string; // Texto de la reseña
productId: string;
reply?: string; // Respuesta del comerciante si hay
dateCreated: string;
}
Mostrar reseñas
async function mostrarResenas(productId, page = 1) {
const response = await komerza.getProductReviews(productId, page);
if (!response.success) return;
const container = document.getElementById("reviews");
container.innerHTML = response.data
.map(
(review) => `
<div class="review">
<div class="rating">${"★".repeat(review.rating)}${"☆".repeat(
5 - review.rating,
)}</div>
<p>${review.reason}</p>
${
review.reply
? `<p class="reply"><strong>Respuesta:</strong> ${review.reply}</p>`
: ""
}
<time>${new Date(review.dateCreated).toLocaleDateString()}</time>
</div>
`,
)
.join("");
// Paginación
if (response.pages > 1) {
container.innerHTML += `
<div class="pagination">
${
page > 1
? `<button onclick="mostrarResenas('${productId}', ${
page - 1
})">Anterior</button>`
: ""
}
<span>Página ${page} de ${response.pages}</span>
${
page < response.pages
? `<button onclick="mostrarResenas('${productId}', ${
page + 1
})">Siguiente</button>`
: ""
}
</div>
`;
}
}
Imágenes del producto
Las imágenes del producto se referencian por nombre de archivo. Construye la URL completa:function getProductImageUrl(storeId, productId, imageName) {
return `https://cdn.komerza.com/stores/${storeId}/products/${productId}/${imageName}`;
}
// Uso
const product = response.data;
const imageUrl = getProductImageUrl(
product.storeId,
product.id,
product.imageNames[0],
);
Galería de imágenes
function mostrarGaleriaImagenes(product) {
const gallery = document.getElementById("gallery");
// Usar imágenes de variante si están disponibles, si no las del producto
const images =
product.variants[0].imageNames.length > 0
? product.variants[0].imageNames
: product.imageNames;
gallery.innerHTML = images
.map(
(img) => `
<img
src="https://cdn.komerza.com/stores/${product.storeId}/products/${product.id}/${img}"
alt="${product.name}"
loading="lazy"
/>
`,
)
.join("");
}
Gestión de stock
Comprueba la disponibilidad de stock antes de añadir al carrito:function puedeAnadirAlCarrito(variant, quantity) {
// stockMode 1 = Ignorado (ilimitado)
if (variant.stockMode === 1) return true;
// Comprobar stock disponible
if (variant.stock < quantity) return false;
// Comprobar límites de cantidad
if (quantity < variant.minimumQuantity) return false;
if (variant.maximumQuantity !== -1 && quantity > variant.maximumQuantity)
return false;
return true;
}
function getEstadoStock(variant) {
if (variant.stockMode === 1) return "En stock";
if (variant.stock === 0) return "Sin stock";
if (variant.stock < 10) return `Solo quedan ${variant.stock}`;
return "En stock";
}
Ejemplo completo
<!DOCTYPE html>
<html>
<head>
<title>Página de producto</title>
<script src="https://cdn.komerza.com/komerza.min.js"></script>
<style>
.product {
max-width: 600px;
margin: 0 auto;
}
.gallery img {
max-width: 100%;
}
.variants {
margin: 20px 0;
}
.reviews {
margin-top: 40px;
}
.review {
border-bottom: 1px solid #eee;
padding: 15px 0;
}
.rating {
color: gold;
}
</style>
</head>
<body>
<div class="product">
<div id="gallery"></div>
<h1 id="name"></h1>
<p id="description"></p>
<p id="rating"></p>
<div class="variants">
<select id="variant-select"></select>
<span id="price"></span>
<span id="stock"></span>
</div>
<div>
<label
>Cantidad: <input type="number" id="quantity" value="1" min="1"
/></label>
<button id="add-btn" onclick="addToCart()">Añadir al carrito</button>
</div>
<div class="reviews">
<h2>Reseñas</h2>
<div id="reviews"></div>
</div>
</div>
<script>
komerza.init("your-store-id");
let product;
let formatter;
async function loadProduct(idOrSlug) {
const [productResponse, fmt] = await Promise.all([
komerza.getProduct(idOrSlug),
komerza.createFormatter(),
]);
if (!productResponse.success) {
alert("Producto no encontrado");
return;
}
product = productResponse.data;
formatter = fmt;
// Mostrar información del producto
document.getElementById("name").textContent = product.name;
document.getElementById("description").textContent =
product.description;
document.getElementById("rating").textContent =
`Valoración: ${product.rating}★`;
// Mostrar imágenes
const images = product.imageNames;
document.getElementById("gallery").innerHTML = images
.map(
(img) =>
`<img src="https://cdn.komerza.com/stores/${product.storeId}/products/${product.id}/${img}" />`,
)
.join("");
// Poblar variantes
const select = document.getElementById("variant-select");
select.innerHTML = product.variants
.map((v) => `<option value="${v.id}">${v.name}</option>`)
.join("");
select.onchange = updateVariantDisplay;
updateVariantDisplay();
loadReviews(1);
}
function updateVariantDisplay() {
const variantId = document.getElementById("variant-select").value;
const variant = product.variants.find((v) => v.id === variantId);
document.getElementById("price").textContent = formatter.format(
variant.cost,
);
// Estado del stock
let stockText = "En stock";
if (variant.stockMode !== 1) {
if (variant.stock === 0) stockText = "Sin stock";
else if (variant.stock < 10)
stockText = `Solo quedan ${variant.stock}`;
}
document.getElementById("stock").textContent = stockText;
// Actualizar límites de cantidad
const qtyInput = document.getElementById("quantity");
qtyInput.min = variant.minimumQuantity;
qtyInput.max =
variant.maximumQuantity === -1 ? 999 : variant.maximumQuantity;
qtyInput.value = variant.minimumQuantity;
// Deshabilitar botón si sin stock
document.getElementById("add-btn").disabled =
variant.stockMode !== 1 && variant.stock === 0;
}
function addToCart() {
const variantId = document.getElementById("variant-select").value;
const quantity = parseInt(document.getElementById("quantity").value);
komerza.addToBasket(product.id, variantId, quantity);
alert("¡Añadido al carrito!");
}
async function loadReviews(page) {
const response = await komerza.getProductReviews(product.id, page);
if (!response.success || response.data.length === 0) {
document.getElementById("reviews").innerHTML =
"<p>Sin reseñas aún</p>";
return;
}
document.getElementById("reviews").innerHTML = response.data
.map(
(review) => `
<div class="review">
<div class="rating">${"★".repeat(review.rating)}${"☆".repeat(
5 - review.rating,
)}</div>
<p>${review.reason}</p>
${review.reply ? `<p><em>Respuesta: ${review.reply}</em></p>` : ""}
</div>
`,
)
.join("");
}
// Cargar producto desde URL o por defecto
const urlParams = new URLSearchParams(window.location.search);
const productId = urlParams.get("product") || "your-default-product-slug";
loadProduct(productId);
</script>
</body>
</html>
Próximos pasos
Formato de moneda
Formatea precios en la moneda de tu tienda
Gestión del carrito
Añade productos al carrito