3097 lines
114 KiB
JSON
3097 lines
114 KiB
JSON
{
|
|
"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"
|
|
}
|
|
]
|
|
}
|