feat(F-203): completed feature

This commit is contained in:
chattie
2026-08-23 23:21:30 +02:00
parent 725a02527a
commit cd2a8ab54d
2 changed files with 148 additions and 146 deletions

View File

@@ -23,7 +23,7 @@
{
"id": "ADM-006",
"title": "Admin: Product Editor Shell",
"description": "Shell del editor de productos con tabs y detecci\u00f3n de cambios sin guardar.",
"description": "Shell del editor de productos con tabs y detección de cambios sin guardar.",
"status": "done",
"priority": "high",
"phase": "products",
@@ -39,7 +39,7 @@
{
"id": "ADM-007",
"title": "Admin: Product General Fields",
"description": "Campos generales del producto: nombre, slug, descripci\u00f3n, marca, categor\u00edas.",
"description": "Campos generales del producto: nombre, slug, descripción, marca, categorías.",
"status": "done",
"priority": "high",
"phase": "products",
@@ -87,7 +87,7 @@
{
"id": "ADM-010",
"title": "Admin: Product Images Section",
"description": "Gestionar im\u00e1genes: subir, reorder, eliminar, marcar principal.",
"description": "Gestionar imágenes: subir, reorder, eliminar, marcar principal.",
"status": "done",
"priority": "medium",
"phase": "products",
@@ -119,7 +119,7 @@
{
"id": "ADM-012",
"title": "Admin: Product Create Flow",
"description": "Ruta /admin/products/new con formulario completo de creaci\u00f3n.",
"description": "Ruta /admin/products/new con formulario completo de creación.",
"status": "done",
"priority": "high",
"phase": "products",
@@ -135,7 +135,7 @@
{
"id": "ADM-016",
"title": "Admin: Inventory Overview",
"description": "Vista de inventario por variante con stock y estado. Filtros y b\u00fasqueda.",
"description": "Vista de inventario por variante con stock y estado. Filtros y búsqueda.",
"status": "done",
"priority": "high",
"phase": "inventory",
@@ -151,7 +151,7 @@
{
"id": "ADM-017",
"title": "Admin: Quick Stock Adjustment",
"description": "Ajuste r\u00e1pido de stock inline con confirmaci\u00f3n y refresh.",
"description": "Ajuste rápido de stock inline con confirmación y refresh.",
"status": "done",
"priority": "high",
"phase": "inventory",
@@ -167,7 +167,7 @@
{
"id": "ADM-018",
"title": "Admin: Customer List",
"description": "Listado de clientes con b\u00fasqueda y paginaci\u00f3n.",
"description": "Listado de clientes con búsqueda y paginación.",
"status": "done",
"priority": "medium",
"phase": "customers",
@@ -218,7 +218,7 @@
{
"id": "ADM-021",
"title": "Admin: Categories CRUD",
"description": "CRUD de categor\u00edas: crear, editar, eliminar categor\u00edas.",
"description": "CRUD de categorías: crear, editar, eliminar categorías.",
"status": "done",
"priority": "medium",
"phase": "categories",
@@ -284,7 +284,7 @@
{
"id": "ADM-025",
"title": "Admin: Reviews Moderation",
"description": "Moderaci\u00f3n de rese\u00f1as: aprobar, rechazar. Requiere BD-05 backend.",
"description": "Moderación de reseñas: aprobar, rechazar. Requiere BD-05 backend.",
"status": "done",
"priority": "low",
"phase": "reviews",
@@ -301,7 +301,7 @@
{
"id": "ADM-026",
"title": "Admin: CMS Pages",
"description": "Gesti\u00f3n de p\u00e1ginas CMS: crear, editar, publicar, despublicar.",
"description": "Gestión de páginas CMS: crear, editar, publicar, despublicar.",
"status": "done",
"priority": "low",
"phase": "cms",
@@ -318,7 +318,7 @@
{
"id": "BD-02",
"title": "Backend: GET /promotions (listado)",
"description": "Endpoint GET /promotions con paginaci\u00f3n y filtros para listado admin. Alta prioridad \u2014 bloquea ADM-024.",
"description": "Endpoint GET /promotions con paginación y filtros para listado admin. Alta prioridad bloquea ADM-024.",
"status": "done",
"priority": "high",
"phase": "backend",
@@ -334,7 +334,7 @@
{
"id": "BD-03",
"title": "Backend: PATCH /promotions/:id",
"description": "Editar promoci\u00f3n existente: c\u00f3digo, tipo, valor, fechas, l\u00edmites.",
"description": "Editar promoción existente: código, tipo, valor, fechas, límites.",
"status": "done",
"priority": "high",
"phase": "backend",
@@ -350,7 +350,7 @@
{
"id": "BD-04",
"title": "Backend: DELETE /promotions/:id",
"description": "Eliminar promoci\u00f3n por ID. MEDIA prioridad.",
"description": "Eliminar promoción por ID. MEDIA prioridad.",
"status": "done",
"priority": "medium",
"phase": "backend",
@@ -366,7 +366,7 @@
{
"id": "BD-05",
"title": "Backend: GET /reviews/admin",
"description": "Listado de rese\u00f1as para moderaci\u00f3n admin con filtros (pendiente/aprobada/rechazada). Alta prioridad \u2014 bloquea ADM-025.",
"description": "Listado de reseñas para moderación admin con filtros (pendiente/aprobada/rechazada). Alta prioridad bloquea ADM-025.",
"status": "done",
"priority": "high",
"phase": "backend",
@@ -433,7 +433,7 @@
{
"id": "BD-09",
"title": "Backend: POST /inventory/bulk-adjust",
"description": "Bulk stock adjustment at\u00f3mico. BAJA prioridad.",
"description": "Bulk stock adjustment atómico. BAJA prioridad.",
"status": "done",
"priority": "low",
"phase": "backend",
@@ -1737,7 +1737,7 @@
},
{
"id": "F-037",
"title": "Seed data: productos categor\u00edas marcas",
"title": "Seed data: productos categorías marcas",
"status": "done",
"depends_on": [
"F-036"
@@ -1928,7 +1928,7 @@
{
"id": "F-047",
"title": "Admin Panel completo",
"description": "Panel de administraci\u00f3n: listado y gesti\u00f3n de pedidos, productos y transiciones de estado.",
"description": "Panel de administración: listado y gestión de pedidos, productos y transiciones de estado.",
"status": "done",
"created_at": "2026-08-16T21:33:10Z",
"gates": {
@@ -1943,8 +1943,8 @@
},
{
"id": "ADM-27",
"title": "Admin: Auto-slug y auto-SEO para marcas, productos y categor\u00edas",
"description": "En admin, al escribir el nombre de marca/producto/categor\u00eda, el slug y los campos SEO deben generarse autom\u00e1ticamente. El usuario puede editarlos manualmente despu\u00e9s. Slug: slugify(name). SEO title: name. SEO description: una frase descriptiva basada en name. El tracking de edici\u00f3n manual (slugManual) debe existir para no sobreescribir lo que el usuario ya personaliz\u00f3.",
"title": "Admin: Auto-slug y auto-SEO para marcas, productos y categorías",
"description": "En admin, al escribir el nombre de marca/producto/categoría, el slug y los campos SEO deben generarse automáticamente. El usuario puede editarlos manualmente después. Slug: slugify(name). SEO title: name. SEO description: una frase descriptiva basada en name. El tracking de edición manual (slugManual) debe existir para no sobreescribir lo que el usuario ya personalizó.",
"status": "done",
"priority": "high",
"gates": {
@@ -1966,7 +1966,7 @@
{
"id": "FIX-01",
"title": "Admin orders page: Cannot read properties of undefined (reading 'length')",
"description": "Runtime TypeError en orders/page.tsx:95. orders se inicializa como [] y se renderiza antes de que load() complete. La soluci\u00f3n es inicializar orders como null y usar orders?.length ?? 0 en el template.",
"description": "Runtime TypeError en orders/page.tsx:95. orders se inicializa como [] y se renderiza antes de que load() complete. La solución es inicializar orders como null y usar orders?.length ?? 0 en el template.",
"status": "done",
"priority": "high",
"type": "fix",
@@ -1987,7 +1987,7 @@
{
"id": "ADM-28",
"title": "Branding: logo oficial y colores de marca",
"description": "Reemplazar el SVG gen\u00e9rico del header por el logo oficial descargado de mercadodevida.es. El logo ya est\u00e1 en project/frontend/public/images/logo.png. Actualizar Header.tsx para usar Image con el logo real. Los colores (#2D6A4F) ya coinciden con la marca. Las p\u00e1ginas de contenido (terms, about, contact) requieren revisi\u00f3n humana antes de publicar \u2014 se haran luego.",
"description": "Reemplazar el SVG genérico del header por el logo oficial descargado de mercadodevida.es. El logo ya está en project/frontend/public/images/logo.png. Actualizar Header.tsx para usar Image con el logo real. Los colores (#2D6A4F) ya coinciden con la marca. Las páginas de contenido (terms, about, contact) requieren revisión humana antes de publicar se haran luego.",
"status": "done",
"priority": "high",
"type": "feature",
@@ -2031,7 +2031,7 @@
{
"id": "ADM-29",
"title": "Backoffice: crear y gestionar usuarios admin",
"description": "En admin, permitir crear, editar y eliminar usuarios backoffice. Cada usuario tiene email, nombre, rol (admin/editor/viewer) y contrase\u00f1a. No confundir con clientes de storefront. Incluye listar usuarios, crear nuevo, editar, activar/desactivar, cambiar contrase\u00f1a, eliminar.",
"description": "En admin, permitir crear, editar y eliminar usuarios backoffice. Cada usuario tiene email, nombre, rol (admin/editor/viewer) y contraseña. No confundir con clientes de storefront. Incluye listar usuarios, crear nuevo, editar, activar/desactivar, cambiar contraseña, eliminar.",
"status": "done",
"priority": "high",
"type": "feature",
@@ -2052,8 +2052,8 @@
},
{
"id": "ADM-30",
"title": "Backoffice: ajustes generales de tienda (nombre, t\u00edtulo, logo)",
"description": "Panel de configuraci\u00f3n general de la tienda en admin. Permite cambiar nombre comercial, t\u00edtulo de la tienda (usado en <title> y SEO), descripci\u00f3n, direcci\u00f3n, tel\u00e9fono, email de contacto, WhatsApp, horarios. Esta info ya existe en businessConfig.ts del backend \u2014 debe poder editarse desde admin y persistirse en DB.",
"title": "Backoffice: ajustes generales de tienda (nombre, título, logo)",
"description": "Panel de configuración general de la tienda en admin. Permite cambiar nombre comercial, título de la tienda (usado en <title> y SEO), descripción, dirección, teléfono, email de contacto, WhatsApp, horarios. Esta info ya existe en businessConfig.ts del backend debe poder editarse desde admin y persistirse en DB.",
"status": "done",
"priority": "medium",
"type": "feature",
@@ -2073,8 +2073,8 @@
},
{
"id": "ADM-31",
"title": "Backoffice: gesti\u00f3n de tipos impositivos (IVA)",
"description": "En admin, permitir configurar los tipos de IVA aplicables a productos. Espa\u00f1a: general 21%, reducido 10%, superreducido 4%. Cada producto o categor\u00eda puede tener un tipo de IVA asociado. El checkout calcula el IVA seg\u00fan el tipo del producto. Backend ya soporta vatRate en pricing \u2014 falta la UI de gesti\u00f3n de tipos impositivos.",
"title": "Backoffice: gestión de tipos impositivos (IVA)",
"description": "En admin, permitir configurar los tipos de IVA aplicables a productos. España: general 21%, reducido 10%, superreducido 4%. Cada producto o categoría puede tener un tipo de IVA asociado. El checkout calcula el IVA según el tipo del producto. Backend ya soporta vatRate en pricing falta la UI de gestión de tipos impositivos.",
"status": "done",
"priority": "medium",
"type": "feature",
@@ -2095,8 +2095,8 @@
},
{
"id": "ADM-32",
"title": "Backoffice: logs de errores y auditor\u00eda",
"description": "En admin, pantalla de logs de errores del sistema. Muestra errores capturados por el backend (audit-logger) con timestamp, severity (error/warn/info), m\u00f3dulo, mensaje y stack trace. Permite filtrar por fecha, severity y m\u00f3dulo. Solo accesible para rol admin. Exportar a CSV.",
"title": "Backoffice: logs de errores y auditoría",
"description": "En admin, pantalla de logs de errores del sistema. Muestra errores capturados por el backend (audit-logger) con timestamp, severity (error/warn/info), módulo, mensaje y stack trace. Permite filtrar por fecha, severity y módulo. Solo accesible para rol admin. Exportar a CSV.",
"status": "done",
"priority": "low",
"type": "feature",
@@ -2117,8 +2117,8 @@
},
{
"id": "ADM-33",
"title": "Backoffice: gesti\u00f3n de m\u00e9todos de pago",
"description": "En admin, panel para configurar los m\u00e9todos de pago activos. M\u00e9todos soportados: tarjeta (Stripe), transferencia bancaria, Contra reembolso, Bizum. Cada m\u00e9todo tiene nombre, descripci\u00f3n, instrucciones, icono y activo/inactivo. El checkout muestra solo los m\u00e9todos activos. El m\u00f3dulo payments ya existe en backend \u2014 falta la UI de gesti\u00f3n.",
"title": "Backoffice: gestión de métodos de pago",
"description": "En admin, panel para configurar los métodos de pago activos. Métodos soportados: tarjeta (Stripe), transferencia bancaria, Contra reembolso, Bizum. Cada método tiene nombre, descripción, instrucciones, icono y activo/inactivo. El checkout muestra solo los métodos activos. El módulo payments ya existe en backend falta la UI de gestión.",
"status": "done",
"priority": "medium",
"type": "feature",
@@ -2138,8 +2138,8 @@
},
{
"id": "ADM-34",
"title": "Backoffice: gesti\u00f3n de m\u00e9todos de env\u00edo",
"description": "En admin, panel para configurar las opciones de env\u00edo disponibles. M\u00e9todos: est\u00e1ndar, express, recogida en tienda. Cada m\u00e9todo tiene nombre, precio (fijo o gratis a partir de X), plazos de entrega, zona geogr\u00e1fica (provincia/CP), activo/inactivo. El m\u00f3dulo shipping ya existe en backend \u2014 falta la UI de gesti\u00f3n.",
"title": "Backoffice: gestión de métodos de envío",
"description": "En admin, panel para configurar las opciones de envío disponibles. Métodos: estándar, express, recogida en tienda. Cada método tiene nombre, precio (fijo o gratis a partir de X), plazos de entrega, zona geográfica (provincia/CP), activo/inactivo. El módulo shipping ya existe en backend falta la UI de gestión.",
"status": "done",
"priority": "medium",
"type": "feature",
@@ -2159,8 +2159,8 @@
},
{
"id": "ADM-35",
"title": "Backoffice: gesti\u00f3n de clientes \u2014 crear y editar",
"description": "Customers page con bot\u00f3n crear cliente (modal), editar displayName/phone (modal). Tipos Customer actualizados con displayName y phone. customersApi.create() y customersApi.update() agregados.",
"title": "Backoffice: gestión de clientes crear y editar",
"description": "Customers page con botón crear cliente (modal), editar displayName/phone (modal). Tipos Customer actualizados con displayName y phone. customersApi.create() y customersApi.update() agregados.",
"status": "done",
"priority": "high",
"type": "feature",
@@ -2181,8 +2181,8 @@
},
{
"id": "FE-01",
"title": "Storefront: b\u00fasqueda sem\u00e1ntica de productos",
"description": "Reemplazar la b\u00fasqueda por keyword exacta en storefront por b\u00fasqueda sem\u00e1ntica/vectorial. El usuario busca en lenguaje natural (ej: '\u4ec0me ayudan las c\u00e1psulas de magnesio para dormir') y el sistema encuentra productos relevantes aunque no contengan esos t\u00e9rminos exactos. Requiere: embedding de productos (descripci\u00f3n + nombre) en el backend, endpoint de b\u00fasqueda que use similitud vectorial o embedding-based matching, UI de resultados en /search. Para MVP se puede usar b\u00fasqueda por texto libre con ILIKE + ranking por relevancia antes de invertir en embeddings.",
"title": "Storefront: búsqueda semántica de productos",
"description": "Reemplazar la búsqueda por keyword exacta en storefront por búsqueda semántica/vectorial. El usuario busca en lenguaje natural (ej: 'me ayudan las cápsulas de magnesio para dormir') y el sistema encuentra productos relevantes aunque no contengan esos términos exactos. Requiere: embedding de productos (descripción + nombre) en el backend, endpoint de búsqueda que use similitud vectorial o embedding-based matching, UI de resultados en /search. Para MVP se puede usar búsqueda por texto libre con ILIKE + ranking por relevancia antes de invertir en embeddings.",
"status": "done",
"priority": "medium",
"type": "feature",
@@ -2204,8 +2204,8 @@
},
{
"id": "FIX-03",
"title": "Storefront: categor\u00edas y marcas centradas",
"description": "Las p\u00e1ginas de categor\u00edas y marcas en el storefront muestran el contenido apilado a la izquierda. Deber\u00eda estar centrado.",
"title": "Storefront: categorías y marcas centradas",
"description": "Las páginas de categorías y marcas en el storefront muestran el contenido apilado a la izquierda. Debería estar centrado.",
"status": "done",
"priority": "medium",
"type": "fix",
@@ -2225,8 +2225,8 @@
},
{
"id": "FIX-04",
"title": "Storefront: logo, footer y tipograf\u00eda de marca",
"description": "Logos renovados con tipograf\u00eda Almagro para 'Natural'. Header: [logo sticker] + [Natural Almagro]. Footer: [Natural Almagro / Mercado de Vida FreeSans] apilado. Fuente Almagro en Dropbox.",
"title": "Storefront: logo, footer y tipografía de marca",
"description": "Logos renovados con tipografía Almagro para 'Natural'. Header: [logo sticker] + [Natural Almagro]. Footer: [Natural Almagro / Mercado de Vida FreeSans] apilado. Fuente Almagro en Dropbox.",
"status": "done",
"priority": "high",
"type": "fix",
@@ -2246,8 +2246,8 @@
},
{
"id": "FIX-05",
"title": "Storefront: normalizar header y tipograf\u00eda",
"description": "Header: solo logo (sticker). Favicon: sticker en 32x32. Tipograf\u00eda: Open Sans en globals.css y layout.tsx.",
"title": "Storefront: normalizar header y tipografía",
"description": "Header: solo logo (sticker). Favicon: sticker en 32x32. Tipografía: Open Sans en globals.css y layout.tsx.",
"status": "done",
"priority": "high",
"type": "fix",
@@ -2268,8 +2268,8 @@
},
{
"id": "FE-02",
"title": "Storefront: barra de b\u00fasqueda en el Header",
"description": "Agregar input de b\u00fasqueda en el Header del storefront con autocompletado y navegaci\u00f3n a /search",
"title": "Storefront: barra de búsqueda en el Header",
"description": "Agregar input de búsqueda en el Header del storefront con autocompletado y navegación a /search",
"status": "done",
"priority": "high",
"labels": [
@@ -2304,8 +2304,8 @@
},
{
"id": "FIX-07",
"title": "Admin: Categor\u00edas \u2014 emojis en acciones",
"description": "Reemplazar texto \"Editar\" / \"Eliminar\" por iconos de l\u00e1piz y papelera en la tabla de categor\u00edas.",
"title": "Admin: Categorías — emojis en acciones",
"description": "Reemplazar texto \"Editar\" / \"Eliminar\" por iconos de lápiz y papelera en la tabla de categorías.",
"status": "done",
"priority": "low",
"phase": "admin",
@@ -2320,8 +2320,8 @@
},
{
"id": "FIX-08",
"title": "Admin: Usuarios backoffice \u2014 emojis en acciones",
"description": "Reemplazar texto \"Editar\" / \"Eliminar\" por iconos de l\u00e1piz y papelera en la tabla de usuarios.",
"title": "Admin: Usuarios backoffice emojis en acciones",
"description": "Reemplazar texto \"Editar\" / \"Eliminar\" por iconos de lápiz y papelera en la tabla de usuarios.",
"status": "done",
"priority": "low",
"phase": "admin",
@@ -2337,7 +2337,7 @@
{
"id": "ADM-36",
"title": "Admin: Dashboard con visualizaciones",
"description": "Reemplazar los 4 stat cards del dashboard por gr\u00e1ficos interactivos: ventas por d\u00eda (l\u00ednea), productos m\u00e1s vendidos (barras), estado de pedidos (donut), revenue mensual.",
"description": "Reemplazar los 4 stat cards del dashboard por gráficos interactivos: ventas por día (línea), productos más vendidos (barras), estado de pedidos (donut), revenue mensual.",
"status": "done",
"priority": "medium",
"phase": "admin",
@@ -2354,7 +2354,7 @@
{
"id": "ADM-37",
"title": "Admin: Editor de producto completo",
"description": "Editor de producto completo con: EAN editable en variante; Atributos checkbox (Bio, Vegano, Sin Gluten, Keto, etc.); Canales obligatorios (online/offline/all); Im\u00e1genes con upload + URL + drag&drop; Precio de oferta en variante; PVP (bruto con IVA) editable en variante; Precio de coste en variante.",
"description": "Editor de producto completo con: EAN editable en variante; Atributos checkbox (Bio, Vegano, Sin Gluten, Keto, etc.); Canales obligatorios (online/offline/all); Imágenes con upload + URL + drag&drop; Precio de oferta en variante; PVP (bruto con IVA) editable en variante; Precio de coste en variante.",
"status": "done",
"priority": "critical",
"phase": "admin",
@@ -2370,7 +2370,7 @@
{
"id": "ADM-38",
"title": "Admin: Pedidos editables",
"description": "Permitir editar pedidos desde el admin: cambiar estado, editar l\u00edneas de pedido (cantidades), a\u00f1adir productos, aplicar descuentos, actualizar datos de env\u00edo y facturaci\u00f3n.",
"description": "Permitir editar pedidos desde el admin: cambiar estado, editar líneas de pedido (cantidades), añadir productos, aplicar descuentos, actualizar datos de envío y facturación.",
"status": "done",
"priority": "high",
"phase": "admin",
@@ -2385,8 +2385,8 @@
},
{
"id": "ADM-39",
"title": "Admin: Clientes \u2014 direcci\u00f3n y facturaci\u00f3n",
"description": "Desde la gesti\u00f3n de clientes, permitir ver/editar direcciones de env\u00edo y datos de facturaci\u00f3n (nombre fiscal, CIF/NIF, direcci\u00f3n completa).",
"title": "Admin: Clientes dirección y facturación",
"description": "Desde la gestión de clientes, permitir ver/editar direcciones de envío y datos de facturación (nombre fiscal, CIF/NIF, dirección completa).",
"status": "done",
"priority": "medium",
"phase": "admin",
@@ -2402,8 +2402,8 @@
},
{
"id": "ADM-40",
"title": "Admin: Auditor\u00eda dentro de Ajustes",
"description": "Mover la p\u00e1gina de auditor\u00eda a la secci\u00f3n de Ajustes. Mostrar m\u00e1s detalle: direcci\u00f3n IP, user-agent, duraci\u00f3n de la operaci\u00f3n, diff de cambios.",
"title": "Admin: Auditoría dentro de Ajustes",
"description": "Mover la página de auditoría a la sección de Ajustes. Mostrar más detalle: dirección IP, user-agent, duración de la operación, diff de cambios.",
"status": "done",
"priority": "medium",
"phase": "admin",
@@ -2419,8 +2419,8 @@
},
{
"id": "ADM-41",
"title": "Admin: Ajustes reorganizados con categor\u00edas",
"description": "Reorganizar Ajustes en subcategor\u00edas: General (nombre, tagline, contacto), Redes sociales, Footer (con support para {{year}} placeholder), Localizaciones (m\u00faltiples tiendas f\u00edsicas con direcci\u00f3n, horarios de apertura), Env\u00edos (zonas y tarifas ya hechas pero integrarlo aqu\u00ed).",
"title": "Admin: Ajustes reorganizados con categorías",
"description": "Reorganizar Ajustes en subcategorías: General (nombre, tagline, contacto), Redes sociales, Footer (con support para {{year}} placeholder), Localizaciones (múltiples tiendas físicas con dirección, horarios de apertura), Envíos (zonas y tarifas ya hechas pero integrarlo aquí).",
"status": "done",
"priority": "medium",
"phase": "admin",
@@ -2436,8 +2436,8 @@
},
{
"id": "ADM-42",
"title": "Admin: CMS \u2014 p\u00e1ginas del footer editables",
"description": "Agregar al CMS las p\u00e1ginas: About, Contacto, Shipping, Privacy, Terms, Cookies. Deben ser editables desde el panel CMS con WYSIWYG y previsualizaci\u00f3n.",
"title": "Admin: CMS — páginas del footer editables",
"description": "Agregar al CMS las páginas: About, Contacto, Shipping, Privacy, Terms, Cookies. Deben ser editables desde el panel CMS con WYSIWYG y previsualización.",
"status": "done",
"priority": "high",
"phase": "admin",
@@ -2453,7 +2453,7 @@
{
"id": "FE-03",
"title": "Storefront: Live search con preview",
"description": "Reemplazar la b\u00fasqueda actual por un live search: al escribir (debounce 300ms) se muestranminiaturas + nombre de producto en un dropdown. Click navega a la ficha. Empty state y fallback a /search.",
"description": "Reemplazar la búsqueda actual por un live search: al escribir (debounce 300ms) se muestranminiaturas + nombre de producto en un dropdown. Click navega a la ficha. Empty state y fallback a /search.",
"status": "done",
"priority": "high",
"phase": "frontend",
@@ -2468,8 +2468,8 @@
},
{
"id": "FE-04",
"title": "Storefront: Ficha de usuario \u2014 direcciones y facturaci\u00f3n",
"description": "P\u00e1gina /account o /profile donde el usuario puede gestionar sus direcciones de env\u00edo (crear/editar/eliminar) y datos de facturaci\u00f3n.",
"title": "Storefront: Ficha de usuario direcciones y facturación",
"description": "Página /account o /profile donde el usuario puede gestionar sus direcciones de envío (crear/editar/eliminar) y datos de facturación.",
"status": "done",
"priority": "medium",
"phase": "frontend",
@@ -2485,8 +2485,8 @@
},
{
"id": "FIX-09",
"title": "Storefront: Checkout \u2014 seguir mostrando login aunque ya est\u00e9 autenticado",
"description": "Fix: cuando el usuario ya est\u00e1 logueado y llega a /checkout, no debe mostrar el formulario de login otra vez. Debe detectar la sesi\u00f3n y mostrar directamente el formulario de env\u00edo/pago.",
"title": "Storefront: Checkout seguir mostrando login aunque ya esté autenticado",
"description": "Fix: cuando el usuario ya está logueado y llega a /checkout, no debe mostrar el formulario de login otra vez. Debe detectar la sesión y mostrar directamente el formulario de envío/pago.",
"status": "done",
"priority": "critical",
"phase": "frontend",
@@ -2502,7 +2502,7 @@
{
"id": "FIX-10",
"title": "Storefront: Precio pegado al borde en tarjetas de producto",
"description": "En las tarjetas de producto (grids de categor\u00edas, marcas, search) el precio se muestra muy pegado a la derecha. Agregar padding-right a .price o al contenedor.",
"description": "En las tarjetas de producto (grids de categorías, marcas, search) el precio se muestra muy pegado a la derecha. Agregar padding-right a .price o al contenedor.",
"status": "done",
"priority": "low",
"phase": "frontend",
@@ -2517,8 +2517,8 @@
},
{
"id": "FE-048",
"title": "B\u00fasqueda fuzzy con sugerencias tipogr\u00e1ficas",
"description": "Live search con fuzzy search que maneje errores tipogr\u00e1ficos leves (fuzziness), sugerencias de correcci\u00f3n ortogr\u00e1fica y ranking mejorado para resultados parciales. Incluye: Levenshtein distance para typos, trigram similarity como fallback, y sugerencias de t\u00e9rminos corregidos cuando no hay resultados.",
"title": "Búsqueda fuzzy con sugerencias tipográficas",
"description": "Live search con fuzzy search que maneje errores tipográficos leves (fuzziness), sugerencias de corrección ortográfica y ranking mejorado para resultados parciales. Incluye: Levenshtein distance para typos, trigram similarity como fallback, y sugerencias de términos corregidos cuando no hay resultados.",
"type": "feature",
"priority": "medium",
"status": "done",
@@ -2533,15 +2533,15 @@
},
{
"id": "FIX-11",
"title": "Admin: quitar Env\u00edos e IVA de Ajustes",
"description": "En Ajustes (/settings) aparecen bloques de Env\u00edos e IVA que ya existen como pantallas propias en el men\u00fa principal (/shipping y /tax-rates). Eliminar esas secciones duplicadas de Ajustes para tener una \u00fanica fuente de verdad.",
"title": "Admin: quitar Envíos e IVA de Ajustes",
"description": "En Ajustes (/settings) aparecen bloques de Envíos e IVA que ya existen como pantallas propias en el menú principal (/shipping y /tax-rates). Eliminar esas secciones duplicadas de Ajustes para tener una única fuente de verdad.",
"status": "done",
"priority": "medium",
"phase": "admin",
"type": "fix",
"created_at": "2026-08-18T20:03:10.712389+00:00",
"acceptance": [
"/settings ya no muestra secciones de Env\u00edos ni IVA",
"/settings ya no muestra secciones de Envíos ni IVA",
"/shipping y /tax-rates siguen funcionando como pantallas propias",
"verify.sh en verde"
],
@@ -2554,8 +2554,8 @@
},
{
"id": "FIX-12",
"title": "Admin: normalizar columna Acciones \u2014 sustituir texto por iconos (emoji)",
"description": "Normalizar la columna de acciones del backoffice SUSTITUYENDO los botones de TEXTO ('Editar'/'Eliminar', como en brands y promotions) por botones-icono (emoji), replicando el patr\u00f3n exacto de /categories y /users: bot\u00f3n con icono SVG, hover de color y atributo title ('Editar'/'Eliminar'). Aplicar a todas las tablas que a\u00fan usan texto.",
"title": "Admin: normalizar columna Acciones sustituir texto por iconos (emoji)",
"description": "Normalizar la columna de acciones del backoffice SUSTITUYENDO los botones de TEXTO ('Editar'/'Eliminar', como en brands y promotions) por botones-icono (emoji), replicando el patrón exacto de /categories y /users: botón con icono SVG, hover de color y atributo title ('Editar'/'Eliminar'). Aplicar a todas las tablas que aún usan texto.",
"status": "done",
"priority": "medium",
"phase": "admin",
@@ -2563,7 +2563,7 @@
"created_at": "2026-08-18T20:03:10.712389+00:00",
"acceptance": [
"Las tablas que usan botones de texto (brands, promotions, etc.) pasan a botones-icono",
"Patr\u00f3n id\u00e9ntico al de /categories: icono SVG + hover + title",
"Patrón idéntico al de /categories: icono SVG + hover + title",
"Siempre acciones editar|eliminar donde aplique",
"Sin regresiones en las acciones existentes"
],
@@ -2576,8 +2576,8 @@
},
{
"id": "FIX-13",
"title": "Admin: CMS \u2014 sustituir texto por iconos en publicar/despublicar y a\u00f1adir edici\u00f3n",
"description": "P\u00e1ginas CMS: sustituir los botones de TEXTO 'Publicar'/'Despublicar' por botones-icono (emoji) siguiendo el patr\u00f3n de /categories, y a\u00f1adir una acci\u00f3n de EDITAR la p\u00e1gina (actualmente inexistente) para modificar slug/t\u00edtulo/cuerpo y guardar los cambios.",
"title": "Admin: CMS sustituir texto por iconos en publicar/despublicar y añadir edición",
"description": "Páginas CMS: sustituir los botones de TEXTO 'Publicar'/'Despublicar' por botones-icono (emoji) siguiendo el patrón de /categories, y añadir una acción de EDITAR la página (actualmente inexistente) para modificar slug/título/cuerpo y guardar los cambios.",
"status": "done",
"priority": "medium",
"phase": "cms",
@@ -2585,7 +2585,7 @@
"created_at": "2026-08-18T20:03:10.712389+00:00",
"acceptance": [
"'Publicar'/'Despublicar' dejan de ser texto y pasan a botones-icono (emoji)",
"Nueva acci\u00f3n para editar una p\u00e1gina CMS existente y guardar cambios",
"Nueva acción para editar una página CMS existente y guardar cambios",
"Los cambios persisten tras recargar"
],
"gates": {
@@ -2597,18 +2597,18 @@
},
{
"id": "FIX-14",
"title": "Backoffice: separaci\u00f3n f\u00edsica \u2014 tabla backoffice_users + auth propia",
"description": "Los usuarios del backoffice y los clientes del storefront NO comparten tabla. Crear tabla f\u00edsica backoffice_users (los clientes storefront siguen en identity_users) con mecanismo de autenticaci\u00f3n/sesi\u00f3n PROPIO y reforzado (pensar en seguridad: sesi\u00f3n/cookie independiente de la del storefront, hashing, rate-limit). Migrar los usuarios admin/editor existentes desde identity_users a backoffice_users.",
"title": "Backoffice: separación física — tabla backoffice_users + auth propia",
"description": "Los usuarios del backoffice y los clientes del storefront NO comparten tabla. Crear tabla física backoffice_users (los clientes storefront siguen en identity_users) con mecanismo de autenticación/sesión PROPIO y reforzado (pensar en seguridad: sesión/cookie independiente de la del storefront, hashing, rate-limit). Migrar los usuarios admin/editor existentes desde identity_users a backoffice_users.",
"status": "done",
"priority": "high",
"phase": "admin",
"type": "fix",
"created_at": "2026-08-18T20:03:10.712389+00:00",
"acceptance": [
"Nueva tabla f\u00edsica backoffice_users; clientes storefront permanecen en identity_users",
"Autenticaci\u00f3n/sesi\u00f3n del backoffice con mecanismo propio y seguro (independiente de mdv_session del storefront)",
"Nueva tabla física backoffice_users; clientes storefront permanecen en identity_users",
"Autenticación/sesión del backoffice con mecanismo propio y seguro (independiente de mdv_session del storefront)",
"Clientes del storefront no aparecen en el backoffice y viceversa",
"Migraci\u00f3n de usuarios admin/editor existentes a backoffice_users",
"Migración de usuarios admin/editor existentes a backoffice_users",
"verify.sh y gates de seguridad en verde"
],
"gates": {
@@ -2641,7 +2641,7 @@
},
{
"id": "FIX-16",
"title": "Admin: editor de productos \u2014 checkboxes de atributos seleccionables",
"title": "Admin: editor de productos checkboxes de atributos seleccionables",
"description": "En el editor de productos, los checkboxes de atributos (Bio, Vegano, Sin Gluten, etc.) no se pueden marcar/seleccionar. Hacer que sean interactivos y que su estado se guarde correctamente.",
"status": "done",
"priority": "high",
@@ -2663,7 +2663,7 @@
{
"id": "FIX-17",
"title": "Admin: precios con 2 decimales (coma o punto), display 12,00 / 12,30",
"description": "El precio se introduce y muestra con dos decimales, aceptando coma o punto indistintamente. Lo que escribe el usuario se muestra con 2 decimales: escribe 12 \u2192 muestra 12,00; escribe 12,30 \u2192 muestra 12,30. En storefront con s\u00edmbolo \u20ac (12,50 \u20ac); en backoffice queda mejor con \u20ac tambi\u00e9n. Internamente guardar correctamente (c\u00e9ntimos).",
"description": "El precio se introduce y muestra con dos decimales, aceptando coma o punto indistintamente. Lo que escribe el usuario se muestra con 2 decimales: escribe 12 muestra 12,00; escribe 12,30 muestra 12,30. En storefront con símbolo € (12,50 ); en backoffice queda mejor con € también. Internamente guardar correctamente (céntimos).",
"status": "done",
"priority": "high",
"phase": "products",
@@ -2671,10 +2671,10 @@
"created_at": "2026-08-18T20:03:10.712389+00:00",
"acceptance": [
"El input acepta 12,50 o 12.50",
"Se muestra siempre con 2 decimales (12 \u2192 12,00; 12,30 \u2192 12,30)",
"Storefront muestra '12,50 \u20ac'",
"Backoffice muestra con 2 decimales (idealmente con \u20ac)",
"Guardado correcto (c\u00e9ntimos internamente)"
"Se muestra siempre con 2 decimales (12 12,00; 12,30 12,30)",
"Storefront muestra '12,50 '",
"Backoffice muestra con 2 decimales (idealmente con )",
"Guardado correcto (céntimos internamente)"
],
"gates": {
"reviewer": true,
@@ -2685,15 +2685,15 @@
},
{
"id": "FIX-18",
"title": "Admin: clientes \u2014 editar datos personales y direcciones (NO credenciales)",
"description": "En la ficha de cliente se pueden editar los datos personales (nombre, tel\u00e9fono) y las direcciones (crear/editar/eliminar). Los datos de login (email/contrase\u00f1a) NO deben poder modificarse desde aqu\u00ed; eso queda para un futuro 'reset password' cuando se implemente el soporte de cuenta.",
"title": "Admin: clientes editar datos personales y direcciones (NO credenciales)",
"description": "En la ficha de cliente se pueden editar los datos personales (nombre, teléfono) y las direcciones (crear/editar/eliminar). Los datos de login (email/contraseña) NO deben poder modificarse desde aquí; eso queda para un futuro 'reset password' cuando se implemente el soporte de cuenta.",
"status": "done",
"priority": "high",
"phase": "customers",
"type": "fix",
"created_at": "2026-08-18T20:03:10.712389+00:00",
"acceptance": [
"Editar datos personales no sensibles (nombre, tel\u00e9fono)",
"Editar datos personales no sensibles (nombre, teléfono)",
"CRUD completo de direcciones del cliente",
"El email/credenciales de login NO son editables desde la ficha",
"Los cambios persisten tras recargar"
@@ -2707,20 +2707,20 @@
},
{
"id": "FIX-19",
"title": "Admin: categor\u00edas \u2014 seleccionar padre + tipo parent/child (child es hoja)",
"description": "En el formulario de categor\u00eda: (1) seleccionar una categor\u00eda padre para anidar, y (2) marcar categor\u00edas como 'parent' (flag booleano). Reglas de orden l\u00f3gico: una PARENT es contenedor y puede tener CHILDS u otras PARENTS; una CHILD es hoja y NO puede tener hijos (si tuviera hijos ser\u00eda parent). Migraci\u00f3n: las categor\u00edas que hoy tienen hijos pasan a parent; el resto a child.",
"title": "Admin: categorías — seleccionar padre + tipo parent/child (child es hoja)",
"description": "En el formulario de categoría: (1) seleccionar una categoría padre para anidar, y (2) marcar categorías como 'parent' (flag booleano). Reglas de orden lógico: una PARENT es contenedor y puede tener CHILDS u otras PARENTS; una CHILD es hoja y NO puede tener hijos (si tuviera hijos sería parent). Migración: las categorías que hoy tienen hijos pasan a parent; el resto a child.",
"status": "done",
"priority": "high",
"phase": "categories",
"type": "fix",
"created_at": "2026-08-18T20:03:10.712389+00:00",
"acceptance": [
"Selector de categor\u00eda padre en crear/editar",
"Flag booleano para marcar una categor\u00eda como 'parent' (contenedor)",
"Una CHILD es hoja: no puede contener hijos (validaci\u00f3n)",
"Selector de categoría padre en crear/editar",
"Flag booleano para marcar una categoría como 'parent' (contenedor)",
"Una CHILD es hoja: no puede contener hijos (validación)",
"Una PARENT puede contener CHILDS u otras PARENTS",
"Migraci\u00f3n: categor\u00edas con hijos \u2192 parent; resto \u2192 child",
"El \u00e1rbol de categor\u00edas se actualiza correctamente"
"Migración: categorías con hijos parent; resto child",
"El árbol de categorías se actualiza correctamente"
],
"gates": {
"reviewer": true,
@@ -2862,12 +2862,12 @@
},
{
"id": "F-052",
"title": "Im\u00e1genes de producto 404 en frontend y storefront",
"title": "Imágenes de producto 404 en frontend y storefront",
"status": "done",
"stage": null,
"type": "fix",
"priority": "high",
"description": "Las im\u00e1genes de producto (URLs /uploads/...) devuelven 404 en el frontend (3003) y storefront (3005) porque los archivos est\u00e1n en public/uploads/ del admin (3004) y el backend no sirve archivos est\u00e1ticos de uploads. Soluci\u00f3n: copiar uploads a frontend/storefront public/, o a\u00f1adir una ruta /uploads/ en el backend que sirva desde el dir del admin.",
"description": "Las imágenes de producto (URLs /uploads/...) devuelven 404 en el frontend (3003) y storefront (3005) porque los archivos están en public/uploads/ del admin (3004) y el backend no sirve archivos estáticos de uploads. Solución: copiar uploads a frontend/storefront public/, o añadir una ruta /uploads/ en el backend que sirva desde el dir del admin.",
"created_at": "2026-08-19T08:23:15Z",
"updated_at": "2026-08-19T08:23:15Z",
"assignee": null,
@@ -2887,7 +2887,7 @@
"stage": null,
"type": "fix",
"priority": "low",
"description": "En la p\u00e1gina de edici\u00f3n de categor\u00edas del admin, el grid de 2 columnas (select de padre + checkbox promote) tiene el segundo item (flex con checkbox) desalineado verticalmente y sin ocupar todo el ancho. Necesita ajustarse para que ambos items est\u00e9n alineados.",
"description": "En la página de edición de categorías del admin, el grid de 2 columnas (select de padre + checkbox promote) tiene el segundo item (flex con checkbox) desalineado verticalmente y sin ocupar todo el ancho. Necesita ajustarse para que ambos items estén alineados.",
"created_at": "2026-08-19T08:23:15Z",
"updated_at": "2026-08-19T08:23:15Z",
"assignee": null,
@@ -2897,7 +2897,7 @@
"qa": true,
"close": true
},
"acceptance": "1. El select de categor\u00eda padre y el checkbox de promote est\u00e1n alineados verticalmente y ambos ocupan todo el ancho disponible.",
"acceptance": "1. El select de categoría padre y el checkbox de promote están alineados verticalmente y ambos ocupan todo el ancho disponible.",
"completed_at": "2026-08-19T08:48:35Z"
},
{
@@ -2907,7 +2907,7 @@
"stage": null,
"type": "fix",
"priority": "high",
"description": "El backend (dist/infrastructure/http/server.js) muere sin dejar trazas en el log y deja a admin/frontend sin API. S\u00edntoma: /api/auth/login devuelve 502 (admin proxy) o 500 (frontend route handler) cuando el backend no responde. Causa ra\u00edz desconocida (no hay error en el log). Mejoras: (1) investigar por qu\u00e9 muere; (2) hacer que monolith.sh detecte y reinicie el backend si muere; (3) usar process.env.NEXT_PUBLIC_API_URL en frontend/src/app/api/auth/login/route.ts en lugar de hardcodear http://127.0.0.1:3000.",
"description": "El backend (dist/infrastructure/http/server.js) muere sin dejar trazas en el log y deja a admin/frontend sin API. Síntoma: /api/auth/login devuelve 502 (admin proxy) o 500 (frontend route handler) cuando el backend no responde. Causa raíz desconocida (no hay error en el log). Mejoras: (1) investigar por qué muere; (2) hacer que monolith.sh detecte y reinicie el backend si muere; (3) usar process.env.NEXT_PUBLIC_API_URL en frontend/src/app/api/auth/login/route.ts en lugar de hardcodear http://127.0.0.1:3000.",
"created_at": "2026-08-19T08:52:44Z",
"updated_at": "2026-08-19T08:52:44Z",
"assignee": null,
@@ -2917,7 +2917,7 @@
"qa": true,
"close": true
},
"acceptance": "1. Backend se mantiene estable bajo carga normal (no muere sin causa aparente). 2. Si el backend muere, monolith.sh lo reinicia autom\u00e1ticamente. 3. /api/auth/login en 3003 usa NEXT_PUBLIC_API_URL configurable. 4. /api/auth/login responde c\u00f3digos del backend (no 500/502) cuando el backend est\u00e1 vivo.",
"acceptance": "1. Backend se mantiene estable bajo carga normal (no muere sin causa aparente). 2. Si el backend muere, monolith.sh lo reinicia automáticamente. 3. /api/auth/login en 3003 usa NEXT_PUBLIC_API_URL configurable. 4. /api/auth/login responde códigos del backend (no 500/502) cuando el backend está vivo.",
"completed_at": "2026-08-19T11:15:30Z"
},
{
@@ -4113,7 +4113,7 @@
"description": "Problem: The admin product listing receives products without brand and expirationDate because catalog serialization omits those fields. In the product editor inventory section, SKU and EAN saves update row state but the display renders from the original variants array, so edited values appear not to persist.. Goal: Include brand and expirationDate in catalog product serialization and render saved SKU/EAN values from the updated row state so edits remain visible after the PATCH succeeds.. Scope IN: backend catalog serialization, admin product listing, admin product inventory section. Scope OUT: No database schema change, no new fields, no API redesign. Type: fix. Priority: high. Risk: low.",
"acceptance": [
"- Product list response includes brand and expirationDate when present",
"- Product listing displays the brand name and expiration date instead of \u2014 for populated products",
"- Product listing displays the brand name and expiration date instead of for populated products",
"- Successful SKU edit remains visible after leaving edit mode",
"- Successful EAN edit remains visible after leaving edit mode",
"- Existing PATCH /products/:id/variants/:variantId behavior remains used",
@@ -4984,8 +4984,8 @@
"id": "F-116",
"type": "feature",
"title": "Translate legacy OpenCart categories to Spanish (Title Case)",
"problem": "F-114 imported 39 categories with English names like HERBALIST, NUTS & SEEDS, BREAD & PASTRIES \u2014 operators want Spanish translations with first letter of each word capitalized to match the rest of the catalog",
"goal": "Translate every legacy category name to Spanish using Title Case (Primera Letra en May\u00fasculas), preserving the diacritics, and replace the existing entries in bulk",
"problem": "F-114 imported 39 categories with English names like HERBALIST, NUTS & SEEDS, BREAD & PASTRIES operators want Spanish translations with first letter of each word capitalized to match the rest of the catalog",
"goal": "Translate every legacy category name to Spanish using Title Case (Primera Letra en Mayúsculas), preserving the diacritics, and replace the existing entries in bulk",
"scope_in": [
"categories module",
"legacy catalog helper",
@@ -4998,7 +4998,7 @@
],
"priority": "med",
"risk": "low",
"description": "Problem: F-114 imported 39 categories with English names like HERBALIST, NUTS & SEEDS, BREAD & PASTRIES \u2014 operators want Spanish translations with first letter of each word capitalized to match the rest of the catalog. Goal: Translate every legacy category name to Spanish using Title Case (Primera Letra en May\u00fasculas), preserving the diacritics, and replace the existing entries in bulk. Scope IN: categories module, legacy catalog helper, seed script update, idempotent update. Scope OUT: no new categories, no brand renames. Type: feature. Priority: med. Risk: low.",
"description": "Problem: F-114 imported 39 categories with English names like HERBALIST, NUTS & SEEDS, BREAD & PASTRIES operators want Spanish translations with first letter of each word capitalized to match the rest of the catalog. Goal: Translate every legacy category name to Spanish using Title Case (Primera Letra en Mayúsculas), preserving the diacritics, and replace the existing entries in bulk. Scope IN: categories module, legacy catalog helper, seed script update, idempotent update. Scope OUT: no new categories, no brand renames. Type: feature. Priority: med. Risk: low.",
"acceptance": [
"Translation table covers every legacy category name",
"Names are translated to Spanish in Title Case",
@@ -5019,9 +5019,9 @@
{
"id": "F-117",
"type": "fix",
"title": "Title Case a las categor\u00edas ya en espa\u00f1ol (F-116 oversight)",
"problem": "F-116 renombr\u00f3 30 categor\u00edas en espa\u00f1ol pero dej\u00f3 en may\u00fasculas FRUTAS Y VERDURAS, SNACKS y GRANOLA que ya estaban en espa\u00f1ol en el legacy",
"goal": "Forzar Title Case en las categor\u00edas que ya estaban en espa\u00f1ol: FRUTAS Y VERDURAS, SNACKS, GRANOLA",
"title": "Title Case a las categorías ya en español (F-116 oversight)",
"problem": "F-116 renombró 30 categorías en español pero dejó en mayúsculas FRUTAS Y VERDURAS, SNACKS y GRANOLA que ya estaban en español en el legacy",
"goal": "Forzar Title Case en las categorías que ya estaban en español: FRUTAS Y VERDURAS, SNACKS, GRANOLA",
"scope_in": [
"categories module",
"translation table",
@@ -5032,11 +5032,11 @@
],
"priority": "low",
"risk": "low",
"description": "Problem: F-116 renombr\u00f3 30 categor\u00edas en espa\u00f1ol pero dej\u00f3 en may\u00fasculas FRUTAS Y VERDURAS, SNACKS y GRANOLA que ya estaban en espa\u00f1ol en el legacy. Goal: Forzar Title Case en las categor\u00edas que ya estaban en espa\u00f1ol: FRUTAS Y VERDURAS, SNACKS, GRANOLA. Scope IN: categories module, translation table, seed script. Scope OUT: no nuevas renombraciones fuera del alcance. Type: fix. Priority: low. Risk: low.",
"description": "Problem: F-116 renombró 30 categorías en español pero dejó en mayúsculas FRUTAS Y VERDURAS, SNACKS y GRANOLA que ya estaban en español en el legacy. Goal: Forzar Title Case en las categorías que ya estaban en español: FRUTAS Y VERDURAS, SNACKS, GRANOLA. Scope IN: categories module, translation table, seed script. Scope OUT: no nuevas renombraciones fuera del alcance. Type: fix. Priority: low. Risk: low.",
"acceptance": [
"FRUTAS Y VERDURAS \u2192 Frutas y Verduras",
"SNACKS \u2192 Snacks",
"GRANOLA \u2192 Granola",
"FRUTAS Y VERDURAS Frutas y Verduras",
"SNACKS Snacks",
"GRANOLA Granola",
"Re-running the seed is idempotent",
"Typecheck, tests, verify pass"
],
@@ -5068,10 +5068,10 @@
"risk": "low",
"description": "Problem: The 5 root categories (Alimentacion, Cosmetica e Higiene, Hogar y Mascotas, Limpieza Ecologica, Suplementos) lack Spanish diacritics; the user wants proper Spanish Title Case with accents. Goal: Update the 5 root categories to use proper Spanish accents. Scope IN: categories module, seed update. Scope OUT: no new categories, no brand renames. Type: fix. Priority: low. Risk: low.",
"acceptance": [
"Alimentacion \u2192 Alimentaci\u00f3n",
"Cosmetica e Higiene \u2192 Cosm\u00e9tica e Higiene",
"Alimentacion Alimentación",
"Cosmetica e Higiene → Cosmética e Higiene",
"Hogar y Mascotas stays as-is (no accent needed)",
"Limpieza Ecologica \u2192 Limpieza Ecol\u00f3gica",
"Limpieza Ecologica Limpieza Ecológica",
"Proveedores stays as-is",
"Suplementos stays as-is",
"Idempotent (DB has unique slug; renaming in place is safe)"
@@ -5191,7 +5191,7 @@
"id": "F-122",
"type": "fix",
"title": "Remove redundant variant warning when product has only one variant",
"problem": "PriceStockSection shows \"\u26a0\ufe0f Este producto tiene X variante(s) heredada(s). Se est\u00e1 editando la principal.\" when extraVariants > 0. The user wants this warning gone because each product has at most one variant (the legacy migrations were for when the model supported multiple variants, but the simplified model has only one). The warning is noise.",
"problem": "PriceStockSection shows \"⚠️ Este producto tiene X variante(s) heredada(s). Se está editando la principal.\" when extraVariants > 0. The user wants this warning gone because each product has at most one variant (the legacy migrations were for when the model supported multiple variants, but the simplified model has only one). The warning is noise.",
"goal": "Drop the warning and the legacy extraVariants state. Confirm the underlying API does not return extra variants.",
"scope_in": [
"PriceStockSection",
@@ -5202,7 +5202,7 @@
],
"priority": "low",
"risk": "low",
"description": "Problem: PriceStockSection shows \"\u26a0\ufe0f Este producto tiene X variante(s) heredada(s). Se est\u00e1 editando la principal.\" when extraVariants > 0. The user wants this warning gone because each product has at most one variant (the legacy migrations were for when the model supported multiple variants, but the simplified model has only one). The warning is noise.. Goal: Drop the warning and the legacy extraVariants state. Confirm the underlying API does not return extra variants.. Scope IN: PriceStockSection, useEffect cleanup. Scope OUT: no behavior changes. Type: fix. Priority: low. Risk: low.",
"description": "Problem: PriceStockSection shows \"⚠️ Este producto tiene X variante(s) heredada(s). Se está editando la principal.\" when extraVariants > 0. The user wants this warning gone because each product has at most one variant (the legacy migrations were for when the model supported multiple variants, but the simplified model has only one). The warning is noise.. Goal: Drop the warning and the legacy extraVariants state. Confirm the underlying API does not return extra variants.. Scope IN: PriceStockSection, useEffect cleanup. Scope OUT: no behavior changes. Type: fix. Priority: low. Risk: low.",
"acceptance": [
"PriceStockSection no longer shows the legacy warning",
"extraVariants state removed",
@@ -5223,7 +5223,7 @@
"id": "F-123",
"type": "fix",
"title": "Remove 'Save product first' warning for single-variant products",
"description": "F-121 dej\u00f3 en ProductEditor.tsx los textos 'Guarda primero el producto para configurar precio, stock y EAN.' (l\u00ednea 286) y 'Guarda primero el producto para subir im\u00e1genes.' (l\u00ednea 374). El bundle servido en :3004 sigue mostrando la restricci\u00f3n cuando productId es undefined (modo creaci\u00f3n). Como todos los productos son single-variant, la restricci\u00f3n no aporta valor. Fix: eliminar los avisos y permitir que PriceStockSection e ImagesSection rendericen siempre, con los botones de guardado deshabilitados y un caption 'Se guardar\u00e1 al crear el producto' hasta que exista productId.",
"description": "F-121 dejó en ProductEditor.tsx los textos 'Guarda primero el producto para configurar precio, stock y EAN.' (línea 286) y 'Guarda primero el producto para subir imágenes.' (línea 374). El bundle servido en :3004 sigue mostrando la restricción cuando productId es undefined (modo creación). Como todos los productos son single-variant, la restricción no aporta valor. Fix: eliminar los avisos y permitir que PriceStockSection e ImagesSection rendericen siempre, con los botones de guardado deshabilitados y un caption 'Se guardará al crear el producto' hasta que exista productId.",
"priority": "high",
"risk": "low",
"status": "done",
@@ -5239,8 +5239,8 @@
{
"id": "F-124",
"type": "fix",
"title": "Two-column layout for Categor\u00edas + Atributos in product editor",
"description": "En /products/[id] y /products/new, los bloques Categor\u00edas y Atributos se renderizan apilados ocupando todo el ancho. Como ambos son cortos (listas/checkbox grid), deber\u00edan ir lado a lado en desktop (grid-cols-2). M\u00f3vil sigue apilado.",
"title": "Two-column layout for Categorías + Atributos in product editor",
"description": "En /products/[id] y /products/new, los bloques Categorías y Atributos se renderizan apilados ocupando todo el ancho. Como ambos son cortos (listas/checkbox grid), deberían ir lado a lado en desktop (grid-cols-2). Móvil sigue apilado.",
"priority": "med",
"risk": "low",
"status": "done",
@@ -5256,8 +5256,8 @@
{
"id": "F-125",
"type": "feature",
"title": "Allow backward status transitions in /orders/[id] + resend email on SHIPPED\u2194PROCESSING",
"description": "En /orders/[id] editar pedido, los status no permiten volver atr\u00e1s (PROCESSING\u2192PENDING, SHIPPED\u2192PROCESSING, etc.). El operador necesita: (1) poder revertir un pedido a un estado anterior si se equivoc\u00f3; (2) cuando un pedido cambia entre PROCESSING y SHIPPED (en cualquier direcci\u00f3n), reenviar el email al cliente con el transportista/tracking actualizado (no solo en la transici\u00f3n inicial a SHIPPED).",
"title": "Allow backward status transitions in /orders/[id] + resend email on SHIPPEDPROCESSING",
"description": "En /orders/[id] editar pedido, los status no permiten volver atrás (PROCESSINGPENDING, SHIPPEDPROCESSING, etc.). El operador necesita: (1) poder revertir un pedido a un estado anterior si se equivocó; (2) cuando un pedido cambia entre PROCESSING y SHIPPED (en cualquier dirección), reenviar el email al cliente con el transportista/tracking actualizado (no solo en la transición inicial a SHIPPED).",
"priority": "high",
"risk": "low",
"status": "done",
@@ -5274,7 +5274,7 @@
"id": "F-126",
"type": "bug",
"title": "Inventory search by partial EAN or product name",
"description": "El buscador en /inventory no filtra correctamente al escribir parte o todo un EAN o nombre de producto. F-093 a\u00f1adi\u00f3 b\u00fasqueda por nombre/EAN pero el operador reporta que sigue sin funcionar bien. Revisar el endpoint /inventory, el frontend /inventory page y confirmar que el query param q se mapea a un ILIKE correcto sobre el nombre del producto y el EAN de la variante.",
"description": "El buscador en /inventory no filtra correctamente al escribir parte o todo un EAN o nombre de producto. F-093 añadió búsqueda por nombre/EAN pero el operador reporta que sigue sin funcionar bien. Revisar el endpoint /inventory, el frontend /inventory page y confirmar que el query param q se mapea a un ILIKE correcto sobre el nombre del producto y el EAN de la variante.",
"priority": "high",
"risk": "low",
"status": "done",
@@ -5291,7 +5291,7 @@
"id": "F-127",
"type": "bug",
"title": "Delete brands or categories still fails in admin UI",
"description": "Tras F-120 (DELETE /categories/:id cascade en backend, migraci\u00f3n 041) y F-022 (DELETE /brands/:id), el operador sigue reportando que no puede eliminar marcas o categor\u00edas. El backend est\u00e1 OK (curl DELETE funciona) pero la UI puede estar mostrando el error antiguo por cach\u00e9 del navegador, o el c\u00f3digo frontend hace un check previo (hasChildren, hasProducts) que devuelve 409 y bloquea el bot\u00f3n.",
"description": "Tras F-120 (DELETE /categories/:id cascade en backend, migración 041) y F-022 (DELETE /brands/:id), el operador sigue reportando que no puede eliminar marcas o categorías. El backend está OK (curl DELETE funciona) pero la UI puede estar mostrando el error antiguo por caché del navegador, o el código frontend hace un check previo (hasChildren, hasProducts) que devuelve 409 y bloquea el botón.",
"priority": "high",
"risk": "low",
"status": "done",
@@ -5308,7 +5308,7 @@
"id": "F-128",
"type": "fix",
"title": "CMS page templates load with current content for in-place modification",
"description": "En /cms, las plantillas del sistema (footer, pol\u00edtica de privacidad, etc.) ya tienen contenido por defecto. Al pulsar Editar sobre una plantilla, se deber\u00eda cargar el contenido actual (no vac\u00edo) en el editor Lexical para poder modificarlo, no partir de un textarea en blanco.",
"description": "En /cms, las plantillas del sistema (footer, política de privacidad, etc.) ya tienen contenido por defecto. Al pulsar Editar sobre una plantilla, se debería cargar el contenido actual (no vacío) en el editor Lexical para poder modificarlo, no partir de un textarea en blanco.",
"priority": "high",
"risk": "low",
"status": "done",
@@ -5325,7 +5325,7 @@
"id": "F-129",
"type": "fix",
"title": "Logs viewer: order DESC and remove autoscroll",
"description": "En el visor de logs (/logs), los eventos deben ordenarse DESC (m\u00e1s reciente arriba). El autoscroll debe estar desactivado para que el usuario vea siempre el \u00faltimo evento en la parte superior sin necesidad de scroll. El polling puede seguir actualizando en vivo.",
"description": "En el visor de logs (/logs), los eventos deben ordenarse DESC (más reciente arriba). El autoscroll debe estar desactivado para que el usuario vea siempre el último evento en la parte superior sin necesidad de scroll. El polling puede seguir actualizando en vivo.",
"priority": "low",
"risk": "low",
"status": "done",
@@ -5359,7 +5359,7 @@
"id": "F-131",
"type": "bug",
"title": "Products list search in admin does not filter",
"description": "El buscador de /products (admin panel) no filtra los productos al escribir. Revisar el input de b\u00fasqueda en la lista, el endpoint backend y los query params enviados. Debe buscar por nombre y slug, idealmente con ILIKE case-insensitive.",
"description": "El buscador de /products (admin panel) no filtra los productos al escribir. Revisar el input de búsqueda en la lista, el endpoint backend y los query params enviados. Debe buscar por nombre y slug, idealmente con ILIKE case-insensitive.",
"priority": "high",
"risk": "low",
"status": "done",
@@ -5376,7 +5376,7 @@
"id": "F-132",
"type": "bug",
"title": "Frontend home DYNAMIC_SERVER_USAGE: FeaturedProducts and CategoriesGrid use cache: 'no-store'",
"description": "Build del frontend falla con DYNAMIC_SERVER_USAGE en / porque (1) FeaturedProducts hace fetch con cache: 'no-store' a /products/search?limit=8, y (2) CategoriesGrid hace fetch con cache: 'no-store' a /categories/tree. El home estaba intent\u00e1ndose prerenderizar est\u00e1ticamente. Fix: usar ISR con revalidate=60 en el home page, o quitar cache: 'no-store' de los dos componentes para que la p\u00e1gina pueda ser SSG/ISR. Adicionalmente hay 4 warnings de turbopack sobre filesystem access din\u00e1mico en frontend/src/app/uploads/[...path]/route.ts (path.join(root, filename)). A\u00f1adir turbopackIgnore comments para silenciarlos.",
"description": "Build del frontend falla con DYNAMIC_SERVER_USAGE en / porque (1) FeaturedProducts hace fetch con cache: 'no-store' a /products/search?limit=8, y (2) CategoriesGrid hace fetch con cache: 'no-store' a /categories/tree. El home estaba intentándose prerenderizar estáticamente. Fix: usar ISR con revalidate=60 en el home page, o quitar cache: 'no-store' de los dos componentes para que la página pueda ser SSG/ISR. Adicionalmente hay 4 warnings de turbopack sobre filesystem access dinámico en frontend/src/app/uploads/[...path]/route.ts (path.join(root, filename)). Añadir turbopackIgnore comments para silenciarlos.",
"priority": "high",
"risk": "low",
"status": "done",
@@ -5393,7 +5393,7 @@
"id": "F-133",
"type": "fix",
"title": "Inline custom weight input inside the Peso unitario dropdown",
"description": "En apps/admin/src/features/products/components/sections/PriceStockSection.tsx, el campo Peso unitario (Gr) tiene un <select> con presets [100,150,200,250,300,350,500,740,Personalizado...]. Cuando se elige 'Personalizado...', aparece un <input> separado debajo del select. El operador reporta que esto rompe la forma visual del grid. Fix: integrar el input custom DENTRO del mismo control (p. ej. <datalist> + input number con list attribute, o un input con suggestion datalist), para que sea un \u00fanico campo visual.",
"description": "En apps/admin/src/features/products/components/sections/PriceStockSection.tsx, el campo Peso unitario (Gr) tiene un <select> con presets [100,150,200,250,300,350,500,740,Personalizado...]. Cuando se elige 'Personalizado...', aparece un <input> separado debajo del select. El operador reporta que esto rompe la forma visual del grid. Fix: integrar el input custom DENTRO del mismo control (p. ej. <datalist> + input number con list attribute, o un input con suggestion datalist), para que sea un único campo visual.",
"priority": "med",
"risk": "low",
"status": "done",
@@ -5410,7 +5410,7 @@
"id": "F-134",
"type": "fix",
"title": "PriceStockSection: allow editing price/stock/EAN/weight fields when pending (no productId)",
"description": "En apps/admin/src/features/products/components/sections/PriceStockSection.tsx, los inputs de Stock, EAN, Peso unitario y Compra m\u00ednima est\u00e1n disabled={... || pending} en estado pending (sin productId). El operador reporta que no puede agregar datos en esos campos al crear un producto nuevo. Los inputs PVP/Coste/Oferta/Neto S\u00cd est\u00e1n editables. Soluci\u00f3n: permitir edici\u00f3n en TODOS los inputs (estado local), pero mantener disabled en los botones Guardar precio/Guardar stock (porque no hay productId para llamar API). Al crear el producto y obtener productId, los valores locales quedan listos para persistir.",
"description": "En apps/admin/src/features/products/components/sections/PriceStockSection.tsx, los inputs de Stock, EAN, Peso unitario y Compra mínima están disabled={... || pending} en estado pending (sin productId). El operador reporta que no puede agregar datos en esos campos al crear un producto nuevo. Los inputs PVP/Coste/Oferta/Neto SÍ están editables. Solución: permitir edición en TODOS los inputs (estado local), pero mantener disabled en los botones Guardar precio/Guardar stock (porque no hay productId para llamar API). Al crear el producto y obtener productId, los valores locales quedan listos para persistir.",
"priority": "high",
"risk": "low",
"status": "done",
@@ -5427,7 +5427,7 @@
"id": "F-135",
"type": "fix",
"title": "ImagesSection drag-and-drop: accept files in pending state, auto-upload after create",
"description": "En apps/admin/src/features/products/components/sections/ImagesSection.tsx, la zona de drop 'Arrastra im\u00e1genes aqu\u00ed para a\u00f1adirlas al producto' no funciona cuando el operador est\u00e1 creando un producto nuevo (/products/new). Mi F-123 a\u00f1adi\u00f3 un bloque 'pending' que renderiza la zona de drop visualmente PERO sin los handlers onDragOver/onDragLeave/onDrop \u2014 solo el input row tiene pointer-events-none. La drop zone acepta drops visuales pero no hace nada. Fix: aceptar drops tambi\u00e9n en estado pending, guardar los archivos en estado local (cola), y subirlos autom\u00e1ticamente al backend cuando productId se setea (despu\u00e9s de Crear producto).",
"description": "En apps/admin/src/features/products/components/sections/ImagesSection.tsx, la zona de drop 'Arrastra imágenes aquí para añadirlas al producto' no funciona cuando el operador está creando un producto nuevo (/products/new). Mi F-123 añadió un bloque 'pending' que renderiza la zona de drop visualmente PERO sin los handlers onDragOver/onDragLeave/onDrop solo el input row tiene pointer-events-none. La drop zone acepta drops visuales pero no hace nada. Fix: aceptar drops también en estado pending, guardar los archivos en estado local (cola), y subirlos automáticamente al backend cuando productId se setea (después de Crear producto).",
"priority": "high",
"risk": "low",
"status": "done",
@@ -5461,7 +5461,7 @@
"id": "F-137",
"type": "fix",
"title": "Remove 'Guardar precio' button in PriceStockSection, save prices via main 'Guardar cambios'",
"description": "apps/admin/src/features/products/components/sections/PriceStockSection.tsx:349 has its own 'Guardar precio' button that PUTs /api/pricing/variants/:id. User wants this removed \u2014 prices must save via the main 'Guardar cambios' button in ProductEditor.tsx:229 alongside the rest of the product payload.",
"description": "apps/admin/src/features/products/components/sections/PriceStockSection.tsx:349 has its own 'Guardar precio' button that PUTs /api/pricing/variants/:id. User wants this removed prices must save via the main 'Guardar cambios' button in ProductEditor.tsx:229 alongside the rest of the product payload.",
"priority": "high",
"risk": "low",
"status": "done",
@@ -5478,7 +5478,7 @@
"id": "F-138",
"type": "bug",
"title": "Auto-seed price row on variant creation to prevent 404 race window",
"description": "Variant 04bfcbc7 was created at 16:44:21 but price row only at 17:08:20 \u2014 24-minute window where GET /api/pricing/variants/<id> returns 404 PRICING_PRICE_NOT_FOUND. Operator saw 3x 404 in console. Root cause: variant creation in catalog module does not insert into pricing_variant_prices. Fix: on CreateProductVariant, also INSERT a row with net_unit_amount_cents=0, vat_rate='general' (or use a sensible default). Add unique constraint check so re-seeding is no-op. Tests: integration for variant creation that confirms price row exists immediately after.",
"description": "Variant 04bfcbc7 was created at 16:44:21 but price row only at 17:08:20 24-minute window where GET /api/pricing/variants/<id> returns 404 PRICING_PRICE_NOT_FOUND. Operator saw 3x 404 in console. Root cause: variant creation in catalog module does not insert into pricing_variant_prices. Fix: on CreateProductVariant, also INSERT a row with net_unit_amount_cents=0, vat_rate='general' (or use a sensible default). Add unique constraint check so re-seeding is no-op. Tests: integration for variant creation that confirms price row exists immediately after.",
"priority": "high",
"risk": "med",
"status": "done",
@@ -5512,7 +5512,7 @@
"id": "F-140",
"type": "feature",
"title": "Order detail page shows customer, shipping address, billing address, payment method",
"description": "apps/admin/src/app/(dashboard)/orders/[id]/page.tsx currently shows only items + state + shipping (tracking, courier, note). Missing: customer name/email/phone, shipping address, billing address, payment method + last 4 + state. Data lives in identity_users, users_addresses, payments_transactions. Fix: (1) backend GET /orders/:id joins + serializes customer/address/payment. (2) admin page renders new sections. (3) keep read-only \u2014 no edits from this page.",
"description": "apps/admin/src/app/(dashboard)/orders/[id]/page.tsx currently shows only items + state + shipping (tracking, courier, note). Missing: customer name/email/phone, shipping address, billing address, payment method + last 4 + state. Data lives in identity_users, users_addresses, payments_transactions. Fix: (1) backend GET /orders/:id joins + serializes customer/address/payment. (2) admin page renders new sections. (3) keep read-only no edits from this page.",
"priority": "high",
"risk": "med",
"status": "done",
@@ -7355,7 +7355,7 @@
{
"id": "F-195",
"type": "fix",
"title": "Hide IVA/Audit/Logs from main sidebar \u2014 only show inside Settings",
"title": "Hide IVA/Audit/Logs from main sidebar only show inside Settings",
"description": "Remove tax-rates/audit/logs from NAV_ITEMS so they do not appear in the main admin sidebar; keep them as sub-pages inside the Settings section.",
"priority": "high",
"risk": "low",
@@ -7372,7 +7372,7 @@
{
"id": "FIX-196",
"type": "fix",
"title": "Fix controlled\u2192uncontrolled input warning in CheckoutClient",
"title": "Fix controlleduncontrolled input warning in CheckoutClient",
"description": "addressToForm assigns addr.postalCode/country directly to form state. When API returns null, the input becomes uncontrolled. Fix: addr.postalCode ?? \"\" and addr.country ?? \"\".",
"priority": "med",
"risk": "low",
@@ -7407,7 +7407,7 @@
"id": "POS-FIX-1",
"type": "fix",
"title": "Add explicit return transitions to POS order state machine",
"description": "POS orders use COMPLETED/REFUNDED/PARTIALLY_REFUNDED states. apply-pos-return.ts bypasses the order state machine (direct UPDATE). ALLOWED_TRANSITIONS for COMPLETED is empty. Should add explicit COMPLETED\u2192REFUNDED and COMPLETED\u2192PARTIALLY_REFUNDED transitions.",
"description": "POS orders use COMPLETED/REFUNDED/PARTIALLY_REFUNDED states. apply-pos-return.ts bypasses the order state machine (direct UPDATE). ALLOWED_TRANSITIONS for COMPLETED is empty. Should add explicit COMPLETEDREFUNDED and COMPLETEDPARTIALLY_REFUNDED transitions.",
"priority": "high",
"risk": "med",
"status": "done",
@@ -7475,7 +7475,7 @@
"id": "FIX-198",
"type": "fix",
"title": "Frontend AuthContext: /api/auth/me missing credentials:include",
"description": "AuthContext initial useEffect calls /api/auth/me WITHOUT credentials:include, so the session cookie is not sent. After login, navigating to / causes /api/auth/me to return 401 (no cookie), setUser(null) is called, session is lost. Fix: add credentials:include to /api/auth/me fetch. Also: login/register response body shape may differ from {id,email,role} \u2014 verify response.",
"description": "AuthContext initial useEffect calls /api/auth/me WITHOUT credentials:include, so the session cookie is not sent. After login, navigating to / causes /api/auth/me to return 401 (no cookie), setUser(null) is called, session is lost. Fix: add credentials:include to /api/auth/me fetch. Also: login/register response body shape may differ from {id,email,role} verify response.",
"priority": "high",
"risk": "low",
"status": "done",
@@ -7526,7 +7526,7 @@
"id": "POS-FIX-5",
"type": "fix",
"title": "POS TPV: close session requires PIN + selfpay mode setting",
"description": "Cierre de caja requiere PIN (autenticaci\u00f3n del cajero). Terminales selfpay deben tener la opci\u00f3n de deshabilitar el cierre. A\u00f1adir terminal.settings.selfpayMode (bool) y closeSessionRequiresPin (bool). Admin puede configurar via PATCH /pos/admin/terminals/:id. TPV lee settings del config endpoint.",
"description": "Cierre de caja requiere PIN (autenticación del cajero). Terminales selfpay deben tener la opción de deshabilitar el cierre. Añadir terminal.settings.selfpayMode (bool) y closeSessionRequiresPin (bool). Admin puede configurar via PATCH /pos/admin/terminals/:id. TPV lee settings del config endpoint.",
"priority": "high",
"risk": "low",
"status": "done",
@@ -7560,7 +7560,7 @@
"id": "F-201",
"type": "feature",
"title": "Reporte de cierre de caja en POS reporting",
"description": "Nuevo apartado en reporting: cierre de caja con saldo inicial, ventas, saldo final al cierre, desglose por formas de pago, y art\u00edculos vendidos. El reporte se genera a partir de los datos de la sesi\u00f3n de caja cerrada (pos_cash_sessions + pos_cash_session_payments + pos_sales).",
"description": "Nuevo apartado en reporting: cierre de caja con saldo inicial, ventas, saldo final al cierre, desglose por formas de pago, y artículos vendidos. El reporte se genera a partir de los datos de la sesión de caja cerrada (pos_cash_sessions + pos_cash_session_payments + pos_sales).",
"priority": "high",
"risk": "low",
"status": "done",
@@ -7576,8 +7576,8 @@
{
"id": "F-202",
"type": "feature",
"title": "Email autom\u00e1tico de cierre de caja con report",
"description": "Cuando se cierra la caja, enviar email con el report de cierre al email destino configurado en SMTP settings. A\u00f1adir campo reportDestinationEmail en la config SMTP (tabla settings o archivo env). A\u00f1adir endpoint GET /pos/admin/settings y PATCH /pos/admin/settings. En closeSession, llamar al mailer con el report.",
"title": "Email automático de cierre de caja con report",
"description": "Cuando se cierra la caja, enviar email con el report de cierre al email destino configurado en SMTP settings. Añadir campo reportDestinationEmail en la config SMTP (tabla settings o archivo env). Añadir endpoint GET /pos/admin/settings y PATCH /pos/admin/settings. En closeSession, llamar al mailer con el report.",
"priority": "high",
"risk": "low",
"status": "done",
@@ -7594,7 +7594,7 @@
"id": "F-203",
"type": "feature",
"title": "Configurar PIN de cajero desde admin panel",
"description": "Desde admin panel, permitir configurar el PIN de cierre de caja (terminal.settings.closeSessionPin) directamente desde la UI de gesti\u00f3n de terminales. A\u00f1adir campo PIN (oculto, editable) en el formulario de edici\u00f3n de terminal. Actualizar PATCH /pos/admin/terminals/:id para aceptar closeSessionPin.",
"description": "Desde admin panel, permitir configurar el PIN de cierre de caja (terminal.settings.closeSessionPin) directamente desde la UI de gestión de terminales. Añadir campo PIN (oculto, editable) en el formulario de edición de terminal. Actualizar PATCH /pos/admin/terminals/:id para aceptar closeSessionPin.",
"priority": "medium",
"risk": "low",
"status": "done",
@@ -7605,7 +7605,7 @@
"qa": true,
"close": true
},
"completed_at": "2026-08-23T07:24:04Z"
"completed_at": "2026-08-23T21:21:30Z"
}
]
}

View File

@@ -372,6 +372,7 @@ export async function registerPosRoutes(app: FastifyInstance, deps: PosRouteDeps
properties: {
selfpayMode: { type: 'boolean' },
closeSessionRequiresPin: { type: 'boolean' },
closeSessionPin: { type: 'string', maxLength: 6 },
},
additionalProperties: true,
},
@@ -391,6 +392,7 @@ export async function registerPosRoutes(app: FastifyInstance, deps: PosRouteDeps
settings: z.object({
selfpayMode: z.boolean().optional(),
closeSessionRequiresPin: z.boolean().optional(),
closeSessionPin: z.string().max(6).optional(),
}).passthrough().optional(),
}),
request.body ?? {},