Saltar al contenido principal

Productos

Archivo: api/src/routes/products.js · Prefijo: /api/products

Los precios de los productos siempre los calcula el servidor. Ver Reglas de negocio → Precios.

GET /api/products​

Lista productos con precios resueltos para el usuario que llama (máximo 500, ordenados por nombre).

Acceso: Autenticado

Query

ParámetroTipoDescripción
activetrue | falseFiltra por estado
categoryIduuidFiltra por categoría
searchstringBusca en nombre o SKU (ILIKE)
priceListIduuidSolo admin/asesor: calcula precios con esa lista. Para clientes se ignora y se usa su lista asignada

Respuesta 200

{
"items": [
{
"id": "…",
"sku": "TOR-001",
"name": "Tornillo drywall 6x1",
"description": null,
"categoryId": "…",
"categoryName": "Tornillería",
"unit": "caja",
"stock": 120,
"quality": "alta",
"imageUrl": "https://…/product-images/abc.webp",
"active": true,
"basePrice": 10000,
"priceListPrice": 9000,
"promotionPrice": 8500,
"finalPrice": 8500,
"priceListId": "…"
}
],
"total": 1
}

GET /api/products/catalog​

Catálogo público paginado, solo productos activos, con precios de la lista por defecto y promociones de alcance all.

Acceso: Público

Query

ParámetroTipoPor defectoDescripción
searchstring—Nombre o SKU
categoryIduuid—Categoría
pageentero1Página (desde 1)
limitentero40Tamaño de página (máx. 200)

Respuesta 200

{
"items": [
{
"id": "…", "sku": "TOR-001", "name": "Tornillo drywall 6x1",
"categoryId": "…", "categoryName": "Tornillería",
"unit": "caja", "stock": 120, "quality": "alta",
"imageUrl": null, "basePrice": 10000, "finalPrice": 9500
}
],
"total": 350,
"page": 1,
"pages": 9
}

POST /api/products​

Crea un producto.

Acceso: admin

Body

CampoTipoRequeridoPor defecto
namestringSí
skustringSíDebe ser único
categoryIduuid | número | nullNo
unitstringNo
stocknúmero ≥ 0No0
qualitystringNop. ej. estandar, alta, premium
imageUrlstringNo
basePricenúmero ≥ 0No0
activebooleanNotrue
descriptionstringNo

Respuesta 201: { "id": "<uuid>" }


PUT /api/products/:id​

Actualiza cualquier subconjunto de los campos de POST. Para limpiar un campo se puede enviar la cadena "null" (se convierte a NULL).

Acceso: admin

Respuesta 200: { "id": "<uuid>" }

Errores: 400 EMPTY_PATCH, 404 NOT_FOUND.


POST /api/products/:id/image​

Sube o reemplaza la imagen del producto en Supabase Storage. La imagen anterior se borra en segundo plano.

Acceso: admin

Body: multipart/form-data con el campo image (JPEG, PNG, WebP o GIF, máximo 5 MB).

curl -X POST "$API/products/$ID/image" \
-H "Authorization: Bearer $TOKEN" \
-F "image=@foto.webp"

Respuesta 200: { "imageUrl": "https://<proyecto>.supabase.co/storage/v1/object/public/product-images/<archivo>" }

Errores

HTTPerrorCausa
400NO_FILENo se envió archivo
400FILE_TOO_LARGEMás de 5 MB
400INVALID_MIMETipo no permitido
400UNEXPECTED_FILECampo distinto de image
404NOT_FOUNDProducto inexistente
Productos desde SIIGO

La mayoría de productos se crean y actualizan con la sincronización de SIIGO, que hace upsert por siigo_id. Los campos locales (category_id, quality, description) no se sobrescriben en la sincronización.