feat(F-157): completed feature

This commit is contained in:
chattie
2026-08-22 17:42:35 +02:00
parent e48577c61a
commit 7159baf851
18 changed files with 255 additions and 599 deletions

View File

@@ -1,253 +1,17 @@
# Sprint completo: Todas las 270 features cerradas
## Estado final (2026-08-22)
- **Backlog**: 270/270 features done ✅
- **verify.sh**: verde
- **tsc**: 0 errores
## Sprint POS (F-003..F-010, POS-003..POS-046)
### Fase 1 POS (POS-003 a POS-010) — COMPLETADO
- **POS-003**: Módulo POS backend (domain types, repos, use cases, unit tests, build-app.ts)
- **POS-004**: Registro de rutas POS (12 rutas administrativas + terminales + sesiones)
- **POS-005**: Búsqueda de productos + CRUD métodos de pago (admin)
- **POS-006**: App Next.js `apps/pos` (15 archivos: configs, layouts, middleware, API client, utils)
- **POS-007**: Pantalla principal del TPV (productos, carrito, descuentos, pago efectivo/tarjeta)
- **POS-008**: POST /pos/sales idempotente (transacción atómica con PostgreSQL)
- **POS-009**: Búsqueda y detalle de clientes para asociación
- **POS-010**: Panel de descuentos (API validación + componente React)
### Fase 2/3 POS (POS-011 a POS-022) — COMPLETADO
- **POS-011**: Lista de ventas, void, receipts, historial de sesiones
- **POS-012**: Reembolsos, impresión de tickets, analytics summary
- **POS-013**: Alertas stock bajo, loyalty, settings, atajos de teclado
- **POS-014**: Turnos, tasas IVA, multi-tienda, notificaciones, reports diarios y EOD
- **POS-015**: Kitchen display, cajón, importación pedidos, integraciones, exportación CSV/JSON
- **POS-016**: Users management, audit log, sync catálogo, time tracking, lookup EAN/SKU
- **POS-017..POS-022**: Split payments, holds, quotes, tips, gift cards, multi-currency, y features restantes de Phase 2/3
### Fase 4/5+6/7 POS (POS-023 a POS-046) — COMPLETADO
- 23 rutas adicionales: advanced analytics, payroll, suppliers, kitchen display status, inventory forecast, supplier orders, order status management, hourly/products/employees reports, categories, tags, stock alerts, y más
## F-149 cerrada (2026-08-22) — Reporting: product category and brand reports
- `reporting-service.ts`: ReportingService con summary() + sales() usando CTEs SQL parametrizados.
- `GET /reporting/summary` + `GET /reporting/sales` con filtros/channel/storeId/terminalId/groupBy/pagination.
- 44 tests reporting (14 unit + 15 route + 15 existing); npm run build 0; boundaries 0 nuevas; verify.sh verde; commit `91044da`.
- Gates: reviewer ✅ / security ✅ / qa ✅ / document ✅ / leader-close ✅.
- **Siguiente**: F-147 (Admin: reporting shell and global filters).
- `049_reporting_payment_lines.js`: tabla `reporting_payment_lines` (13 columnas, FK→orders_orders+pos_stores, 3 CHECK, 3 índices), inmutable (refunds como nuevas filas). patrón: INSERT-only.
- `reporting-payment-lines.itest.ts` 16/16 ✅ (DB real).
- npm run build 0; boundaries 0 nuevas; verify.sh verde; commit `62a368f`.
- Gates: reviewer ✅ / security ✅ / qa ✅ / document ✅ / leader-close ✅.
- **Siguiente**: F-146 (Reporting: service summary and sales API).
## F-148 cerrada (2026-08-22) — Admin: sales dashboard and channel views
## F-147 cerrada (2026-08-22) — Admin: reporting shell and global filters
## F-146 cerrada (2026-08-22) — Reporting: service summary and sales API
## F-145 cerrada (2026-08-22) — Reporting: payment lines and POS cash-safe capture
## F-144 cerrada (2026-08-22) — Reporting snapshots: store/VAT/cost/shipping
- `048_reporting_store_shipping_snapshots.js`: `orders_orders.store_id` (uuid NOT NULL DEFAULT default-store + FK→pos_stores + idx), `orders_orders.shipping_cents` (integer NOT NULL DEFAULT 0), `orders_items.cost_at_sale_cents` (bigint nullable), `orders_items.vat_rate` (text nullable).
- `reporting-snapshots.itest.ts` 3/3 ✅ (DB real, migration 001→048 aplicada).
- tsc 0; boundaries 0 nuevas; verify.sh verde; commit `2ea628f`.
- Gates: reviewer ✅ / security ✅ / qa ✅ / document ✅ / leader-close ✅.
- **Siguiente**: F-145 (Reporting: payment lines and POS cash-safe capture).
## F-138 cerrada (2026-08-22) — auto-seed default price row on variant creation
- `POST /products(:id/variants)` create ahora inserta inmediatamente una fila default en `pricing_variant_prices` (`net_unit_amount_cents=0`, `vat_rate='general'`, `currency='EUR'`) vía `PricingService.seedVariantPrice` (inyectado en `CreateProductVariant`, best-effort, `ON CONFLICT DO NOTHING`). Cierra la ventana 404 de `GET /pricing/variants/:id`. Backend-only, sin migración.
- Fuente: pricing `seedVariantPrice` (ports+service+PgPricingRepository), catalog `CreateProductVariant` seed (best-effort), `CatalogRoutesDeps += pricing`, `build-app.ts` hoist `pricing` const; `+` tests `variant-use-cases.test.ts` (3) + AC itest en `catalog.itest.ts`.
- Gates: implementer ✅ / reviewer APPROVED ✅ / security APPROVED ✅ / qa APPROVED ✅ / leader close ✅.
- Verificación: `tsc --noEmit` 0 errores; `npm test` 209 passed / 57 skipped (+3 nuevos); `lint:boundaries` sin violaciones nuevas (R1 preexistente `security.routes.ts → log-broadcaster` no introducido); `./scripts/verify.sh` verde (pre-close + post-close tras corregir `leader-close.json` verdict CLOSED→APPROVED).
- Commit: `feat(F-138): completed feature` (fb01593, amendado).
## F-154 cerrada (2026-08-22) — separate customers from internal users
- `GET /users` (Customers) now returns **storefront customers only** (`identity_users.role = 'customer'` filtro literal en `PgProfileRepository.listCustomers`, COUNT + SELECT). `GET /users/:id` owner-or-admin inalterado.
- `GET /admin/users` (Users) now returns **internos/backoffice only** (`role <> 'customer'` base literal; `?role=admin|editor` afinando dentro de internos; `?role=customer` → vacío).
- Frontend: dropdown de Users quita opción `customer` (backend ya fuerza la separación). Customers page sin cambio (llama /users → ahora customer-only).
- Tests: 6 nuevos unitarios (mock, sin DB) en `pg-profile-repository.test.ts` (3) + `security.routes.test.ts` (3). `users.itest.ts` AC2/AC3 flip: /users devuelve ben (customer) no ana (admin). Full suite 206 passed / 56 skipped, sin regresiones.
- Gates: implementer ✅ / reviewer APPROVED ✅ / security APPROVED ✅ / qa APPROVED ✅ / leader close ✅.
- `tsc --noEmit` 0 errores (API + apps/admin); eslint/prettier limpios en archivos tocados; `lint:boundaries` sin violaciones NUEVAS (R1 preexistente en `security.routes.ts → log-broadcaster`, no introducido por F-154, verificado via git diff); `verify.sh` exit 0.
- Commit: `feat(F-154): completed feature`.
## F-153 cerrada (2026-08-22) — customer email on order view
- Asocia el email del cliente (`identity_users.email`, NOT NULL) al order read model vía `LEFT JOIN identity_users` en el repo, y lo expone en `serializeOrder` (detail + lista) y en la notificación del force-transition admin (usa `order.email`; se elimina el lookup inline).
- `OrderView.email?: string | null` (additivo, opcional en fixtures de test → serializado como `null`). Sin migración. Boundary intacta (orders→identity_users es referencia SQL, no import TS).
- Tests: 3 nuevos en `pg-order-repository.test.ts` (email resuelto vía JOIN, `null` sin usuario vinculado, `undefined` cuando no existe). Suite 200 passed / 56 skipped, sin regresiones.
- Gates: implementer ✅ / reviewer APPROVED ✅ / security APPROVED ✅ / qa APPROVED ✅ / leader close ✅.
- `tsc --noEmit` 0 errores; eslint 0; prettier baseline-only; `lint:boundaries` sin violaciones nuevas; `verify.sh` exit 0.
- Commit: `feat(F-153): completed feature` + `chore: reset runtime after F-153`.
- Pendiente siguiente por orden: **F-154** (separate customers from internal users).
## Sesión 2026-08-22 — F-152 cerrada (emails on account creation + order confirmation)
- `F-152` cerrada: welcome email (`account_created`) on `POST /auth/register` y order confirmation email on `POST /payments/webhook` (PaymentSucceeded).
Best-effort (fire-and-forget + `request.log.warn`), idempotent (gate `outcome.kind === "processed"`),
XSS-safe (`buildWelcomeEmail` escapea name), SMTP config de `store_settings`.
- Gates: implementer ✅ / reviewer APPROVED ✅ / security APPROVED ✅ / qa APPROVED ✅ / leader close ✅.
- `tsc --noEmit` 0 errores; prettier+eslint limpios en archivos tocados; `lint:boundaries` sin nuevas violaciones;
197 tests ✅; `git diff --check` ✅; `verify.sh` exit 0.
- Commits: `feat(F-152): completed feature` + `chore: reset runtime after F-152` (push omitido, sin remote `origin`).
- `runtime-status.json` reseteado a idle.
- Pendiente siguiente por orden: `F-153` (customer email missing).
## Sesión 2026-08-22 — F-156 cerrada (CMS dynamic rendering)
Backlog: **269 features, 212 done, 57 pending, 0 in_progress, 0 blocked**.
- `F-156` cerrada: CMS content-managed pages (`/about`, `/contact`, `/shipping`) dejan de servir caché stale. `fetchPage` usa `{ cache: 'no-store' }` y las páginas exportan `dynamic = 'force-dynamic'`; el fallback estático y el RBAC se mantienen.
- Gates: implementer ✅ / reviewer APPROVED ✅ / security APPROVED ✅ / qa APPROVED ✅ / leader close ✅.
- Verificado runtime: `curl -sI /about` → 200 con `Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate`; verificador temporal de DB apareció y desapareció sin rebuild.
- `verify.sh` green; cambios commiteados como `feat(F-156): completed feature`; `runtime-status.json` reseteado a idle.
- Pendientes inmediatos por orden sugerido: `F-152` (emails account creation/order confirmation) → `F-153` (customer email missing) → `F-154` (separate customers from internal users). Los tickets Reporting P0 pendientes son `F-143`..`F-150`.
## Sesión 2026-08-21 — Reporting preparado y builds Next estabilizados
Backlog: **264 features, 209 done, 55 pending, 0 in_progress, 0 blocked**.
- `F-141` cerrada: raíces `turbopack`/`outputFileTracingRoot` explícitas en customer frontend y páginas de catálogo dinámicas para que el build no dependa del API.
- `F-142` cerrada: `docs/reporting/REPORTING_ARCHITECTURE.md` y `docs/reporting/REPORTING_TASKS.md` entregados tras analizar modelos reales, datos faltantes, API, rendimiento, caché, RBAC y roadmap P0-P3.
- Tickets Reporting P0 abiertos: `F-143`..`F-150`; todos `pending`.
- `verify.sh` exit 0 y `runtime-status.json` reseteado a idle.
- Próximo ciclo: iniciar `F-143` (contracts, filtros, comparación y RBAC de Reporting) antes de implementar dashboards.
## Sesión 2026-08-21 — backlog cerrado (nota inicial, desfasada)
Backlog: 185 features (185 done, 0 pending, 0 in_progress) según la nota original.
Últimas features cerradas en esa nota: **F-117**, **F-116**, **F-115**, **F-112**, **F-100**.
Tras esa nota se cerraron **18 features adicionales** (F-118..F-135) sin actualizar `current.md`. Quedan reflejadas en `backlog/features.json` y en `work/history.md`.
## F-117 cerrada (2026-08-21)
Fix de F-116: renombre las 9 categorías que quedaron en mayúsculas (FRUTAS Y VERDURAS, SNACKS, GRANOLA, SUPLEMENTS, FACIAL, CORPORAL, ASEO PERSONAL, HIERBAS MEDICINALES, PROVEEDORES). Re-ejecución idempotente.
## F-116 cerrada (2026-08-21)
Traducción de las 30 categorías legacy a Español Title Case (Alimentación, Cosmética, Bebidas, Vino, Cerveza, Macrobiótica, etc.). Slugs conservados.
## F-115 cerrada (2026-08-21)
Rever de F-100: SKU-MV-{uuid} restaurado en creación de producto y lazy migration. Eliminado endpoint `/products/sku:generate` y el editor de SKU en admin.
## F-112 cerrada (2026-08-21)
Disclaimer de contenido asistido por IA en la ficha de producto. Migración 040 añade `catalog_products.ai_assisted`. Flag se activa automáticamente al generar contenido con IA. Frontend muestra el `<aside>` con los dos avisos.
## F-100 cerrada (2026-08-21)
SKU automático desde el título. Helper puro `sku.ts` con `generateSkuFromTitle` + `uniqueSku` (9 tests). Endpoint `POST /products/sku:generate` y helper mantenido como utilidad para F-117.
## F-112 cerrada (2026-08-21)
Disclaimer de contenido asistido por IA en la ficha de producto. Migración 040 añade `catalog_products.ai_assisted boolean NOT NULL DEFAULT false`. Flag se activa automáticamente al generar contenido con IA (`POST /products/:id/generate-seo`). Frontend (`apps/frontend`) muestra el `<aside>` con los dos avisos requeridos cuando el flag está en true. Tests: 160 → 169.
## F-100 cerrada (2026-08-21)
SKU automático desde el título. Helper puro `src/modules/catalog/domain/sku.ts` con `generateSkuFromTitle` + `uniqueSku` (9 tests). `POST /products` y la migración perezosa de variantes ahora generan SKUs tipo `MV-ESPELTA-ECOLOGICA` en lugar de `SKU-MV-${uuid}`. Endpoint admin `POST /products/sku:generate` para preview. UI admin: SKU editable + botón regenerar en `PriceStockSection`.
## F-114 cerrada (2026-08-21)
Importa las 76 categorías de `oc_category_description` de OpenCart. Crea 39 categorías nuevas y 32 marcas (nombres de marca van al módulo `brands_brands`), excluye marcadores legacy y duplicados. Script idempotente en `scripts/seed-legacy-categories.mjs`. Helpers puros en `src/modules/categories/legacy/legacy-catalog.ts` con 15 tests. Catálogo: 12→51 categorías, 5→37 marcas.
## F-113 cerrada y desplegada (2026-08-21)
Email al cliente en procesando/enviado con tracking y courier editable. Bug fixed: UI admin → `/orders/:id/transitions/admin`. Courier persistido (`orders_orders.courier`, migración 039). Lista editable en `store_settings.shipping_couriers`. `SHIPPED` exige tracking **y** courier. Tests: 135 → 145. Deploy manual.
## Última incidencia resuelta (2026-08-20)
F-099 en build. El sistema deja de simular el envío de recuperación en logs y añade SMTP configurable desde Ajustes → SMTP / Email. También se incorporan generación de descripción normal vacía, proxy de logs con cookie httpOnly y navegación de login/logout solo con icono y tooltip.
## Última incidencia resuelta (2026-08-20)
F-095 cerrada con todos los gates aprobados. Las imágenes URL se descargan y almacenan en uploads locales antes de adjuntarse.
## Incidencia anterior (2026-08-20)
Añadir una imagen por URL devolvía 400 porque el flujo hacía un PATCH vacío del producto y adjuntaba directamente la URL remota.
## Última incidencia resuelta (2026-08-20)
F-094 cerrada con todos los gates aprobados. Publicar permite crear variantes y explica SKU/EAN, precios y stock.
## Incidencia anterior (2026-08-20)
Precios e Inventario indicaban que las variantes se creaban desde Publicar, pero Publicar no ofrecía creación ni explicación.
## Última incidencia resuelta (2026-08-20)
F-093 cerrada con todos los gates aprobados. Inventario busca ahora por EAN o nombre de producto.
## Incidencia anterior (2026-08-20)
La búsqueda de Inventario enviaba `q`, pero el listado admin solo filtraba por nombre de producto.
## Última incidencia resuelta (2026-08-20)
F-092 cerrada con todos los gates aprobados. `Fecha de caducidad` está ahora en General; backend y admin fueron reconstruidos y desplegados.
## Incidencia anterior (2026-08-20)
`Fecha de caducidad` estaba en la pestaña SEO de la ficha de producto. F-092 la movió a General sin cambiar el estado, payload ni guardado.
## Última incidencia resuelta (2026-08-20)
F-091 cerrada con todos los gates aprobados. Los conflictos de SKU/EAN indican ahora qué campo está duplicado y Enter + blur no generan PATCH simultáneos.
## Incidencia anterior (2026-08-20)
El PATCH de variantes devolvía `409 PRODUCT_VARIANT_CODE_EXISTS` cuando el SKU o EAN ya estaba usado por otra variante. El editor mostraba solo `Error` y la combinación Enter + blur podía intentar enviar dos veces.
## Última incidencia resuelta (2026-08-20)
F-090 cerrada con todos los gates aprobados. El listado ya recibe marca/caducidad y los valores SKU/EAN guardados permanecen visibles tras salir del campo.
## Incidencia anterior (2026-08-20)
El listado admin mostraba `—` para marca/caducidad porque la serialización del catálogo omitía ambos campos. En el editor de producto, SKU/EAN se guardaban en `rows` pero la vista no editable usaba el array `variants` inicial y volvía a mostrar valores antiguos.
## Última incidencia resuelta (2026-08-20)
F-089 cerrada con todos los gates aprobados. El enlace del listado admin apunta ahora a `http://192.168.18.93:3003/products/<slug>`, ruta del frontend customer.
## Incidencia anterior (2026-08-20)
El enlace de producto del listado admin apuntaba a `http://192.168.18.93:3003/productos/<slug>`, pero el frontend customer usa `/products/<slug>`. F-089 corrigió únicamente ese path.
## Última incidencia resuelta (2026-08-20)
F-088 cerrada con todos los gates aprobados. El admin responde HTTP 200 en `http://192.168.18.93:3004/`.
## Symptom reportado por el operador (2026-08-20)
`http://192.168.18.93:3004/``ERR_CONNECTION_REFUSED`. El servicio admin del monolito no se mantiene en pie. Diagnóstico:
- `monolith.sh prod status` → admin `exited during startup`, log: `Could not find a production build in the '.next' directory`.
- `apps/admin/.next/` existe pero no contiene `BUILD_ID` ni `required-server-files.json`.
- `npx tsc --noEmit` en `apps/admin``error TS2353` en `tax-rates/page.tsx(59,33)`: `'appliesTo' does not exist in type 'Partial<{ name: string; ratePercent: number; active: boolean; }>'`.
## Root cause
F-085 añadió el handler `saveTipo` que llama a `taxApi.update(id, { appliesTo })` y extendió la página con UI de edición inline, pero olvidó extender la firma de `taxApi.update` en `apps/admin/src/lib/api-client.ts`. Resultado: `next build` aborta por typecheck, no se genera `BUILD_ID`, `next start` falla y el admin no puede servir nada en :3004. El backend en `pricing.routes.ts` ya valida y persiste `appliesTo`, así que el fix es 100% frontend (type only).
## Fix
Ampliar la firma `Partial<{...}>` de `taxApi.update` para aceptar `appliesTo: 'general' | 'reduced' | 'super-reduced'` (mismo enum que el backend). Después: `npm run build` en `apps/admin` produce `BUILD_ID`, `monolith.sh prod` mantiene el admin vivo y `http://192.168.18.93:3004/` responde 200.
## Pending tickets (2026-08-21)
Pendientes: F-100 (descartable, sustituida por F-109), F-101, F-102, F-107, F-108, F-109, F-110, F-111, F-112.
Orden sugerido: F-108 → F-109 → F-107 → F-110 → F-111 → F-101 → F-102 → F-112.
## Redefiniciones de intake del operador (2026-08-21)
- **F-101 (alcance reducido)**: El renderizado actual de descripciones IA ya funciona. Solo queda justificar el texto de la descripción en la ficha de producto del frontend. NO hace falta conversión Markdown→HTML.
- **F-102 (redefinida)**: El peso del producto es 1 por unidad (si pide 3, peso = 3 × peso unitario). Lo llamado "pack" es en realidad un **selector de compra mínima**: cantidad mínima de compra por producto; el frontend bloquea la compra por debajo de ese mínimo. Además, nueva opción de envío: límite de envío gratuito por rango de peso en cada tipo de envío y un **max weight** por tipo de envío.
- **F-112 (nueva)**: Disclaimer de contenido IA en fichas de producto del frontend:
- "Parte del contenido de esta ficha puede haber sido generado o asistido mediante inteligencia artificial y revisado antes de su publicación."
- "La composición y características del producto pueden cambiar. Consulta siempre la etiqueta y la información del fabricante antes de consumirlo o utilizarlo."
## Nota de intake (2026-08-20)
El operador reportó `ERR_CONNECTION_REFUSED` en :3004. El triage lo une a F-085 (cambio parcial sin actualizar la firma TS). Se crea F-088 para reparar la regresión sin reabrir F-085, y se ejecuta por orquestra secuencial.
# Feature activa: F-157 — Reporting sections live on Reporting page
## Problema
El menú principal muestra Dashboard, Ventas y Productos de Reporting como entradas indentadas. El operador requiere una única entrada principal **Reporting**. Al abrirla, sus apartados deben aparecer dentro del área de Reporting, igual que las pestañas internas de Ajustes.
## Alcance
- El sidebar principal muestra solo `Reporting`.
- `/reporting` abre por defecto el apartado Dashboard.
- Dashboard, Ventas y Productos se muestran como navegación local dentro de Reporting.
- La navegación local permanece visible en las páginas de los tres apartados.
- No se cambian APIs ni permisos de Reporting.
## Aceptación
1. No aparecen `/reporting/dashboard`, `/reporting/sales` ni `/reporting/products` en el menú principal.
2. Al pulsar Reporting aparece una lista local con Dashboard, Ventas y Productos.
3. El apartado activo queda visualmente marcado.
4. El build del admin y `verify.sh` pasan.