Productos
API de Productos
Endpoints para administrar tu catálogo de productos, incluyendo marcas y categorías para su organización.
Listar Productos
GET /api/products
Permiso: products:read
Obtén una lista paginada de productos activos del tenant actual.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| search | string | No | Busca por nombre de producto o modelo/SKU (sin distinguir mayúsculas) |
| brandId | string | No | Filtra por ID de marca |
| categoryId | string | No | Filtra por ID de categoría |
| page | number | No | Número de página (por defecto: 1) |
Response 200 OK:
{
"data": [
{
"id": "clxyz...",
"name": "Laptop HP ProBook 450",
"description": "Laptop empresarial con procesador i7",
"modelSku": "PB450-G10",
"price": "18500.00",
"isActive": true,
"brand": { "id": "...", "name": "HP" },
"category": { "id": "...", "name": "Laptops" },
"createdAt": "2026-03-01T10:00:00.000Z"
}
],
"total": 120,
"page": 1,
"totalPages": 6
}Ejemplo con cURL:
curl -X GET "https://cotizera.com/api/products?search=laptop&brandId=clxyz&page=1" \
-H "Cookie: next-auth.session-token=YOUR_TOKEN"Crear Producto
POST /api/products
Permiso: products:create
Crea un nuevo producto. Si se proporciona modelSku, debe ser único dentro del tenant.
Request Body:
{
"name": "Monitor Dell UltraSharp 27\"",
"description": "Monitor 4K IPS con USB-C",
"modelSku": "U2723QE",
"price": 12500,
"brandId": "clxyz...",
"categoryId": "clxyz..."
}| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Nombre del producto (mínimo 1 carácter) |
| description | string | No | Descripción del producto |
| modelSku | string | No | Número de modelo o SKU (único por tenant si se proporciona) |
| price | number | Yes | Precio unitario (> 0) |
| brandId | string | No | ID de la marca |
| categoryId | string | No | ID de la categoría |
Response 201 Created: Devuelve el producto creado con marca y categoría incluidas.
Errores:
| Status | Error | Cuándo |
|---|---|---|
| 400 | Mensaje de validación Zod | Entrada inválida |
| 403 | "Sin permisos" | El rol no tiene products:create |
| 409 | "Ya existe un producto con ese modelo/SKU" | SKU duplicado en el tenant |
Efectos secundarios:
- Dispara el evento webhook
product.created - Activa el paso de onboarding
first_product - Registra una entrada de auditoría con acción
create
Obtener un Producto
GET /api/products/:id
Permiso: products:read
Obtén un producto activo individual con los detalles de marca y categoría.
Response 200 OK: Devuelve el objeto del producto con brand y category incluidos.
Errores:
| Status | Error | Cuándo |
|---|---|---|
| 404 | "Producto no encontrado" | Producto no encontrado, inactivo o de otro tenant |
Actualizar Producto
PUT /api/products/:id
Permiso: products:update
Actualiza un producto existente. Si se cambia el modelSku, se vuelve a validar la unicidad.
Request Body: Mismo esquema que Crear Producto.
Response 200 OK: Devuelve el producto actualizado con marca y categoría.
Errores:
| Status | Error | Cuándo |
|---|---|---|
| 404 | "Producto no encontrado" | Producto no encontrado o inactivo |
| 409 | "Ya existe un producto con ese modelo/SKU" | El nuevo SKU entra en conflicto con un producto existente |
Eliminar Producto (Soft Delete)
DELETE /api/products/:id
Permiso: products:delete
Elimina un producto de forma lógica estableciendo isActive en false.
Response 200 OK:
{
"success": true
}Marcas
GET /api/brands
Lista todas las marcas activas del tenant actual, ordenadas alfabéticamente.
Response 200 OK:
[
{ "id": "clxyz...", "name": "HP", "tenantId": "...", "isActive": true },
{ "id": "clxyz...", "name": "Dell", "tenantId": "...", "isActive": true }
]POST /api/brands
Crea una nueva marca. El nombre debe ser único dentro del tenant.
Request Body:
{
"name": "Lenovo"
}| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Nombre de la marca (mínimo 1 carácter) |
Response 201 Created: Devuelve la marca creada.
Errores:
| Status | Error | Cuándo |
|---|---|---|
| 409 | "Ya existe una marca con ese nombre" | Nombre de marca duplicado en el tenant |
Categorías
GET /api/categories
Lista todas las categorías activas del tenant actual, ordenadas alfabéticamente.
Response 200 OK:
[
{ "id": "clxyz...", "name": "Laptops", "tenantId": "...", "isActive": true },
{ "id": "clxyz...", "name": "Monitores", "tenantId": "...", "isActive": true }
]POST /api/categories
Crea una nueva categoría. El nombre debe ser único dentro del tenant.
Request Body:
{
"name": "Impresoras"
}| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Nombre de la categoría (mínimo 1 carácter) |
Response 201 Created: Devuelve la categoría creada.
Errores:
| Status | Error | Cuándo |
|---|---|---|
| 409 | "Ya existe una categoria con ese nombre" | Nombre de categoría duplicado en el tenant |