Files
mercadodevida/project/specs-admin/TASKS.md
2026-08-17 22:23:10 +02:00

668 lines
19 KiB
Markdown

# Admin Panel Tasks — MercadoDeVida
## Convenciones
- Cada tarea = un archivo `ADM-XXX.md` en `specs/admin/`
- Cada tarea tiene: Goal, API contracts, Desktop UX, Responsive UX, Security, Error States, Acceptance Criteria, Tests
- Dependencias: otras tareas o BD-XXX
---
## BACKEND DEPENDENCIES (precondiciones)
| ID | Descripción | Prioridad |
|-------|------------------------------------------|-----------|
| BD-01 | GET /auth/me → { user, role } | CRÍTICA |
| BD-02 | GET /promotions (listado con paginación) | ALTA |
| BD-03 | PATCH /promotions/:id | ALTA |
| BD-04 | DELETE /promotions/:id | MEDIA |
| BD-05 | GET /reviews/admin (todas, con filtros) | ALTA |
| BD-06 | PATCH /products/:id/state | MEDIA |
| BD-07 | DELETE /products/:id | MEDIA |
| BD-08 | DELETE /brands/:id | MEDIA |
| BD-09 | POST /inventory/bulk-adjust | BAJA |
| BD-10 | GET /admin/audit | MEDIA |
| BD-11 | GET /admin/stats (KPIs del dashboard) | MEDIA |
---
## PHASE 1 — Foundation
### ADM-001 — Crear proyecto Next.js en apps/admin
**Goal:** Nuevo workspace Next.js 16 + TypeScript + Tailwind bajo `apps/admin/`
**Dependencies:** Ninguna
**Modules:** `apps/admin/` (nuevo)
**API:** N/A (no hay código aún)
**Permissions:** N/A
**Desktop UX:** N/A
**Responsive UX:** N/A
**Security:** N/A
**Error States:** N/A
**Acceptance Criteria:**
- `apps/admin/` existe con `package.json`, `tsconfig.json`, `next.config.ts`, `tailwind.config.ts`
- `npm run build` compila sin errores en `apps/admin/`
- Rutas dinámicas ignoradas en `next.config.ts`
**Tests:** Unit: verificar que el proyecto compila
---
### ADM-002 — API Client + Types compartidos
**Goal:** Cliente HTTP tipado que consume el backend, con adapters por feature
**Dependencies:** BD-01 (GET /auth/me)
**Modules:** `apps/admin/lib/api-client.ts`, `apps/admin/lib/permissions.ts`, `apps/admin/types/`
**API:** Todos los endpoints del backend (sección 2.1 de SPEC.md)
**Permissions:** `can(user, permission)` function
**Desktop UX:** N/A
**Responsive UX:** N/A
**Security:**
- Token de sesión en cookie `HttpOnly`
- CSRF protection vía SameSite=Lax
- No exponer token en JS
**Error States:**
- 401 → redirect a /login
- 403 → toast "Sin permisos"
- 404 → página de no encontrado
- 500 → toast "Error del servidor"
**Acceptance Criteria:**
- `ApiClient` hace fetch al backend con cookie de sesión
- Mapeo de errores HTTP → mensajes de usuario
- Tipos para Products, Orders, Customers, etc.
- Feature API adapters (ordersApi, productsApi, etc.)
**Tests:**
- Unit: ApiClient.get/post/patch/delete
- Unit: can() permission checks
- Unit: error mapping
---
### ADM-003 — Login + Auth flow
**Goal:** Página de login funcional, logout, sesión persistente
**Dependencies:** BD-01, ADM-002
**Modules:** `apps/admin/app/(auth)/login/`, `apps/admin/app/api/auth/[...nextauth]/`
**API:** POST /auth/login, POST /auth/logout, GET /auth/me
**Permissions:** N/A
**Desktop UX:**
```
┌────────────────────────────────────────┐
│ │
│ [MercadoDeVida Logo] │
│ │
│ Iniciar sesión │
│ │
│ Email [_______________] │
│ Password [_______________] │
│ │
│ [ Iniciar sesión ] │
│ │
└────────────────────────────────────────┘
```
**Responsive UX:** Mismo layout, centrado en móvil
**Security:**
- HttpOnly cookie para sesión
- Rate limit del backend (429)
- No mostrar errores específicos de credenciales
**Error States:**
- Credenciales inválidas → "Email o contraseña incorrectos"
- Rate limited → "Demasiados intentos. Espera X segundos"
- Servidor caído → "Error de conexión. Intenta de nuevo"
**Acceptance Criteria:**
- Login exitoso → redirect a /admin
- Sesión válida al recargar → mantiene /admin
- Sesión inválida → redirect a /admin/login
- Logout → limpia cookie, redirect a /admin/login
**Tests:**
- E2E: login exitoso
- E2E: login fallido
- E2E: logout
- E2E: sesión expirada
---
## PHASE 2 — Admin Shell
### ADM-004 — Admin Shell (sidebar + layout)
**Goal:** Layout reutilizable con navegación, usuario, y protección de rutas
**Dependencies:** ADM-002, ADM-003
**Modules:** `apps/admin/app/(dashboard)/layout.tsx`, `apps/admin/components/admin/`
**API:** N/A
**Permissions:** `can()` en navegación
**Desktop UX:**
```
┌──────────────┬─────────────────────────────────┐
│ 🏠 Dashboard │ Header: [MercadoDeVida Admin] │
│ 📦 Productos │ User: admin@mdv.es | Logout │
│ 🧾 Pedidos ├─────────────────────────────────┤
│ 📊 Inventario│ │
│ 👥 Clientes │ CONTENT │
│ 🏷️ Categorías│ │
│ 🏷️ Marcas │ │
│ 🏷️ Promociones│ │
│ ⭐ Reseñas │ │
│ 📄 CMS │ │
│ ⚙️ Ajustes │ │
└──────────────┴─────────────────────────────────┘
```
**Responsive UX:**
- Mobile: sidebar colapsado en hamburger menu
- Tablet: sidebar como drawer
- Desktop: sidebar fija 240px
**Security:**
- Middleware que verifica sesión en cada request
- Nav items ocultos según permisos
- Rutas /admin/* requieren auth (redirect a /admin/login)
**Error States:**
- Sin sesión → redirect login
- Error de permisos → toast
**Acceptance Criteria:**
- Sidebar muestra items según permisos del usuario
- Header muestra email + logout
- Mobile toggle funciona
- Navegación activa con estilo distintivo
- Loading skeleton en transición de auth
**Tests:**
- Component: Sidebar renders correct items for role
- E2E: unauthorized redirected to login
- E2E: sidebar navigation works
---
## PHASE 3 — MVP: Products
### ADM-005 — Product List Route
**Goal:** Ruta `/admin/products` con tabla paginada, búsqueda y filtros
**Dependencies:** ADM-004
**Modules:** `apps/admin/app/(dashboard)/products/page.tsx`
**API:** GET /products/search?q=&limit=20&offset=0
**Permissions:** products.read
**Desktop UX:**
```
Header + Sidebar
┌─────────────────────────────────────────────────────────┐
│ Productos [+ Crear producto] │
├─────────────────────────────────────────────────────────┤
│ 🔍 Buscar... [Filtros ▼] │
├────┬────────────────────┬────────┬────────┬────────────┤
│ │ Producto │ Marca │ Precio │ Estado │
├────┼────────────────────┼────────┼────────┼────────────┤
│ □ │ Almendras Crudas │ EcoVida│ €8.95 │ ● Activo │
│ □ │ Vitamina D3+K2 │ NatPhar│ €12.50 │ ● Activo │
├────┴────────────────────┴────────┴────────┴────────────┤
│ ← Anterior Página 1 de 3 Siguiente → │
└─────────────────────────────────────────────────────────┘
```
**Responsive UX:** Tabla scroll horizontal en móvil; cards en mobile
**Security:**products.read
**Error States:**
- Loading: skeleton
- Empty: "No hay productos"
- Error: retry button
**Acceptance Criteria:**
- Productos listados con nombre, marca, precio
- Búsqueda por nombre
- Paginación funcional
- Filtro por estado (activo/inactivo)
- Link a detalle
**Tests:**
- Unit: pagination params
- Component: ProductTable renders
- E2E: search filters products
---
### ADM-006 — Product Editor Shell
**Goal:** Shell del editor con tabs y detección de cambios sin guardar
**Dependencies:** ADM-005
**Modules:** `apps/admin/features/products/components/ProductEditor.tsx`
**API:** N/A (state local)
**Permissions:** products.write
**Desktop UX:** Tabs: General | Precios | Inventario | Imágenes | SEO | Publicar
**Responsive UX:** Tabs colapsados en accordion en móvil
**Security:**products.write
**Acceptance Criteria:**
- Cada tab es una sección separada
- Unsaved changes detected al navegar
- Confirm dialog antes de salir
**Tests:** Component: unsaved changes detected
---
### ADM-007 — Product General Fields
**Goal:** Campos generales: nombre, slug, descripción, marca, categorías
**Dependencies:** ADM-006
**Modules:** `apps/admin/features/products/components/sections/GeneralSection.tsx`
**API:** PATCH /products/:id (nombre, descripción, brandId, categoryIds)
**Permissions:** products.write
**Acceptance Criteria:**
- Campos pre-poblados con datos actuales
- Slug auto-generado desde nombre
- Selector de marca
- Selector de categorías (multiselect)
- Validación de requerido
**Tests:** Unit: slug auto-generation
---
### ADM-008 — Product Pricing Fields
**Goal:** Campos de precio: precio neto, tasa IVA, variantes
**Dependencies:** ADM-006
**Modules:** `apps/admin/features/products/components/sections/PricingSection.tsx`
**API:** GET /products/:id/variants, PATCH /products/:id/variants/:variantId
**Permissions:** products.write
**Acceptance Criteria:**
- Lista de variantes con SKU, EAN, atributos
- Precio por variante
- IVA (general 21%, reducido 10%)
**Tests:** Unit: gross price calculation (only display, backend owns truth)
---
### ADM-009 — Product Inventory Section
**Goal:** Ver y ajustar stock por variante
**Dependencies:** ADM-006
**Modules:** `apps/admin/features/products/components/sections/InventorySection.tsx`
**API:** GET /inventory/:variantId/availability, PUT /inventory/:variantId/stock
**Permissions:** inventory.write
**Acceptance Criteria:**
- Stock actual visible por variante
- Input para nuevo stock
- Botón "Actualizar"
- Confirmación de cambio
**Tests:** Integration: stock update flow
---
### ADM-010 — Product Images Section
**Goal:** Gestionar imágenes: subir, reordenar, eliminar
**Dependencies:** ADM-006
**Modules:** `apps/admin/features/products/components/sections/ImagesSection.tsx`
**API:** GET /products/:id/images, POST /products/:id/images, DELETE /products/:id/images/:imageId, PATCH /products/:id/images/reorder
**Permissions:** products.write
**Acceptance Criteria:**
- Grid de imágenes actuales
- Upload de nueva imagen
- Drag to reorder
- Marcar como principal
- Eliminar con confirmación
**Tests:** E2E: image upload + reorder
---
### ADM-011 — Product SEO Section
**Goal:** Campos SEO: title, description, index/noindex
**Dependencies:** ADM-006
**Modules:** `apps/admin/features/products/components/sections/SEOSection.tsx`
**API:** PATCH /products/:id (seoTitle, seoDescription)
**Permissions:** products.write
**Acceptance Criteria:**
- SEO title con contador (max 60 chars)
- Meta description con contador (max 160 chars)
- Preview de snippet Google
- Toggle index/noindex
**Tests:** Component: character counters
---
### ADM-012 — Product Create Flow
**Goal:** Ruta `/admin/products/new` para crear producto nuevo
**Dependencies:** ADM-006
**Modules:** `apps/admin/app/(dashboard)/products/new/page.tsx`
**API:** POST /products
**Permissions:** products.write
**Acceptance Criteria:**
- Formulario completo con todos los campos
- POST al crear
- Redirect a detalle del nuevo producto
**Tests:** E2E: create product end-to-end
---
## PHASE 3 — MVP: Orders
### ADM-013 — Order List Route
**Goal:** Ruta `/admin/orders` con listado paginado y filtros
**Dependencies:** ADM-004
**Modules:** `apps/admin/app/(dashboard)/orders/page.tsx`
**API:** GET /orders (el nuevo endpoint creado en F-047)
**Permissions:** orders.read
**Desktop UX:**
```
┌─────────────────────────────────────────────────────────┐
│ Pedidos Filtros: [Estado ▼] │
├──────┬─────────────┬──────────┬────────┬────────────────┤
│ #ID │ Cliente │ Total │ Estado │ Fecha │
├──────┼─────────────┼──────────┼────────┼────────────────┤
│ abc… │ maria@… │ €45.83 │ ● Paid │ 15 ago 2026 │
│ abc… │ juan@… │ €23.10 │ ● Pend │ 14 ago 2026 │
├──────┴─────────────┴──────────┴────────┴────────────────┤
│ ← Anterior Página 1 Siguiente → │
└─────────────────────────────────────────────────────────┘
```
**Responsive UX:** Cards en móvil
**Acceptance Criteria:**
- Filtros: estado (PENDING, PAID, PROCESSING, SHIPPED, DELIVERED, CANCELLED)
- Búsqueda por ID o email de cliente
- Paginación
- Link a detalle
**Tests:** E2E: filter orders by status
---
### ADM-014 — Order Detail Page
**Goal:** Ruta `/admin/orders/[id]` con detalle completo y acciones
**Dependencies:** ADM-013
**Modules:** `apps/admin/app/(dashboard)/orders/[id]/page.tsx`
**API:** GET /orders/:id/admin, GET /payments/orders/:id/transactions
**Permissions:** orders.read
**Acceptance Criteria:**
- Header: ID, estado badge, fecha
- Resumen: subtotal, descuentos, IVA, total
- Línea de tiempo de estados
- Lista de items con nombres, cantidades, precios
- Dirección de envío
- Transacciones de pago
- Botones de acción según estado
**Tests:** E2E: view order detail
---
### ADM-015 — Order State Transitions
**Goal:** Botones de acción para cambiar estado de pedido
**Dependencies:** ADM-014
**Modules:** `apps/admin/app/(dashboard)/orders/[id]/page.tsx` (actions)
**API:** POST /orders/:id/transitions/admin
**Permissions:** orders.write
**Acceptance Criteria:**
- Solo acciones válidas para el estado actual se muestran
- Confirmación antes de ejecutar
- Optimistic: esperar respuesta del backend
- Refresh del detalle post-transición
- Timeline actualizada
**State machine visible en UI:**
```
PENDING → [Marcar Pagado] | [Cancelar]
PAID → [Procesar] | [Cancelar] | [Reembolsar]
PROCESSING → [Enviar] | [Cancelar] | [Reembolsar]
SHIPPED → [Marcar Entregado] | [Reembolso parcial]
```
**Tests:** E2E: transition order state through valid path
---
## PHASE 3 — MVP: Inventory
### ADM-020 — Inventory Overview
**Goal:** Vista de inventario por variante con stock y estado
**Dependencies:** ADM-004
**Modules:** `apps/admin/app/(dashboard)/inventory/page.tsx`
**API:** GET /products/search (todas), GET /products/:id/variants, GET /inventory/:variantId/availability
**Permissions:** inventory.read
**Acceptance Criteria:**
- Tabla: SKU, producto, variante, stock disponible, estado
- Filtros: sin stock, stock bajo, en stock
- Búsqueda por SKU o nombre
**Tests:** E2E: filter by stock status
---
### ADM-021 — Quick Stock Adjustment
**Goal:** Ajuste rápido de stock desde la vista de inventario
**Dependencies:** ADM-020
**Modules:** Inline en inventory page
**API:** PUT /inventory/:variantId/stock
**Permissions:** inventory.write
**Acceptance Criteria:**
- Input inline para nuevo stock
- Validación: número >= 0
- Confirmación antes de guardar
- Refresco del valor tras guardado
**Tests:** Integration: stock adjustment flow
---
## PHASE 4 — Secondary Features
### ADM-023 — Customer List
**Goal:** `/admin/customers` — listado de clientes
**Dependencies:** ADM-004
**API:** GET /users
**Permissions:** customers.read
### ADM-024 — Customer Detail
**Goal:** `/admin/customers/[id]` — detalle con pedidos
**Dependencies:** ADM-023
**API:** GET /users/:id, GET /orders (filtrado por user)
**Permissions:** customers.read
### ADM-025 — Customer Edit
**Goal:** Editar datos de cliente
**Dependencies:** ADM-024
**API:** PATCH /users/:id
**Permissions:** customers.write
### ADM-026 — Categories CRUD
**Goal:** `/admin/categories` — gestión de categorías
**Dependencies:** ADM-004
**API:** GET /categories/tree, POST /categories, PATCH /categories/:id, DELETE /categories/:id
**Permissions:** categories.write
### ADM-027 — Brands CRUD
**Goal:** `/admin/brands` — gestión de marcas
**Dependencies:** ADM-004
**API:** GET /brands, POST /brands, PATCH /brands/:id
**Permissions:** brands.write
### ADM-031 — Dashboard Widgets
**Goal:** `/admin` — stats operativos
**Dependencies:** ADM-004, BD-11
**API:** GET /admin/stats (o agregación de endpoints existentes)
**Permissions:** orders.read, inventory.read
---
## PHASE 5 — Extensions
### ADM-032 — Promotions CRUD (requiere BD-02, BD-03)
**Goal:** `/admin/promotions` — gestión de promociones
**API:** GET /promotions, POST /promotions, PATCH /promotions/:id, DELETE /promotions/:id
**Permissions:** promotions.write
### ADM-035 — Reviews Moderation (requiere BD-05)
**Goal:** `/admin/reviews` — moderación de reseñas
**API:** GET /reviews/admin, PATCH /reviews/:id/moderate
**Permissions:** reviews.moderate
### ADM-038 — CMS Pages
**Goal:** `/admin/cms` — gestión de páginas de contenido
**API:** GET /cms/pages/:slug, POST /cms/pages, PATCH /cms/pages/:id, POST /cms/pages/:id/publish, POST /cms/pages/:id/unpublish
**Permissions:** cms.write
---
## Definición de Done por tarea
Cada tarea ADM-XXX se marca DONE cuando:
- [ ] SPEC.md de la tarea aprobado
- [ ] Código implementado
- [ ] Tests unitarios pasando
- [ ] Tests E2E pasando (si aplica)
- [ ] Build pasa sin errores TS
- [ ] Verificado con curl/curl manual