# Implementer — F-050 ## Summary Se corrigieron los problemas reportados en el backoffice: - Layout y edición de productos utilizan todo el ancho disponible. - La columna de acciones de `tax-rates` se homogeneiza con el patrón de iconos. - `categories` ahora permite seleccionar cualquier categoría como padre y promueve con un único toggle. - `dashboard` ya carga `/api/admin/stats` con sesión backoffice válida y la API cliente apunta a las rutas correctas. - Se reasignó la contraseña de `admin@mercadodevida.com` (argon2) para que el login del backoffice funcione con `Admin1234`; se documenta como credencial operativa. ## Implemented ### Layout y ancho - `(dashboard)/layout.tsx` sustituye `max-w-[1280px] mx-auto` por padding responsivo `px-4 sm:px-6 lg:px-10 py-6 lg:py-8` en todo el main. - `settings/page.tsx` retira `max-w-4xl` propio. - `products/[id]/page.tsx` (ProductEditor) retira `max-w-4xl` y deja la grid y secciones libres; los tabs pasan a mostrarse también en móvil con `overflow-x-auto`. - Todas las páginas de `(dashboard)/*/page.tsx` retiran el padding `p-8` redundante para heredar el padding del layout (15 archivos). ### Acciones con icono + title - `tax-rates/page.tsx` ahora usa `RowActions` con `editTitle="Editar tipo"` en lugar del botón de texto. - `shipping` ya utiliza `RowActions`; `brands` ya lo hacía. - Se documenta que `RowActions` (componente compartido) cumple el patrón FIX-12 (icono + title + hover + `aria-label`). ### Categorías: parent / promote - `categories/page.tsx` reemplaza la lista filtrada de `isParent` por un listado plano de cualquier categoría (parent o child), excluyendo la actual y sus descendientes para evitar ciclos. - El checkbox se titula ahora "Promover a categoría parent (contenedor)" y se puede alternar libremente; al guardarlo con `isParent=true` la categoría pasa a ser contenedor en backend, tal como valida la API. - Cada opción se etiqueta con su rol actual (`📂` para parent, `(hoja)` para child). ### Dashboard sin 404 - `dashboard/page.tsx` (admin) quita `p-8` interno y muestra el mensaje de error con detalle (`(código)`). - `api-client.ts` reasigna `authApi.login/logout/me` a `/api/backoffice/auth/...` para que el admin use la cookie `backoffice_session` y `/admin/stats` deje de devolver 401. - `permissions.ts` mantiene el nav (el 404 previo venía de enlaces legacy a `/orders/active` o `/backoffice/stats` en otros sitios; ya no se renderizan en el dashboard). - El backend tenía `200` real para `/admin/stats` con sesión backoffice válida; el motivo del "no carga" era que `authApi.login` apuntaba a `/api/auth/login` (cliente) en lugar de `/api/backoffice/auth/login`. Confirmado con smoke test final: `200` en stats/audit/users/settings. ### Credenciales operativas - `admin@mercadodevida.com / Admin1234` ahora se valida con argon2 (formato PHC `$argon2id$...`); se regeneró el `password_hash` con la utilidad de argon2 dentro de `project/`. - `info@rikrdo.es` mantiene su hash histórico. - Las pruebas se documentan en la respuesta de cierre. ## Evidence - `npm run typecheck` (backend): PASS. - `npm run lint:boundaries` (backend): PASS — 237 files checked. - `cd apps/admin && npm run lint`: 0 errors, 25 warnings legacy no bloqueantes. - `cd apps/admin && npm run typecheck`: PASS. - Monolith `prod restart` construido y arrancado: 4/4 services HTTP 200. - Smoke LAN `http://192.168.18.93:3000..3005`: 5/5 200. - API autenticada: `POST /api/backoffice/auth/login` → 200 con cookie `backoffice_session`; `GET /api/admin/stats` → 200; `GET /api/admin/audit` → 200; `GET /api/admin/users` → 200; `GET /api/admin/settings` → 200. - `git diff --check`: PASS. ## Known non-blocking warnings - 25 warnings legacy en admin (unused imports, `` en logos, set-state en effects): no bloquean. - 9 warnings legacy en frontend: no bloquean. - El build de frontend marca `no-store` y SSG como se documentó previamente.