# 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, `<img>` 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.
