# 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