fix(inventory-opt): inventory optimization: pagination, queries, filters for 10k+ products

This commit is contained in:
Deploy
2026-08-26 18:07:49 +02:00
parent 49dfd00406
commit c19626bea3
13 changed files with 1114 additions and 361 deletions

View File

@@ -0,0 +1,63 @@
# Arquitectura — INVENTORY-OPT
## Problema
La pantalla admin de inventario no escala a 10k+ productos porque hoy:
- pide `productsApi.list(limit=100)`
- por cada producto hace `getVariants`
- por cada fila hace `getAvailability`
- por cada fila hace `getVariantPrice`
Eso introduce N+1 HTTP + N+1 SQL y además no tiene paginación real del inventario.
## Diseño propuesto
### 1) Nuevo endpoint backend optimizado
Crear `GET /inventory/admin/overview` en el módulo `inventory`.
Query params:
- `q`
- `filter`
- `limit`
- `offset`
Respuesta:
- `items[]` con fila ya enriquecida (producto + variante + stock + precio + caducidad)
- `total`
- `stats` con conteos globales (`inStock`, `lowStock`, `outOfStock`)
### 2) Query única paginada
La query leerá directamente:
- `catalog_products`
- `catalog_product_variants`
- `inventory_stock`
- `pricing_variant_prices`
Con esto evitamos las múltiples rondas actuales desde el admin.
### 3) Filtros server-side
Mover al backend los filtros ya visibles en UI:
- `all`
- `in_stock`
- `low_stock`
- `out_of_stock`
- `expiring`
- `low_margin`
### 4) UI admin
Actualizar `project/apps/admin/src/app/(dashboard)/inventory/page.tsx` para:
- consumir el nuevo endpoint
- usar paginación real
- mantener búsqueda debounced
- mantener filtros existentes
- evitar llamadas por fila
## No entra
- Refactor de módulos no relacionados
- Reescritura de pricing o inventory core
- Cambios en TPV/storefront
## Validación
- typecheck backend/admin
- build backend/admin
- verify.sh
- prueba funcional manual de búsqueda/filtros/paginación

View File

@@ -0,0 +1,79 @@
# Implementer evidence — INVENTORY-OPT
## Cambios realizados
### 1) Nuevo endpoint backend optimizado
Añadí `GET /inventory/admin/overview` en:
- `project/src/modules/inventory/api/inventory.routes.ts`
El endpoint:
- pagina server-side (`limit`, `offset`)
- busca por `nombre`, `SKU` o `EAN`
- aplica filtros server-side:
- `all`
- `in_stock`
- `low_stock`
- `out_of_stock`
- `expiring`
- `low_margin`
- devuelve filas ya enriquecidas con:
- producto
- variante
- stock
- precio
- margen
- caducidad
- devuelve además `stats` agregados para cards del dashboard de inventario
### 2) Eliminación del N+1 en admin inventory
Antes la página hacía:
- `productsApi.list(limit=100)`
- `getVariants()` por producto
- `getAvailability()` por fila
- `getVariantPrice()` por fila
Ahora la página consume una única llamada paginada:
- `inventoryApi.listOverview(...)`
### 3) UI admin paginada
Actualicé:
- `project/apps/admin/src/app/(dashboard)/inventory/page.tsx`
- `project/apps/admin/src/lib/api-client.ts`
- `project/apps/admin/src/types/index.ts`
La página ahora:
- usa búsqueda debounced server-side
- usa filtros server-side
- muestra paginación real
- sigue permitiendo editar inline:
- stock
- EAN
- caducidad
### 4) Test de integración real PostgreSQL
Añadí:
- `project/src/app/tests/inventory-admin-overview.itest.ts`
Valida:
- paginación
- búsqueda por EAN
- filtro `low_margin`
- filtro `expiring`
- stats agregados
## Validación
- `cd project && npm run typecheck`
- `cd project && npm run build`
- `cd project/apps/admin && npm run build`
- `cd project && TEST_DATABASE_URL=postgres://mdv:mdv_dev_only@localhost:5432/mercadodevida_test npx vitest run src/app/tests/inventory-admin-overview.itest.ts --no-file-parallelism`
- `./scripts/verify.sh`
- `git diff --check`
## Decisiones técnicas
- No introduje un módulo nuevo: reutilicé `inventory.routes.ts` porque el problema principal era de lectura agregada para admin.
- El inventario se optimiza a nivel de query y tráfico HTTP, que era el cuello de botella real para 10k+ productos.
- Mantuve edición inline en la UI para no romper el flujo actual del admin.
## Deuda / siguiente paso
- Si el volumen sigue creciendo, el siguiente paso natural sería añadir índices específicos para búsquedas admin por nombre/SKU/EAN o una vista materializada/consulta especializada.
- La build del admin sigue mostrando warnings preexistentes de Turbopack sobre acceso dinámico a ficheros en uploads; no están relacionados con INVENTORY-OPT.

