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."
]
}

View File

@@ -1,97 +1,13 @@
{
"feature_id": "CLUB-001",
"stage": "build",
"agent": "implementer",
"action": "Club fase 1 backend implementado y validado",
"state": "done",
"next_agent": "reviewer",
"waiting_for": "review_gate",
"updated_at": "2026-08-26T15:52:21Z",
"feature_id": "INVENTORY-OPT",
"stage": "close",
"agent": "leader",
"action": "Cerrar INVENTORY-OPT y commit/push automático",
"state": "running",
"next_agent": "leader",
"waiting_for": "close",
"updated_at": "2026-08-26T16:07:40Z",
"timeline": [
{
"ts": "2026-08-25T20:50:29Z",
"agent": "implementer",
"stage": "build",
"state": "running",
"message": "Fix storefront card bg, stock cap, return human readable, PATCH 502"
},
{
"ts": "2026-08-25T20:54:10Z",
"agent": "implementer",
"stage": "build",
"state": "running",
"message": "Fix storefront card bg, stock cap, return human readable, PATCH 502, selfpay button rename"
},
{
"ts": "2026-08-25T21:03:29Z",
"agent": "implementer",
"stage": "build",
"state": "running",
"message": "Fix receipt-settings 400 and quick products save"
},
{
"ts": "2026-08-26T05:34:46Z",
"agent": "implementer",
"stage": "build",
"state": "running",
"message": "Implementando buscador + lista de hasta 8 productos rápidos"
},
{
"ts": "2026-08-26T05:43:09Z",
"agent": "implementer",
"stage": "build",
"state": "done",
"message": "Buscador + lista de 8 + guardado validado vía API"
},
{
"ts": "2026-08-26T08:48:04Z",
"agent": "implementer",
"stage": "build",
"state": "running",
"message": "Fix ticket IVA=0 y ajustar mensaje de stock insuficiente en frontend"
},
{
"ts": "2026-08-26T09:06:24Z",
"agent": "implementer",
"stage": "build",
"state": "done",
"message": "Fix ticket IVA=0 y sincronización checkout/cart duplicada"
},
{
"ts": "2026-08-26T10:40:21Z",
"agent": "reviewer",
"stage": "review_gate",
"state": "running",
"message": "Revisión técnica de IVA POS y sync checkout"
},
{
"ts": "2026-08-26T10:40:45Z",
"agent": "reviewer",
"stage": "review_gate",
"state": "done",
"message": "Revisión técnica aprobada para IVA POS y sync checkout"
},
{
"ts": "2026-08-26T10:40:48Z",
"agent": "security",
"stage": "security_gate",
"state": "running",
"message": "Revisión de seguridad de POS pricing y checkout sync"
},
{
"ts": "2026-08-26T10:41:15Z",
"agent": "security",
"stage": "security_gate",
"state": "done",
"message": "Revisión de seguridad aprobada"
},
{
"ts": "2026-08-26T10:41:25Z",
"agent": "qa",
"stage": "qa_gate",
"state": "running",
"message": "QA de receipts POS y checkout sync"
},
{
"ts": "2026-08-26T10:41:58Z",
"agent": "qa",
@@ -147,6 +63,90 @@
"stage": "build",
"state": "done",
"message": "Club fase 1 backend implementado y validado"
},
{
"ts": "2026-08-26T15:59:00Z",
"agent": "implementer",
"stage": "build",
"state": "blocked",
"message": "CLUB-001 pausada: priorizar INVENTORY-OPT, SHIPPING-ZONES y batch de fixes solicitado por usuario"
},
{
"ts": "2026-08-26T15:59:14Z",
"agent": "architect",
"stage": "design",
"state": "running",
"message": "Analizar inventario para optimización con 10k+ productos"
},
{
"ts": "2026-08-26T16:00:10Z",
"agent": "architect",
"stage": "design",
"state": "done",
"message": "Diseño completado para inventario paginado y sin N+1"
},
{
"ts": "2026-08-26T16:00:10Z",
"agent": "implementer",
"stage": "build",
"state": "running",
"message": "Implementar endpoint optimizado y UI paginada de inventario"
},
{
"ts": "2026-08-26T16:06:51Z",
"agent": "implementer",
"stage": "build",
"state": "done",
"message": "Inventario optimizado con paginación y sin N+1"
},
{
"ts": "2026-08-26T16:06:54Z",
"agent": "reviewer",
"stage": "review_gate",
"state": "running",
"message": "Revisión técnica de optimización de inventario"
},
{
"ts": "2026-08-26T16:07:05Z",
"agent": "reviewer",
"stage": "review_gate",
"state": "done",
"message": "Revisión técnica aprobada para inventario"
},
{
"ts": "2026-08-26T16:07:08Z",
"agent": "security",
"stage": "security_gate",
"state": "running",
"message": "Revisión de seguridad de inventory admin overview"
},
{
"ts": "2026-08-26T16:07:19Z",
"agent": "security",
"stage": "security_gate",
"state": "done",
"message": "Revisión de seguridad aprobada para inventario"
},
{
"ts": "2026-08-26T16:07:22Z",
"agent": "qa",
"stage": "qa_gate",
"state": "running",
"message": "QA de inventario paginado"
},
{
"ts": "2026-08-26T16:07:34Z",
"agent": "qa",
"stage": "qa_gate",
"state": "done",
"message": "QA aprobada para inventario optimizado"
},
{
"ts": "2026-08-26T16:07:40Z",
"agent": "leader",
"stage": "close",
"state": "running",
"message": "Cerrar INVENTORY-OPT y commit/push automático"
}
]
}