3. Catálogo de productos
Cubre categories, taxes, products (incluye inventories en su creación), modifier_groups y custom_fields.
Historial de ejecución
| Ciclo | Fecha | Ejecutado por | Casos ejecutados | Resultado |
|---|---|---|---|---|
| 1 | — | — | — | Pendiente |
Categorías
CP CAT-01: Crear categoría solo con nombre (único campo obligatorio)
- Resultado esperado: se crea con el resto de campos (
description,color,icon,parent_id) vacíos/nulos, sin error.- Resultado obtenido: Pendiente.
CP CAT-02: Crear subcategoría con
parent_idde una categoría inexistente
- Resultado esperado a verificar: ¿el backend valida que
parent_idexista, o se guarda igual un ID huérfano? Si se guarda igual, es un hallazgo — el frontend podría mostrar un árbol de categorías roto.- Resultado obtenido: Pendiente.
CP CAT-03: Crear categoría sin nombre
- Resultado esperado: error de validación claro en el formulario.
- Resultado obtenido: Pendiente.
CP CAT-04: Jerarquía circular (A es padre de B, luego B es padre de A)
- Resultado esperado a verificar: ¿el backend lo previene? Si no, el árbol de categorías quedaría en un ciclo infinito al renderizarse — anotar si el frontend se cuelga o simplemente no lo muestra.
- Resultado obtenido: Pendiente.
Impuestos
CP TAX-01: Crear impuesto tipo
percentagecon tasa 19 (IVA Colombia)
- Resultado esperado: se crea correctamente; queda disponible al elegir impuesto en productos.
- Resultado obtenido: Pendiente.
CP TAX-02: Crear impuesto tipo
fixed
- Resultado esperado: se crea con un valor fijo en vez de porcentaje; confirmar que el cálculo en ventas lo trata distinto a uno de tipo
percentage(no debería multiplicar por el subtotal).- Resultado obtenido: Pendiente.
CP TAX-03: Tasa negativa
- Pasos: crear impuesto con
rate: -5.- Resultado esperado: debe rechazarse (
binding:"min=0").- Resultado obtenido: Pendiente.
CP TAX-04: Tipo inválido (ej.
"porcentaje"en español en vez de"percentage")
- Resultado esperado: error 400 claro (
oneof=percentage fixed), no un 500 ni un impuesto mal guardado.- Resultado obtenido: Pendiente.
CP TAX-05: Marcar dos impuestos como
is_defaulta la vez
- Resultado esperado a verificar: ¿el backend desmarca automáticamente el anterior, o quedan dos impuestos “por defecto” compitiendo? Si el frontend asume que solo hay uno, puede mostrar comportamiento inconsistente al elegir impuesto por defecto en productos nuevos.
- Resultado obtenido: Pendiente.
Productos — los 4 tipos
CP PROD-01: Crear producto tipo
product(plato/mercancía física) completo
- Pasos: nombre, SKU, precio, costo, impuesto, categoría, stock mínimo, imagen, precio/stock por tienda.
- Resultado esperado: se crea con su registro de
inventoriespor cada tienda donde se definió precio/stock.- Resultado obtenido: Pendiente.
CP PROD-02: Crear producto tipo
service
- Pasos: mismo formulario, tipo “Servicio”, con
duration_minutes(ej. 30).- Resultado esperado a verificar: ¿un producto tipo
servicegenera también un registro de inventario (no debería, un servicio no tiene stock)? Si el formulario sigue pidiendo “stock mínimo” para un servicio, es un problema de usabilidad — el campo no aplica.- Resultado obtenido: Pendiente.
CP PROD-03: Crear producto tipo
comboconcombo_items
- Pasos: crear un combo que incluya 2 productos ya existentes vía
combo_items: [{product_id, quantity}].- Resultado esperado: al vender el combo, ¿descuenta inventario de los productos incluidos, o solo del combo en sí? Verificar en el módulo de ventas (caso VTA) después de crear este producto.
- Resultado obtenido: Pendiente.
CP PROD-04: Crear producto tipo
comboreferenciando unproduct_idinexistente dentro decombo_items
- Resultado esperado a verificar: ¿se valida la existencia de cada ítem del combo, o se guarda un combo roto que fallará solo al intentar venderlo?
- Resultado obtenido: Pendiente.
CP PROD-05: Crear producto tipo
ingredientconrecipe_items(receta)
- Objetivo: un producto final (ej. “Hamburguesa”) consume ingredientes vía
recipe_items: [{ingredient_id, quantity, unit}].- Resultado esperado: al vender el producto final, debe descontarse inventario del ingrediente, no solo del producto final.
- Resultado obtenido: Pendiente.
CP PROD-06: Producto sin tipo (campo vacío)
- Resultado esperado: error 400 (
binding:"required,oneof=...").- Resultado obtenido: Pendiente.
CP PROD-07: Tipo inválido (ej.
"producto"en español)
- Resultado esperado: error 400 claro, no 500.
- Resultado obtenido: Pendiente.
CP PROD-08: Precio negativo
- Resultado esperado a verificar: el DTO no tiene
binding:"min=0"enPrice— probar si el backend lo acepta tal cual. Si lo acepta, es un hallazgo real: un producto con precio negativo podría generar ventas con totales negativos.- Resultado obtenido: Pendiente.
CP PROD-09: SKU duplicado entre dos productos de la misma empresa
- Resultado esperado a verificar: ¿hay restricción de unicidad de SKU por empresa? Si no la hay, buscar por SKU (
GET /products/sku/:id) sería ambiguo — confirmar qué devuelve en ese caso.- Resultado obtenido: Pendiente.
CP PROD-10: Categoría inexistente en
category_ids
- Resultado esperado a verificar: similar a CAT-02, confirmar si se valida la existencia de las categorías referenciadas.
- Resultado obtenido: Pendiente.
CP PROD-11: Subir una imagen de producto con un archivo que no es imagen (ej. un
.png)
- Resultado esperado: el backend/frontend debe rechazar por tipo de contenido real, no solo por extensión del nombre de archivo.
- Resultado obtenido: Pendiente.
CP PROD-12: Subir una imagen de producto muy pesada (ej. 20MB)
- Resultado esperado a verificar: ¿hay límite de tamaño? Si no, confirmar qué pasa (timeout, error de servidor, o se sube igual y degrada el rendimiento del listado de productos).
- Resultado obtenido: Pendiente.
CP PROD-13: Desactivar un producto (
is_active=false) que ya tiene ventas asociadas
- Resultado esperado: debe desaparecer del POS para nuevas ventas, pero las ventas históricas que lo incluyen no deben romperse ni perder el nombre del producto (los
sale_itemsguardanproduct_namepropio, independiente del producto actual — confirmar que efectivamente funciona así).- Resultado obtenido: Pendiente.
CP PROD-14: Ocultar producto del menú pero mantenerlo en el POS (
show_in_menu=false,show_in_pos=true)
- Resultado esperado: no debe aparecer en la carta online pública, pero sí en el POS interno.
- Resultado obtenido: Pendiente.
Grupos de modificadores
CP MOD-01: Grupo obligatorio con mínimo 1 y máximo 1 (ej. “Tamaño”: pequeño/mediano/grande)
- Pasos: crear grupo con
min_selections=1, max_selections=1, agregar 3 opciones.- Resultado esperado: en el POS, al agregar el producto, debe exigir elegir exactamente una opción antes de poder agregarlo al carrito.
- Resultado obtenido: Pendiente.
CP MOD-02: Grupo opcional con múltiples selecciones (ej. “Extras”: 0 a 3)
- Pasos:
min_selections=0, max_selections=3.- Resultado esperado: el POS debe permitir no elegir ninguno, o hasta 3, y bloquear un cuarto.
- Resultado obtenido: Pendiente.
CP MOD-03:
min_selectionsmayor quemax_selections(ej. min=3, max=1)
- Resultado esperado a verificar: configuración contradictoria — ¿el backend la rechaza, o queda guardada y el POS simplemente no deja agregar nunca el producto (loop de validación imposible)?
- Resultado obtenido: Pendiente.
CP MOD-04: Opción de modificador con
price_adjustmentnegativo (ej. “sin queso”: -$1.000)
- Resultado esperado: debe restar correctamente del total de la línea, sin dejarlo en negativo si el precio base es menor al ajuste.
- Resultado obtenido: Pendiente.
Campos personalizados
CP CF-01: Crear campo personalizado obligatorio para el modelo “producto” (ej. “Proveedor sugerido”)
- Pasos:
model: "product",type(verificar tipos soportados: texto/número/selección),required: true.- Resultado esperado: al crear un producto nuevo, el formulario debe exigir ese campo; productos ya existentes creados antes de este campo personalizado no deberían bloquear el sistema (verificar que no rompan al editarlos).
- Resultado obtenido: Pendiente.
CP CF-02: Campo personalizado con
modelque no corresponde a ningún módulo real (ej."xyz")
- Resultado esperado a verificar: ¿se valida contra una lista fija de modelos soportados, o acepta cualquier string sin que nunca se use en ningún formulario (campo “fantasma”)?
- Resultado obtenido: Pendiente.
Notas de usabilidad y sugerencias
Se completa durante la ejecución.