View File

@@ -0,0 +1,21 @@
{
"feature_id": "INVENTORY-OPT",
"agent": "leader",
"stage": "close",
"verdict": "APPROVED",
"leader": "leader",
"summary": "INVENTORY-OPT cerrada: inventario admin con paginación real, filtros server-side y eliminación del N+1.",
"gates_summary": {
"reviewer": "APPROVED",
"security": "APPROVED",
"qa": "APPROVED"
},
"artifacts": [
"architect.md",
"implementer.md",
"reviewer.json",
"security.json",
"qa.json",
"leader-close.json"
]
}

View File

@@ -0,0 +1,30 @@
{
"feature_id": "INVENTORY-OPT",
"agent": "qa",
"stage": "qa_gate",
"verdict": "APPROVED",
"qa_check": "qa",
"summary": "QA aprobado: inventario admin ahora escala con paginación server-side, búsqueda/filtros server-side y sin N+1 por fila.",
"test_results": {
"automated": [
"./scripts/verify.sh ✅",
"cd project && npm run typecheck ✅",
"cd project && npm run build ✅",
"cd project/apps/admin && npm run build ✅",
"cd project && TEST_DATABASE_URL=postgres://mdv:mdv_dev_only@localhost:5432/mercadodevida_test npx vitest run src/app/tests/inventory-admin-overview.itest.ts --no-file-parallelism ✅"
],
"coverage": [
"búsqueda admin por nombre/SKU/EAN",
"filtro low_margin",
"filtro expiring",
"paginación y stats agregados"
],
"manual_smoke_recommended": [
"Comprobar en /inventory que cambiar stock inline refresca correctamente la fila al editar dentro de páginas distintas.",
"Comprobar UX con búsquedas y filtros sobre un dataset grande real."
]
},
"notes": [
"El warning de Turbopack sobre uploads en admin build es preexistente y no bloquea esta feature."
]
}

View File

@@ -0,0 +1,29 @@
{
"feature_id": "INVENTORY-OPT",
"agent": "reviewer",
"stage": "review_gate",
"verdict": "APPROVED",
"checks": [
{
"item": "Admin inventory no longer performs N+1 product/variant/stock/price fetches and now reads a single paginated backend overview endpoint",
"ok": true
},
{
"item": "Server-side query supports search by product name, SKU and EAN plus the existing stock/expiry/low-margin filters",
"ok": true
},
{
"item": "Admin UI preserves inline stock/EAN/expiry editing while moving pagination and filtering to the server",
"ok": true
},
{
"item": "Changed files validated by backend typecheck/build, admin build, verify.sh and a real PostgreSQL integration test for the overview endpoint",
"ok": true
}
],
"issues": [],
"notes": [
"The endpoint is variant-based, which matches inventory reality better than the old first-variant-per-product approximation.",
"Admin build still emits unrelated existing Turbopack warnings about dynamic uploads filesystem access; not introduced by this feature."
]
}

View File

@@ -0,0 +1,19 @@
{
"feature_id": "INVENTORY-OPT",
"agent": "security",
"stage": "security_gate",
"verdict": "APPROVED",
"security_check": "security",
"summary": "Aprobado: la optimización de inventario no introduce nuevas superficies sensibles y mantiene autenticación/validación correctas.",
"checks": {
"auth": "OK: el nuevo endpoint /inventory/admin/overview sigue protegido con authenticate + requireRole(admin).",
"injection": "OK: búsqueda y paginación siguen parametrizadas; el ORDER BY y filtros son ramas estáticas controladas por enum de zod, sin interpolación arbitraria del usuario.",
"data_exposure": "OK: solo devuelve información de inventario/precio ya disponible para el admin; no añade datos personales ni secretos.",
"xss": "OK: la UI sigue renderizando texto plano y no introduce HTML crudo ni APIs inseguras del navegador.",
"dependencies": "OK: no se añadieron dependencias nuevas."
},
"notes": [
"La búsqueda administrativa por nombre/SKU/EAN es de bajo riesgo porque usa parámetros SQL y la ruta está restringida a admin.",
"No se detectan regresiones de permisos ni de almacenamiento de credenciales."
]
}