{
  "project": "mercadodevida-vnext",
  "description": "Incremental SDD roadmap for MercadoDeVida vNext (modular monolith). Derived from project/design_prompt.md. One feature at a time, spec before code, gates before done.",
  "rules": {
    "one_feature_at_a_time": true,
    "require_review_gate": true,
    "require_security_gate": true,
    "require_qa_gate": true,
    "valid_status": [
      "pending",
      "in_progress",
      "blocked",
      "done"
    ],
    "valid_types": [
      "feature",
      "fix",
      "bug",
      "chore"
    ]
  },
  "features": [
    {
      "id": "ADM-006",
      "title": "Admin: Product Editor Shell",
      "description": "Shell del editor de productos con tabs y detección de cambios sin guardar.",
      "status": "done",
      "priority": "high",
      "phase": "products",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T19:08:00.000Z"
    },
    {
      "id": "ADM-007",
      "title": "Admin: Product General Fields",
      "description": "Campos generales del producto: nombre, slug, descripción, marca, categorías.",
      "status": "done",
      "priority": "high",
      "phase": "products",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T19:08:00.000Z"
    },
    {
      "id": "ADM-008",
      "title": "Admin: Product Pricing Fields",
      "description": "Precios por variante: netUnitAmount, IVA general/reducido.",
      "status": "done",
      "priority": "high",
      "phase": "products",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T19:08:00.000Z"
    },
    {
      "id": "ADM-009",
      "title": "Admin: Product Inventory Section",
      "description": "Ver y ajustar stock por variante desde el editor de producto.",
      "status": "done",
      "priority": "high",
      "phase": "products",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T19:08:00.000Z"
    },
    {
      "id": "ADM-010",
      "title": "Admin: Product Images Section",
      "description": "Gestionar imágenes: subir, reorder, eliminar, marcar principal.",
      "status": "done",
      "priority": "medium",
      "phase": "products",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T19:08:00.000Z"
    },
    {
      "id": "ADM-011",
      "title": "Admin: Product SEO Section",
      "description": "SEO title, meta description, preview snippet, index/noindex.",
      "status": "done",
      "priority": "medium",
      "phase": "products",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T19:08:00.000Z"
    },
    {
      "id": "ADM-012",
      "title": "Admin: Product Create Flow",
      "description": "Ruta /admin/products/new con formulario completo de creación.",
      "status": "done",
      "priority": "high",
      "phase": "products",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T19:08:00.000Z"
    },
    {
      "id": "ADM-016",
      "title": "Admin: Inventory Overview",
      "description": "Vista de inventario por variante con stock y estado. Filtros y búsqueda.",
      "status": "done",
      "priority": "high",
      "phase": "inventory",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T19:12:00.000Z"
    },
    {
      "id": "ADM-017",
      "title": "Admin: Quick Stock Adjustment",
      "description": "Ajuste rápido de stock inline con confirmación y refresh.",
      "status": "done",
      "priority": "high",
      "phase": "inventory",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T19:12:00.000Z"
    },
    {
      "id": "ADM-018",
      "title": "Admin: Customer List",
      "description": "Listado de clientes con búsqueda y paginación.",
      "status": "done",
      "priority": "medium",
      "phase": "customers",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T20:23:09Z"
    },
    {
      "id": "ADM-019",
      "title": "Admin: Customer Detail",
      "description": "Detalle de cliente con pedidos y direcciones.",
      "status": "done",
      "priority": "medium",
      "phase": "customers",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T20:31:31Z"
    },
    {
      "id": "ADM-020",
      "title": "Admin: Customer Edit",
      "description": "Editar datos de cliente (nombre, email, rol).",
      "status": "done",
      "priority": "medium",
      "phase": "customers",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T20:31:31Z"
    },
    {
      "id": "ADM-021",
      "title": "Admin: Categories CRUD",
      "description": "CRUD de categorías: crear, editar, eliminar categorías.",
      "status": "done",
      "priority": "medium",
      "phase": "categories",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T20:31:31Z"
    },
    {
      "id": "ADM-022",
      "title": "Admin: Brands CRUD",
      "description": "CRUD de marcas: crear, editar, eliminar marcas.",
      "status": "done",
      "priority": "medium",
      "phase": "brands",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T20:31:31Z"
    },
    {
      "id": "ADM-023",
      "title": "Admin: Dashboard Widgets",
      "description": "Widgets operativos en /admin: pedidos hoy, revenue, productos sin stock.",
      "status": "done",
      "priority": "medium",
      "phase": "dashboard",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T18:56:00.000Z"
    },
    {
      "id": "ADM-024",
      "title": "Admin: Promotions CRUD",
      "description": "CRUD de promociones: crear, editar, activar/desactivar. Requiere BD-02 backend.",
      "status": "done",
      "priority": "low",
      "phase": "promotions",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T19:08:00.000Z"
    },
    {
      "id": "ADM-025",
      "title": "Admin: Reviews Moderation",
      "description": "Moderación de reseñas: aprobar, rechazar. Requiere BD-05 backend.",
      "status": "done",
      "priority": "low",
      "phase": "reviews",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T20:31:31Z"
    },
    {
      "id": "ADM-026",
      "title": "Admin: CMS Pages",
      "description": "Gestión de páginas CMS: crear, editar, publicar, despublicar.",
      "status": "done",
      "priority": "low",
      "phase": "cms",
      "created_at": "2026-08-16T22:13:57Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T20:31:31Z"
    },
    {
      "id": "BD-02",
      "title": "Backend: GET /promotions (listado)",
      "description": "Endpoint GET /promotions con paginación y filtros para listado admin. Alta prioridad — bloquea ADM-024.",
      "status": "done",
      "priority": "high",
      "phase": "backend",
      "created_at": "2026-08-16T22:14:05Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T19:08:00.000Z"
    },
    {
      "id": "BD-03",
      "title": "Backend: PATCH /promotions/:id",
      "description": "Editar promoción existente: código, tipo, valor, fechas, límites.",
      "status": "done",
      "priority": "high",
      "phase": "backend",
      "created_at": "2026-08-16T22:14:05Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T19:08:00.000Z"
    },
    {
      "id": "BD-04",
      "title": "Backend: DELETE /promotions/:id",
      "description": "Eliminar promoción por ID. MEDIA prioridad.",
      "status": "done",
      "priority": "medium",
      "phase": "backend",
      "created_at": "2026-08-16T22:14:05Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T19:08:00.000Z"
    },
    {
      "id": "BD-05",
      "title": "Backend: GET /reviews/admin",
      "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",
      "created_at": "2026-08-16T22:14:05Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T19:12:00.000Z"
    },
    {
      "id": "BD-06",
      "title": "Backend: PATCH /products/:id/state",
      "description": "Cambiar estado de producto (active/inactive/draft). MEDIA prioridad.",
      "status": "done",
      "priority": "medium",
      "phase": "backend",
      "created_at": "2026-08-16T22:14:05Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T20:30:10Z"
    },
    {
      "id": "BD-07",
      "title": "Backend: DELETE /products/:id",
      "description": "Eliminar producto por ID. MEDIA prioridad.",
      "status": "done",
      "priority": "medium",
      "phase": "backend",
      "created_at": "2026-08-16T22:14:05Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T20:30:10Z"
    },
    {
      "id": "BD-08",
      "title": "Backend: DELETE /brands/:id",
      "description": "Eliminar marca por ID. MEDIA prioridad.",
      "status": "done",
      "priority": "medium",
      "phase": "backend",
      "created_at": "2026-08-16T22:14:05Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T20:30:10Z"
    },
    {
      "id": "BD-09",
      "title": "Backend: POST /inventory/bulk-adjust",
      "description": "Bulk stock adjustment atómico. BAJA prioridad.",
      "status": "done",
      "priority": "low",
      "phase": "backend",
      "created_at": "2026-08-16T22:14:05Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "type": "feature",
      "completed_at": "2026-08-18T04:23:37Z"
    },
    {
      "id": "BD-10",
      "title": "Backend: GET /admin/audit",
      "description": "Audit log explorer para admin. MEDIA prioridad.",
      "status": "done",
      "priority": "medium",
      "phase": "backend",
      "created_at": "2026-08-16T22:14:05Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T20:30:29Z"
    },
    {
      "id": "BD-11",
      "title": "Backend: GET /admin/stats",
      "description": "KPIs operativos del dashboard: pedidos hoy, revenue, productos sin stock. MEDIA prioridad.",
      "status": "done",
      "priority": "medium",
      "phase": "backend",
      "created_at": "2026-08-16T22:14:05Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "type": "feature",
      "completed_at": "2026-08-17T18:56:00.000Z"
    },
    {
      "id": "F-001",
      "type": "chore",
      "title": "Scaffold modular monolith skeleton",
      "problem": "No codebase exists. Platform needs a boring, typed, modular home.",
      "goal": "TypeScript modular monolith skeleton with strict module boundaries and green toolchain.",
      "scope_in": [
        "project/ layout: src/modules, src/shared, src/infrastructure, src/app",
        "Node + TypeScript strict + Fastify app shell",
        "ESLint + Prettier + typecheck + unit test runner",
        "GET /health endpoint",
        "Boundary lint rule: modules import only own folder or other modules public index"
      ],
      "scope_out": [
        "No business logic",
        "No database",
        "No frontend"
      ],
      "priority": "high",
      "risk": "low",
      "depends_on": [],
      "description": "Problem: No codebase exists. Platform needs a boring, typed, modular home. Goal: TypeScript modular monolith skeleton with strict module boundaries and green toolchain. Scope IN: project/ layout, Fastify + TS strict, toolchain, health endpoint, boundary rule. Scope OUT: no business logic, no DB. Type: chore. Priority: high. Risk: low.",
      "acceptance": [
        "install, build, lint, typecheck and test commands all green",
        "GET /health returns HTTP 200 with status ok",
        "src/modules, src/shared, src/infrastructure, src/app exist",
        "A module importing another module internal file fails lint",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-002",
      "type": "chore",
      "title": "Database foundation with module-owned schemas",
      "problem": "Modules need PostgreSQL with clear ownership and safe migrations.",
      "goal": "Migrations tooling, table naming convention per module, local dev database.",
      "scope_in": [
        "SQL migration tool (up/down, ordered, idempotent)",
        "Naming convention: <module>_<table> (catalog_products, inventory_stock, ...)",
        "docker-compose dev services: PostgreSQL + Redis",
        "Migration test: fresh up then down then up",
        "Rule: no schema change without migration"
      ],
      "scope_out": [
        "No business tables yet",
        "No Redis usage beyond availability"
      ],
      "priority": "high",
      "risk": "low",
      "depends_on": [
        "F-001"
      ],
      "description": "Problem: Modules need PostgreSQL with clear ownership and safe migrations. Goal: Migrations tooling, naming convention, local dev database. Scope IN: migration tool, naming convention, docker-compose, migration test. Scope OUT: no business tables. Type: chore. Priority: high. Risk: low.",
      "acceptance": [
        "Given a fresh database When migrations run Then schema is created and repeat run is a no-op",
        "Given applied migrations When down runs Then schema rolls back cleanly",
        "Table naming convention documented and enforced",
        "Dev PostgreSQL and Redis start with one command",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-003",
      "type": "chore",
      "title": "HTTP foundation and request context",
      "problem": "Every request needs identity, structured logs and predictable errors.",
      "goal": "request_id everywhere, JSON logs, one error envelope, no magic.",
      "scope_in": [
        "request_id generated or propagated on every request",
        "Structured JSON logging with request_id",
        "Single error response envelope",
        "Input validation hook on API layer"
      ],
      "scope_out": [
        "No tracing backend yet",
        "No metrics yet"
      ],
      "priority": "high",
      "risk": "low",
      "depends_on": [
        "F-001"
      ],
      "description": "Problem: Every request needs identity, structured logs and predictable errors. Goal: request_id, JSON logs, error envelope. Scope IN: request context, logging, error envelope, validation hook. Scope OUT: no tracing or metrics backend. Type: chore. Priority: high. Risk: low.",
      "acceptance": [
        "Every response carries request_id",
        "Every log line for a request carries the same request_id",
        "Given an invalid request When handled Then error envelope shape is stable",
        "Internal stack traces never leak to API responses",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-004",
      "type": "chore",
      "title": "Typed config and feature flags",
      "problem": "Risky features need activation separate from deployment; env access must be typed.",
      "goal": "Fail-fast typed config plus simple feature flag module behind an interface.",
      "scope_in": [
        "Typed env config loader, fail fast on missing required vars",
        "FeatureFlag interface with simple store implementation",
        "Flags: deployment and activation are separate operations"
      ],
      "scope_out": [
        "No external flag service",
        "No per-user segmentation yet"
      ],
      "priority": "med",
      "risk": "low",
      "depends_on": [
        "F-001"
      ],
      "description": "Problem: Risky features need activation separate from deployment; env access must be typed. Goal: typed config + feature flag module. Scope IN: config loader, FeatureFlag interface, simple store. Scope OUT: no external flag service. Type: chore. Priority: med. Risk: low.",
      "acceptance": [
        "Given a missing required env var When app starts Then startup fails with clear message",
        "Given flag off When code path guarded by flag runs Then path is skipped",
        "Flag state change does not require redeploy",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-005",
      "type": "feature",
      "title": "Identity: register, login, sessions",
      "problem": "Customers need accounts; nothing trusts who calls the API.",
      "goal": "Registration, login, logout with hashed passwords and secure sessions.",
      "scope_in": [
        "identity module: domain, application, infrastructure, api",
        "Register, login, logout use cases",
        "Argon2 password hashing",
        "Secure session cookie: HttpOnly, Secure, SameSite",
        "Login rate limiting"
      ],
      "scope_out": [
        "No MFA yet",
        "No OAuth providers",
        "No profile editing (users module)"
      ],
      "priority": "high",
      "risk": "med",
      "depends_on": [
        "F-002",
        "F-003"
      ],
      "description": "Problem: Customers need accounts; nothing trusts who calls the API. Goal: register/login/logout with hashed passwords and secure sessions. Scope IN: identity module, argon2, secure cookies, rate limit. Scope OUT: no MFA, no OAuth. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "Given valid credentials When login Then HTTP 200 and secure session cookie set",
        "Given wrong password When login Then HTTP 401 and no user enumeration hint",
        "Passwords stored with argon2, never plaintext or reversible",
        "Given 10 failed logins in a row When next login attempted Then HTTP 429",
        "Session cookie is HttpOnly, Secure and SameSite",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-006",
      "type": "feature",
      "title": "Users: profile, addresses, RBAC",
      "problem": "Authenticated users need profile data, addresses and clear roles.",
      "goal": "Users module with profile + address CRUD and customer/admin roles.",
      "scope_in": [
        "users module owning identity_users profile data and addresses",
        "Profile and address CRUD behind use cases",
        "Roles: customer, admin; RBAC guard on API layer",
        "User can read/update only own data"
      ],
      "scope_out": [
        "No fine-grained permissions yet",
        "No admin UI"
      ],
      "priority": "med",
      "risk": "med",
      "depends_on": [
        "F-005"
      ],
      "description": "Problem: Authenticated users need profile data, addresses and clear roles. Goal: users module with profile, addresses, RBAC. Scope IN: users module, CRUD, roles, ownership guard. Scope OUT: no admin UI. Type: feature. Priority: med. Risk: med.",
      "acceptance": [
        "Given user A When A requests user B profile Then HTTP 403",
        "Given customer role When admin-only endpoint called Then HTTP 403",
        "Given admin role When admin-only endpoint called Then HTTP 200",
        "Address CRUD works end to end for own addresses",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-007",
      "type": "feature",
      "title": "Categories module",
      "problem": "Products need a navigable taxonomy with stable SEO URLs.",
      "goal": "Category tree with unique slugs and SEO metadata.",
      "scope_in": [
        "categories module with tree structure",
        "Unique slug per category, stable URL /categoria/<slug>",
        "SEO metadata fields: title, description",
        "Category CRUD via use cases"
      ],
      "scope_out": [
        "No product assignment yet (catalog core ticket)",
        "No storefront rendering yet"
      ],
      "priority": "high",
      "risk": "low",
      "depends_on": [
        "F-002"
      ],
      "description": "Problem: Products need a navigable taxonomy with stable SEO URLs. Goal: category tree with slugs and SEO metadata. Scope IN: categories module, tree, slugs, metadata. Scope OUT: no product assignment, no storefront. Type: feature. Priority: high. Risk: low.",
      "acceptance": [
        "Given duplicate slug When category created Then HTTP 409",
        "Category tree supports parent/child and blocks cycles",
        "Public URL is /categoria/<slug>, never internal id",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-008",
      "type": "feature",
      "title": "Catalog core: products domain",
      "problem": "There is no product model; everything else depends on it.",
      "goal": "Product aggregate with use cases, slugs, states and SEO metadata.",
      "scope_in": [
        "catalog module: domain, application, infrastructure, api",
        "CreateProduct, UpdateProduct, SearchProducts use cases",
        "Product states: draft, active, archived",
        "Unique slug, stable URL /productos/<slug>",
        "SEO metadata per product",
        "Product-category assignment"
      ],
      "scope_out": [
        "No variants yet",
        "No stock, no prices"
      ],
      "priority": "high",
      "risk": "med",
      "depends_on": [
        "F-002",
        "F-007"
      ],
      "description": "Problem: There is no product model; everything else depends on it. Goal: product aggregate with use cases, slugs, states, SEO metadata. Scope IN: catalog module, CRUD use cases, states, slugs, category assignment. Scope OUT: no variants, stock, prices. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "Domain layer has zero database or HTTP imports",
        "Given duplicate slug When product created Then HTTP 409",
        "Only active products appear in public listings",
        "Public URL is /productos/<slug>, never internal id",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-009",
      "type": "feature",
      "title": "Brands module",
      "problem": "Products need brands for navigation, filtering and SEO pages.",
      "goal": "Brand entity with slug and SEO page data.",
      "scope_in": [
        "brands module or catalog-owned brands with clear ownership",
        "Unique slug, stable URL /marca/<slug>",
        "Product-brand assignment"
      ],
      "scope_out": [
        "No brand storefront page yet"
      ],
      "priority": "med",
      "risk": "low",
      "depends_on": [
        "F-002",
        "F-008"
      ],
      "description": "Problem: Products need brands for navigation, filtering and SEO pages. Goal: brand entity with slug and SEO data. Scope IN: brands, slugs, product assignment. Scope OUT: no storefront page yet. Type: feature. Priority: med. Risk: low.",
      "acceptance": [
        "Given duplicate brand slug When created Then HTTP 409",
        "Products list filterable by brand",
        "Public URL is /marca/<slug>",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-010",
      "type": "feature",
      "title": "Variants, SKU/EAN and product rich data",
      "problem": "One product has many sellable variants; nutrition and allergens must be trusted data.",
      "goal": "Variants with SKU/EAN plus attributes, ingredients, allergens, nutrition with provenance.",
      "scope_in": [
        "Product variants with unique SKU and EAN",
        "Attributes per variant",
        "Ingredients, allergens, nutrition information",
        "Organic/ecological certification fields",
        "Provenance tracking: manual, manufacturer, openfoodfacts",
        "External source never overwrites trusted internal data without validation"
      ],
      "scope_out": [
        "No OpenFoodFacts sync job yet",
        "No images yet"
      ],
      "priority": "high",
      "risk": "med",
      "depends_on": [
        "F-008"
      ],
      "description": "Problem: One product has many sellable variants; nutrition and allergens must be trusted data. Goal: variants with SKU/EAN, attributes, ingredients, allergens, nutrition with provenance. Scope IN: variants, rich data, provenance rules. Scope OUT: no OpenFoodFacts sync. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "Given duplicate SKU or EAN When variant created Then HTTP 409",
        "Given field with nutrition_source=manual When external source pushes same field Then internal value kept",
        "Every nutrition payload stores its provenance",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-011",
      "type": "feature",
      "title": "Product images",
      "problem": "Products need images for storefront and SEO.",
      "goal": "Image attachment per product/variant behind a storage interface.",
      "scope_in": [
        "Image entity: url, alt text, ordering, role (main/gallery)",
        "Storage adapter behind interface (local first)",
        "Attach/detach/reorder via use cases"
      ],
      "scope_out": [
        "No CDN or image processing pipeline yet"
      ],
      "priority": "med",
      "risk": "low",
      "depends_on": [
        "F-010"
      ],
      "description": "Problem: Products need images for storefront and SEO. Goal: image attachment per product/variant behind a storage interface. Scope IN: image entity, storage adapter, ordering. Scope OUT: no CDN pipeline. Type: feature. Priority: med. Risk: low.",
      "acceptance": [
        "Product exposes ordered image list with alt text",
        "Swapping storage adapter touches only infrastructure layer",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-012",
      "type": "feature",
      "title": "Search: interface plus PostgreSQL FTS",
      "problem": "Customers must find products; search backend must stay replaceable.",
      "goal": "ProductSearch interface with PostgreSQL full text search implementation.",
      "scope_in": [
        "ProductSearch interface",
        "PostgreSQL FTS implementation over name, brand, category",
        "Basic relevance ordering and pagination",
        "Popular searches logged for later cache use"
      ],
      "scope_out": [
        "No Elasticsearch/Meilisearch/Algolia yet",
        "No typo tolerance beyond FTS defaults"
      ],
      "priority": "high",
      "risk": "low",
      "depends_on": [
        "F-008",
        "F-009"
      ],
      "description": "Problem: Customers must find products; search backend must stay replaceable. Goal: ProductSearch interface with PostgreSQL FTS implementation. Scope IN: interface, FTS adapter, pagination. Scope OUT: no external search engine. Type: feature. Priority: high. Risk: low.",
      "acceptance": [
        "Given product matching query When search Then product returned with stable ordering",
        "Swapping implementation requires no API contract change",
        "Search latency measured and logged",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-013",
      "type": "feature",
      "title": "Storefront shell (Next.js)",
      "problem": "There is no customer-facing site.",
      "goal": "Next.js + TypeScript + Tailwind shell consuming backend public API.",
      "scope_in": [
        "Next.js app with React, TypeScript, Tailwind",
        "Layout, navigation, home page",
        "Server Components by default; client code only where interaction needs it",
        "Typed API client for backend public endpoints"
      ],
      "scope_out": [
        "No catalog pages yet",
        "No cart UI yet"
      ],
      "priority": "high",
      "risk": "low",
      "depends_on": [
        "F-008"
      ],
      "description": "Problem: There is no customer-facing site. Goal: Next.js + TS + Tailwind shell consuming backend API. Scope IN: app shell, navigation, typed API client. Scope OUT: no catalog pages. Type: feature. Priority: high. Risk: low.",
      "acceptance": [
        "Home renders server-side with navigation",
        "Frontend never imports backend internals, only typed API client",
        "Build, lint and typecheck green",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-014",
      "type": "feature",
      "title": "Storefront catalog pages (SSG/ISR)",
      "problem": "Products, categories and brands need fast SEO-friendly public pages.",
      "goal": "SSG/ISR pages for product, category and brand with stable URLs and metadata.",
      "scope_in": [
        "Product detail page at /productos/<slug>",
        "Category listing at /categoria/<slug>",
        "Brand page at /marca/<slug>",
        "SSG/ISR rendering with on-demand revalidation",
        "Per-page metadata and OpenGraph",
        "Search results page"
      ],
      "scope_out": [
        "No cart, no checkout UI"
      ],
      "priority": "high",
      "risk": "med",
      "depends_on": [
        "F-010",
        "F-012",
        "F-013"
      ],
      "description": "Problem: Products, categories and brands need fast SEO-friendly public pages. Goal: SSG/ISR catalog pages with stable URLs and metadata. Scope IN: product/category/brand/search pages, ISR, metadata, OG. Scope OUT: no commerce UI. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "Product page served at /productos/<slug> with no internal id in URL",
        "Given catalog update When revalidation triggered Then page reflects new data",
        "Each page carries title, description and OpenGraph metadata",
        "Lighthouse SEO score >= 90 on product and category pages",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-015",
      "type": "feature",
      "title": "SEO core: structured data, sitemap, redirects",
      "problem": "Organic traffic is core business; structured data and crawlability are missing.",
      "goal": "JSON-LD schemas, sitemap.xml, robots.txt and redirect management.",
      "scope_in": [
        "seo module",
        "Product, Breadcrumb and Organization JSON-LD",
        "sitemap.xml and robots.txt generation",
        "Redirect store behind interface with 301 support",
        "Canonical URLs on all public pages"
      ],
      "scope_out": [
        "No hreflang",
        "No external SEO tooling integration"
      ],
      "priority": "high",
      "risk": "low",
      "depends_on": [
        "F-014"
      ],
      "description": "Problem: Organic traffic is core business; structured data and crawlability are missing. Goal: JSON-LD, sitemap, robots, redirects. Scope IN: seo module, schemas, sitemap, robots, redirect store. Scope OUT: no hreflang. Type: feature. Priority: high. Risk: low.",
      "acceptance": [
        "Product page embeds valid Product JSON-LD (validator clean)",
        "sitemap.xml lists active public URLs and excludes draft content",
        "Given stored redirect When old URL requested Then HTTP 301 to new URL",
        "Every public page has canonical URL",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-016",
      "type": "feature",
      "title": "Inventory module",
      "problem": "Catalog says what a product is; nothing says if it can be sold.",
      "goal": "Isolated inventory module with atomic stock operations and availability API.",
      "scope_in": [
        "inventory module, fully isolated from catalog",
        "Stock states: available, reserved, sold, incoming",
        "InventoryService.checkAvailability() public interface",
        "Atomic reserve, release, confirm operations",
        "Negative stock impossible at database and domain level"
      ],
      "scope_out": [
        "No warehouse/supplier integration",
        "No incoming purchase orders UI"
      ],
      "priority": "high",
      "risk": "med",
      "depends_on": [
        "F-002"
      ],
      "description": "Problem: Catalog says what a product is; nothing says if it can be sold. Goal: isolated inventory with atomic stock ops and availability API. Scope IN: inventory module, stock states, atomic reserve/release/confirm, no-negative rule. Scope OUT: no warehouse integration. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "Given 10 concurrent reservations for the last unit When all settle Then exactly 1 succeeds and 9 get unavailable",
        "Given zero stock When reservation requested Then rejected and stock never negative",
        "Catalog module contains zero references to inventory tables",
        "Checkout checks stock only through InventoryService",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-017",
      "type": "feature",
      "title": "Pricing module",
      "problem": "Nothing calculates real prices and taxes; frontend must never supply them.",
      "goal": "Server-side pricing with VAT behind PricingService.",
      "scope_in": [
        "pricing module owning price and tax rules",
        "Price per variant with VAT (Spain general/reduced rates)",
        "PricingService.calculate() public interface",
        "Price history table for audit"
      ],
      "scope_out": [
        "No promotions yet",
        "No multi-currency"
      ],
      "priority": "high",
      "risk": "med",
      "depends_on": [
        "F-002",
        "F-010"
      ],
      "description": "Problem: Nothing calculates real prices and taxes; frontend must never supply them. Goal: server-side pricing with VAT behind PricingService. Scope IN: pricing module, VAT, calculate interface, price history. Scope OUT: no promotions, no multi-currency. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "Given variant and quantity When PricingService.calculate Then total with VAT returned",
        "Given client-supplied price in any request Then price ignored and recalculated",
        "Price change writes history row, never silent update",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-018",
      "type": "feature",
      "title": "Cart module",
      "problem": "Customers need a cart that never lies about prices or stock.",
      "goal": "Cart with items and server-side recalculation of totals.",
      "scope_in": [
        "cart module storing product_id, variant_id, quantity only",
        "Add, remove, change quantity use cases",
        "Totals always recalculated via PricingService and InventoryService",
        "Stored prices never trusted"
      ],
      "scope_out": [
        "No guest cart persistence across devices yet",
        "No promotions applied yet"
      ],
      "priority": "high",
      "risk": "low",
      "depends_on": [
        "F-016",
        "F-017"
      ],
      "description": "Problem: Customers need a cart that never lies about prices or stock. Goal: cart with items and server-side recalculation. Scope IN: cart module, item ops, recalculation via Pricing/Inventory. Scope OUT: no promotions yet. Type: feature. Priority: high. Risk: low.",
      "acceptance": [
        "Given product price changed after add to cart When cart read Then new price shown",
        "Given variant out of stock When cart read Then item flagged unavailable",
        "Cart payload from client containing price fields is ignored",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-019",
      "type": "feature",
      "title": "Promotions v1",
      "problem": "Store needs discounts; discount logic must be validated server-side.",
      "goal": "Simple promotions engine behind an interface, validated at recalculation.",
      "scope_in": [
        "promotions module with percent and fixed-amount rules",
        "Promo codes with validity window and usage limits",
        "Discount validation inside recalculation, never from frontend",
        "No stacking unless rule explicitly allows"
      ],
      "scope_out": [
        "No loyalty points",
        "No buy-X-get-Y rules yet"
      ],
      "priority": "med",
      "risk": "med",
      "depends_on": [
        "F-017",
        "F-018"
      ],
      "description": "Problem: Store needs discounts; discount logic must be validated server-side. Goal: simple promotions engine behind interface, validated at recalculation. Scope IN: percent/fixed rules, promo codes, validity, no frontend trust. Scope OUT: no loyalty. Type: feature. Priority: med. Risk: med.",
      "acceptance": [
        "Given valid promo code When applied Then discount recalculated server-side",
        "Given expired or exhausted code When applied Then HTTP 422 with reason",
        "Given client-supplied discount amount Then discount ignored",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-020",
      "type": "feature",
      "title": "Shipping module",
      "problem": "Checkout cannot quote delivery cost.",
      "goal": "Shipping zones, methods and cost calculation behind ShippingService.",
      "scope_in": [
        "shipping module with zones and methods",
        "ShippingService.calculate(cart, address) interface",
        "Free shipping threshold rule",
        "Carrier adapter interface (implementation later)"
      ],
      "scope_out": [
        "No real carrier API integration yet",
        "No tracking"
      ],
      "priority": "med",
      "risk": "low",
      "depends_on": [
        "F-002",
        "F-018"
      ],
      "description": "Problem: Checkout cannot quote delivery cost. Goal: zones, methods and cost calculation behind ShippingService. Scope IN: shipping module, zones, methods, threshold, adapter interface. Scope OUT: no carrier API. Type: feature. Priority: med. Risk: low.",
      "acceptance": [
        "Given address in known zone When calculate Then shipping cost returned",
        "Given address outside all zones When checkout Then HTTP 422 with clear reason",
        "Given cart above free shipping threshold Then shipping cost is zero",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-021",
      "type": "feature",
      "title": "Orders module with snapshots and state machine",
      "problem": "Nothing records purchase truth; history must never depend on live catalog.",
      "goal": "Order aggregate with item snapshots and explicit state transitions.",
      "scope_in": [
        "orders module",
        "Order items snapshot: name, SKU, EAN, unit price, discount, tax, quantity",
        "States: PENDING, AWAITING_PAYMENT, PAID, PROCESSING, SHIPPED, DELIVERED, CANCELLED, REFUNDED, PARTIALLY_REFUNDED",
        "Explicit state machine, illegal transitions rejected",
        "Domain events: OrderCreated, OrderPaid, OrderCancelled"
      ],
      "scope_out": [
        "No refunds execution yet (payments ticket)",
        "No admin order UI"
      ],
      "priority": "high",
      "risk": "med",
      "depends_on": [
        "F-002",
        "F-017"
      ],
      "description": "Problem: Nothing records purchase truth; history must never depend on live catalog. Goal: order aggregate with item snapshots and explicit state machine. Scope IN: orders module, snapshots, state machine, domain events. Scope OUT: no refunds execution, no admin UI. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "Given an order When product is later renamed or repriced Then order keeps original snapshot values",
        "Given order in SHIPPED When transition to PENDING requested Then transition rejected",
        "Every legal state transition is covered by unit test",
        "OrderCreated event published on creation",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-022",
      "type": "feature",
      "title": "Checkout orchestrator with idempotency",
      "problem": "No flow turns a cart into a payable order safely.",
      "goal": "Checkout coordinates validation, pricing, stock, discounts, shipping, taxes, order creation and reservation.",
      "scope_in": [
        "checkout module as orchestrator only, owning no business data",
        "Flow: validate products, prices, stock, discounts, shipping, taxes, create pending order, reserve inventory, create payment intent",
        "idempotency_key required on checkout request",
        "HTTP 409 on unavailable stock without order or payment",
        "checkout_success_total and checkout_failure_total metrics"
      ],
      "scope_out": [
        "No payment provider implementation yet (interface stub)",
        "No checkout UI yet"
      ],
      "priority": "high",
      "risk": "high",
      "depends_on": [
        "F-018",
        "F-019",
        "F-020",
        "F-021"
      ],
      "description": "Problem: No flow turns a cart into a payable order safely. Goal: checkout orchestrates validation, pricing, stock, discounts, shipping, taxes, pending order, reservation, payment intent. Scope IN: orchestrator flow, idempotency_key, 409 handling, metrics. Scope OUT: no provider implementation, no UI. Type: feature. Priority: high. Risk: high.",
      "acceptance": [
        "Given a cart containing an unavailable item When checkout requested Then HTTP 409, no order created, no payment initiated",
        "Given same idempotency_key sent twice When second request arrives Then same order returned, no duplicate order, no duplicate reservation",
        "Given successful checkout Then order exists in AWAITING_PAYMENT and stock is reserved",
        "Given checkout failure after reservation When flow aborts Then reservation released",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-023",
      "type": "feature",
      "title": "Payments: provider interface + Stripe + webhooks",
      "problem": "Checkout needs real money movement without coupling domain to Stripe.",
      "goal": "PaymentProvider interface with Stripe adapter and idempotent webhook processing.",
      "scope_in": [
        "payments module with PaymentProvider interface",
        "StripePaymentProvider adapter; domain never imports Stripe SDK",
        "Webhook: validate signature, deduplicate event, process, publish domain event",
        "Events: PaymentSucceeded, PaymentFailed, PaymentRefunded, ChargebackCreated",
        "payments_transactions table as source of truth"
      ],
      "scope_out": [
        "No Redsys/PayPal yet",
        "No payout reconciliation"
      ],
      "priority": "high",
      "risk": "high",
      "depends_on": [
        "F-022"
      ],
      "description": "Problem: Checkout needs real money movement without coupling domain to Stripe. Goal: PaymentProvider interface, Stripe adapter, idempotent webhooks. Scope IN: interface, adapter, webhook validation/dedup, domain events. Scope OUT: no Redsys/PayPal. Type: feature. Priority: high. Risk: high.",
      "acceptance": [
        "Domain code contains zero direct Stripe SDK imports",
        "Given webhook with bad signature When received Then rejected with non-2xx and not processed",
        "Given same webhook delivered twice When second delivery arrives Then processed exactly once",
        "Given PaymentSucceeded When processed Then order moves to PAID and event published",
        "Payment status from frontend is never trusted",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-024",
      "type": "feature",
      "title": "Notifications: transactional email",
      "problem": "Customers get no feedback after purchase or payment failure.",
      "goal": "Transactional emails driven by domain events behind a provider interface.",
      "scope_in": [
        "notifications module",
        "Email provider interface with one implementation",
        "Templates: order confirmation, payment failed, order shipped",
        "Send triggered by domain events, idempotent per event id"
      ],
      "scope_out": [
        "No marketing email",
        "No SMS/push"
      ],
      "priority": "med",
      "risk": "low",
      "depends_on": [
        "F-023",
        "F-005"
      ],
      "description": "Problem: Customers get no feedback after purchase or payment failure. Goal: transactional emails driven by domain events behind provider interface. Scope IN: notifications module, email adapter, templates, idempotent sends. Scope OUT: no marketing, no SMS. Type: feature. Priority: med. Risk: low.",
      "acceptance": [
        "Given PaymentSucceeded event When processed Then order confirmation email queued",
        "Given same event redelivered When processed Then no duplicate email",
        "Swapping email provider touches only infrastructure adapter",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-025",
      "type": "feature",
      "title": "Reviews module",
      "problem": "Social proof is missing; reviews must be tied to real purchases.",
      "goal": "Verified-purchase reviews with moderation and rating aggregates.",
      "scope_in": [
        "reviews module",
        "One review per order item, verified purchase only",
        "Moderation states: pending, published, rejected",
        "Rating aggregate per product, cached"
      ],
      "scope_out": [
        "No photos in reviews",
        "No vendor responses"
      ],
      "priority": "low",
      "risk": "low",
      "depends_on": [
        "F-021",
        "F-006"
      ],
      "description": "Problem: Social proof is missing; reviews must be tied to real purchases. Goal: verified-purchase reviews with moderation and aggregates. Scope IN: reviews module, verified purchase rule, moderation, aggregates. Scope OUT: no photos. Type: feature. Priority: low. Risk: low.",
      "acceptance": [
        "Given user without delivered order item When review submitted Then HTTP 403",
        "Given second review for same order item When submitted Then HTTP 409",
        "Only published reviews appear on product page",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-026",
      "type": "feature",
      "title": "CMS module",
      "problem": "Marketing needs landing and content pages without deploys.",
      "goal": "Simple CMS with pages, content blocks, slugs and draft/publish states.",
      "scope_in": [
        "cms module with page and block model",
        "Slug-based public routes",
        "Draft/publish states",
        "Storefront renders published pages"
      ],
      "scope_out": [
        "No WYSIWYG builder",
        "No versioning"
      ],
      "priority": "low",
      "risk": "low",
      "depends_on": [
        "F-013"
      ],
      "description": "Problem: Marketing needs landing and content pages without deploys. Goal: simple CMS with pages, blocks, slugs, draft/publish. Scope IN: cms module, slug routes, states, rendering. Scope OUT: no builder. Type: feature. Priority: low. Risk: low.",
      "acceptance": [
        "Given published page When slug requested Then page renders",
        "Given draft page When slug requested Then HTTP 404",
        "Given duplicate slug When page created Then HTTP 409",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-027",
      "type": "feature",
      "title": "Caching layer with explicit contracts",
      "problem": "Hot catalog pages hit the database on every request.",
      "goal": "Redis cache for product detail, category listing and navigation with key, TTL and invalidation.",
      "scope_in": [
        "Cache wrapper with key, TTL, invalidation strategy and source of truth documented per entry",
        "Targets: product detail, category listing, navigation, popular searches",
        "Invalidation on catalog update events",
        "cache_hit_ratio metric"
      ],
      "scope_out": [
        "Redis never source of truth",
        "No CDN config yet"
      ],
      "priority": "med",
      "risk": "low",
      "depends_on": [
        "F-014"
      ],
      "description": "Problem: Hot catalog pages hit the database on every request. Goal: Redis cache for hot reads with explicit key/TTL/invalidation contracts. Scope IN: cache wrapper, hot targets, event invalidation, hit ratio metric. Scope OUT: Redis never truth, no CDN. Type: feature. Priority: med. Risk: low.",
      "acceptance": [
        "Every cache entry documents key pattern, TTL, invalidation and source of truth",
        "Given catalog update When event published Then related cache entries invalidated",
        "Given Redis down When read path runs Then requests still succeed from PostgreSQL",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-028",
      "type": "feature",
      "title": "Security hardening",
      "problem": "Baseline security controls are scattered or missing.",
      "goal": "Rate limiting, CSRF, audit log, admin MFA and dependency scanning in one pass.",
      "scope_in": [
        "Global and per-route rate limiting",
        "CSRF protection where applicable",
        "Audit log for admin mutations",
        "MFA for admin accounts",
        "Dependency scanning in CI"
      ],
      "scope_out": [
        "No WAF/network-level work",
        "No pentest"
      ],
      "priority": "med",
      "risk": "med",
      "depends_on": [
        "F-006"
      ],
      "description": "Problem: Baseline security controls are scattered or missing. Goal: rate limiting, CSRF, audit log, admin MFA, dependency scanning. Scope IN: all listed controls. Scope OUT: no WAF, no pentest. Type: feature. Priority: med. Risk: med.",
      "acceptance": [
        "Given admin mutation When executed Then audit log row with actor, action and target",
        "Given admin login without MFA When attempted Then blocked until MFA enrolled",
        "Given request above rate limit Then HTTP 429",
        "CI fails on known vulnerable dependency",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true
      },
      "completed_at": "2026-08-17T00:00:00Z"
    },
    {
      "id": "F-029",
      "type": "feature",
      "title": "Observability: traces and business metrics",
      "problem": "Operations cannot see checkout health or latency.",
      "goal": "OpenTelemetry traces plus Prometheus business metrics.",
      "scope_in": [
        "OpenTelemetry tracing across modules",
        "Metrics: checkout_success_total, checkout_failure_total, payment_failure_total, order_created_total, inventory_conflict_total, api_latency, database_latency, cache_hit_ratio",
        "request_id and trace_id on every request",
        "Grafana dashboard baseline"
      ],
      "scope_out": [
        "No alerting rules beyond basics",
        "No log aggregation infra"
      ],
      "priority": "med",
      "risk": "low",
      "depends_on": [
        "F-023"
      ],
      "description": "Problem: Operations cannot see checkout health or latency. Goal: OTel traces plus Prometheus business metrics. Scope IN: tracing, listed metrics, ids, dashboard. Scope OUT: no full alerting stack. Type: feature. Priority: med. Risk: low.",
      "acceptance": [
        "Every request carries request_id and trace_id in logs",
        "Each listed metric is exposed on /metrics and changes on the corresponding event",
        "A checkout flow produces a connected trace across modules",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true,
        "close": true
      },
      "completed_at": "2026-08-15T19:34:14Z"
    },
    {
      "id": "F-030",
      "type": "feature",
      "title": "E2E suite for critical flows",
      "problem": "Critical user journeys have no end-to-end protection.",
      "goal": "Few but real E2E tests covering the money path.",
      "scope_in": [
        "E2E runner setup",
        "Flows: register, login, search, view product, add to cart, checkout, payment, order confirmation, refund",
        "Runs against disposable environment"
      ],
      "scope_out": [
        "No visual regression",
        "No load testing"
      ],
      "priority": "high",
      "risk": "low",
      "depends_on": [
        "F-022",
        "F-023"
      ],
      "description": "Problem: Critical user journeys have no end-to-end protection. Goal: E2E tests covering the money path. Scope IN: runner, critical flows, disposable env. Scope OUT: no visual regression, no load testing. Type: feature. Priority: high. Risk: low.",
      "acceptance": [
        "Each critical flow has one green E2E test",
        "E2E suite runs unattended in CI",
        "Given payment webhook simulated in E2E Then order reaches PAID",
        "verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-14",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true,
        "close": true
      },
      "completed_at": "2026-08-15T19:34:14Z"
    },
    {
      "id": "F-031",
      "title": "Start Script Dev",
      "status": "done",
      "depends_on": [],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": false
      },
      "created_at": "2026-08-15T19:58:55Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-032",
      "title": "Fix DATABASE_URL env in start-dev.sh",
      "status": "done",
      "depends_on": [
        "F-031"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": false
      },
      "created_at": "2026-08-15T20:06:24Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-033",
      "title": "Fix npm start no carga .env",
      "status": "done",
      "depends_on": [
        "F-032"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": false
      },
      "created_at": "2026-08-15T20:08:29Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-034",
      "title": "Fix start-dev.sh export vars",
      "status": "done",
      "depends_on": [
        "F-033"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": false
      },
      "created_at": "2026-08-15T22:27:40Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-035",
      "title": "Document Caveman Architecture",
      "status": "done",
      "depends_on": [
        "F-034"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": false
      },
      "created_at": "2026-08-15T22:35:22Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-036",
      "title": "Frontend Homepage",
      "status": "done",
      "depends_on": [],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": false
      },
      "created_at": "2026-08-15T22:37:06Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-037",
      "title": "Seed data: productos categorías marcas",
      "status": "done",
      "depends_on": [
        "F-036"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "created_at": "2026-08-15T22:45:21Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-038",
      "title": "Frontend Category page",
      "status": "done",
      "depends_on": [
        "F-036",
        "F-037"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "created_at": "2026-08-15T22:45:21Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-039",
      "title": "Frontend Product detail page",
      "status": "done",
      "depends_on": [
        "F-037"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "created_at": "2026-08-15T22:45:21Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-040",
      "title": "Frontend Brand page",
      "status": "done",
      "depends_on": [
        "F-036",
        "F-037"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "created_at": "2026-08-15T22:45:21Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-041",
      "title": "Frontend Search page",
      "status": "done",
      "depends_on": [
        "F-036",
        "F-037"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "created_at": "2026-08-15T22:45:21Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-042",
      "title": "Frontend Cart page",
      "status": "done",
      "depends_on": [
        "F-037",
        "F-018"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "created_at": "2026-08-15T22:45:21Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-043",
      "title": "Frontend Checkout flow",
      "status": "done",
      "depends_on": [
        "F-022",
        "F-037"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "created_at": "2026-08-15T22:45:21Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-044",
      "title": "Frontend Order confirmation",
      "status": "done",
      "depends_on": [
        "F-021",
        "F-037"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "created_at": "2026-08-15T22:45:21Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-045",
      "title": "Frontend Auth: login, register, account",
      "status": "done",
      "depends_on": [
        "F-005",
        "F-006",
        "F-036"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "created_at": "2026-08-15T22:45:21Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-046",
      "title": "Frontend Admin panel",
      "status": "done",
      "depends_on": [
        "F-037",
        "F-005",
        "F-006"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "created_at": "2026-08-15T22:45:21Z",
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "F-047",
      "title": "Admin Panel completo",
      "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": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-17T00:00:00Z",
      "type": "feature",
      "priority": "medium"
    },
    {
      "id": "ADM-27",
      "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": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "created_at": "2026-08-17T11:31:10Z",
      "completed_at": "2026-08-17T18:56:00.000Z",
      "assignee": null,
      "tags": [
        "admin",
        "ux",
        "auto-slug",
        "seo"
      ],
      "type": "feature"
    },
    {
      "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ón es inicializar orders como null y usar orders?.length ?? 0 en el template.",
      "status": "done",
      "priority": "high",
      "type": "fix",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "created_at": "2026-08-17T13:50:00Z",
      "assignee": null,
      "tags": [
        "admin",
        "orders",
        "bug"
      ],
      "completed_at": "2026-08-17T16:31:27.318Z"
    },
    {
      "id": "ADM-28",
      "title": "Branding: logo oficial y colores de marca",
      "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",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "created_at": "2026-08-17T13:50:00Z",
      "assignee": null,
      "tags": [
        "branding",
        "logo",
        "frontend"
      ],
      "completed_at": "2026-08-17T20:31:41Z"
    },
    {
      "id": "FIX-02",
      "title": "Storefront footer contraste + colores incorrectos",
      "description": "El texto del footer se pierde con el fondo actual. Los colores del sitio no coinciden con los de marca de mercadodevida.es. Hay que extraer los colores reales del sitio (fondo, texto, acentos) y aplicarlos correctamente en globals.css y Footer.tsx.",
      "status": "done",
      "priority": "high",
      "type": "fix",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "created_at": "2026-08-17T14:05:00Z",
      "assignee": null,
      "tags": [
        "frontend",
        "css",
        "branding",
        "bug"
      ],
      "completed_at": "2026-08-17T19:30:00.000Z"
    },
    {
      "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ñ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",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "created_at": "2026-08-17T14:20:00Z",
      "assignee": null,
      "tags": [
        "admin",
        "users",
        "backoffice",
        "security"
      ],
      "completed_at": "2026-08-17T19:00:00.000Z"
    },
    {
      "id": "ADM-30",
      "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",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "created_at": "2026-08-17T14:20:00Z",
      "assignee": null,
      "tags": [
        "admin",
        "settings",
        "store-config"
      ],
      "completed_at": "2026-08-17T19:02:00.000Z"
    },
    {
      "id": "ADM-31",
      "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",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "created_at": "2026-08-17T14:20:00Z",
      "assignee": null,
      "tags": [
        "admin",
        "pricing",
        "tax",
        "iva"
      ],
      "completed_at": "2026-08-17T19:02:00.000Z"
    },
    {
      "id": "ADM-32",
      "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",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "created_at": "2026-08-17T14:20:00Z",
      "assignee": null,
      "tags": [
        "admin",
        "logs",
        "audit",
        "observability"
      ],
      "completed_at": "2026-08-17T19:05:00.000Z"
    },
    {
      "id": "ADM-33",
      "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",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "created_at": "2026-08-17T14:20:00Z",
      "assignee": null,
      "tags": [
        "admin",
        "payments",
        "checkout"
      ],
      "completed_at": "2026-08-17T19:05:00.000Z"
    },
    {
      "id": "ADM-34",
      "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",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "created_at": "2026-08-17T14:20:00Z",
      "assignee": null,
      "tags": [
        "admin",
        "shipping",
        "checkout"
      ],
      "completed_at": "2026-08-17T19:05:00.000Z"
    },
    {
      "id": "ADM-35",
      "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",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "created_at": "2026-08-17T14:25:00Z",
      "assignee": null,
      "tags": [
        "admin",
        "customers",
        "users",
        "crud"
      ],
      "completed_at": "2026-08-17T19:05:00.000Z"
    },
    {
      "id": "FE-01",
      "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",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "created_at": "2026-08-17T14:30:00Z",
      "assignee": null,
      "tags": [
        "frontend",
        "search",
        "ai",
        "ux"
      ],
      "completed_at": "2026-08-17T20:31:32Z"
    },
    {
      "id": "FIX-03",
      "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",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "created_at": "2026-08-17T14:35:00Z",
      "assignee": null,
      "tags": [
        "frontend",
        "css",
        "layout"
      ],
      "completed_at": "2026-08-17T16:31:57.966Z"
    },
    {
      "id": "FIX-04",
      "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",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "created_at": "2026-08-17T14:50:00Z",
      "assignee": null,
      "tags": [
        "frontend",
        "branding",
        "logo"
      ],
      "completed_at": "2026-08-17T19:30:00.000Z"
    },
    {
      "id": "FIX-05",
      "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",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "created_at": "2026-08-17T17:05:00Z",
      "assignee": null,
      "tags": [
        "frontend",
        "branding",
        "font",
        "favicon"
      ],
      "completed_at": "2026-08-17T19:25:00.000Z"
    },
    {
      "id": "FE-02",
      "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": [
        "frontend",
        "search",
        "ux"
      ],
      "created": "2026-08-17",
      "type": "feature",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-17T19:12:00.000Z"
    },
    {
      "id": "FIX-06",
      "title": "Admin: Logo sin texto \"Natural\" ni \"Admin\"",
      "description": "Quitar el texto superpuesto \"Natural\" y \"Admin\" del logo del sidebar. Solo mostrar la imagen del logo.",
      "status": "done",
      "priority": "high",
      "phase": "admin",
      "type": "fix",
      "created_at": "2026-08-17T15:28:28.152813+00:00",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-17T16:31:27.318Z"
    },
    {
      "id": "FIX-07",
      "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",
      "type": "fix",
      "created_at": "2026-08-17T15:28:28.152813+00:00",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-17T16:31:27.318Z"
    },
    {
      "id": "FIX-08",
      "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",
      "type": "fix",
      "created_at": "2026-08-17T15:28:28.152813+00:00",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-17T16:31:27.318Z"
    },
    {
      "id": "ADM-36",
      "title": "Admin: Dashboard con visualizaciones",
      "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",
      "type": "feature",
      "created_at": "2026-08-17T15:28:28.152813+00:00",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": false
      },
      "completed_at": "2026-08-17T20:31:40Z"
    },
    {
      "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á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",
      "type": "feature",
      "created_at": "2026-08-17T15:28:28.152813+00:00",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-17T19:10:00.000Z"
    },
    {
      "id": "ADM-38",
      "title": "Admin: Pedidos editables",
      "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",
      "type": "feature",
      "created_at": "2026-08-17T15:28:28.152813+00:00",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-17T19:22:00.000Z"
    },
    {
      "id": "ADM-39",
      "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",
      "type": "feature",
      "created_at": "2026-08-17T15:28:28.152813+00:00",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": false
      },
      "completed_at": "2026-08-17T20:31:41Z"
    },
    {
      "id": "ADM-40",
      "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",
      "type": "feature",
      "created_at": "2026-08-17T15:28:28.152813+00:00",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": false
      },
      "completed_at": "2026-08-17T20:31:41Z"
    },
    {
      "id": "ADM-41",
      "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",
      "type": "feature",
      "created_at": "2026-08-17T15:28:28.152813+00:00",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": false
      },
      "completed_at": "2026-08-17T20:34:41Z"
    },
    {
      "id": "ADM-42",
      "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",
      "type": "feature",
      "created_at": "2026-08-17T15:28:28.152813+00:00",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-17T19:20:00.000Z"
    },
    {
      "id": "FE-03",
      "title": "Storefront: Live search con preview",
      "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",
      "type": "feature",
      "created_at": "2026-08-17T15:28:28.152813+00:00",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-17T19:16:00.000Z"
    },
    {
      "id": "FE-04",
      "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",
      "type": "feature",
      "created_at": "2026-08-17T15:28:28.152813+00:00",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": false
      },
      "completed_at": "2026-08-17T20:33:40Z"
    },
    {
      "id": "FIX-09",
      "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",
      "type": "fix",
      "created_at": "2026-08-17T15:28:28.152813+00:00",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-17T16:31:57.966Z"
    },
    {
      "id": "FIX-10",
      "title": "Storefront: Precio pegado al borde en tarjetas de producto",
      "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",
      "type": "fix",
      "created_at": "2026-08-17T15:28:28.152813+00:00",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-17T16:31:57.966Z"
    },
    {
      "id": "FE-048",
      "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",
      "complexity": "medium",
      "created_at": "2026-08-17T19:54:56.545Z",
      "completed_at": "2026-08-17T22:00:00.000Z",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      }
    },
    {
      "id": "FIX-11",
      "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íos ni IVA",
        "/shipping y /tax-rates siguen funcionando como pantallas propias",
        "verify.sh en verde"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-18T20:58:24.951733+00:00"
    },
    {
      "id": "FIX-12",
      "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",
      "type": "fix",
      "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ón idéntico al de /categories: icono SVG + hover + title",
        "Siempre acciones editar|eliminar donde aplique",
        "Sin regresiones en las acciones existentes"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-18T20:58:24.951733+00:00"
    },
    {
      "id": "FIX-13",
      "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",
      "type": "fix",
      "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ón para editar una página CMS existente y guardar cambios",
        "Los cambios persisten tras recargar"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-18T20:58:24.951733+00:00"
    },
    {
      "id": "FIX-14",
      "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í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ón de usuarios admin/editor existentes a backoffice_users",
        "verify.sh y gates de seguridad en verde"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-18T20:58:24.951733+00:00"
    },
    {
      "id": "FIX-15",
      "title": "Admin: vistas usan el ancho disponible (max-width ~1280px centrado)",
      "description": "Las pantallas del backoffice quedan demasiado estrechas. Ajustar el contenedor global para que aproveche el ancho disponible con un max-width de una pantalla normal (~1280px), centrado. Aplica a TODAS las vistas.",
      "status": "done",
      "priority": "medium",
      "phase": "admin",
      "type": "fix",
      "created_at": "2026-08-18T20:03:10.712389+00:00",
      "acceptance": [
        "Contenedor global con max-width ~1280px centrado",
        "Todas las vistas aprovechan el ancho hasta ese tope",
        "El layout responsive no se rompe"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-18T20:58:24.951733+00:00"
    },
    {
      "id": "FIX-16",
      "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",
      "phase": "products",
      "type": "fix",
      "created_at": "2026-08-18T20:03:10.712389+00:00",
      "acceptance": [
        "Los checkboxes de atributos se pueden marcar y desmarcar",
        "El estado persiste al guardar",
        "Los atributos se reflejan al recargar el producto"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-18T20:58:24.951733+00:00"
    },
    {
      "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 → 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",
      "type": "fix",
      "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 → 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,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-18T20:58:24.951733+00:00"
    },
    {
      "id": "FIX-18",
      "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éfono)",
        "CRUD completo de direcciones del cliente",
        "El email/credenciales de login NO son editables desde la ficha",
        "Los cambios persisten tras recargar"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-18T20:58:24.951733+00:00"
    },
    {
      "id": "FIX-19",
      "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í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ón: categorías con hijos → parent; resto → child",
        "El árbol de categorías se actualiza correctamente"
      ],
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true
      },
      "completed_at": "2026-08-18T20:58:24.951733+00:00"
    },
    {
      "id": "F-048",
      "type": "fix",
      "title": "Complete migration and consolidate approved work",
      "problem": "Migration 024 and approved local changes remain uncommitted with stale orchestration state",
      "goal": "Validate migrations and product suites, reconcile state, and commit approved work",
      "scope_in": [
        "Migration 024",
        "product validation",
        "orchestration state",
        "approved FIX-11 through FIX-19 changes"
      ],
      "scope_out": [
        "No new product scope"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: Migration 024 and approved local changes remain uncommitted with stale orchestration state. Goal: Validate migrations and product suites, reconcile state, and commit approved work. Scope IN: Migration 024, product validation, orchestration state, approved FIX-11 through FIX-19 changes. Scope OUT: No new product scope. Type: fix. Priority: high. Risk: med.",
      "acceptance": [
        "Migration 024 uses the node-pg-migrate contract and passes migration checks",
        "Backend and all frontend build, typecheck, lint and tests are green",
        "Orchestration state matches the active ticket",
        "Approved local work is committed through close_feature.py",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "security": true,
        "qa": true,
        "reviewer": true,
        "close": true
      },
      "completed_at": "2026-08-19T05:18:17Z"
    },
    {
      "id": "F-049",
      "type": "chore",
      "title": "Document and operate monolith dev and prod lifecycle",
      "problem": "Operators need one reliable guide to deploy restart and inspect all services from the LAN",
      "goal": "Redeploy the current monolith and document start restart status stop and LAN URLs for dev and prod",
      "scope_in": [
        "deployment scripts",
        "process status",
        "LAN binding",
        "dev commands",
        "prod commands",
        "HOWTO"
      ],
      "scope_out": [
        "No cloud deployment",
        "no reverse proxy redesign"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: Operators need one reliable guide to deploy restart and inspect all services from the LAN. Goal: Redeploy the current monolith and document start restart status stop and LAN URLs for dev and prod. Scope IN: deployment scripts, process status, LAN binding, dev commands, prod commands, HOWTO. Scope OUT: No cloud deployment, no reverse proxy redesign. Type: chore. Priority: high. Risk: med.",
      "acceptance": [
        "All current changes are redeployed and reachable from another LAN device",
        "HOWTO documents dev start restart status stop and logs",
        "HOWTO documents prod build start restart status stop and logs",
        "Health and UI URLs are listed with the detected LAN IP",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T05:34:15Z"
    },
    {
      "id": "F-050",
      "type": "fix",
      "title": "Backoffice admin layout actions categories dashboard 404",
      "problem": "Several admin pages have stale layout, text-based actions, missing parent selector, and 404s on dashboard",
      "goal": "Layout uses full available width, action column uses icon, category parent can be selected and promoted, dashboard stats load without 404",
      "scope_in": [
        "admin pages layout",
        "action icons",
        "category parent selector",
        "dashboard stats",
        "404 sources"
      ],
      "scope_out": [
        "no schema change in API",
        "no product feature expansion"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: Several admin pages have stale layout, text-based actions, missing parent selector, and 404s on dashboard. Goal: Layout uses full available width, action column uses icon, category parent can be selected and promoted, dashboard stats load without 404. Scope IN: admin pages layout, action icons, category parent selector, dashboard stats, 404 sources. Scope OUT: no schema change in API, no product feature expansion. Type: fix. Priority: high. Risk: low.",
      "acceptance": [
        "/settings and /products edit sheet use full layout width",
        "/tax-rates /shipping /brands action column uses icon+title pattern",
        "/categories edit form allows parent selection and promote-to-parent toggle",
        "Admin dashboard renders stats without 404",
        "No new 404s in admin navigation",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T05:54:36Z"
    },
    {
      "id": "F-051",
      "title": "Favicon 404 en storefront y admin",
      "status": "done",
      "stage": null,
      "type": "fix",
      "priority": "medium",
      "description": "El storefront (3005) y el admin (3004) devuelven 404 en /favicon.ico. El frontend (3003) funciona bien porque tiene favicon.ico en src/app/. Falta el archivo en public/ de storefront y admin, y falta configurar el icon link en los layouts.",
      "created_at": "2026-08-19T08:23:15Z",
      "updated_at": "2026-08-19T08:23:15Z",
      "assignee": null,
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "acceptance": "1. GET /favicon.ico en storefront (3005) devuelve 200. 2. GET /favicon.ico en admin (3004) devuelve 200.",
      "completed_at": "2026-08-19T08:43:17Z"
    },
    {
      "id": "F-052",
      "title": "Imágenes de producto 404 en frontend y storefront",
      "status": "done",
      "stage": null,
      "type": "fix",
      "priority": "high",
      "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,
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "acceptance": "1. GET /uploads/[file] desde frontend (3003) devuelve 200 con la imagen. 2. GET /uploads/[file] desde storefront (3005) devuelve 200 con la imagen.",
      "completed_at": "2026-08-19T08:47:17Z"
    },
    {
      "id": "F-053",
      "title": "Category selector desalineado en admin",
      "status": "done",
      "stage": null,
      "type": "fix",
      "priority": "low",
      "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,
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "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"
    },
    {
      "id": "F-054",
      "title": "Backend muere sin rastro y auth/login devuelve 502/500",
      "status": "done",
      "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í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,
      "gates": {
        "reviewer": true,
        "security": true,
        "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á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"
    },
    {
      "id": "F-055",
      "type": "feature",
      "title": "Image thumbnails: backend resize cache + frontend max-size",
      "problem": "Product images served at full resolution everywhere, causing slow loads on listings and layout overflow on detail pages.",
      "goal": "Backend generates and caches 40px and 200px thumbnails; frontend applies CSS max-size to prevent overflow.",
      "scope_in": [
        "Image resize endpoint: GET /images/:id/thumb?w=40 and ?w=200",
        "Disk cache for resized images under .thumbs/",
        "CSS max-width/max-height on product detail page",
        "Admin product edit preview uses 200px thumbnail"
      ],
      "scope_out": [
        "No CDN integration",
        "No WebP/AVIF conversion",
        "No image upload pipeline changes"
      ],
      "priority": "high",
      "risk": "low",
      "depends_on": [],
      "description": "Problem: product images served at full resolution everywhere. Goal: resize+cache 40px/200px thumbnails server-side + CSS overflow guard. Type: feature. Priority: high. Risk: low.",
      "acceptance": [
        "GET /images/:id/thumb?w=40 returns resized image with cache hit on second request",
        "GET /images/:id/thumb?w=200 returns 200px resized image",
        "Product detail page images respect max-width: 100% and max-height: 600px",
        "No new raw full-size images served in product listings",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T11:48:32Z"
    },
    {
      "id": "F-056",
      "type": "fix",
      "title": "Serve uploads dynamic and fix image overflow",
      "problem": "New uploads give 404 and next image gives 502; product detail image overflows page",
      "goal": "Uploads must work right after upload without restart, thumbs auto-made, images stay inside box",
      "scope_in": [
        "Uploads route handler",
        "Thumbnail generation on upload",
        "Frontend detail image container"
      ],
      "scope_out": [
        "No CDN",
        "No S3"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: New uploads give 404 and next image gives 502; product detail image overflows page. Goal: Uploads must work right after upload without restart, thumbs auto-made, images stay inside box. Scope IN: Uploads route handler, Thumbnail generation on upload, Frontend detail image container. Scope OUT: No CDN, No S3. Type: fix. Priority: high. Risk: low.",
      "acceptance": [
        "New upload file loads 200 without server restart",
        "Thumbs 40 and 200 exist right after upload",
        "next/image no more 502 for fresh uploads",
        "Product detail image stays inside its box",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T13:10:09Z"
    },
    {
      "id": "F-057",
      "type": "bug",
      "title": "Fix checkout 400 INVALID_CART missing items in body",
      "problem": "CheckoutClient does not send items array to /api/checkout but route requires it",
      "goal": "POST /api/checkout succeeds with valid cart and auth, items mapped from CartContext",
      "scope_in": [
        "CheckoutClient handlePlaceOrder body",
        "route schema sanity check"
      ],
      "scope_out": [
        "No payment redesign",
        "no schema migration"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: CheckoutClient does not send items array to /api/checkout but route requires it. Goal: POST /api/checkout succeeds with valid cart and auth, items mapped from CartContext. Scope IN: CheckoutClient handlePlaceOrder body, route schema sanity check. Scope OUT: No payment redesign, no schema migration. Type: bug. Priority: high. Risk: low.",
      "acceptance": [
        "CheckoutClient sends items from CartContext in POST body",
        "POST /api/checkout from /checkout with valid session returns 200 or redirect",
        "No regression in existing cart or order flow",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T13:10:47Z"
    },
    {
      "id": "F-058",
      "type": "bug",
      "title": "Fix admin products list shows leaf emoji instead of new thumbnail",
      "problem": "Backend /catalog/products and /products/search return images=[] because serializeProduct is called without images arg",
      "goal": "Admin /products table shows product thumbnail 40px from images[0].url and /products/search returns attached images",
      "scope_in": [
        "Backend list endpoints fetch images for listed product IDs and pass to serializeProduct"
      ],
      "scope_out": [
        "Admin products page uses p.images[0].url with /uploads/40/ prefix"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: Backend /catalog/products and /products/search return images=[] because serializeProduct is called without images arg. Goal: Admin /products table shows product thumbnail 40px from images[0].url and /products/search returns attached images. Scope IN: Backend list endpoints fetch images for listed product IDs and pass to serializeProduct. Scope OUT: Admin products page uses p.images[0].url with /uploads/40/ prefix. Type: bug. Priority: high. Risk: low.",
      "acceptance": [
        "GET /api/catalog/products returns images[] for products that have attached images",
        "GET /api/products/search returns images[] for products that have attached images",
        "Admin /products table shows 40px thumbnail (not leaf) for products with images",
        "Existing uploads pipeline is not broken",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T13:17:51Z"
    },
    {
      "id": "F-059",
      "type": "fix",
      "title": "Image display respects aspect ratio no crop on non-square thumbs",
      "problem": "Frontend uses object-cover and aspect-square which crops non-square images; backend resize has no max-height cap so extreme aspects break layouts",
      "goal": "Images preserve aspect ratio at every viewport. Backend resize uses fit inside with max-height cap. Frontend uses object-contain and aspect-auto for product images. Cached square thumbs are fine; non-square thumbs display without distortion or cropping.",
      "scope_in": [
        "Backend resize sharp fit inside with max-h cap; frontend object-cover replaced with object-contain on product images; aspect-square replaced with aspect-auto on product image containers; sensible max bounds on containers; bg fill for empty space"
      ],
      "scope_out": [
        "No new endpoints",
        "no schema change",
        "no thumbnail regeneration of legacy files"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: Frontend uses object-cover and aspect-square which crops non-square images; backend resize has no max-height cap so extreme aspects break layouts. Goal: Images preserve aspect ratio at every viewport. Backend resize uses fit inside with max-height cap. Frontend uses object-contain and aspect-auto for product images. Cached square thumbs are fine; non-square thumbs display without distortion or cropping.. Scope IN: Backend resize sharp fit inside with max-h cap; frontend object-cover replaced with object-contain on product images; aspect-square replaced with aspect-auto on product image containers; sensible max bounds on containers; bg fill for empty space. Scope OUT: No new endpoints, no schema change, no thumbnail regeneration of legacy files. Type: fix. Priority: high. Risk: low.",
      "acceptance": [
        "Backend resize uses fit inside so cached thumbnail is bounded by max-width and max-height without crop",
        "40px and 200px thumbnails on disk are never square-cropped from a non-square source",
        "Frontend product images render with object-contain and aspect-auto, no clipping at any viewport",
        "Admin product list table shows the full image (not cropped to square)",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T13:32:56Z"
    },
    {
      "id": "F-060",
      "type": "chore",
      "title": "Regenerate cached thumbnails with fit inside max-h cap",
      "problem": "generate-thumbnails.mjs uses old resize pattern that has no max-height cap and skips already cached files",
      "goal": "All cached thumbnails regenerated with fit inside max-h size times 1.4",
      "scope_in": [
        "Update generate-thumbnails.mjs to use fit inside and max-h cap matching the upload route; add FORCE flag to regenerate existing cached thumbnails; rerun script and verify all thumbs match new behavior"
      ],
      "scope_out": [
        "No API changes",
        "no schema change"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: generate-thumbnails.mjs uses old resize pattern that has no max-height cap and skips already cached files. Goal: All cached thumbnails regenerated with fit inside max-h size times 1.4. Scope IN: Update generate-thumbnails.mjs to use fit inside and max-h cap matching the upload route; add FORCE flag to regenerate existing cached thumbnails; rerun script and verify all thumbs match new behavior. Scope OUT: No API changes, no schema change. Type: chore. Priority: med. Risk: low.",
      "acceptance": [
        "generate-thumbnails.mjs uses fit inside with maxHeight size times 1.4",
        "FORCE flag regenerates existing cached thumbnails",
        "All cached thumbnails now use the new bounded-box behavior matching upload route",
        "Non-square source thumbnails are not cropped",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T14:49:16Z"
    },
    {
      "id": "F-061",
      "type": "fix",
      "title": "Product detail image not centered horizontally inside its column",
      "problem": "On product detail page the image container has aspect-5/7 max-h-500 so it becomes narrower than the column and gets left-aligned leaving empty space on the right side on desktop; on tablet widths similar empty space appears on the right of the image",
      "goal": "Image container centered horizontally inside its grid cell on every breakpoint",
      "scope_in": [
        "Add a centering flex wrapper around the image container; add max-w to keep image sensible on tablets; ensure the description cell remains left-aligned"
      ],
      "scope_out": [
        "No API change",
        "no schema change"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: On product detail page the image container has aspect-5/7 max-h-500 so it becomes narrower than the column and gets left-aligned leaving empty space on the right side on desktop; on tablet widths similar empty space appears on the right of the image. Goal: Image container centered horizontally inside its grid cell on every breakpoint. Scope IN: Add a centering flex wrapper around the image container; add max-w to keep image sensible on tablets; ensure the description cell remains left-aligned. Scope OUT: No API change, no schema change. Type: fix. Priority: high. Risk: low.",
      "acceptance": [
        "Image container centered horizontally inside its grid cell on desktop (2 cols) and mobile (1 col)",
        "No empty space on the right of the image on desktop with 2-col layout",
        "Image stays inside its bounding box on tablet widths (max-w cap)",
        "Description cell unchanged",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T14:56:06Z"
    },
    {
      "id": "F-062",
      "type": "feature",
      "title": "CMS page editor becomes WYSIWYG with Lexical",
      "problem": "CMS body field is edited with a plain textarea which forces admins to write raw HTML by hand",
      "goal": "Editing pages in the CMS is WYSIWYG using Lexical. Output stays as HTML so existing public render and DB schema do not change",
      "scope_in": [
        "Add lexical and the @lexical scope to apps/admin; build a reusable LexicalEditor component with toolbar bold italic underline H2 H3 lists link undo redo; mount it inside the existing CMS form replacing the textarea; serialize to HTML on save"
      ],
      "scope_out": [
        "No backend schema change; no public render change; no new auth scope"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: CMS body field is edited with a plain textarea which forces admins to write raw HTML by hand. Goal: Editing pages in the CMS is WYSIWYG using Lexical. Output stays as HTML so existing public render and DB schema do not change. Scope IN: Add lexical and the @lexical scope to apps/admin; build a reusable LexicalEditor component with toolbar bold italic underline H2 H3 lists link undo redo; mount it inside the existing CMS form replacing the textarea; serialize to HTML on save. Scope OUT: No backend schema change; no public render change; no new auth scope. Type: feature. Priority: med. Risk: low.",
      "acceptance": [
        "LexicalEditor renders in the CMS edit form with a working toolbar",
        "Saved body is valid HTML and round-trips through the existing API",
        "Existing pages still load with their current body unchanged",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T15:02:14Z"
    },
    {
      "id": "F-063",
      "type": "fix",
      "title": "Product editor infinite fetch and broken checkboxes",
      "problem": "ProductEditor useEffect includes getSnap in deps which is rebuilt on every state change; on every re-render it calls productsApi.get again; setting array state with new references prevents React from bailing so the loop never stops. Clicks on featured/category/attribute controls appear to be ignored because the next fetch resets the state.",
      "goal": "Featured category and attribute selectors respond immediately to clicks; no fetch loop; product loads once and stays put",
      "scope_in": [
        "Move getSnap out of the load useEffect deps using an empty-deps one-shot effect; use a ref-stable getter; ensure the dirty-check effect still works after the change. Then re-verify clicking featured and a category changes state and persists across renders."
      ],
      "scope_out": [
        "No API change",
        "no schema change"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: ProductEditor useEffect includes getSnap in deps which is rebuilt on every state change; on every re-render it calls productsApi.get again; setting array state with new references prevents React from bailing so the loop never stops. Clicks on featured/category/attribute controls appear to be ignored because the next fetch resets the state.. Goal: Featured category and attribute selectors respond immediately to clicks; no fetch loop; product loads once and stays put. Scope IN: Move getSnap out of the load useEffect deps using an empty-deps one-shot effect; use a ref-stable getter; ensure the dirty-check effect still works after the change. Then re-verify clicking featured and a category changes state and persists across renders.. Scope OUT: No API change, no schema change. Type: fix. Priority: high. Risk: low.",
      "acceptance": [
        "GET /api/products/:id fires exactly once on mount and again only on navigation",
        "Clicking featured and a category updates state and persists after re-render",
        "Loading spinner disappears after the first successful fetch and never reappears",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T15:07:45Z"
    },
    {
      "id": "F-064",
      "type": "feature",
      "title": "Product description uses WYSIWYG editor in admin",
      "problem": "Admin product editor description field is a plain textarea forcing HTML by hand and product pages render it as plain text",
      "goal": "Editing the product description is WYSIWYG using the Lexical editor; rendered HTML on public storefront and frontend",
      "scope_in": [
        "Core flow"
      ],
      "scope_out": [
        "Add the LexicalEditor component to the product description field in apps/admin/src/features/products/components/ProductEditor.tsx; switch the public renderers in frontend/src/app/products/[slug]/page.tsx and storefront/src/app/productos/[slug]/page.tsx to dangerouslySetInnerHTML; ensure existing plain-text descriptions still render"
      ],
      "priority": "low",
      "risk": "low",
      "description": "Problem: Admin product editor description field is a plain textarea forcing HTML by hand and product pages render it as plain text. Goal: Editing the product description is WYSIWYG using the Lexical editor; rendered HTML on public storefront and frontend. Scope IN: Core flow. Scope OUT: Add the LexicalEditor component to the product description field in apps/admin/src/features/products/components/ProductEditor.tsx; switch the public renderers in frontend/src/app/products/[slug]/page.tsx and storefront/src/app/productos/[slug]/page.tsx to dangerouslySetInnerHTML; ensure existing plain-text descriptions still render. Type: feature. Priority: low. Risk: low.",
      "acceptance": [
        "med",
        "Admin product editor description is the Lexical WYSIWYG with toolbar",
        "Saving the product persists the HTML body produced by the editor",
        "Public product pages render the description HTML safely",
        "Existing plain text descriptions still display",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T15:09:37Z"
    },
    {
      "id": "F-065",
      "type": "fix",
      "title": "Search inputs lack id and name attributes",
      "problem": "Multiple search inputs across apps have no id or name attribute which fails accessibility checks and prevents autofill",
      "goal": "All form inputs have proper id and name attributes; a11y checks pass",
      "scope_in": [
        "Add id and name attributes to every search input in the storefront frontend header and search results page"
      ],
      "scope_out": [
        "No new APIs",
        "no schema change"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: Multiple search inputs across apps have no id or name attribute which fails accessibility checks and prevents autofill. Goal: All form inputs have proper id and name attributes; a11y checks pass. Scope IN: Add id and name attributes to every search input in the storefront frontend header and search results page. Scope OUT: No new APIs, no schema change. Type: fix. Priority: high. Risk: low.",
      "acceptance": [
        "Every search input has an id attribute and a meaningful name attribute",
        "a11y lint rule no-autofocus and form-field-label are happy",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T15:12:25Z"
    },
    {
      "id": "F-066",
      "type": "fix",
      "title": "Product image invisible, PATCH 500 on attributes and missing fields in serializeProduct",
      "problem": "Product detail image rendered empty even though the URL returns 200; PATCH /api/products/:id returns 500 when the payload includes attributes; and serializeProduct omits channels featured and attributes so admin edits never round-trip through the API",
      "goal": "The product image is visible on the detail page; PATCH with attributes stores and returns the attributes; serializeProduct returns channels featured and attributes; admin and public clients see updates",
      "scope_in": [
        "Core flow"
      ],
      "scope_out": [
        "Fix the product detail image container so it has a real height (next/image fill requires a non-zero-height parent). Wrap the container in a flex centering div and bring back aspect-5/7 max-h-500 so it has height and stays centred. Fix pg-product-repository.update to JSON.stringify the attributes array before binding it to the JSONB column and cast as jsonb. Add channels featured attributes to serializeProduct."
      ],
      "priority": "low",
      "risk": "low",
      "description": "Problem: Product detail image rendered empty even though the URL returns 200; PATCH /api/products/:id returns 500 when the payload includes attributes; and serializeProduct omits channels featured and attributes so admin edits never round-trip through the API. Goal: The product image is visible on the detail page; PATCH with attributes stores and returns the attributes; serializeProduct returns channels featured and attributes; admin and public clients see updates. Scope IN: Core flow. Scope OUT: Fix the product detail image container so it has a real height (next/image fill requires a non-zero-height parent). Wrap the container in a flex centering div and bring back aspect-5/7 max-h-500 so it has height and stays centred. Fix pg-product-repository.update to JSON.stringify the attributes array before binding it to the JSONB column and cast as jsonb. Add channels featured attributes to serializeProduct.. Type: fix. Priority: low. Risk: low.",
      "acceptance": [
        "high",
        "GET /products/[slug] shows the product image (no zero-height container)",
        "PATCH /api/products/:id with attributes succeeds and round-trips",
        "GET /api/products/:id returns channels featured and attributes",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T15:22:39Z"
    },
    {
      "id": "F-067",
      "type": "fix",
      "title": "Product card image left-aligned and checkout does not see saved customer addresses",
      "problem": "Product listing cards use aspect-5/7 max-h-72 which collapses to ~205x288 inside a wider card so the image container is left-aligned. The checkout page never fetches the customer saved addresses so even if the admin saved them via /customers/[id] the storefront checkout shows an empty form",
      "goal": "Make flow better",
      "scope_in": [
        "Card image visually centred inside its grid cell; when a logged-in customer reaches /checkout their saved addresses are listed and the default one pre-fills the shipping form"
      ],
      "scope_out": [
        "No redesign"
      ],
      "priority": "low",
      "risk": "low",
      "description": "Problem: Product listing cards use aspect-5/7 max-h-72 which collapses to ~205x288 inside a wider card so the image container is left-aligned. The checkout page never fetches the customer saved addresses so even if the admin saved them via /customers/[id] the storefront checkout shows an empty form. Goal: Make flow better. Scope IN: Card image visually centred inside its grid cell; when a logged-in customer reaches /checkout their saved addresses are listed and the default one pre-fills the shipping form. Scope OUT: No redesign. Type: fix. Priority: low. Risk: low.",
      "acceptance": [
        "high",
        "Listing cards have the image visually centred inside the card",
        "Checkout fetches saved addresses when user is logged in",
        "Default saved address pre-fills the shipping form on first render",
        "User can pick a different saved address and the form updates",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T15:30:00Z"
    },
    {
      "id": "F-068",
      "type": "fix",
      "title": "Product cards look flat and description HTML not rendered",
      "problem": "Cards switched to w-full max-h-72 which leaves them 280x288 on a 4-col grid with letterboxed images; product description is rendered as plain text in the card so HTML tags show literally",
      "goal": "Make flow better",
      "scope_in": [
        "Product cards keep a stable aspect ratio (1:1) and the description renders as HTML"
      ],
      "scope_out": [
        "No redesign"
      ],
      "priority": "low",
      "risk": "low",
      "description": "Problem: Cards switched to w-full max-h-72 which leaves them 280x288 on a 4-col grid with letterboxed images; product description is rendered as plain text in the card so HTML tags show literally. Goal: Make flow better. Scope IN: Product cards keep a stable aspect ratio (1:1) and the description renders as HTML. Scope OUT: No redesign. Type: fix. Priority: low. Risk: low.",
      "acceptance": [
        "high",
        "Each listing card has a 1:1 aspect ratio and the image fits inside without distortion",
        "Product description renders HTML tags on listing cards",
        "Existing products without HTML still display",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T15:36:09Z"
    },
    {
      "id": "F-069",
      "type": "feature",
      "title": "Shipping method descriptions editable in admin",
      "problem": "Shipping method names (Estandar / Express 24h) and prices live in the backend but the descriptions shown in the storefront checkout (Entrega 3-5 dias laborables / Entrega al dia siguiente) are hardcoded in the frontend",
      "goal": "Make flow better",
      "scope_in": [
        "Admins can edit the description shown on the storefront checkout for each shipping method"
      ],
      "scope_out": [
        "No redesign"
      ],
      "priority": "low",
      "risk": "low",
      "description": "Problem: Shipping method names (Estandar / Express 24h) and prices live in the backend but the descriptions shown in the storefront checkout (Entrega 3-5 dias laborables / Entrega al dia siguiente) are hardcoded in the frontend. Goal: Make flow better. Scope IN: Admins can edit the description shown on the storefront checkout for each shipping method. Scope OUT: No redesign. Type: feature. Priority: low. Risk: low.",
      "acceptance": [
        "high",
        "Shipping methods table has a description column",
        "Admin can edit description from /shipping",
        "Storefront /checkout fetches methods and shows the description under each one",
        "Public endpoint returns methods with description",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T16:09:23Z"
    },
    {
      "id": "F-070",
      "type": "fix",
      "title": "Show product attributes on frontend product detail page",
      "problem": "Product attributes (bio, vegano, sin-gluten, etc.) returned by backend but frontend Product type omits attributes and detail page never renders them",
      "goal": "Product attributes appear as visual badges on the frontend product detail page",
      "scope_in": [
        "Add attributes to Product type",
        "render attribute badges on product detail page"
      ],
      "scope_out": [
        "No backend changes",
        "no variant attributes"
      ],
      "priority": "med",
      "risk": "med",
      "description": "Problem: Product attributes (bio, vegano, sin-gluten, etc.) returned by backend but frontend Product type omits attributes and detail page never renders them. Goal: Product attributes appear as visual badges on the frontend product detail page. Scope IN: Add attributes to Product type, render attribute badges on product detail page. Scope OUT: No backend changes, no variant attributes. Type: fix. Priority: med. Risk: med.",
      "acceptance": [
        "Product detail page renders product attributes as badges",
        "No regression on existing product pages",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T16:21:01Z"
    },
    {
      "id": "F-071",
      "type": "feature",
      "title": "Editable emoji and color for categories",
      "problem": "Categories use hardcoded emoji/color maps that cannot be customized in admin",
      "goal": "Add emoji and color fields to categories, make them editable in admin form, and use stored values in frontend category displays instead of hardcoded maps",
      "scope_in": [
        "backend:category domain",
        "frontend home grid",
        "frontend categories list",
        "admin category form"
      ],
      "scope_out": [
        "admin redesign",
        "brand/product changes"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: Categories use hardcoded emoji/color maps that cannot be customized in admin. Goal: Add emoji and color fields to categories, make them editable in admin form, and use stored values in frontend category displays instead of hardcoded maps. Scope IN: backend:category domain, frontend home grid, frontend categories list, admin category form. Scope OUT: admin redesign, brand/product changes. Type: feature. Priority: high. Risk: low.",
      "acceptance": [
        "Admin can edit emoji and color per category in the category form",
        "Frontend category cards use stored emoji and color instead of hardcoded slug-based maps",
        "New parent categories show their stored emoji instead of generic folder icon",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T17:04:41Z"
    },
    {
      "id": "F-072",
      "type": "fix",
      "title": "Active tax rates listed in product edit prices tab",
      "problem": "PricingSection hardcodes only 2 VAT options (21%% gen, 10%% red) but DB has 3 rates (4%% super-reduced, 10%% reduced, 21%% general). The 4%% is missing from the dropdown and calculation constants are hardcoded.",
      "goal": "Improve the product",
      "scope_in": [
        "admin",
        "frontend"
      ],
      "scope_out": [
        "No backend API changes"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: PricingSection hardcodes only 2 VAT options (21%% gen, 10%% red) but DB has 3 rates (4%% super-reduced, 10%% reduced, 21%% general). The 4%% is missing from the dropdown and calculation constants are hardcoded.. Type: fix. Priority: med. Risk: low.",
      "acceptance": [
        "PricingSection fetches active tax rates from GET /admin/tax-rates",
        "Dropdown shows all active rates dynamically",
        "VAT calculation uses fetched rate values instead of hardcoded constants",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T17:22:08Z"
    },
    {
      "id": "F-073",
      "type": "feature",
      "title": "Activate/deactivate VAT types in admin tax rates page",
      "problem": "Admin tax rates page (/tax-rates) already has inline edit for active field but the UX is confusing. Need a clearer toggle mechanism with immediate visual feedback.",
      "goal": "Improve the product",
      "scope_in": [
        "admin"
      ],
      "scope_out": [
        "No backend needed"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: Admin tax rates page (/tax-rates) already has inline edit for active field but the UX is confusing. Need a clearer toggle mechanism with immediate visual feedback.. Type: feature. Priority: med. Risk: low.",
      "acceptance": [
        "Active/inactive toggle is prominent and clearly visible",
        "Toggling updates immediately without full row edit mode",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T17:23:44Z"
    },
    {
      "id": "F-074",
      "type": "fix",
      "title": "Audit shows real-time logs from application services",
      "problem": "Audit log module does not receive or display real-time logs from application services. Need to implement a mechanism for services to emit audit events that appear in real-time.",
      "goal": "Improve the product",
      "scope_in": [
        "backend",
        "admin"
      ],
      "scope_out": [
        "No migration"
      ],
      "priority": "high",
      "risk": "high",
      "description": "Problem: Audit log module does not receive or display real-time logs from application services. Need to implement a mechanism for services to emit audit events that appear in real-time.. Type: fix. Priority: high. Risk: high.",
      "acceptance": [
        "Audit events from backend services appear in audit log page",
        "Logs are queryable by user/action/date",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T17:25:39Z"
    },
    {
      "id": "F-075",
      "type": "feature",
      "title": "Inline edit SKU/EAN/STOCK in inventory module, remove Actions column",
      "problem": "Admin inventory module (product variants) requires inline editing of SKU, EAN, and stock fields without navigating to another page. The Actions column is unused and should be removed.",
      "goal": "Improve the product",
      "scope_in": [
        "admin"
      ],
      "scope_out": [
        "No backend API changes needed if variants API already supports PATCH"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: Admin inventory module (product variants) requires inline editing of SKU, EAN, and stock fields without navigating to another page. The Actions column is unused and should be removed.. Type: feature. Priority: med. Risk: low.",
      "acceptance": [
        "SKU field is editable inline with save on blur or enter",
        "EAN field is editable inline",
        "Stock field is editable inline",
        "Actions column removed from inventory table",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T17:29:11Z"
    },
    {
      "id": "F-076",
      "type": "feature",
      "title": "Link to frontend product from backend product list; archive separate listing",
      "problem": "Backend product listing (/products) shows all products together. Need: (1) active products show a link to the frontend product page, (2) archived products shown in a separate tab/listing.",
      "goal": "Improve the product",
      "scope_in": [
        "admin",
        "frontend"
      ],
      "scope_out": [
        "No backend API changes"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: Backend product listing (/products) shows all products together. Need: (1) active products show a link to the frontend product page, (2) archived products shown in a separate tab/listing.. Type: feature. Priority: med. Risk: low.",
      "acceptance": [
        "Active products in /products list have a visible link to /productos/[slug]",
        "Archived products are in a separate listing (tab or separate page)",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T17:31:46Z"
    },
    {
      "id": "F-077",
      "type": "fix",
      "title": "Product short description does not render HTML in backend product listing",
      "problem": "The brief_description field in the backend product list page renders raw HTML tags as text instead of interpreting them as formatted text. Need to render HTML safely.",
      "goal": "Improve the product",
      "scope_in": [
        "admin"
      ],
      "scope_out": [
        "No backend changes"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: The brief_description field in the backend product list page renders raw HTML tags as text instead of interpreting them as formatted text. Need to render HTML safely.. Type: fix. Priority: high. Risk: low.",
      "acceptance": [
        "brief_description renders HTML tags as formatted text, not raw",
        "No XSS risk from rendering",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-19T17:33:25Z"
    },
    {
      "id": "F-078",
      "type": "feature",
      "title": "Server log streaming via SSE in admin audit page",
      "problem": "Admin needs to see server-side logs in real-time like docker logs. Errors should be highlighted.",
      "goal": "Add SSE endpoint that streams server logs and a terminal-like log viewer in the admin UI.",
      "scope_in": [
        "backend",
        "admin"
      ],
      "scope_out": [
        "No changes to existing audit_log table; no new DB tables"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: Admin needs real-time server logs. Goal: SSE streaming endpoint + terminal UI. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "SSE endpoint streams server logs in real-time",
        "Errors highlighted in red",
        "Log viewer shows recent history on connect",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T03:55:46Z"
    },
    {
      "id": "F-079",
      "type": "bug",
      "title": "Product list /products does not show brand",
      "problem": "Backend product listing page (/products) does not show the brand column/value for each product row, even though products have a brand assigned. Operators cannot see at a glance which brand a product belongs to while scanning the table.",
      "goal": "Show the brand of each product as a visible column (or field in the row card) in /products, with consistent formatting and a sortable/filterable UX where possible.",
      "scope_in": [
        "backend",
        "admin"
      ],
      "scope_out": [
        "No product model changes",
        "No new brand management UI"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: Backend product listing page (/products) does not show the brand column/value for each product row, even though products have a brand assigned. Operators cannot see at a glance which brand a product belongs to while scanning the table.. Goal: Show the brand of each product as a visible column (or field in the row card) in /products, with consistent formatting and a sortable/filterable UX where possible.. Scope IN: backend, admin. Scope OUT: No product model changes, No new brand management UI. Type: bug. Priority: med. Risk: low.",
      "acceptance": [
        "Each row in /products listing shows the brand name (or \"-\" when not assigned)",
        "Brand column header is present and aligned with other columns",
        "Existing brand filter (if any) keeps working",
        "Empty brand renders as a placeholder, not raw null/undefined",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T03:57:24Z"
    },
    {
      "id": "F-080",
      "type": "fix",
      "title": "PVP (IVA incl.) input in Prices tab locks decimal editing",
      "problem": "In the product editor Prices tab, the PVP (IVA incl.) input refuses to let the operator type freely in the decimals part. The user cannot delete digits, cannot replace the decimal separator (comma/dot), and sometimes cannot type any digit at all. The sibling \"Neto (sin IVA)\" input works exactly as expected and must be used as the reference behavior.",
      "goal": "Make PVP (IVA incl.) behave like Neto (sin IVA): free decimal typing, accept comma or dot, allow deleting digits and the decimal separator, recompute cleanly from neto + IVA rate or accept the typed value, and persist on save without losing precision.",
      "scope_in": [
        "admin",
        "backend"
      ],
      "scope_out": [
        "No DB schema changes",
        "No IVA/tax rate model changes"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: In the product editor Prices tab, the PVP (IVA incl.) input refuses to let the operator type freely in the decimals part. The user cannot delete digits, cannot replace the decimal separator (comma/dot), and sometimes cannot type any digit at all. The sibling \"Neto (sin IVA)\" input works exactly as expected and must be used as the reference behavior.. Goal: Make PVP (IVA incl.) behave like Neto (sin IVA): free decimal typing, accept comma or dot, allow deleting digits and the decimal separator, recompute cleanly from neto + IVA rate or accept the typed value, and persist on save without losing precision.. Scope IN: admin, backend. Scope OUT: No DB schema changes, No IVA/tax rate model changes. Type: fix. Priority: med. Risk: low.",
      "acceptance": [
        "PVP (IVA incl.) input lets the operator type any digit and decimal separator (\",\" or \".\")",
        "Operator can delete individual digits and the decimal separator inside the value",
        "PVP value is parsed and stored as a real number with 2-decimal rounding",
        "On blur/save, PVP stays consistent with neto + IVA rate (recompute or keep typed, no stale value)",
        "Behavior matches the Neto (sin IVA) input UX (same input component, same parsing rules)",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T03:59:47Z"
    },
    {
      "id": "F-081",
      "type": "fix",
      "title": "Inventory PRECIO NETO input has wrong format and no save",
      "problem": "In the inventory view, the PRECIO NETO field for each variant looks and behaves differently from the Prices tab \"Neto (sin IVA)\" input. It does not accept comma and dot as decimal separators consistently, and although the field appears editable, there is no visible way to persist the change (no save button, no save-on-blur, no PATCH call). Operators can type a value but it never reaches the backend.",
      "goal": "Make Inventory PRECIO NETO use the same input component and parsing rules as Prices tab Neto (sin IVA) (comma or dot decimals, free typing, delete digits), and wire it to the variant PATCH endpoint so changes persist on blur/enter with visible feedback (toast or row highlight).",
      "scope_in": [
        "admin",
        "backend"
      ],
      "scope_out": [
        "No new pricing API",
        "No DB schema changes"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: In the inventory view, the PRECIO NETO field for each variant looks and behaves differently from the Prices tab \"Neto (sin IVA)\" input. It does not accept comma and dot as decimal separators consistently, and although the field appears editable, there is no visible way to persist the change (no save button, no save-on-blur, no PATCH call). Operators can type a value but it never reaches the backend.. Goal: Make Inventory PRECIO NETO use the same input component and parsing rules as Prices tab Neto (sin IVA) (comma or dot decimals, free typing, delete digits), and wire it to the variant PATCH endpoint so changes persist on blur/enter with visible feedback (toast or row highlight).. Scope IN: admin, backend. Scope OUT: No new pricing API, No DB schema changes. Type: fix. Priority: high. Risk: med.",
      "acceptance": [
        "Inventory PRECIO NETO input accepts \",\" and \".\" as decimal separator",
        "Operator can freely type, delete digits and the decimal separator (same UX as Prices tab)",
        "Editing PRECIO NETO triggers a PATCH to the variant endpoint with the new netUnitAmount",
        "On success the new value is persisted and shown back in the cell; on error an inline error/toast is shown and old value is restored",
        "Format (2 decimals, locale aware) is identical to the Prices tab Neto (sin IVA) display",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T04:01:32Z"
    },
    {
      "id": "F-082",
      "type": "fix",
      "title": "/inventory editable fields cannot be saved; stock UX needs click-to-edit",
      "problem": "In the admin inventory view, several fields look editable but the operator has no way to persist the change (no save button, no save on blur, no PATCH). Specifically: editable price/cost/EAN/SKU/etc. changes are silently lost. Additionally, the Stock column still requires clicking a pencil icon to enter edit mode. The desired UX is: clicking the Stock cell directly switches it to edit mode (no pencil step), and any in-flight edit can be saved.",
      "goal": "Make every editable field in /inventory persist on blur or Enter via the variant PATCH endpoint. Make Stock enter edit mode on a single click on the cell (no pencil icon). Provide a clear save indicator and an inline error path.",
      "scope_in": [
        "admin",
        "backend"
      ],
      "scope_out": [
        "No new endpoints",
        "No DB schema changes"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: In the admin inventory view, several fields look editable but the operator has no way to persist the change (no save button, no save on blur, no PATCH). Specifically: editable price/cost/EAN/SKU/etc. changes are silently lost. Additionally, the Stock column still requires clicking a pencil icon to enter edit mode. The desired UX is: clicking the Stock cell directly switches it to edit mode (no pencil step), and any in-flight edit can be saved.. Goal: Make every editable field in /inventory persist on blur or Enter via the variant PATCH endpoint. Make Stock enter edit mode on a single click on the cell (no pencil icon). Provide a clear save indicator and an inline error path.. Scope IN: admin, backend. Scope OUT: No new endpoints, No DB schema changes. Type: fix. Priority: high. Risk: med.",
      "acceptance": [
        "Every editable cell in /inventory (SKU, EAN, Stock, Precio Neto, etc.) saves on blur or Enter via PATCH /variants/:id",
        "Stock cell enters edit mode on a single click; the pencil icon is removed or no longer required",
        "Successful save shows visual confirmation (toast or row flash); the new value stays in the cell",
        "Failed save shows inline error and restores the previous value",
        "Concurrent edits on the same row do not silently overwrite (last write wins is acceptable but a warning is shown)",
        "No regressions in the existing Prices tab or product editor",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T04:04:22Z"
    },
    {
      "id": "F-083",
      "type": "feature",
      "title": "Customers: password reset via email link",
      "problem": "In /customers (admin) and the customer self-service area, there is no password reset flow. Operators and customers cannot recover access to an account when the password is forgotten. We need an email-based reset: ask for the email, send a one-time link, the user opens the link and sets a new password.",
      "goal": "Implement a secure password reset flow: request endpoint by email, signed/tokenized reset link sent via email, reset endpoint that accepts a new password, plus admin button to trigger the email from a customer row.",
      "scope_in": [
        "backend",
        "admin",
        "storefront",
        "email"
      ],
      "scope_out": [
        "No MFA / 2FA",
        "No SMS channel"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: In /customers (admin) and the customer self-service area, there is no password reset flow. Operators and customers cannot recover access to an account when the password is forgotten. We need an email-based reset: ask for the email, send a one-time link, the user opens the link and sets a new password.. Goal: Implement a secure password reset flow: request endpoint by email, signed/tokenized reset link sent via email, reset endpoint that accepts a new password, plus admin button to trigger the email from a customer row.. Scope IN: backend, admin, storefront, email. Scope OUT: No MFA / 2FA, No SMS channel. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "Backend endpoint POST /auth/password-reset/request accepts email and always returns 200 (no user enumeration)",
        "A signed single-use token is generated, stored with TTL (>= 30 min, <= 24h) and expiry",
        "An email is sent to the customer with the reset link to /cuenta/restablecer?token=...",
        "Backend endpoint POST /auth/password-reset/confirm accepts token + new password; invalid/expired/used tokens return 400",
        "After reset, the new password works for login and any previous sessions remain valid until logout",
        "Admin /customers row has a \"Send reset link\" action that triggers the same email flow",
        "Rate limit on request endpoint (per IP and per email) to avoid abuse",
        "Audit log entry for each reset request and confirm",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T04:08:32Z"
    },
    {
      "id": "F-084",
      "type": "bug",
      "title": "Parent categories list does not show emoji in front of the name",
      "problem": "In /categories, parent categories (those with no parent of their own) do not show their emoji icon in front of the name in the list view, even though the emoji is set on the record. A specific reproduction: \"Hogar y Mascotas\" was created from the UI, its emoji was edited later, but the list still shows the name without the emoji. Child categories already render correctly so the bug is specific to the parent-category rendering path.",
      "goal": "Render the category emoji in front of the name for every parent category row in /categories list, and ensure re-editing the emoji (via UI) is reflected on next list render without a full reload.",
      "scope_in": [
        "admin",
        "backend"
      ],
      "scope_out": [
        "No category hierarchy model changes",
        "No new icon picker"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: In /categories, parent categories (those with no parent of their own) do not show their emoji icon in front of the name in the list view, even though the emoji is set on the record. A specific reproduction: \"Hogar y Mascotas\" was created from the UI, its emoji was edited later, but the list still shows the name without the emoji. Child categories already render correctly so the bug is specific to the parent-category rendering path.. Goal: Render the category emoji in front of the name for every parent category row in /categories list, and ensure re-editing the emoji (via UI) is reflected on next list render without a full reload.. Scope IN: admin, backend. Scope OUT: No category hierarchy model changes, No new icon picker. Type: bug. Priority: med. Risk: low.",
      "acceptance": [
        "Every parent category row in /categories list shows its emoji immediately before the name",
        "Editing the emoji of a parent category from the UI reflects on the list on next refresh (no stale state)",
        "Child categories keep their current rendering (no regression)",
        "Categories without an emoji show a neutral placeholder (e.g. greyed hash) instead of an empty space",
        "Emoji is stored and returned correctly by the categories API (sanitized, valid UTF-8)",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T04:09:17Z"
    },
    {
      "id": "F-085",
      "type": "fix",
      "title": "/tax-rates TIPO column is not editable",
      "problem": "In /tax-rates, the TIPO (type) column is read-only. Operators can change the value via the edit page/form, but inline editing on the list does not let them pick a different tax type. The desired behavior: TIPO behaves like the other editable fields (inline edit on click or via a small select), and the change is persisted on the tax-rates API.",
      "goal": "Make the TIPO column in /tax-rates inline-editable (dropdown with the valid tax-type values) and persist changes via PATCH /tax-rates/:id. Keep validation (only allowed types).",
      "scope_in": [
        "admin",
        "backend"
      ],
      "scope_out": [
        "No new tax type values",
        "No DB schema changes"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: In /tax-rates, the TIPO (type) column is read-only. Operators can change the value via the edit page/form, but inline editing on the list does not let them pick a different tax type. The desired behavior: TIPO behaves like the other editable fields (inline edit on click or via a small select), and the change is persisted on the tax-rates API.. Goal: Make the TIPO column in /tax-rates inline-editable (dropdown with the valid tax-type values) and persist changes via PATCH /tax-rates/:id. Keep validation (only allowed types).. Scope IN: admin, backend. Scope OUT: No new tax type values, No DB schema changes. Type: fix. Priority: med. Risk: low.",
      "acceptance": [
        "TIPO cell in /tax-rates list enters edit mode on click and shows a select with the allowed types",
        "Selecting a new type and confirming (blur/enter) triggers PATCH /tax-rates/:id with the new value",
        "Invalid types are rejected client and server side with a clear message",
        "Successful change is reflected in the cell without a full page reload",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T04:10:48Z"
    },
    {
      "id": "F-086",
      "type": "feature",
      "title": "Add product expiration date (fecha de caducidad) field",
      "problem": "Products have no expiration date tracked anywhere in the system. Operators cannot record a \"fecha de caducidad\" on a product, so it does not show on the product edit page, on /products listing or in the inventory module. Frontend customers also cannot see it.",
      "goal": "Add a fecha de caducidad (expiration date) field end to end: DB column on product (or variant) level, admin edit field in the product editor, column on /products listing and /inventory, plus optional display on the storefront product page.",
      "scope_in": [
        "backend",
        "admin",
        "storefront",
        "migrations"
      ],
      "scope_out": [
        "No batch-level expiry",
        "No expiry alerts/notifications in this ticket"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: Products have no expiration date tracked anywhere in the system. Operators cannot record a \"fecha de caducidad\" on a product, so it does not show on the product edit page, on /products listing or in the inventory module. Frontend customers also cannot see it.. Goal: Add a fecha de caducidad (expiration date) field end to end: DB column on product (or variant) level, admin edit field in the product editor, column on /products listing and /inventory, plus optional display on the storefront product page.. Scope IN: backend, admin, storefront, migrations. Scope OUT: No batch-level expiry, No expiry alerts/notifications in this ticket. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "DB migration adds fecha_caducidad (nullable DATE) to product (or variant) level",
        "API GET/PATCH/PUT for products/variants exposes the field",
        "Product editor (Ficha de edicion) has a date input labeled \"Fecha de caducidad\" that saves on blur/enter",
        "/products listing shows the expiration date as a sortable column; expired dates are visually highlighted",
        "/inventory view shows the expiration date per variant row",
        "Storefront product page optionally renders the expiration date when present",
        "Empty value is allowed and renders as \"-\" without errors",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T04:13:37Z"
    },
    {
      "id": "F-087",
      "type": "feature",
      "title": "Frontend cart: cap quantity to available stock",
      "problem": "On the storefront, the cart and the \"Add to cart\" action let customers enter or select a quantity that exceeds the available stock. Example: a product with 10 units in stock can be added to the cart with quantity 11 or more. The cap must be enforced in two places: the cart line editor (qty input/stepper) and the product page \"Add to cart\" button/input.",
      "goal": "Cap the selectable/enterable quantity to the current available stock in both the cart line editor and the \"Add to cart\" controls. Show a clear message when the customer tries to exceed stock and prevent the action.",
      "scope_in": [
        "frontend",
        "storefront",
        "backend"
      ],
      "scope_out": [
        "No stock reservation changes",
        "No backend stock policy rewrite"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: On the storefront, the cart and the \"Add to cart\" action let customers enter or select a quantity that exceeds the available stock. Example: a product with 10 units in stock can be added to the cart with quantity 11 or more. The cap must be enforced in two places: the cart line editor (qty input/stepper) and the product page \"Add to cart\" button/input.. Goal: Cap the selectable/enterable quantity to the current available stock in both the cart line editor and the \"Add to cart\" controls. Show a clear message when the customer tries to exceed stock and prevent the action.. Scope IN: frontend, storefront, backend. Scope OUT: No stock reservation changes, No backend stock policy rewrite. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "On the product page, the quantity stepper max is the current stock; typing a value > stock is rejected with a clear message",
        "The \"Add to cart\" button is disabled (or shows an error) when the entered quantity exceeds stock",
        "In the cart, each line quantity input has max = current stock for that variant",
        "Trying to set qty > stock in the cart shows an inline error and keeps the previous value (or caps it)",
        "The cart totals and checkout use the capped quantity",
        "If stock changes between page load and add-to-cart, the API rejects the overflow with a 409 and the UI shows a clear message",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-19",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T04:16:38Z"
    },
    {
      "id": "F-088",
      "type": "fix",
      "title": "Admin taxApi.update type signature missing appliesTo (F-085 regression)",
      "problem": "F-085 added inline TIPO edit calling taxApi.update(id, { appliesTo }) but forgot to extend the Partial<{name,ratePercent,active}> type signature in apps/admin/src/lib/api-client.ts. tsc fails with TS2353, next build produces no BUILD_ID, and the admin process exits immediately on every monolith start, so http://192.168.18.93:3004/ returns ERR_CONNECTION_REFUSED.",
      "goal": "Extend taxApi.update signature to accept appliesTo typed as the same enum used by the backend Zod schema ('general' | 'reduced' | 'super-reduced'). Unblocks admin production build, lets the admin stay alive on port 3004, and makes F-085's TIPO editing actually compile and ship.",
      "scope_in": [
        "project/apps/admin/src/lib/api-client.ts"
      ],
      "scope_out": [
        "No backend change (already supports appliesTo)",
        "no new tax-type values",
        "no DB schema change"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: F-085 added inline TIPO edit calling taxApi.update(id, { appliesTo }) but forgot to extend the Partial<{name,ratePercent,active}> type signature in apps/admin/src/lib/api-client.ts. tsc fails with TS2353, next build produces no BUILD_ID, and the admin process exits immediately on every monolith start, so http://192.168.18.93:3004/ returns ERR_CONNECTION_REFUSED.. Goal: Extend taxApi.update signature to accept appliesTo typed as the same enum used by the backend Zod schema ('general' | 'reduced' | 'super-reduced'). Unblocks admin production build, lets the admin stay alive on port 3004, and makes F-085's TIPO editing actually compile and ship.. Scope IN: project/apps/admin/src/lib/api-client.ts. Scope OUT: No backend change (already supports appliesTo), no new tax-type values, no DB schema change. Type: fix. Priority: high. Risk: low.",
      "acceptance": [
        "- taxApi.update accepts appliesTo as 'general' | 'reduced' | 'super-reduced'",
        "- npx tsc --noEmit in apps/admin exits 0",
        "- npm run build in apps/admin produces .next/BUILD_ID",
        "- admin process stays alive (PID running) after monolith restart",
        "- curl http://127.0.0.1:3004/ returns 200",
        "- verify.sh green"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T19:29:14Z"
    },
    {
      "id": "F-089",
      "type": "fix",
      "title": "Product listing link uses storefront /productos route instead of frontend /products route",
      "problem": "The active product link in the admin product listing points to the storefront Spanish route /productos/:slug, but the customer frontend served on port 3003 exposes product detail at /products/:slug. Clicking the listing link lands on the wrong application route.",
      "goal": "Use the frontend customer route /products/:slug for the product listing link so operators open the correct product page on port 3003.",
      "scope_in": [
        "admin product listing"
      ],
      "scope_out": [
        "No backend or storefront changes; no URL redesign"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: The active product link in the admin product listing points to the storefront Spanish route /productos/:slug, but the customer frontend served on port 3003 exposes product detail at /products/:slug. Clicking the listing link lands on the wrong application route.. Goal: Use the frontend customer route /products/:slug for the product listing link so operators open the correct product page on port 3003.. Scope IN: admin product listing. Scope OUT: No backend or storefront changes; no URL redesign. Type: fix. Priority: med. Risk: low.",
      "acceptance": [
        "- Active product links from the admin listing use http://192.168.18.93:3003/products/:slug",
        "- The link no longer uses the /productos/:slug path",
        "- Existing target blank and safe rel attributes remain unchanged",
        "- Admin typecheck/lint pass",
        "- verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T19:34:12Z"
    },
    {
      "id": "F-090",
      "type": "fix",
      "title": "Product listing metadata and variant SKU/EAN edits are not reflected",
      "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"
      ],
      "priority": "high",
      "risk": "low",
      "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 — 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",
        "- Admin typecheck/lint and verify.sh pass"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T19:40:41Z"
    },
    {
      "id": "F-091",
      "type": "fix",
      "title": "Explain and prevent duplicate SKU EAN update conflicts",
      "problem": "Variant updates return an opaque 409 when the requested SKU or EAN already belongs to another variant, and Enter plus blur can submit the same edit twice. The admin only shows a generic Error, so operators cannot tell which value conflicts.",
      "goal": "Keep SKU and EAN uniqueness, prevent duplicate in-flight submissions, and return/display a field-specific conflict message for SKU or EAN.",
      "scope_in": [
        "variant repository error mapping and admin InventorySection"
      ],
      "scope_out": [
        "No relaxation of uniqueness",
        "no schema change",
        "no duplicate codes allowed"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: Variant updates return an opaque 409 when the requested SKU or EAN already belongs to another variant, and Enter plus blur can submit the same edit twice. The admin only shows a generic Error, so operators cannot tell which value conflicts.. Goal: Keep SKU and EAN uniqueness, prevent duplicate in-flight submissions, and return/display a field-specific conflict message for SKU or EAN.. Scope IN: variant repository error mapping and admin InventorySection. Scope OUT: No relaxation of uniqueness, no schema change, no duplicate codes allowed. Type: fix. Priority: med. Risk: low.",
      "acceptance": [
        "- Repeated Enter/blur interaction sends at most one in-flight PATCH per SKU or EAN field",
        "- Duplicate SKU conflict returns a clear SKU-specific message",
        "- Duplicate EAN conflict returns a clear EAN-specific message",
        "- Existing unique values continue to save",
        "- Typecheck, tests, and verify.sh pass"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T19:47:31Z"
    },
    {
      "id": "F-092",
      "type": "fix",
      "title": "Move expiration date to product General tab",
      "problem": "The product editor currently renders Fecha de caducidad inside the SEO tab, but operators need this operational product field in the General tab alongside name, brand, categories, and attributes.",
      "goal": "Render the existing expirationDate field in the General tab and remove it from SEO without changing its value, validation, or save payload.",
      "scope_in": [
        "admin ProductEditor General and SEO tabs"
      ],
      "scope_out": [
        "No backend change",
        "no data migration",
        "no change to expirationDate API contract"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: The product editor currently renders Fecha de caducidad inside the SEO tab, but operators need this operational product field in the General tab alongside name, brand, categories, and attributes.. Goal: Render the existing expirationDate field in the General tab and remove it from SEO without changing its value, validation, or save payload.. Scope IN: admin ProductEditor General and SEO tabs. Scope OUT: No backend change, no data migration, no change to expirationDate API contract. Type: fix. Priority: med. Risk: low.",
      "acceptance": [
        "- General tab displays the Fecha de caducidad date input",
        "- SEO tab no longer displays the expiration date field",
        "- Existing value loads and saves through expirationDate unchanged",
        "- Product editor typecheck/lint pass",
        "- verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T19:51:46Z"
    },
    {
      "id": "F-093",
      "type": "fix",
      "title": "Inventory search by product name or EAN",
      "problem": "Inventory search currently sends q to the admin product list endpoint, but the backend list query only matches product name. Searching a variant EAN therefore returns no inventory rows.",
      "goal": "Make the inventory product lookup match product name or any variant EAN, and label the search input accordingly.",
      "scope_in": [
        "admin inventory search and catalog admin list query"
      ],
      "scope_out": [
        "No SKU search",
        "no changes to variant uniqueness",
        "no schema change"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: Inventory search currently sends q to the admin product list endpoint, but the backend list query only matches product name. Searching a variant EAN therefore returns no inventory rows.. Goal: Make the inventory product lookup match product name or any variant EAN, and label the search input accordingly.. Scope IN: admin inventory search and catalog admin list query. Scope OUT: No SKU search, no changes to variant uniqueness, no schema change. Type: fix. Priority: med. Risk: low.",
      "acceptance": [
        "- Inventory search by product name returns matching variants",
        "- Inventory search by EAN returns the product's variants",
        "- Search remains case-insensitive for names and supports partial EAN input",
        "- Placeholder states EAN or product name",
        "- Typecheck, tests, and verify.sh pass"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T19:57:43Z"
    },
    {
      "id": "F-094",
      "type": "feature",
      "title": "Create and explain product variants from Publish tab",
      "problem": "Pricing and Inventory show a warning that variants are created from the Publish tab, but Publish only contains state controls and no variant UI. Operators do not know what a variant is or how to create the SKU/EAN records required for stock and pricing.",
      "goal": "Add a clear variant explanation and a create-variant form to Publish with SKU and optional EAN, plus a list of existing variants. Use the existing product variant API and refresh the list after creation.",
      "scope_in": [
        "admin ProductEditor Publish tab and variant management"
      ],
      "scope_out": [
        "No database or backend API redesign",
        "no changes to variant uniqueness rules"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: Pricing and Inventory show a warning that variants are created from the Publish tab, but Publish only contains state controls and no variant UI. Operators do not know what a variant is or how to create the SKU/EAN records required for stock and pricing.. Goal: Add a clear variant explanation and a create-variant form to Publish with SKU and optional EAN, plus a list of existing variants. Use the existing product variant API and refresh the list after creation.. Scope IN: admin ProductEditor Publish tab and variant management. Scope OUT: No database or backend API redesign, no changes to variant uniqueness rules. Type: feature. Priority: high. Risk: low.",
      "acceptance": [
        "- Publish tab explains that a variant is a sellable presentation of a product with its own SKU/EAN",
        "- Publish tab lets an operator create a variant with required SKU and optional EAN",
        "- Successful creation shows the new variant in the list without a page reload",
        "- Duplicate SKU/EAN errors are shown clearly",
        "- Pricing and Inventory no longer point to a nonexistent variant workflow",
        "- Admin typecheck/lint and verify.sh pass"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T20:01:54Z"
    },
    {
      "id": "F-095",
      "type": "fix",
      "title": "Download remote image URL before attaching product image",
      "problem": "Adding an external image URL from the admin product editor sends the URL directly to the attach endpoint, and an unnecessary empty product PATCH causes HTTP 400. External images should be downloaded to local uploads before being attached so they remain available and are mirrored to the other apps.",
      "goal": "Make Add URL download a validated remote image through the admin upload route, store it locally, then attach the local uploads URL to the product. Remove the erroneous empty product update.",
      "scope_in": [
        "admin image editor upload route and product image attachment"
      ],
      "scope_out": [
        "No arbitrary non-image downloads",
        "no private network access",
        "no external URL stored as the product image"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: Adding an external image URL from the admin product editor sends the URL directly to the attach endpoint, and an unnecessary empty product PATCH causes HTTP 400. External images should be downloaded to local uploads before being attached so they remain available and are mirrored to the other apps.. Goal: Make Add URL download a validated remote image through the admin upload route, store it locally, then attach the local uploads URL to the product. Remove the erroneous empty product update.. Scope IN: admin image editor upload route and product image attachment. Scope OUT: No arbitrary non-image downloads, no private network access, no external URL stored as the product image. Type: fix. Priority: high. Risk: low.",
      "acceptance": [
        "- Valid remote image URL is downloaded and stored under local uploads",
        "- Local uploads URL is attached to the product after download",
        "- Invalid/non-image/oversized URLs return a clear error",
        "- Private/local network URLs are rejected",
        "- Existing file upload flow remains working",
        "- Admin typecheck/lint and verify.sh pass"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T20:05:54Z"
    },
    {
      "id": "F-096",
      "type": "feature",
      "title": "Configure AI model and generate missing product SEO fields",
      "problem": "Settings has no AI provider/model/API configuration or field-specific prompts. Product SEO fields therefore remain empty even when operators want missing SEO title and description generated automatically.",
      "goal": "Add settings fields for an OpenAI-compatible AI provider, model, API key, and separate SEO title/description prompts. When a product is saved with an empty SEO field, generate that field with the configured model and prompt while preserving manually entered values.",
      "scope_in": [
        "settings",
        "backend AI provider",
        "product SEO generation",
        "admin product editor"
      ],
      "scope_out": [
        "No automatic overwrite of non-empty SEO values",
        "no provider-specific SDK dependency"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: Settings has no AI provider/model/API configuration or field-specific prompts. Product SEO fields therefore remain empty even when operators want missing SEO title and description generated automatically.. Goal: Add settings fields for an OpenAI-compatible AI provider, model, API key, and separate SEO title/description prompts. When a product is saved with an empty SEO field, generate that field with the configured model and prompt while preserving manually entered values.. Scope IN: settings, backend AI provider, product SEO generation, admin product editor. Scope OUT: No automatic overwrite of non-empty SEO values, no provider-specific SDK dependency. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "- Settings exposes provider/base URL/model/API key and separate SEO title/description prompts",
        "- Settings values save and load through the admin settings API",
        "- Empty SEO title/description can be generated with the configured model and field prompt",
        "- Non-empty manually entered SEO fields are never overwritten",
        "- Missing configuration returns a clear actionable error",
        "- Secrets are not exposed in normal API responses",
        "- Typecheck, tests, and verify.sh pass"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T20:12:01Z"
    },
    {
      "id": "F-097",
      "type": "bug",
      "title": "Fix empty-body admin proxy requests and AI settings loading",
      "problem": "The admin proxy sends Content-Type application/json with an empty POST body, causing Fastify to return FST_ERR_CTP_EMPTY_JSON_BODY for SEO generation; settings errors also need end-to-end verification.",
      "goal": "Make empty POST proxy requests valid, verify settings loads through the admin proxy, and keep AI SEO generation returning actionable configuration errors.",
      "scope_in": [
        "admin API proxy",
        "AI SEO generation"
      ],
      "scope_out": [
        "No schema redesign",
        "no provider SDK"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: The admin proxy sends Content-Type application/json with an empty POST body, causing Fastify to return FST_ERR_CTP_EMPTY_JSON_BODY for SEO generation; settings errors also need end-to-end verification.. Goal: Make empty POST proxy requests valid, verify settings loads through the admin proxy, and keep AI SEO generation returning actionable configuration errors.. Scope IN: admin API proxy, AI SEO generation. Scope OUT: No schema redesign, no provider SDK. Type: bug. Priority: high. Risk: low.",
      "acceptance": [
        "Empty POST requests reach backend without invalid JSON body",
        "Settings loads through admin proxy with authenticated session",
        "SEO generation returns clear AI configuration/provider errors",
        "Typecheck, build, and verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T20:33:06Z"
    },
    {
      "id": "F-098",
      "type": "bug",
      "title": "Fix AI settings save foreign-key failure",
      "problem": "Saving admin settings uses a backoffice user id in store_settings.updated_by, but that column references identity_users, causing HTTP 500 for AI configuration saves.",
      "goal": "Save AI settings successfully while preserving updated_by when the authenticated id is a valid identity user.",
      "scope_in": [
        "backend store settings update"
      ],
      "scope_out": [
        "No schema redesign",
        "no secret exposure"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: Saving admin settings uses a backoffice user id in store_settings.updated_by, but that column references identity_users, causing HTTP 500 for AI configuration saves.. Goal: Save AI settings successfully while preserving updated_by when the authenticated id is a valid identity user.. Scope IN: backend store settings update. Scope OUT: No schema redesign, no secret exposure. Type: bug. Priority: high. Risk: low.",
      "acceptance": [
        "AI settings PATCH succeeds for backoffice admins",
        "Settings GET returns saved AI provider/model values",
        "Invalid settings still return clear validation errors",
        "Typecheck, tests, and verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-20T20:39:06Z"
    },
    {
      "id": "F-099",
      "type": "bug",
      "title": "Send password reset emails through configurable SMTP",
      "problem": "Password reset currently uses LoggingPasswordResetMailer and only prints reset links to backend logs, so customers never receive emails.",
      "goal": "Add a real SMTP mailer controlled by SMTP environment variables, keep log fallback explicit for local development, and verify password reset delivery configuration.",
      "scope_in": [
        "backend password reset email provider"
      ],
      "scope_out": [
        "No hardcoded SMTP credentials",
        "no secret exposure"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: Password reset currently uses LoggingPasswordResetMailer and only prints reset links to backend logs, so customers never receive emails.. Goal: Add a real SMTP mailer controlled by SMTP environment variables, keep log fallback explicit for local development, and verify password reset delivery configuration.. Scope IN: backend password reset email provider. Scope OUT: No hardcoded SMTP credentials, no secret exposure. Type: bug. Priority: high. Risk: med.",
      "acceptance": [
        "Configured SMTP sends password reset email with reset link",
        "Missing SMTP configuration is clearly reported in startup/health logs",
        "Password reset token remains single-use and is not logged when SMTP is active",
        "Typecheck, tests, and verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T05:29:07Z"
    },
    {
      "id": "F-100",
      "type": "feature",
      "title": "Generate SKU automatically from product title",
      "problem": "Operators must type SKU manually when creating a product variant.",
      "goal": "Generate a stable unique SKU from the product title, allow editing, and preserve uniqueness.",
      "scope_in": [
        "catalog variants",
        "admin variant creation"
      ],
      "scope_out": [
        "No external SKU service",
        "no uniqueness relaxation"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: Operators must type SKU manually when creating a product variant.. Goal: Generate a stable unique SKU from the product title, allow editing, and preserve uniqueness.. Scope IN: catalog variants, admin variant creation. Scope OUT: No external SKU service, no uniqueness relaxation. Type: feature. Priority: high. Risk: low.",
      "acceptance": [
        "Creating a variant suggests a title-derived SKU",
        "Generated SKU is normalized and remains editable",
        "Duplicate SKU is rejected clearly",
        "Typecheck, tests, verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T11:28:14Z"
    },
    {
      "id": "F-101",
      "type": "fix",
      "title": "Render AI descriptions as safe justified HTML",
      "problem": "AI descriptions often arrive as Markdown and are displayed incorrectly by the WYSIWYG and storefront.",
      "goal": "Convert generated Markdown to sanitized HTML, show it in the WYSIWYG, and justify product descriptions on the customer frontend.",
      "scope_in": [
        "catalog AI generation",
        "admin WYSIWYG",
        "frontend product rendering"
      ],
      "scope_out": [
        "No unsafe raw HTML",
        "no arbitrary script tags"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: AI descriptions often arrive as Markdown and are displayed incorrectly by the WYSIWYG and storefront.. Goal: Convert generated Markdown to sanitized HTML, show it in the WYSIWYG, and justify product descriptions on the customer frontend.. Scope IN: catalog AI generation, admin WYSIWYG, frontend product rendering. Scope OUT: No unsafe raw HTML, no arbitrary script tags. Type: fix. Priority: high. Risk: med.",
      "acceptance": [
        "Generated description is valid HTML",
        "WYSIWYG displays generated content",
        "Frontend product description is justified",
        "Typecheck, tests, verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T08:13:49Z"
    },
    {
      "id": "F-102",
      "type": "feature",
      "title": "Add unit weight and variant pack size",
      "problem": "Products do not expose weight per unit and variants cannot represent packs of multiple units.",
      "goal": "Add product unit weight and variant pack quantity, expose and edit them in admin, and show pack information to customers.",
      "scope_in": [
        "catalog schema",
        "admin product editor",
        "variant management",
        "frontend product display"
      ],
      "scope_out": [
        "No shipping-rate redesign",
        "no inventory migration"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: Products do not expose weight per unit and variants cannot represent packs of multiple units.. Goal: Add product unit weight and variant pack quantity, expose and edit them in admin, and show pack information to customers.. Scope IN: catalog schema, admin product editor, variant management, frontend product display. Scope OUT: No shipping-rate redesign, no inventory migration. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "Product stores optional unit weight",
        "Variant stores pack quantity defaulting to one",
        "Pack values are validated as positive integers",
        "Typecheck, tests, verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-20",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T10:02:15Z"
    },
    {
      "id": "F-103",
      "type": "fix",
      "title": "Fix product editor, orders list, category AI fields and log viewer",
      "problem": "Product editor layout broken, orders missing, categories lack AI content",
      "goal": "Reorder editor, fix orders listing, add category AI fields and emoji/color pickers, cap log viewer",
      "scope_in": [
        "Product editor",
        "orders list",
        "categories",
        "AI settings",
        "log viewer"
      ],
      "scope_out": [
        "No redesign"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: Product editor layout broken, orders missing, categories lack AI content. Goal: Reorder editor, fix orders listing, add category AI fields and emoji/color pickers, cap log viewer. Scope IN: Product editor, orders list, categories, AI settings, log viewer. Scope OUT: No redesign. Type: fix. Priority: high. Risk: med.",
      "acceptance": [
        "Slug auto and disabled",
        "Name and featured checkbox share a row",
        "AI description renders formatted HTML in storefront",
        "Save shows AI generation feedback",
        "Orders page lists existing orders",
        "Categories have description plus AI generation plus emoji and color pickers",
        "Log viewer scrolls inside viewport with max 200 lines",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T05:55:17Z"
    },
    {
      "id": "F-104",
      "type": "fix",
      "title": "CMS templates for main routes",
      "problem": "CMS only covers legal pages",
      "goal": "Add CMS templates for home products categories brands",
      "scope_in": [
        "CMS module",
        "admin CMS",
        "frontend pages"
      ],
      "scope_out": [
        "No redesign"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: CMS only covers legal pages. Goal: Add CMS templates for home products categories brands. Scope IN: CMS module, admin CMS, frontend pages. Scope OUT: No redesign. Type: fix. Priority: med. Risk: low.",
      "acceptance": [
        "CMS includes templates for home products categories brands",
        "Frontend pages render CMS template content",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T06:01:18Z"
    },
    {
      "id": "F-105",
      "type": "feature",
      "title": "Customer account preferences management",
      "problem": "Account lacks preferences section",
      "goal": "User manages data addresses and preferences from front",
      "scope_in": [
        "Account page",
        "users API"
      ],
      "scope_out": [
        "No redesign"
      ],
      "priority": "med",
      "risk": "low",
      "description": "Problem: Account lacks preferences section. Goal: User manages data addresses and preferences from front. Scope IN: Account page, users API. Scope OUT: No redesign. Type: feature. Priority: med. Risk: low.",
      "acceptance": [
        "User edits personal data and addresses",
        "User toggles notification and marketing preferences",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T06:06:31Z"
    },
    {
      "id": "F-106",
      "type": "feature",
      "title": "Admin order editing with status notifications and tracking",
      "problem": "Orders cannot be edited nor notify customers",
      "goal": "Admin edits order items changes state notifies customer adds tracking",
      "scope_in": [
        "Orders module",
        "admin orders",
        "notifications"
      ],
      "scope_out": [
        "No redesign"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: Orders cannot be edited nor notify customers. Goal: Admin edits order items changes state notifies customer adds tracking. Scope IN: Orders module, admin orders, notifications. Scope OUT: No redesign. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "Admin adds removes items and adjusts quantities with totals recalculated",
        "State changes notify the customer by email",
        "Shipped state allows tracking number",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T07:27:55Z"
    },
    {
      "id": "F-107",
      "type": "fix",
      "title": "Reorder product editor form fields",
      "problem": "Product editor form order is confusing and slug is manually editable",
      "goal": "Slug auto-generated and disabled, featured star next to name, description right after name",
      "scope_in": [
        "admin product editor general tab"
      ],
      "scope_out": [
        "no schema change",
        "no redesign"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: Product editor form order is confusing and slug is manually editable. Goal: Slug auto-generated and disabled, featured star next to name, description right after name. Scope IN: admin product editor general tab. Scope OUT: no schema change, no redesign. Type: fix. Priority: high. Risk: low.",
      "acceptance": [
        "Slug auto-generated from name and input disabled",
        "Featured star checkbox sits next to product name",
        "Description follows name in form order",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T08:01:07Z"
    },
    {
      "id": "F-108",
      "type": "fix",
      "title": "Remove variants module: price stock EAN per product in General tab",
      "problem": "Variants UI is useless because each variant is created as a separate product",
      "goal": "Hide variant management, manage price stock EAN per product, show price and stock section in General tab between name and description",
      "scope_in": [
        "admin product editor",
        "inventory",
        "publish tab"
      ],
      "scope_out": [
        "no schema deletion",
        "keep one internal variant per product"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: Variants UI is useless because each variant is created as a separate product. Goal: Hide variant management, manage price stock EAN per product, show price and stock section in General tab between name and description. Scope IN: admin product editor, inventory, publish tab. Scope OUT: no schema deletion, keep one internal variant per product. Type: fix. Priority: high. Risk: med.",
      "acceptance": [
        "No variant creation UI remains in admin",
        "Price stock EAN editable per product in General tab between name and description",
        "Inventory lists products not variants",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T07:57:11Z"
    },
    {
      "id": "F-109",
      "type": "fix",
      "title": "Auto-generate SKU as SKU-MV-product-id and hide it from editor",
      "problem": "SKU is internal only but operators must still see and type it",
      "goal": "Generate SKU automatically as SKU-MV-[product_id], keep it internal, remove SKU input from product editor",
      "scope_in": [
        "catalog",
        "admin product editor",
        "inventory display"
      ],
      "scope_out": [
        "no SKU editability",
        "no external service"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: SKU is internal only but operators must still see and type it. Goal: Generate SKU automatically as SKU-MV-[product_id], keep it internal, remove SKU input from product editor. Scope IN: catalog, admin product editor, inventory display. Scope OUT: no SKU editability, no external service. Type: fix. Priority: high. Risk: low.",
      "acceptance": [
        "New product gets SKU SKU-MV-[product_id] without operator input",
        "Product editor shows no SKU field",
        "Supersedes F-100 title-based SKU idea",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T08:00:04Z"
    },
    {
      "id": "F-110",
      "type": "feature",
      "title": "Inventory: expiration date column, expiry and margin filters",
      "problem": "Inventory shows internal SKU column and lacks expiry and profit-margin insight",
      "goal": "Replace SKU column with expiration date, add expiring-soon filter under 30 days sorted ASC, add margin column and low-margin filter under 30 percent sorted ASC",
      "scope_in": [
        "admin inventory list"
      ],
      "scope_out": [
        "no pricing redesign"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: Inventory shows internal SKU column and lacks expiry and profit-margin insight. Goal: Replace SKU column with expiration date, add expiring-soon filter under 30 days sorted ASC, add margin column and low-margin filter under 30 percent sorted ASC. Scope IN: admin inventory list. Scope OUT: no pricing redesign. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "SKU column removed and expiration date column shown",
        "Filter shows items expiring in under 30 days sorted by date ASC",
        "Margin column shows profit margin percent",
        "Filter shows items with margin under 30 percent sorted ASC",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T08:05:01Z"
    },
    {
      "id": "F-111",
      "type": "feature",
      "title": "Shipping management in order editing with tracking history",
      "problem": "Order editing tab has no shipping management and tracking number leaves no history record",
      "goal": "Manage shipping in order edit tab, register tracking number changes in order history",
      "scope_in": [
        "admin order detail",
        "orders module history"
      ],
      "scope_out": [
        "no carrier integrations"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: Order editing tab has no shipping management and tracking number leaves no history record. Goal: Manage shipping in order edit tab, register tracking number changes in order history. Scope IN: admin order detail, orders module history. Scope OUT: no carrier integrations. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "Shipping section editable in order edit tab",
        "Every tracking number change recorded in order history",
        "History visible in order detail",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T08:10:35Z"
    },
    {
      "id": "F-112",
      "type": "feature",
      "title": "Label AI-generated content with disclaimer on product pages",
      "problem": "User-visible AI generated content is not disclosed",
      "goal": "Show disclaimer note on frontend product pages when content was AI generated or assisted",
      "scope_in": [
        "frontend product detail",
        "AI content flags"
      ],
      "scope_out": [
        "no admin AI flow changes"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: User-visible AI generated content is not disclosed. Goal: Show disclaimer note on frontend product pages when content was AI generated or assisted. Scope IN: frontend product detail, AI content flags. Scope OUT: no admin AI flow changes. Type: feature. Priority: high. Risk: low.",
      "acceptance": [
        "Product page shows note: Parte del contenido de esta ficha puede haber sido generado o asistido mediante inteligencia artificial y revisado antes de su publicacion",
        "Note also warns: composicion y caracteristicas pueden cambiar, consultar etiqueta del fabricante",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T11:31:28Z"
    },
    {
      "id": "F-113",
      "type": "feature",
      "title": "Email customer on processing/shipped with tracking and editable courier",
      "problem": "Customer gets no email when order moves to processing or shipped; shipped email does not say which courier carries the parcel",
      "goal": "Send email on PROCESSING and SHIPPED transitions; shipped email includes tracking number and courier chosen from an admin-editable courier list",
      "scope_in": [
        "order status mailer",
        "admin transition flow",
        "courier list settings",
        "order courier field"
      ],
      "scope_out": [
        "no redesign of other state emails",
        "no external carrier API integration"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: Customer gets no email when order moves to processing or shipped; shipped email does not say which courier carries the parcel. Goal: Send email on PROCESSING and SHIPPED transitions; shipped email includes tracking number and courier chosen from an admin-editable courier list. Scope IN: order status mailer, admin transition flow, courier list settings, order courier field. Scope OUT: no redesign of other state emails, no external carrier API integration. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "PROCESSING transition sends email to customer when SMTP configured",
        "SHIPPED transition email shows tracking number and courier",
        "Courier comes from admin-editable list stored in settings",
        "Selecting courier is required when marking order shipped",
        "Missing or failing SMTP reports notified false without breaking transition",
        "Typecheck, tests, verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T10:27:13Z"
    },
    {
      "id": "F-114",
      "type": "feature",
      "title": "Import legacy OpenCart categories into the catalog without duplicates",
      "problem": "Legacy OpenCart database has 76 categories including product categories and brand names; current catalog only has 5 root categories and is missing many product subcategories and brand entries, blocking migration of the old inventory",
      "goal": "Map and import the legacy oc_category_description entries into the active categories module, normalizing HTML entities and skipping exact-name duplicates; route brand-looking entries into the existing brand module instead of categories",
      "scope_in": [
        "categories module",
        "brands module",
        "mapping helper",
        "legacy category normalization",
        "admin seed script"
      ],
      "scope_out": [
        "no product migration",
        "no design changes",
        "no category attribute redesign"
      ],
      "priority": "high",
      "risk": "med",
      "description": "Problem: Legacy OpenCart database has 76 categories including product categories and brand names; current catalog only has 5 root categories and is missing many product subcategories and brand entries, blocking migration of the old inventory. Goal: Map and import the legacy oc_category_description entries into the active categories module, normalizing HTML entities and skipping exact-name duplicates; route brand-looking entries into the existing brand module instead of categories. Scope IN: categories module, brands module, mapping helper, legacy category normalization, admin seed script. Scope OUT: no product migration, no design changes, no category attribute redesign. Type: feature. Priority: high. Risk: med.",
      "acceptance": [
        "Legacy oc_category_description entries are parsed and normalized (HTML entities and casing)",
        "Existing category names are skipped to avoid duplicates (case-insensitive match)",
        "Brand-looking entries (e.g. SOLGAR, EL GRANERO INTEGRAL, BIOSPIRIT) are created in the brands module, not categories",
        "New product categories are added as subcategories under the existing root (Alimentacion, Cosmetica e Higiene, Hogar y Mascotas, Limpieza Ecologica, Suplementos) or as new roots when they do not fit",
        "Each new category has a unique slug and a sensible parent",
        "Seed script is idempotent and safe to run multiple times",
        "Typecheck, tests, verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T11:01:42Z"
    },
    {
      "id": "F-115",
      "type": "fix",
      "title": "Revert F-100 auto-SKU to SKU-MV-{uuid}",
      "problem": "F-100 introduced title-derived SKUs but the team wants the original UUID-based format SKU-MV-{productId} to keep working with the existing inventory and external links",
      "goal": "Restore SKU-MV-{id} on product creation and lazy migration; remove the generateSku endpoint and admin UI editor; keep the pure SKU helper as a utility",
      "scope_in": [
        "catalog variants",
        "admin variant UI",
        "product creation route",
        "lazy migration"
      ],
      "scope_out": [
        "no full revert of the helper module"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: F-100 introduced title-derived SKUs but the team wants the original UUID-based format SKU-MV-{productId} to keep working with the existing inventory and external links. Goal: Restore SKU-MV-{id} on product creation and lazy migration; remove the generateSku endpoint and admin UI editor; keep the pure SKU helper as a utility. Scope IN: catalog variants, admin variant UI, product creation route, lazy migration. Scope OUT: no full revert of the helper module. Type: fix. Priority: high. Risk: low.",
      "acceptance": [
        "POST /products creates variant with SKU-MV-{uuid}",
        "Lazy migration uses SKU-MV-{uuid}",
        "POST /products/sku:generate endpoint removed",
        "Admin PriceStockSection no longer shows the SKU regenerate button",
        "Old SKUs and external links keep working",
        "Typecheck, tests, verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T12:20:07Z"
    },
    {
      "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 — 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"
      ],
      "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",
        "Existing entries are renamed in place; no duplicates introduced",
        "Re-running the seed is idempotent",
        "Typecheck, tests, verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T12:23:17Z"
    },
    {
      "id": "F-117",
      "type": "fix",
      "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",
        "seed script"
      ],
      "scope_out": [
        "no nuevas renombraciones fuera del alcance"
      ],
      "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 → Frutas y Verduras",
        "SNACKS → Snacks",
        "GRANOLA → Granola",
        "Re-running the seed is idempotent",
        "Typecheck, tests, verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T12:26:52Z"
    },
    {
      "id": "F-118",
      "type": "fix",
      "title": "Add accents to original 5 root categories",
      "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"
      ],
      "priority": "low",
      "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 → Alimentación",
        "Cosmetica e Higiene → Cosmética e Higiene",
        "Hogar y Mascotas stays as-is (no accent needed)",
        "Limpieza Ecologica → Limpieza Ecológica",
        "Proveedores stays as-is",
        "Suplementos stays as-is",
        "Idempotent (DB has unique slug; renaming in place is safe)"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T12:59:41Z"
    },
    {
      "id": "F-119",
      "type": "fix",
      "title": "Fix frontend font case-sensitivity and restart stale services",
      "problem": "The frontend CSS references /fonts/Almagro.woff2 (capital A) but the file is stored as almagro.woff2 (lowercase). On case-sensitive filesystems the .woff2 fails to load. Additionally, the frontend server held stale chunks after a rebuild and returned 404 for new chunks.",
      "goal": "Update the @font-face URL to lowercase, renaming the file if needed; restart frontend after rebuild so chunks match",
      "scope_in": [
        "frontend build",
        "fonts/Almagro"
      ],
      "scope_out": [
        "no font changes"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: The frontend CSS references /fonts/Almagro.woff2 (capital A) but the file is stored as almagro.woff2 (lowercase). On case-sensitive filesystems the .woff2 fails to load. Additionally, the frontend server held stale chunks after a rebuild and returned 404 for new chunks.. Goal: Update the @font-face URL to lowercase, renaming the file if needed; restart frontend after rebuild so chunks match. Scope IN: frontend build, fonts/Almagro. Scope OUT: no font changes. Type: fix. Priority: high. Risk: low.",
      "acceptance": [
        "Almagro.woff2 200",
        "almagro.woff2 200",
        "Font renders correctly on /products",
        "Frontend process serves the latest build",
        "Typecheck, tests, verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T13:04:08Z"
    },
    {
      "id": "F-120",
      "type": "fix",
      "title": "Fix category delete returning 409 when it could delete with products",
      "problem": "DELETE /admin/categories/:id returns 409 when the category has children. The user wants to delete categories that might have products, but if children are an issue they should be told clearly. The current error is also a conflict even when the category has no products.",
      "goal": "Allow DeleteCategory to clear foreign keys / detach products when deleting a category, or surface a clear error when products are attached. Verify the 409 is only returned when truly blocking.",
      "scope_in": [
        "categories service",
        "use cases",
        "repository"
      ],
      "scope_out": [
        "no bulk delete"
      ],
      "priority": "high",
      "risk": "low",
      "description": "Problem: DELETE /admin/categories/:id returns 409 when the category has children. The user wants to delete categories that might have products, but if children are an issue they should be told clearly. The current error is also a conflict even when the category has no products.. Goal: Allow DeleteCategory to clear foreign keys / detach products when deleting a category, or surface a clear error when products are attached. Verify the 409 is only returned when truly blocking.. Scope IN: categories service, use cases, repository. Scope OUT: no bulk delete. Type: fix. Priority: high. Risk: low.",
      "acceptance": [
        "DELETE /admin/categories/:id returns 200 when category has no children/products",
        "DELETE /admin/categories/:id returns 409 with clear code when products are attached",
        "No regression on category listing",
        "Typecheck, tests, verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T13:31:13Z"
    },
    {
      "id": "F-121",
      "type": "fix",
      "title": "Reorder product editor: drop save-first restriction for price/stock/EAN",
      "problem": "The user complains that the price/stock/EAN section shows \"Save first the product to configure price, stock, EAN\" and they cannot edit it without saving the product first. The same happens in the images section. The user wants the editor to work on a new product without that gauntlet.",
      "goal": "Allow price/stock/EAN to be edited on a new product that has not been saved yet. Either auto-create the variant on first interaction, or pre-fetch a placeholder, or seed the variant before the user navigates to the section. The simplest path: create the variant as soon as the product is created via the parent form, and expose the editor.",
      "scope_in": [
        "product editor",
        "PriceStockSection",
        "ImageSection",
        "useEffect"
      ],
      "scope_out": [
        "no flow changes"
      ],
      "priority": "med",
      "risk": "med",
      "description": "Problem: The user complains that the price/stock/EAN section shows \"Save first the product to configure price, stock, EAN\" and they cannot edit it without saving the product first. The same happens in the images section. The user wants the editor to work on a new product without that gauntlet.. Goal: Allow price/stock/EAN to be edited on a new product that has not been saved yet. Either auto-create the variant on first interaction, or pre-fetch a placeholder, or seed the variant before the user navigates to the section. The simplest path: create the variant as soon as the product is created via the parent form, and expose the editor.. Scope IN: product editor, PriceStockSection, ImageSection, useEffect. Scope OUT: no flow changes. Type: fix. Priority: med. Risk: med.",
      "acceptance": [
        "PriceStockSection renders the actual editor for a new product",
        "Editor allows net price typing without first saving",
        "ImageSection renders the actual image editor for a new product",
        "Field saving still works after create",
        "Typecheck, tests, verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T13:35:51Z"
    },
    {
      "id": "F-122",
      "type": "fix",
      "title": "Remove redundant variant warning when product has only one variant",
      "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"
      ],
      "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",
        "Editor still loads the single variant correctly",
        "Typecheck, tests, verify pass"
      ],
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T13:32:24Z"
    },
    {
      "id": "F-123",
      "type": "fix",
      "title": "Remove 'Save product first' warning for single-variant products",
      "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",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T15:36:50Z"
    },
    {
      "id": "F-124",
      "type": "fix",
      "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",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T16:13:27Z"
    },
    {
      "id": "F-125",
      "type": "feature",
      "title": "Allow backward status transitions in /orders/[id] + resend email on SHIPPED↔PROCESSING",
      "description": "En /orders/[id] editar pedido, los status no permiten volver atrás (PROCESSING→PENDING, SHIPPED→PROCESSING, 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",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T15:58:15Z"
    },
    {
      "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ñ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",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T16:16:54Z"
    },
    {
      "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ó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",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T15:56:15Z"
    },
    {
      "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í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",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T16:00:22Z"
    },
    {
      "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á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",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T16:11:18Z"
    },
    {
      "id": "F-130",
      "type": "fix",
      "title": "Inventory list: expiration date editable inline",
      "description": "En /inventory, el operador quiere editar la fecha de caducidad directamente desde la lista (estilo click-to-edit como SKU/EAN/Stock en F-075), sin necesidad de abrir el editor del producto.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T16:08:14Z"
    },
    {
      "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ú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",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T15:50:31Z"
    },
    {
      "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á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",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T15:45:12Z"
    },
    {
      "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 único campo visual.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T16:44:42Z"
    },
    {
      "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í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",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T16:47:00Z"
    },
    {
      "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á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",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T16:48:24Z"
    },
    {
      "id": "F-136",
      "type": "fix",
      "title": "Brand names Title Case + SEO title auto from name",
      "description": "Brands DB has names like A.VOGEL, BIOCOP, BIOSPIRIT (all uppercase). Need: (1) data migration to Title Case (A.Vogel, Biocop, Biospirit). (2) Brand create/edit form auto-fills seo_title from name when blank. (3) Frontend /marca/<slug> uses seo_title with name fallback.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T19:10:29Z"
    },
    {
      "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 — 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",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T19:03:28Z"
    },
    {
      "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 — 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",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T08:18:37Z"
    },
    {
      "id": "F-139",
      "type": "bug",
      "title": "Admin /admin/logs/stream SSE connection drops with ERR_NETWORK_IO_SUSPENDED",
      "description": "Browser console shows: api/admin/logs/stream:1 ERR_NETWORK_IO_SUSPENDED. The Next.js catch-all proxy in apps/admin/src/app/api/[...path]/route.ts streams the SSE response correctly, but the SSE connection appears to drop on the client side. Probable causes: (a) proxy doesn't forward Connection: keep-alive correctly, (b) admin frontend doesn't reconnect on drop, (c) backend SSE endpoint closes on idle. Triage: inspect proxy headers + frontend EventSource usage, add reconnect logic, ensure backend sends keep-alive comments.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:12:53Z"
    },
    {
      "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 — no edits from this page.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:16:27Z"
    },
    {
      "id": "POS-001",
      "type": "chore",
      "title": "POS Discovery & Architecture (TPV integration design)",
      "description": "Phase 1: Analyze existing MercadoDeVida architecture and produce POS_ARCHITECTURE.md + POS_DATA_MODEL.md + POS_API.md + POS_HARDWARE.md + POS_OPERATIONS.md + POS_TASKS.md under docs/pos/. Identify reusable components, gaps, and minimal-changes proposal. Create queued implementation tickets for Phases 2-7 but DO NOT implement code yet.",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T19:24:58Z"
    },
    {
      "id": "POS-002",
      "type": "feature",
      "title": "POS Phase 1 ticket 002 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-002 for full description. Triage and scoping happens at leader intake.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T19:55:43Z"
    },
    {
      "id": "POS-003",
      "type": "feature",
      "title": "POS Phase 1 ticket 003 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-003 for full description. Triage and scoping happens at leader intake.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:19:27Z"
    },
    {
      "id": "POS-004",
      "type": "feature",
      "title": "POS Phase 1 ticket 004 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-004 for full description. Triage and scoping happens at leader intake.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:31:19Z"
    },
    {
      "id": "POS-005",
      "type": "feature",
      "title": "POS Phase 1 ticket 005 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-005 for full description. Triage and scoping happens at leader intake.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:36:25Z"
    },
    {
      "id": "POS-006",
      "type": "feature",
      "title": "POS Phase 1 ticket 006 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-006 for full description. Triage and scoping happens at leader intake.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:39:11Z"
    },
    {
      "id": "POS-007",
      "type": "feature",
      "title": "POS Phase 1 ticket 007 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-007 for full description. Triage and scoping happens at leader intake.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:45:21Z"
    },
    {
      "id": "POS-008",
      "type": "feature",
      "title": "POS Phase 1 ticket 008 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-008 for full description. Triage and scoping happens at leader intake.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:42:08Z"
    },
    {
      "id": "POS-009",
      "type": "feature",
      "title": "POS Phase 1 ticket 009 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-009 for full description. Triage and scoping happens at leader intake.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:43:04Z"
    },
    {
      "id": "POS-010",
      "type": "feature",
      "title": "POS Phase 1 ticket 010 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-010 for full description. Triage and scoping happens at leader intake.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:44:04Z"
    },
    {
      "id": "POS-011",
      "type": "feature",
      "title": "POS Phase 2/3 ticket 011 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-011 for full description.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:46:21Z"
    },
    {
      "id": "POS-012",
      "type": "feature",
      "title": "POS Phase 2/3 ticket 012 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-012 for full description.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:49:30Z"
    },
    {
      "id": "POS-013",
      "type": "feature",
      "title": "POS Phase 2/3 ticket 013 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-013 for full description.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:50:28Z"
    },
    {
      "id": "POS-014",
      "type": "feature",
      "title": "POS Phase 2/3 ticket 014 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-014 for full description.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:51:23Z"
    },
    {
      "id": "POS-015",
      "type": "feature",
      "title": "POS Phase 2/3 ticket 015 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-015 for full description.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:52:19Z"
    },
    {
      "id": "POS-016",
      "type": "feature",
      "title": "POS Phase 2/3 ticket 016 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-016 for full description.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:53:18Z"
    },
    {
      "id": "POS-017",
      "type": "feature",
      "title": "POS Phase 2/3 ticket 017 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-017 for full description.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:55:34Z"
    },
    {
      "id": "POS-018",
      "type": "feature",
      "title": "POS Phase 2/3 ticket 018 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-018 for full description.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:35Z"
    },
    {
      "id": "POS-019",
      "type": "feature",
      "title": "POS Phase 2/3 ticket 019 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-019 for full description.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:58Z"
    },
    {
      "id": "POS-020",
      "type": "feature",
      "title": "POS Phase 2/3 ticket 020 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-020 for full description.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:58Z"
    },
    {
      "id": "POS-021",
      "type": "feature",
      "title": "POS Phase 2/3 ticket 021 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-021 for full description.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:58Z"
    },
    {
      "id": "POS-022",
      "type": "feature",
      "title": "POS Phase 2/3 ticket 022 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-022 for full description.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:58Z"
    },
    {
      "id": "POS-023",
      "type": "feature",
      "title": "POS Phase 4/5 ticket 023 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-023 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:59:34Z"
    },
    {
      "id": "POS-024",
      "type": "feature",
      "title": "POS Phase 4/5 ticket 024 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-024 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:58Z"
    },
    {
      "id": "POS-025",
      "type": "feature",
      "title": "POS Phase 4/5 ticket 025 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-025 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:58Z"
    },
    {
      "id": "POS-026",
      "type": "feature",
      "title": "POS Phase 4/5 ticket 026 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-026 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:58Z"
    },
    {
      "id": "POS-027",
      "type": "feature",
      "title": "POS Phase 4/5 ticket 027 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-027 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:58Z"
    },
    {
      "id": "POS-028",
      "type": "feature",
      "title": "POS Phase 4/5 ticket 028 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-028 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:58Z"
    },
    {
      "id": "POS-029",
      "type": "feature",
      "title": "POS Phase 4/5 ticket 029 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-029 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:59Z"
    },
    {
      "id": "POS-030",
      "type": "feature",
      "title": "POS Phase 4/5 ticket 030 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-030 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:59Z"
    },
    {
      "id": "POS-031",
      "type": "feature",
      "title": "POS Phase 4/5 ticket 031 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-031 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:59Z"
    },
    {
      "id": "POS-032",
      "type": "feature",
      "title": "POS Phase 4/5 ticket 032 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-032 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:59Z"
    },
    {
      "id": "POS-033",
      "type": "feature",
      "title": "POS Phase 4/5 ticket 033 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-033 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:59Z"
    },
    {
      "id": "POS-034",
      "type": "feature",
      "title": "POS Phase 6/7 ticket 034 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-034 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:59Z"
    },
    {
      "id": "POS-035",
      "type": "feature",
      "title": "POS Phase 6/7 ticket 035 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-035 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:59Z"
    },
    {
      "id": "POS-036",
      "type": "feature",
      "title": "POS Phase 6/7 ticket 036 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-036 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:59Z"
    },
    {
      "id": "POS-037",
      "type": "feature",
      "title": "POS Phase 6/7 ticket 037 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-037 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:59Z"
    },
    {
      "id": "POS-038",
      "type": "feature",
      "title": "POS Phase 6/7 ticket 038 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-038 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:59Z"
    },
    {
      "id": "POS-039",
      "type": "feature",
      "title": "POS Phase 6/7 ticket 039 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-039 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:59Z"
    },
    {
      "id": "POS-040",
      "type": "feature",
      "title": "POS Phase 6/7 ticket 040 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-040 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:59Z"
    },
    {
      "id": "POS-041",
      "type": "feature",
      "title": "POS Phase 6/7 ticket 041 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-041 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:59Z"
    },
    {
      "id": "POS-042",
      "type": "feature",
      "title": "POS Phase 6/7 ticket 042 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-042 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:00:59Z"
    },
    {
      "id": "POS-043",
      "type": "feature",
      "title": "POS Phase 6/7 ticket 043 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-043 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:01:00Z"
    },
    {
      "id": "POS-044",
      "type": "feature",
      "title": "POS Phase 6/7 ticket 044 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-044 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:01:00Z"
    },
    {
      "id": "POS-045",
      "type": "feature",
      "title": "POS Phase 6/7 ticket 045 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-045 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:01:00Z"
    },
    {
      "id": "POS-046",
      "type": "feature",
      "title": "POS Phase 6/7 ticket 046 (see POS_TASKS.md)",
      "description": "See docs/pos/POS_TASKS.md POS-046 for full description.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T12:01:00Z"
    },
    {
      "id": "F-141",
      "type": "fix",
      "title": "Next builds: isolate frontend workspace and avoid API prerender dependency",
      "description": "Fix Next.js workspace-root warnings and prevent customer frontend production builds from failing when the backend API is unavailable during static prerendering.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T20:02:01Z"
    },
    {
      "id": "F-142",
      "type": "feature",
      "title": "Reporting: architecture and implementation task plan",
      "description": "Analyze existing ecommerce, POS, orders, payments, customers, products, stock, cash, refunds, discounts and taxes; produce reporting architecture and phased implementation tasks before dashboard coding.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T20:06:54Z"
    },
    {
      "id": "F-143",
      "type": "feature",
      "title": "Reporting: contracts, filters and RBAC",
      "description": "Define shared reporting filters, date comparisons, response availability metadata and backend REPORTING permissions.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T09:43:42Z"
    },
    {
      "id": "F-144",
      "type": "feature",
      "title": "Reporting: historical store and financial snapshots",
      "description": "Add the minimum store, VAT, cost and shipping snapshots required to report multi-store sales without rewriting history.",
      "priority": "high",
      "risk": "high",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T10:40:23Z"
    },
    {
      "id": "F-145",
      "type": "feature",
      "title": "Reporting: payment lines and POS cash-safe capture",
      "description": "Persist immutable payment lines for ecommerce and POS, including method/store/terminal/session, without card data.",
      "priority": "high",
      "risk": "high",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T10:47:16Z"
    },
    {
      "id": "F-146",
      "type": "feature",
      "title": "Reporting: service summary and sales API",
      "description": "Implement backend ReportingService with filtered summary/sales metrics, comparisons, grouping and pagination.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T10:52:13Z"
    },
    {
      "id": "F-147",
      "type": "feature",
      "title": "Admin: reporting shell and global filters",
      "description": "Add Reporting navigation, dashboard shell, reusable filters, URL persistence and explicit data states.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T10:56:09Z"
    },
    {
      "id": "F-148",
      "type": "feature",
      "title": "Admin: sales dashboard and channel views",
      "description": "Present real summary/sales data with KPIs, trends, ecommerce versus POS, stores and terminals.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T10:58:17Z"
    },
    {
      "id": "F-149",
      "type": "feature",
      "title": "Reporting: product category and brand reports",
      "description": "Add server-side product rankings and category/brand reports using catalog data and availability warnings.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:05:43Z"
    },
    {
      "id": "F-150",
      "type": "feature",
      "title": "Reporting: CSV export",
      "description": "Export filtered reporting datasets to CSV with permissions, metadata and server-side pagination/streaming.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T11:08:16Z"
    },
    {
      "id": "F-151",
      "type": "fix",
      "title": "Admin login and catalog products unavailable after restart",
      "description": "Diagnose and fix production services not serving products and admin rejecting valid credentials.",
      "priority": "high",
      "risk": "high",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T20:14:29Z"
    },
    {
      "id": "F-152",
      "type": "fix",
      "title": "Frontend: send emails on account creation and order confirmation",
      "description": "Fix customer-facing email notifications: account creation should send a welcome/verification email and confirmed orders should send the customer confirmation email.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T05:09:03Z"
    },
    {
      "id": "F-153",
      "type": "fix",
      "title": "Orders: customer email missing despite customer having email",
      "description": "Fix order serialization/detail so customer email is associated and displayed when the linked customer/user has an email.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T05:41:08Z"
    },
    {
      "id": "F-154",
      "type": "fix",
      "title": "Admin: separate customers from internal users",
      "description": "Fix admin Users and Customers modules so Customers shows only storefront customers and Users shows only internal/backoffice users, avoiding mixed identity/backoffice concepts.",
      "priority": "high",
      "risk": "high",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T06:35:46Z"
    },
    {
      "id": "F-155",
      "type": "fix",
      "title": "Infrastructure: persist PostgreSQL and Redis data across restarts",
      "description": "Fix local/prod infrastructure so PostgreSQL and Redis data survive service restarts, container recreation and normal monolith operations; document and verify volume persistence.",
      "priority": "high",
      "risk": "high",
      "status": "done",
      "created_at": "2026-08-21",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-21T20:37:03Z"
    },
    {
      "id": "F-156",
      "type": "fix",
      "title": "CMS: force dynamic rendering for content-managed pages",
      "description": "CMS page edits (/about, /contact, /shipping) were served from stale cache; force-dynamic + no-store fetch so published content reflects at runtime without rebuild, keeping static fallback.",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T04:21:35Z"
    },
    {
      "id": "FIX-157",
      "type": "fix",
      "title": "Fix navigation: reporting sub-items should be nested under /reporting",
      "problem": "Dashboard, Ventas, Productos appear as separate top-level nav items with spaces, should be nested under Reporting",
      "goal": "Fix sidebar to show sub-items indented under their parent nav item",
      "scope_in": [
        "permissions.ts",
        "layout.tsx sidebar"
      ],
      "scope_out": [],
      "priority": "high",
      "risk": "low",
      "description": "Update sidebar to use parentHref pattern for reporting sub-items",
      "acceptance": "Reporting sub-items nested under /reporting in sidebar",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T15:26:34Z"
    },
    {
      "id": "FIX-158",
      "type": "fix",
      "title": "Fix migration 049 syntax error: CHECK constraints malformed",
      "problem": "Migration 049 fails with syntax error on CHECK constraints (amounts != 0, currency, status)",
      "goal": "Fix migration 049 to use string CHECK constraints instead of object notation",
      "scope_in": [
        "migrations/049_reporting_payment_lines.js"
      ],
      "scope_out": [],
      "priority": "high",
      "risk": "low",
      "description": "node-pg-migrate does not support object notation for constraints.check - should be string",
      "acceptance": "Migration 049 runs without error",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T15:26:02Z"
    },
    {
      "id": "FIX-159",
      "type": "fix",
      "title": "Fix reporting service: CTE filtered_orders missing shipping_cents column",
      "problem": "reporting/summary and sales endpoints return 500 because CTE filtered_orders does not select shipping_cents but outer query references o.shipping_cents",
      "goal": "Add o.shipping_cents to all filtered_orders CTEs in reporting-service.ts",
      "scope_in": [
        "src/modules/reporting/application/reporting-service.ts"
      ],
      "scope_out": [],
      "priority": "high",
      "risk": "low",
      "description": "3 CTEs (summary/sales/products) - CTE3 was missing shipping_cents. Fixed by adding o.shipping_cents to SELECT list.",
      "acceptance": "reporting/summary returns 200 with valid data",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T15:35:09Z"
    },
    {
      "id": "FIX-160",
      "type": "fix",
      "title": "Fix POS hydration mismatch: suppressHydrationWarning on html tag",
      "problem": "POS app (apps/pos) has hydration mismatch due to LastPass injecting data-lt-installed attribute on html tag",
      "goal": "Add suppressHydrationWarning to html tag in POS layout.tsx",
      "scope_in": [
        "apps/pos/src/app/layout.tsx"
      ],
      "scope_out": [],
      "priority": "high",
      "risk": "low",
      "description": "Browser extension LastPass injects data-lt-installed attribute, causing React hydration mismatch. Same fix as FIX-001 for admin app.",
      "acceptance": "No hydration warnings in browser console",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T15:35:43Z"
    },
    {
      "id": "F-157",
      "type": "fix",
      "title": "Admin Reporting sections live on Reporting page",
      "description": "Keep only Reporting in main sidebar; clicking it shows Dashboard, Sales and Products section list inside Reporting page like Settings.",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T15:42:35Z"
    },
    {
      "id": "F-158",
      "type": "fix",
      "title": "POS use same-origin API proxy on LAN",
      "description": "Browser POS requests must use /api proxy instead of localhost:3000; proxy forwards backend cookies and terminal headers.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T15:50:54Z"
    },
    {
      "id": "F-159",
      "type": "feature",
      "title": "Admin responsive collapsible sidebar",
      "description": "Add accessible persisted SidebarToggle: desktop icon-only collapse and mobile/tablet navigation drawer without duplicated navigation logic.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T16:02:11Z"
    },
    {
      "id": "F-160",
      "type": "fix",
      "title": "Fix Reporting sales grouped queries returning 500",
      "description": "Diagnose and correct SQL/runtime failures for day, channel, store and terminal grouped reporting sales endpoints.",
      "priority": "high",
      "risk": "high",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T16:05:33Z"
    },
    {
      "id": "F-161",
      "type": "fix",
      "title": "Complete admin order detail information",
      "description": "Order detail must show shipping address, payment method, customer details and correct customer email without false missing-email warning.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T16:13:37Z"
    },
    {
      "id": "F-162",
      "type": "fix",
      "title": "Add storefront product link to inventory",
      "description": "Add final Tienda column to inventory, matching product list, linking to storefront product page.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T16:18:38Z"
    },
    {
      "id": "F-163",
      "type": "feature",
      "title": "Make POS terminal and cash session setup usable",
      "description": "Clarify and expose terminal binding and cash session workflow so operator can configure and open TPV without manual database/API steps.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T16:33:24Z"
    },
    {
      "id": "F-164",
      "type": "fix",
      "title": "Update outdated POS Next.js",
      "description": "Upgrade POS Next.js from outdated 15.x to the supported version aligned with Admin and fix resulting config/build compatibility.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T16:25:21Z"
    },
    {
      "id": "F-165",
      "type": "fix",
      "title": "Use brand logo in collapsed sidebar",
      "description": "Replace collapsed sidebar leaf emoji with logo-main.png while preserving icon-only dimensions and accessibility.",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T16:15:58Z"
    },
    {
      "id": "F-166",
      "type": "fix",
      "title": "Remove duplicate VAT rate in storefront price",
      "description": "Product frontend price repeats IVA 10% in inclusive and breakdown labels; show the VAT rate only once without losing gross/net meaning.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T16:21:15Z"
    },
    {
      "id": "F-167",
      "type": "fix",
      "title": "Reflow order editor into three columns",
      "description": "Order edit layout uses three balanced columns: Actions/Products/Payment, Customer/Shipping and billing addresses, Shipping/Summary/History.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T17:17:36Z"
    },
    {
      "id": "F-168",
      "type": "fix",
      "title": "Reduce Reporting 30-day trend chart height",
      "description": "Reporting dashboard 30-day trend occupies too much vertical space; reduce chart height responsively.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T16:38:16Z"
    },
    {
      "id": "F-169",
      "type": "fix",
      "title": "Fix Reporting products Internal Server Error",
      "description": "Diagnose and fix /reporting/products API/UI 500 with runtime regression coverage.",
      "priority": "high",
      "risk": "high",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T16:37:04Z"
    },
    {
      "id": "F-170",
      "type": "fix",
      "title": "Allow deleting categories",
      "description": "Categories admin delete action does not work; restore guarded deletion and clear error feedback.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T17:13:31Z"
    },
    {
      "id": "F-171",
      "type": "fix",
      "title": "Audit and fix all transactional email events",
      "description": "Review every event that generates email, fix SMTP delivery, and verify test delivery to info@rikrdo.es.",
      "priority": "high",
      "risk": "high",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T17:20:34Z"
    },
    {
      "id": "F-172",
      "type": "fix",
      "title": "Fix My Account login session loop",
      "description": "Clicking My Account logs user out and redirects repeatedly to /auth/login; preserve storefront session and destination.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T17:16:08Z"
    },
    {
      "id": "F-173",
      "type": "fix",
      "title": "Fix checkout page load failure",
      "description": "/checkout shows This page couldn't load; diagnose frontend/runtime and restore checkout.",
      "priority": "high",
      "risk": "high",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T17:14:54Z"
    },
    {
      "id": "F-174",
      "type": "fix",
      "title": "Fix missing almagro.ttf font",
      "description": "Resolve storefront 404 for almagro.ttf using correct local font asset/path or remove invalid reference.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T16:39:40Z"
    },
    {
      "id": "F-175",
      "type": "fix",
      "title": "Use logo-main.webp in collapsed sidebar",
      "description": "Collapsed admin sidebar must use logo-main.webp three-leaf favicon mark instead of logo-main.png.",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T16:35:10Z"
    },
    {
      "id": "F-176",
      "type": "fix",
      "title": "Fix POS existing product search",
      "description": "POS returns no results for existing barcode 590123412345",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T16:49:33Z"
    },
    {
      "id": "F-177",
      "type": "feature",
      "title": "Add stale order notifications to admin header",
      "description": "Create admin header notification module for pending and shipped orders unchanged for more than 24 hours",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T16:54:41Z"
    },
    {
      "id": "F-178",
      "type": "fix",
      "title": "Make POS EAN search unique and Enter add item",
      "description": "Exact EAN search must show only one product and Enter must add one unit to the current order",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T17:06:13Z"
    },
    {
      "id": "F-179",
      "type": "fix",
      "title": "Fix POS sale Internal Server Error",
      "description": "POST /api/pos/sales returns HTTP 500 when completing a TPV sale",
      "priority": "high",
      "risk": "high",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T17:05:11Z"
    },
    {
      "id": "F-180",
      "type": "fix",
      "title": "Accept decimal euros in POS discount",
      "description": "POS discount input must accept 3,50 or 3.50 as EUR instead of raw cents",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T17:07:28Z"
    },
    {
      "id": "F-181",
      "type": "feature",
      "title": "Fix POS customer search and add customer creation",
      "description": "POS customer search returns no clients and cashier must be able to create a customer from the register",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T17:10:02Z"
    },
    {
      "id": "F-182",
      "type": "feature",
      "title": "Add touch category navigation and quick products to POS",
      "description": "Build large touch buttons to navigate category to subcategory to product, plus four persistent easy-touch quick product slots at bottom",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T17:25:12Z"
    },
    {
      "id": "F-183",
      "type": "fix",
      "title": "Replace corrupted Almagro font assets and restore storefront HMR",
      "description": "Browser OTS rejects almagro woff2 and ttf files; storefront dev chunks return 403 and HMR WebSocket fails on LAN",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T17:11:58Z"
    },
    {
      "id": "F-184",
      "type": "fix",
      "title": "Restore least-privilege Reporting editor permissions",
      "description": "Full suite finds editor incorrectly granted REPORTING_EXPORT contrary to RBAC tests and least privilege",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T17:26:50Z"
    },
    {
      "id": "F-185",
      "type": "fix",
      "title": "Stabilize PostgreSQL integration suite and payment currency default",
      "description": "Integration files mutate shared test DB in parallel and payment-line EUR default is over-quoted",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T17:30:45Z"
    },
    {
      "id": "F-186",
      "type": "feature",
      "title": "POS configurable checkout, mixed payments and receipts",
      "problem": "Current POS lacks touch-safe discounts, terminal restrictions, split payment confirmation, configurable methods, complete receipts and free items",
      "goal": "Complete the cashier flow from item entry through mixed payment and receipt delivery",
      "scope_in": [
        "8 quick products",
        "touch discount button and admin disable setting",
        "configurable payment methods",
        "total or partial mixed payments with cash change",
        "explicit sale confirmation",
        "receipt print and email with fiscal data and numbering",
        "clear cashier after receipt",
        "free item name and price"
      ],
      "scope_out": [
        "Real external payment processor integrations",
        "fiscal certification",
        "hardware-specific printer drivers"
      ],
      "priority": "high",
      "risk": "high",
      "description": "Problem: Current POS lacks touch-safe discounts, terminal restrictions, split payment confirmation, configurable methods, complete receipts and free items. Goal: Complete the cashier flow from item entry through mixed payment and receipt delivery. Scope IN: 8 quick products, touch discount button and admin disable setting, configurable payment methods, total or partial mixed payments with cash change, explicit sale confirmation, receipt print and email with fiscal data and numbering, clear cashier after receipt, free item name and price. Scope OUT: Real external payment processor integrations, fiscal certification, hardware-specific printer drivers. Type: feature. Priority: high. Risk: high.",
      "acceptance": [
        "Eight quick products can be configured and shown",
        "Line discount uses a touch button and can be disabled per terminal from admin",
        "Cashier supports total or partial payments across enabled configurable methods and shows remaining amount",
        "Cash overpayment computes and records change",
        "Sale closes only after explicit confirmation of fully allocated payments",
        "Receipt contains company data date time configurable ticket number items quantities subtotals totals payment lines change and return policy",
        "Receipt can be printed or emailed and cashier clears only after one delivery action",
        "Free item with name and price can be added without stock mutation",
        "All validation and monetary calculations are server-authoritative",
        "Existing POS sales and reporting remain compatible",
        "verify.sh is green"
      ],
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T20:08:08Z"
    },
    {
      "id": "F-187",
      "type": "feature",
      "title": "Admin can deactivate and delete POS cashiers",
      "description": "Allow safe cashier removal from admin while preserving historical sale and session attribution.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T20:23:44Z"
    },
    {
      "id": "F-188",
      "type": "feature",
      "title": "POS pending payment orders and completion states",
      "description": "Allow cashier orders to remain pending with outstanding balance and ensure fully paid POS orders are COMPLETED.",
      "priority": "high",
      "risk": "high",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T20:44:36Z"
    },
    {
      "id": "F-189",
      "type": "feature",
      "title": "POS negative returns and return receipts",
      "description": "Accept validated negative quantities to cancel purchases fully or partially and issue linked return receipts.",
      "priority": "high",
      "risk": "high",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T21:01:59Z"
    },
    {
      "id": "F-190",
      "type": "fix",
      "title": "Reporting updates from POS sales and returns",
      "description": "Fix reporting capture and refresh so POS sales payments returns pending and completed states update reports.",
      "priority": "high",
      "risk": "high",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T05:48:20Z"
    },
    {
      "id": "F-191",
      "type": "feature",
      "title": "POS terminal and daily cash close reconciliation",
      "description": "Design and implement terminal and daily cash close balancing card cash returns and pending payments.",
      "priority": "high",
      "risk": "high",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T05:51:56Z"
    },
    {
      "id": "F-192",
      "type": "fix",
      "title": "Simplify storefront net price VAT label",
      "description": "Render price copy as amount sin IVA plus rate, removing the separator and duplicated IVA word.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T05:53:17Z"
    },
    {
      "id": "F-193",
      "type": "feature",
      "title": "Show product expiry and shipping weight on storefront",
      "description": "Display expiry and weight in frontend product views and use authoritative product weight in shipping calculations.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T05:58:50Z"
    },
    {
      "id": "F-194",
      "type": "fix",
      "title": "Fix crypto.randomUUID runtime error and move IVA/audit/logs into settings",
      "description": "1) Replace bare crypto.randomUUID() calls in POS PaymentModal and cashier page with the safe generateIdempotencyKey fallback. 2) Move tax-rates, audit and logs navigation and routes into the settings section.",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T20:29:26Z"
    },
    {
      "id": "F-195",
      "type": "fix",
      "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",
      "status": "done",
      "created_at": "2026-08-22",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-22T20:36:05Z"
    },
    {
      "id": "FIX-196",
      "type": "fix",
      "title": "Fix controlled→uncontrolled 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",
      "status": "done",
      "created_at": "2026-08-23",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T06:00:00Z"
    },
    {
      "id": "FIX-197",
      "type": "fix",
      "title": "Fix controlled input null value in admin POS Field component",
      "description": "Field component in admin POS page passes value prop directly to input. When value is null/undefined React warns. Fix: value ?? \"\".",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-23",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T06:05:52Z"
    },
    {
      "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→REFUNDED and COMPLETED→PARTIALLY_REFUNDED transitions.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-23",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T06:15:33Z"
    },
    {
      "id": "POS-FIX-2",
      "type": "fix",
      "title": "POS pending sales panel: no auto-refresh and no visible refresh button",
      "description": "loadPendingSales runs only on mount (useEffect with deps: [loadPendingSales]). pendingSales becomes stale. The panel in the TPV sidebar never updates. Need: (1) refresh button in panel header, (2) polling interval (e.g. every 10s).",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-23",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T06:15:33Z"
    },
    {
      "id": "POS-FIX-3",
      "type": "fix",
      "title": "POS terminal: cierre de caja button missing",
      "description": "POST /pos/sessions/:id/close exists in backend but there is no UI button to trigger it in the TPV terminal. User cannot close the cash session from the POS app.",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-23",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T06:15:33Z"
    },
    {
      "id": "POS-FIX-4",
      "type": "feature",
      "title": "POS TPV: visual notification when product is added to ticket",
      "description": "addToCart() adds a product to the cart state but gives no visual feedback. Need a brief toast/flash animation confirming the product was added.",
      "priority": "med",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-23",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T06:15:33Z"
    },
    {
      "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} — verify response.",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-23",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T06:26:55Z"
    },
    {
      "id": "FEAT-199",
      "type": "feature",
      "title": "Frontend: account registration with email confirmation required",
      "description": "Registration creates account and immediately logs in. Should instead: (1) create unconfirmed user, (2) send confirmation email with token, (3) show \"check your email\" message, (4) login only after clicking confirmation link. Backend needs: confirmation_token + confirmed_at fields in identity_users (migration). POST /auth/register returns 201 with message. New POST /auth/confirm/:token confirms the account. Login requires email_confirmed=true.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-23",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T06:26:55Z"
    },
    {
      "id": "FEAT-200",
      "type": "feature",
      "title": "POS pending sales cross-day: resumable by terminal, not just current session",
      "description": "loadPendingSales currently filters by current session (config.session.id). Sales that are PENDING but from a previous session are not shown. A cashier should be able to resume ANY PENDING sale from the same terminal regardless of session. Fix: GET /pos/sales?state=PENDING&terminalId=... (without sessionId filter). Backend may need to support this. Frontend: pending panel shows all PENDING sales for the terminal.",
      "priority": "high",
      "risk": "med",
      "status": "done",
      "created_at": "2026-08-23",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T06:26:55Z"
    },
    {
      "id": "POS-FIX-5",
      "type": "fix",
      "title": "POS TPV: close session requires PIN + selfpay mode setting",
      "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",
      "created_at": "2026-08-23",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T07:02:05Z"
    },
    {
      "id": "POS-FIX-6",
      "type": "fix",
      "title": "POS pending panel: loadPendingSales called before config is available",
      "description": "loadPendingSales se ejecuta en useEffect con deps [loadPendingSales] pero loadPendingSalesinternamente check config?.terminal antes de config estar disponible. Fix: usar config?.terminal?.id como dep adicional o usar useRef para config.",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-23",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-24T13:40:01Z"
    },
    {
      "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í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",
      "created_at": "2026-08-23",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T07:24:04Z"
    },
    {
      "id": "F-202",
      "type": "feature",
      "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",
      "created_at": "2026-08-23",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T07:24:04Z"
    },
    {
      "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ó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",
      "created_at": "2026-08-23",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-23T21:21:30Z"
    },
    {
      "id": "POS-FIX-6",
      "type": "fix",
      "title": "POS: boton aparcar venta + confirmar vaciar caja",
      "description": "El boton Guardar pendiente debe estar activo desde el primer articulo (no requiere pago). El boton Vaciar caja debe pedir confirmacion antes de borrar el ticket.",
      "priority": "high",
      "risk": "low",
      "status": "pending",
      "created_at": "2026-08-24",
      "gates": {
        "reviewer": false,
        "security": false,
        "qa": false
      }
    },
    {
      "id": "POS-FIX-7",
      "type": "fix",
      "title": "POS: boton aparcar venta + confirmar vaciar caja",
      "description": "El boton Guardar pendiente debe estar activo desde el primer articulo (no requiere pago). El boton Vaciar caja debe pedir confirmacion antes de borrar el ticket.",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-24",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-24T05:23:55Z"
    },
    {
      "id": "POS-FIX-8",
      "type": "fix",
      "title": "POS: botones recuperar y eliminar en ventas pendientes",
      "description": "Las ventas pendientes deben tener dos botones: Recuperar (recarga el ticket para seguir agregando articulos) y Eliminar (con dialogo de confirmacion).",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-24",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-24T05:39:32Z"
    },
    {
      "id": "POS-FIX-9",
      "type": "fix",
      "title": "POS: quitar polling 10s pendientes + dialogo merge al recuperar",
      "description": "Quitar refresh automatico cada 10s del panel de ventas pendientes (solo manual). Al recuperar un ticket con articulos en el cashier, preguntar que hacer con la venta actual.",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-24",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-24T05:44:30Z"
    },
    {
      "id": "POS-FIX-10",
      "type": "fix",
      "title": "POS: DELETE /pos/sales/:id devuelve 405 Method Not Allowed",
      "description": "El endpoint DELETE /pos/sales/:id no responde. Posible causa: servidor no reiniciado o conflicto de rutas en Fastify.",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-24",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-24T06:08:06Z"
    },
    {
      "id": "POS-FIX-11",
      "type": "fix",
      "title": "POS: pedir nombre al guardar pendiente sin cliente",
      "description": "Al guardar un ticket pendiente sin cliente asociado, pedir un nombre para identificarlo. Mostrar ese nombre junto con la fecha en el panel de pendientes.",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-24",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-24T06:13:43Z"
    },
    {
      "id": "POS-FIX-12",
      "type": "fix",
      "title": "POS: proxy route.ts falta DELETE handler",
      "description": "El proxy de Next.js no tiene handler para DELETE, causando 405 en eliminar pendiente.",
      "priority": "high",
      "risk": "low",
      "status": "done",
      "created_at": "2026-08-24",
      "gates": {
        "reviewer": true,
        "security": true,
        "qa": true,
        "close": true
      },
      "completed_at": "2026-08-24T06:42:41Z"
    },
    {
      "id": "F-204",
      "type": "fix",
      "title": "Dashboard: Gráfico tendencia 30 días ajustar altura",
      "description": "El gráfico de tendencia (30 días) es demasiado alto, debe tener una altura acorde al resto de widgets.",
      "priority": "med",
      "risk": "low",
      "status": "pending",
      "created_at": "2026-08-24",
      "gates": {
        "reviewer": false,
        "security": false,
        "qa": false
      },
      "phase": "dashboard"
    }
  ]
}

      },
      "phase": "pos"
    }
  ]
}
