feat(F-142): completed feature

This commit is contained in:
chattie
2026-08-21 22:06:54 +02:00
parent 022347349d
commit 064626b851
9 changed files with 823 additions and 4 deletions

View File

@@ -6237,6 +6237,128 @@
"description": "Analyze existing ecommerce, POS, orders, payments, customers, products, stock, cash, refunds, discounts and taxes; produce reporting architecture and phased implementation tasks before dashboard coding.", "description": "Analyze existing ecommerce, POS, orders, payments, customers, products, stock, cash, refunds, discounts and taxes; produce reporting architecture and phased implementation tasks before dashboard coding.",
"priority": "high", "priority": "high",
"risk": "med", "risk": "med",
"status": "done",
"created_at": "2026-08-21",
"gates": {
"reviewer": true,
"security": true,
"qa": true,
"close": true
},
"completed_at": "2026-08-21T20:06:54Z"
},
{
"id": "F-143",
"type": "feature",
"title": "Reporting: contracts, filters and RBAC",
"description": "Define shared reporting filters, date comparisons, response availability metadata and backend REPORTING permissions.",
"priority": "high",
"risk": "med",
"status": "pending",
"created_at": "2026-08-21",
"gates": {
"reviewer": false,
"security": false,
"qa": false
}
},
{
"id": "F-144",
"type": "feature",
"title": "Reporting: historical store and financial snapshots",
"description": "Add the minimum store, VAT, cost and shipping snapshots required to report multi-store sales without rewriting history.",
"priority": "high",
"risk": "high",
"status": "pending",
"created_at": "2026-08-21",
"gates": {
"reviewer": false,
"security": false,
"qa": false
}
},
{
"id": "F-145",
"type": "feature",
"title": "Reporting: payment lines and POS cash-safe capture",
"description": "Persist immutable payment lines for ecommerce and POS, including method/store/terminal/session, without card data.",
"priority": "high",
"risk": "high",
"status": "pending",
"created_at": "2026-08-21",
"gates": {
"reviewer": false,
"security": false,
"qa": false
}
},
{
"id": "F-146",
"type": "feature",
"title": "Reporting: service summary and sales API",
"description": "Implement backend ReportingService with filtered summary/sales metrics, comparisons, grouping and pagination.",
"priority": "high",
"risk": "med",
"status": "pending",
"created_at": "2026-08-21",
"gates": {
"reviewer": false,
"security": false,
"qa": false
}
},
{
"id": "F-147",
"type": "feature",
"title": "Admin: reporting shell and global filters",
"description": "Add Reporting navigation, dashboard shell, reusable filters, URL persistence and explicit data states.",
"priority": "high",
"risk": "med",
"status": "pending",
"created_at": "2026-08-21",
"gates": {
"reviewer": false,
"security": false,
"qa": false
}
},
{
"id": "F-148",
"type": "feature",
"title": "Admin: sales dashboard and channel views",
"description": "Present real summary/sales data with KPIs, trends, ecommerce versus POS, stores and terminals.",
"priority": "high",
"risk": "med",
"status": "pending",
"created_at": "2026-08-21",
"gates": {
"reviewer": false,
"security": false,
"qa": false
}
},
{
"id": "F-149",
"type": "feature",
"title": "Reporting: product category and brand reports",
"description": "Add server-side product rankings and category/brand reports using catalog data and availability warnings.",
"priority": "high",
"risk": "med",
"status": "pending",
"created_at": "2026-08-21",
"gates": {
"reviewer": false,
"security": false,
"qa": false
}
},
{
"id": "F-150",
"type": "feature",
"title": "Reporting: CSV export",
"description": "Export filtered reporting datasets to CSV with permissions, metadata and server-side pagination/streaming.",
"priority": "high",
"risk": "med",
"status": "pending", "status": "pending",
"created_at": "2026-08-21", "created_at": "2026-08-21",
"gates": { "gates": {

View File

@@ -0,0 +1,295 @@
# Reporting — Arquitectura y análisis de datos
**Estado:** discovery / diseño inicial (sin dashboards ni endpoints implementados)
**Fecha:** 2026-08-21
**Tienda por defecto:** Natural - Mercado de Vida
## 1. Decisiones ejecutivas
1. **La fuente de verdad sigue siendo el monolito transaccional PostgreSQL.** Reporting no crea ventas, pagos, stock ni clientes paralelos.
2. **El hecho base es `orders_orders` + `orders_items`.** Tanto ecommerce como TPV deben persistir una venta como pedido, distinguiéndola mediante `orders_orders.source` (`ecommerce`, `pos`, `admin`).
3. **Los cálculos viven en backend**, en un módulo `reporting`, no en React. El frontend recibe métricas preparadas, metadatos de disponibilidad y paginación.
4. **Primera versión: SQL directo parametrizado sobre PostgreSQL**, con CTEs y consultas separadas por informe. No se añade una tabla de agregados hasta medir volumen y latencia real.
5. **Filtros son un contrato común y reproducible:** fechas con zona horaria explícita, tienda/canal/terminal/cajero/método de pago/estado/producto/categoría/marca/cliente. La misma estructura alimenta API, URL y exportación.
6. **No se muestran métricas no justificables.** Margen, IVA por tipo, método de pago, devoluciones detalladas y diferencias de caja quedan como `unavailable` hasta disponer de datos históricos fiables.
## 2. Inventario de modelos existentes
### 2.1 Hechos transaccionales
| Tabla | Grano | Datos aprovechables | Limitaciones actuales |
|---|---|---|---|
| `orders_orders` | Un pedido/venta | `id`, usuario, estado, moneda EUR, subtotal, descuento, impuesto, total, fechas, `source`, terminal y sesión POS | No tiene `store_id` directo. El total no separa portes. `user_id` es NULL para walk-in POS. |
| `orders_items` | Una línea de pedido | Producto/variante, SKU/EAN/nombre snapshot, precio unitario, descuento, impuesto, cantidad, fecha | No guarda `cost_at_sale`, tipo de IVA, marca/categoría snapshot ni devolución por línea. |
| `payments_transactions` | Un evento transaccional de proveedor | importe, moneda, estado, proveedor, order_id, fecha, idempotencia | No guarda método de pago, tienda, terminal, sesión, importes parciales de devolución ni FK a order. Eventos sin `order_id` son posibles. |
| `inventory_movements` | Un movimiento de variante/tienda | variante, tienda, operación, cantidad, fecha | No tiene `order_id`, usuario, motivo ni referencia de devolución; no permite casar una confirmación con una venta concreta. |
| `pos_cash_sessions` | Una sesión de caja/terminal | tienda, terminal, cajero, apertura/cierre, esperado, contado, diferencia | No existe tabla de movimientos de caja ni relación de pagos POS. |
### 2.2 Dimensiones y configuración
| Tabla | Uso en reporting |
|---|---|
| `identity_users`, `users_profiles` | Clientes ecommerce; `orders_orders.user_id` permite clientes únicos, nuevos y recurrentes. POS walk-in no tiene cliente. |
| `backoffice_users` | Cajeros/admin/editor; debe ser el actor de terminal/sesión y de auditoría, no un cliente. |
| `catalog_products`, `catalog_product_variants` | Nombre, estado, variante, SKU/EAN, peso, marca/categorías actuales. |
| `brands_brands`, `categories_categories`, `catalog_product_categories` | Dimensiones actuales de marca y categoría; los cambios posteriores afectan a informes históricos si no se añade snapshot/dimensión versionada. |
| `pricing_variant_prices` | Precio actual, oferta, coste actual y tipo de IVA. El coste actual **no** puede recalcular margen histórico. |
| `pricing_price_history` | Evolución del precio, no coste histórico de cada venta. |
| `promotions_promotions`, `cart_carts.promo_code` | Definición y uso de promociones; el pedido no guarda el código aplicado como snapshot. |
| `tax_rates` | Configuración actual de IVA por aplicación; no acredita el tipo usado en una venta histórica. |
| `pos_stores`, `pos_terminals`, `pos_payment_methods` | Multi-tienda, terminales y configuración de métodos. La configuración de método no es una transacción de pago. |
| `orders_order_history` | Auditoría de transiciones; útil para timeline, no sustituye devoluciones ni pagos. |
## 3. Relaciones de reporting
```text
orders_orders (1)
├── orders_items (N) ── catalog_products / variants
├── payments_transactions (N, order_id nullable)
├── identity_users (0..1)
├── pos_terminals (0..1) ── pos_stores
├── pos_cash_sessions (0..1) ── pos_stores + backoffice_users
└── orders_order_history (N)
orders_items.variant_id
├── inventory_stock (N: store)
├── inventory_movements (N: store)
└── pricing_variant_prices (1, current only)
```
**Regla de joins:** los endpoints de reporting deben partir de un CTE `filtered_orders` y unir líneas/agregados después. No hacer una consulta por pedido, producto o cliente (N+1).
## 4. Grano y definiciones de métricas
El servicio debe declarar en cada respuesta `dataAvailability` y `definitions`.
### Disponibles con el modelo actual
- **Pedidos/tickets:** `COUNT(DISTINCT o.id)` con estados incluidos explícitos.
- **Total cobrado registrado:** `SUM(o.total_cents)` para ventas no canceladas, según la política de estados del endpoint.
- **Ventas de mercancía brutas:** `SUM(i.unit_price_cents * i.quantity)`.
- **Descuentos registrados:** `SUM(o.discount_cents)` o `SUM(i.discount_cents)`; nunca sumar ambos.
- **Impuesto registrado:** `SUM(o.tax_cents)` / `SUM(i.tax_cents)`; la API debe escoger un solo grano.
- **Unidades:** `SUM(i.quantity)`.
- **Ticket medio:** total cobrado / pedidos, con división segura por cero.
- **Canal:** `o.source`.
- **Terminal/sesión/cajero:** cuando la venta tenga `terminal_id`/`cash_session_id`; ecommerce sin asignación POS queda explícitamente como `null`/`online`.
- **Productos:** líneas agrupadas por `product_id`/`variant_id`, usando snapshots de nombre/SKU/EAN.
- **Clientes únicos/nuevos/recurrentes:** usuarios en pedidos, siempre separando walk-ins POS y excluyendo `user_id IS NULL` del denominador de clientes.
- **Productos sin movimiento:** stock actual por tienda + última fecha de `orders_items` en venta válida. Es una aproximación hasta que los movimientos tengan `order_id`.
### No disponibles todavía o solo aproximables
- **Ventas netas sin portes:** `orders_orders` no separa shipping; hoy solo puede publicarse `total cobrado` y `ventas de mercancía` con nombres claros.
- **Método de pago:** `payments_transactions` no tiene `payment_method_id`/código; `pos_payment_methods` solo es catálogo de configuración.
- **Pago mixto:** no hay líneas de pago por pedido.
- **Devoluciones por importe, producto, motivo o usuario:** solo existen estados `REFUNDED`/`PARTIALLY_REFUNDED` y eventos de pago genéricos.
- **IVA por tipo:** las líneas guardan `tax_cents`, pero no guardan el `vat_rate` aplicado en el momento de la venta.
- **Margen histórico:** `cost_cents` actual no es `cost_at_sale`; no se muestra beneficio/margen hasta añadir snapshot de coste.
- **Descuento por cupón/tipo:** se guarda el total de descuento, no el cupón aplicado en `orders_orders`.
- **Caja por método/movimiento:** hay campos de cierre pero no entradas, salidas, retiradas ni pagos POS asociados.
- **Comparación histórica por categoría/marca:** usa la relación actual del catálogo, no una dimensión histórica; debe etiquetarse como clasificación actual o versionarse.
## 5. Correcciones de modelo necesarias antes de P0 financiero
No se deben ocultar estas carencias con cálculos frontend. Tickets de datos deben evaluar:
1. Añadir `orders_orders.store_id` NOT NULL con FK a `pos_stores`, backfill de la tienda por defecto e índice `(store_id, created_at)`. POS y ecommerce deben escribirlo explícitamente.
2. Añadir a `orders_items` snapshots de `vat_rate` y `cost_at_sale_cents` nullable. Si el coste es NULL, margen es `unavailable`.
3. Crear `order_payments`/`orders_payment_lines` como líneas de pago inmutables: order, método, tienda, terminal, sesión, importe, moneda, provider reference, estado y timestamps. No almacenar PAN/CVV.
4. Crear `order_refunds` y líneas opcionales con importe, cantidad, motivo, actor, canal y timestamps; enlazar eventos de proveedor sin duplicarlos.
5. Persistir el `promo_code`/promotion id y el descuento aplicado por pedido/línea como snapshot.
6. Separar shipping en los totales (`shipping_cents` o una tabla de cargos) para no llamar “ventas netas” al total con portes.
7. Añadir movimientos de caja inmutables (`cash_in`, `cash_out`, `sale`, `refund`, `opening`, `closing`) vinculados a sesión y, cuando aplique, order/payment.
Cada cambio necesita migración, backfill/compatibilidad y ticket independiente; no forma parte de un dashboard improvisado.
## 6. Contrato API propuesto
Prefijo recomendado: `/reporting`. Todas las rutas requieren sesión de backoffice y permisos específicos.
```text
GET /reporting/summary
GET /reporting/sales
GET /reporting/products
GET /reporting/categories
GET /reporting/brands
GET /reporting/payments
GET /reporting/cash-sessions
GET /reporting/discounts
GET /reporting/refunds
GET /reporting/customers
GET /reporting/inventory
GET /reporting/taxes
GET /reporting/dimensions/stores
GET /reporting/dimensions/terminals
GET /reporting/export/:report.csv
GET /reporting/export/:report.xlsx (fase posterior)
```
### Query común
```text
from=2026-08-01T00:00:00Z
&to=2026-08-31T23:59:59Z
&compare=previous_equal|previous_calendar|none
&channel=all|ecommerce|pos|admin
&storeId=<uuid> (repetible)
&terminalId=<uuid> (repetible)
&cashierId=<uuid> (repetible)
&paymentMethodId=<uuid> (repetible, cuando exista)
&productId=<uuid> (repetible)
&categoryId=<uuid> (repetible)
&brandId=<uuid> (repetible)
&customerId=<uuid>
&state=PAID,COMPLETED,...
&groupBy=day|week|month|hour|store|channel|terminal|cashier|payment
&page=1&pageSize=50
&sort=-revenue
```
El parser debe rechazar fechas invertidas, límites excesivos, IDs inválidos y combinaciones no soportadas. Rango inclusivo de inicio y exclusivo de fin (`[from,to)`) para evitar doble conteo.
### Respuesta común
```json
{
"range": {"from":"...","to":"...","timezone":"Europe/Madrid"},
"filters": {"channel":"pos","storeIds":[]},
"comparison": {"range":null,"available":true},
"dataAvailability": {"grossSales":true,"margin":false,"paymentMethod":false},
"items": [],
"totals": {},
"updatedAt": "2026-08-21T20:00:00Z",
"cache": {"hit":false,"maxAgeSeconds":30}
}
```
Los importes son céntimos enteros y la API entrega además `currency: EUR`. El porcentaje de variación debe ser `null` cuando el período anterior sea cero/no comparable, nunca `Infinity`.
## 7. Consultas y rendimiento
### CTE base
```sql
WITH filtered_orders AS (
SELECT o.*
FROM orders_orders o
WHERE o.created_at >= $1
AND o.created_at < $2
AND o.state IN (...)
AND ($3::text IS NULL OR o.source = $3)
AND (cardinality($4::uuid[]) = 0 OR o.store_id = ANY($4))
), filtered_items AS (
SELECT i.*, o.source, o.store_id, o.terminal_id
FROM orders_items i
JOIN filtered_orders o ON o.id = i.order_id
)
SELECT ...
```
La versión inicial debe medir `EXPLAIN (ANALYZE, BUFFERS)` con datos representativos. Índices recomendados solo tras confirmar planes:
- `orders_orders (created_at, source, state)` — valorar parciales según estados.
- `orders_orders (store_id, created_at)` una vez exista `store_id`.
- `orders_orders (terminal_id, created_at)` y `(cash_session_id, created_at)`.
- `orders_items (created_at)` para última venta; `(variant_id, created_at)` para inventario.
- `payments_transactions (created_at, status)` y `(order_id, created_at)`.
- `inventory_movements (store_id, variant_id, created_at)`.
No añadir índices duplicados indiscriminadamente: comparar con los índices existentes y medir.
### Caché
- P0 summary/sales: cache corta 3060 s, key = reporte + hash ordenado de filtros.
- Tablas de dimensiones: 5 min o invalidación al cambiar catálogo.
- Exportaciones grandes: job/stream posterior, nunca cargar todo en React.
- Respuesta siempre indica `updatedAt` y `cache.maxAgeSeconds`.
- Materialized views/reporting tables quedan fuera hasta que `EXPLAIN` y volumen justifiquen su coste.
## 8. Frontend Admin
Ruta raíz: `/reporting`. Subrutas previstas:
```text
/reporting Resumen
/reporting/sales Ventas
/reporting/products Productos
/reporting/customers Clientes
/reporting/payments Pagos
/reporting/cash Caja
/reporting/discounts Descuentos
/reporting/refunds Devoluciones
/reporting/inventory Inventario
/reporting/taxes IVA
```
Componentes reutilizables previstos:
- `ReportingLayout`, `ReportingFilters`, `DateRangePicker`, `ComparisonSelector`.
- `KpiCard`, `ComparisonKpi`, `AvailabilityBadge`, `ReportingEmptyState`.
- `SalesChart`, `ChannelBreakdown`, `Heatmap` (P2), `ReportingTable`.
- `StoreSelector`, `TerminalSelector`, `CashierSelector`, `ProductSelector`, `ExportButton`.
Los filtros se serializan en query params, con valores normalizados y sin secretos. La navegación drill-down conserva el filtro cuando la dimensión destino lo soporta.
## 9. Permisos y seguridad
El backend debe ser la autoridad, no solo `visibleNavItems` del frontend. Extender RBAC con permisos:
```text
REPORTING_VIEW
REPORTING_SALES
REPORTING_PRODUCTS
REPORTING_CUSTOMERS
REPORTING_INVENTORY
REPORTING_PAYMENTS
REPORTING_CASH
REPORTING_FINANCIAL
REPORTING_EXPORT
REPORTING_ADMIN
```
Compatibilidad inicial: `admin` puede ver todo; `editor` y roles POS necesitan asignación explícita. `REPORTING_FINANCIAL` protege costes/margen, fiscal detallado y diferencias de caja. Aplicar autorización también a exports y a cada filtro de tienda/terminal, evitando que un cajero consulte otra tienda.
No devolver emails, direcciones ni identificadores de clientes salvo que el informe tenga permiso de clientes. Parametrizar todos los valores SQL y limitar `pageSize`/rango máximo.
## 10. Criterios de datos y estados
- Por defecto, ventas = estados `PAID`, `PROCESSING`, `SHIPPED`, `DELIVERED`, `COMPLETED`, `PARTIALLY_REFUNDED`; excluir `PENDING`, `AWAITING_PAYMENT`, `CANCELLED` y decidir cómo netear refund cuando exista la tabla de devoluciones.
- La respuesta diferencia `loading`, `empty`, `no_data`, `partial_data`, `unavailable` y error; no convierte una métrica no disponible en `0`.
- Todas las horas se almacenan en UTC y se agrupan por zona configurada (`Europe/Madrid` inicialmente).
- Los cambios de catálogo no deben reescribir snapshots de pedido.
- Exportación debe incluir rango, zona horaria, filtros, fecha de generación y columnas seleccionadas.
## 11. Riesgos
| Riesgo | Mitigación |
|---|---|
| `orders_orders` no tiene tienda para ecommerce | Añadir `store_id` antes del filtro multi-tienda obligatorio. |
| Totales mezclan mercancía, IVA, descuento y portes | Exponer nombres exactos y añadir cargos separados antes de “net sales”. |
| Datos POS aún incompletos | P0 de pagos/caja depende de líneas de pago y movimientos de caja. |
| JOIN de líneas duplica totales | CTE por grano: agregar líneas antes de unir dimensiones 1:N. |
| Coste actual usado históricamente | Rechazar margen hasta disponer de `cost_at_sale_cents`. |
| Reclasificación histórica | Añadir snapshots o declarar “clasificación actual”. |
| Consultas pesadas | Límites, índices medidos, cache corta, paginación server-side y EXPLAIN. |
| Fuga entre tiendas | Scope de tienda en SQL + autorización por usuario/terminal + tests negativos. |
| Export bloqueante | CSV streaming primero; XLSX/PDF en fase posterior/job. |
## 12. Evolución futura
La frontera estable será:
```text
Transactional modules
Reporting query/read model (sin duplicar verdad)
ReportingService / report definitions
Reporting API + export adapter
Admin Reporting UI
```
Más adelante puede insertarse una agregación/materialized view detrás de la misma interfaz cuando el volumen lo requiera. Forecasting, alertas, cohortes, RFM, informes programados, PDF y BI externo son P3 y no forman parte de la primera implementación.

View File

@@ -0,0 +1,241 @@
# Reporting — Roadmap de tareas
Este documento divide la implementación en tickets pequeños y verificables. **F-142 solo entrega discovery/arquitectura; no implementa dashboards.** Las referencias `RPT-*` son identificadores de trabajo del plan y se han abierto en backlog como:
| Plan | Ticket backlog | Título |
|---|---|---|
| RPT-001 | F-143 | Contracts, filters and RBAC |
| RPT-002 | F-144 | Historical store and financial snapshots |
| RPT-003 | F-145 | Payment lines and POS cash-safe capture |
| RPT-004 | F-146 | ReportingService, summary and sales API |
| RPT-005 | F-147 | Admin reporting shell and global filters |
| RPT-006 | F-148 | Sales dashboard and channel views |
| RPT-007 | F-149 | Product, category and brand reports |
| RPT-008 | F-150 | CSV export |
RPT-009 en adelante se abrirá como backlog cuando sus dependencias P0 estén aprobadas.
## Dependencias y orden recomendado
```text
RPT-001 contratos/filtros/RBAC
├── RPT-002 datos históricos mínimos (store, tax/cost snapshots)
├── RPT-003 líneas de pago POS/ecommerce
└── RPT-004 ReportingService + summary/sales
├── RPT-005 Admin shell + filtros URL
├── RPT-006 Sales dashboard / channel / store / terminal
├── RPT-007 Product/category/brand reports
└── RPT-008 CSV export
RPT-003 ──> RPT-011 Payments report
RPT-002 ──> RPT-012 Tax report / RPT-013 margin
POS cash implementation ──> RPT-014 cash report
Refund model ──> RPT-015 refunds report
```
Regla: ningún ticket debe inventar datos ausentes; si una métrica no tiene fuente, se incorpora como `unavailable` o se crea primero el ticket de modelo correspondiente.
---
# P0 — Base útil y primera entrega
## RPT-001 — Reporting contracts, filters and permissions
**Objetivo:** crear `ReportingFilters`, parser común, rangos relativos, comparación y permisos backend/frontend.
**Incluye:**
- intervalos `[from,to)` en UTC + zona `Europe/Madrid` para agrupaciones;
- presets hoy/ayer/7/30 días/mes/año/período anterior;
- `channel`, tiendas, terminales, cajeros, producto, categoría, marca, cliente, estado;
- paginación, orden y límite máximo;
- respuesta común con `range`, `comparison`, `dataAvailability`, `updatedAt`;
- permisos `REPORTING_*`, incluyendo `REPORTING_FINANCIAL` y `REPORTING_EXPORT`;
- tests de fechas invertidas, zona, cero y filtros combinados.
**No incluye:** SQL de reportes ni UI final.
## RPT-002 — Reporting data snapshots and store scope
**Objetivo:** hacer reportables de forma fiable las dimensiones históricas obligatorias.
**Evaluar/implementar en migraciones:**
- `orders_orders.store_id` NOT NULL, FK, backfill y escritura desde ecommerce/POS/admin;
- snapshot `vat_rate` y `cost_at_sale_cents` nullable en `orders_items`;
- snapshot de marca/categoría solo si el negocio necesita histórico estable, no copiar el catálogo sin justificación;
- separación de `shipping_cents` de los totales;
- índices medidos para fecha/tienda/canal.
**Aceptación:** migración reversible/documentada, ventas antiguas conservan valores, margen/IVA siguen `unavailable` si faltan snapshots.
## RPT-003 — Payment lines and cash-safe payment capture
**Objetivo:** poder informar efectivo/tarjeta/otros y pagos mixtos sin guardar datos de tarjeta.
**Incluye:**
- tabla inmutable `order_payment_lines` o equivalente, con order, método, tienda, terminal, sesión, importe, currency, provider ref, estado y timestamps;
- integración de venta ecommerce y POS con la línea real;
- idempotencia por evento/provider;
- no PAN/CVV;
- relación con reembolsos preparada.
**Aceptación:** una venta con uno o varios métodos se puede sumar exactamente una vez; pruebas de duplicado y cross-store.
## RPT-004 — ReportingService, summary and sales API
**Objetivo:** primera API real de reporting a partir de pedidos/líneas.
**Endpoints:** `GET /reporting/summary` y `GET /reporting/sales`.
**Métricas P0:** ventas totales registradas, pedidos/tickets, unidades, ticket medio, descuentos e impuestos registrados; tendencia y comparación cuando haya período anterior.
**Agrupaciones:** día, semana, mes, canal, tienda y terminal cuando existan.
**Aceptación:** SQL parametrizado, sin N+1, estados documentados, respuesta de disponibilidad, tests unitarios de fórmulas e integración con ecommerce/POS.
## RPT-005 — Admin Reporting shell and global filters
**Objetivo:** añadir `Reporting` al menú Admin y una pantalla `/reporting` reutilizando Auth/API/layout existentes.
**Componentes:** `ReportingLayout`, `ReportingFilters`, `DateRangePicker`, `ComparisonSelector`, `KpiCard`, `ComparisonKpi`, loading/error/empty/no-data states.
**Aceptación:** filtros se reflejan en URL, sobreviven navegación válida, tablet usable, no se cargan listas masivas en frontend, backend autoriza cada petición.
## RPT-006 — Sales dashboard and channel/store views
**Objetivo:** presentar summary/sales real sin recalcular en React.
**Incluye:** KPIs, tendencia, ecommerce vs TPV, tiendas, terminales y tabla paginada con drill-down a pedidos.
**Aceptación:** filtros globales, comparación equivalente, importes EUR en céntimos convertidos solo para presentación, `unavailable` cuando no hay store/terminal histórico.
## RPT-007 — Product, category and brand reports
**Objetivo:** ranking de productos y desglose de categorías/marcas usando catálogo real.
**Incluye:** top 10/25/50, unidades, ventas de mercancía, precio medio, descuento, devoluciones si existen; filtros y orden server-side.
**Nota:** marcar que categoría/marca es clasificación actual mientras no exista snapshot histórico.
## RPT-008 — CSV export
**Objetivo:** exportar summary/sales/products respetando exactamente filtros, rango, orden y columnas.
**Aceptación:** stream/paginación server-side, BOM/encoding adecuado para Excel, cabecera con fecha/zona/filtros, permission `REPORTING_EXPORT`, límites y auditoría.
## RPT-009 — Reporting frontend API client and table primitives
**Objetivo:** adaptar `api-client.ts` con tipos de respuesta y tabla reutilizable sin duplicar fetch/serialización.
**Incluye:** abort/cancel de consultas, estados de error, columnas configurables y enlaces drill-down.
## RPT-010 — P0 integration and regression suite
**Objetivo:** fixtures transaccionales y pruebas end-to-end.
**Casos:** venta ecommerce, venta POS `COMPLETED`, pedido cancelado, descuento, stock confirmado, comparación de período, filtro tienda no autorizada y export con filtros.
---
# P1 — Informes operativos y comerciales
## RPT-011 — Payments report
Depende de RPT-003. Importe, operaciones, porcentaje, medio y devoluciones por método; admite pago mixto y filtra método/tienda/terminal.
## RPT-012 — Fiscal/IVA report
Depende del snapshot fiscal de RPT-002. Base imponible, tipo de IVA, cuota y total usando datos de la venta, nunca configuración actual.
## RPT-013 — Customers report
Clientes únicos, nuevos, recurrentes, pedidos, ventas, ticket y última compra. Ocultar PII según permiso y separar walk-ins.
## RPT-014 — Inventory report
Stock actual por tienda, vendido, stock bajo/sin stock, rotación aproximada y días de stock solo con denominador suficiente. Usa `inventory_stock`, movimientos y pedidos sin duplicar ventas.
## RPT-015 — Products without movement
Configurable 30/60/90 días: stock positivo + última venta anterior al umbral. Indicar “sin historial” distinto de “sin movimiento”.
## RPT-016 — Categories and brands detail
Tablas con ventas/unidades/pedidos/% y navegación a producto; clasificación histórica documentada.
## RPT-017 — Discounts report
Depende de snapshot de promoción. Total, porcentaje, media, código/tipo, producto/categoría/tienda/terminal/cajero y descuentos manuales identificables.
## RPT-018 — Refund model and report
Crear primero `order_refunds`/líneas y luego importe, cantidad, motivo, producto, canal, tienda, terminal, usuario y porcentaje sobre ventas. No inferir importe desde estado.
## RPT-019 — Cash sessions report
Depende de movimientos de caja/pagos POS. Apertura, ventas efectivo, esperado, contado, diferencia, cajero, terminal, tienda y filtro “solo diferencias”. Proteger con `REPORTING_CASH`/`REPORTING_FINANCIAL`.
## RPT-020 — Drill-down navigation
Ventas → canal → tienda → terminal → producto → pedido/ticket, conservando filtros compatibles y evitando IDs sensibles en URLs no autorizadas.
## RPT-021 — Period comparison UX
Presets y período equivalente; mostrar valor actual, anterior y porcentaje/null. Tests para períodos con cero y distinta duración.
## RPT-022 — Server-side reporting tables
Paginación, búsqueda, sorting, selección de columnas y límites. Nunca cargar decenas de miles de filas en React.
---
# P2 — Análisis avanzado condicionado a datos
## RPT-023 — Historical cost and gross margin
Solo tras `cost_at_sale_cents`: revenue, COGS, gross profit y margen por producto/categoría/marca/tienda/canal. No usar el coste actual.
## RPT-024 — Sales by hour and weekday
Agrupación en zona local, line/bar chart y tabla de horas punta/valle. Validar volumen mínimo para evitar conclusiones engañosas.
## RPT-025 — Sales heatmap
Heatmap día de semana/hora por ventas o tickets, con selector de métrica y accesibilidad alternativa en tabla.
## RPT-026 — Advanced stock analytics
Rotación, velocidad de venta, días de stock y productos sin movimiento con ventanas configurables y advertencia de aproximación.
## RPT-027 — XLSX export
Exportación server-side con columnas/formato/filtros y límites; conservar auditoría y evitar bloqueo del request.
## RPT-028 — PDF export
Solo para informes donde el formato aporte valor (resumen/caja/fiscal); no convertir tablas grandes a PDF sin paginación.
---
# P3 — Evolución futura, no implementar ahora
- RPT-029 — Forecast de ventas.
- RPT-030 — Forecast y alertas de stock.
- RPT-031 — Alertas de negocio.
- RPT-032 — Informes programados/email.
- RPT-033 — ABC de productos, cohortes y RFM.
- RPT-034 — Integración con BI/API externa.
- RPT-035 — Materialized views/agregaciones cuando volumen y EXPLAIN lo justifiquen.
## Definition of Done común
- Contrato/API/documentación actualizados.
- Backend calcula métricas; frontend no duplica reglas financieras.
- Query parametrizada y plan revisado para consultas críticas.
- Permiso backend + prueba negativa cross-store.
- Estados loading/empty/error/no-data/unavailable.
- Unit tests de cálculos y integration tests de datos reales.
- CSV/XLSX respeta filtros si aplica.
- `npm run build`, `npm test`, `./scripts/verify.sh` y gates reviewer/security/qa verdes.

View File

@@ -0,0 +1,41 @@
# F-142 — Implementer evidence
## Entrega
Se completó la fase obligatoria de discovery antes de crear componentes o endpoints:
- `docs/reporting/REPORTING_ARCHITECTURE.md`
- `docs/reporting/REPORTING_TASKS.md`
También se abrieron los tickets P0 iniciales en el backlog mediante `scripts/new_ticket.py`:
- F-143 — contracts, filters and RBAC
- F-144 — historical store and financial snapshots
- F-145 — payment lines and POS cash-safe capture
- F-146 — ReportingService, summary and sales API
- F-147 — Admin reporting shell and global filters
- F-148 — sales dashboard and channel views
- F-149 — product/category/brand reports
- F-150 — CSV export
## Hallazgos principales
- La fuente única actual es `orders_orders` + `orders_items`, pero las ventas todavía no tienen `store_id` directo.
- TPV ya distingue `source`, `terminal_id` y `cash_session_id`, aunque el flujo POS completo todavía está en tickets POS posteriores.
- Pagos actuales son eventos de proveedor y no contienen método de pago ni líneas para pagos mixtos.
- No existe un modelo de devoluciones detallado ni movimientos de caja inmutables.
- `orders_items` conserva precio/descuento/impuesto, pero no `vat_rate` ni `cost_at_sale`; por tanto IVA por tipo y margen se marcan como no disponibles.
- El total de pedido no separa portes, por lo que la API futura debe distinguir total cobrado de ventas de mercancía.
- Categoría/marca actuales no son snapshots históricos.
## Scope deliberadamente excluido
No se implementaron endpoints, tablas reporting paralelas, dashboards, datos mock, cálculos financieros frontend ni caché prematura. La implementación comienza en F-143/F-144/F-145 y respeta el orden de dependencias documentado.
## Verificación
```text
./scripts/verify.sh ✅
backlog válido (264) ✅
una sola feature activa (F-142) ✅
```

View File

@@ -0,0 +1,20 @@
{
"feature_id": "F-142",
"agent": "leader",
"verdict": "APPROVED",
"summary": "F-142 cerrado: arquitectura de Reporting integrada con los modelos actuales, análisis de datos disponibles/faltantes, contrato API conceptual, estrategia de consultas/caché/RBAC y roadmap P0-P3 entregados antes de implementar dashboards.",
"checks": [
"reviewer.json APPROVED",
"security.json APPROVED",
"qa.json APPROVED",
"docs/reporting/REPORTING_ARCHITECTURE.md exists",
"docs/reporting/REPORTING_TASKS.md exists",
"P0 backlog tickets F-143..F-150 created with scripts/new_ticket.py",
"F-142 only in_progress during work",
"verify.sh exit 0",
"git diff --check exit 0"
],
"commit_message": "docs(reporting): add architecture and phased task plan",
"next_step": "Start F-143: Reporting contracts, filters and RBAC",
"closed_at": "2026-08-21T20:08:00Z"
}

View File

@@ -0,0 +1,20 @@
{
"feature_id": "F-142",
"agent": "qa",
"stage": "qa_gate",
"verdict": "APPROVED",
"reviewed_at": "2026-08-21T20:07:30Z",
"summary": "La fase de preparación de Reporting está completa y verificable. Los documentos cubren los 12 apartados requeridos, el plan P0-P3 está creado y el backlog contiene los tickets P0 sin activar una segunda feature.",
"acceptance_traceability": [
{"criterion":"Architecture file generated","ok":true,"evidence":"docs/reporting/REPORTING_ARCHITECTURE.md exists and documents models, relations, metrics, missing data, queries, indexes, API, components, cache, permissions and performance risks"},
{"criterion":"Task file generated","ok":true,"evidence":"docs/reporting/REPORTING_TASKS.md exists with dependency graph, P0/P1/P2/P3 and Definition of Done"},
{"criterion":"P0 coverage","ok":true,"evidence":"P0 includes dashboard, filters, sales, channel/store/terminal, products, payments, CSV and RBAC via RPT-001..RPT-010"},
{"criterion":"P1/P2/P3 coverage","ok":true,"evidence":"Cash, discounts, refunds, categories, customers, inventory, comparisons/drill-down, margins, heatmap, stock advanced, PDF, forecasting, alerts, scheduled reports and BI are classified"},
{"criterion":"No unjustified metrics","ok":true,"evidence":"Architecture marks margin, payment method, detailed refunds, VAT by rate and cash movement as unavailable until source data exists"},
{"criterion":"Backlog tickets created through script","ok":true,"evidence":"F-143..F-150 created by scripts/new_ticket.py; all remain pending; F-142 is the only in_progress feature"},
{"criterion":"Harness verification","ok":true,"evidence":"./scripts/verify.sh → backlog válido (264 features), runtime-status válido, exit 0"}
],
"checks": [],
"issues": [],
"notes":"No application code was intentionally changed for Reporting. The next implementation ticket is F-143, followed by data prerequisites F-144/F-145 before financial dashboards."
}

View File

@@ -0,0 +1,19 @@
{
"feature_id": "F-142",
"agent": "reviewer",
"stage": "review_gate",
"verdict": "APPROVED",
"reviewed_at": "2026-08-21T20:06:30Z",
"summary": "La arquitectura de Reporting está documentada antes de implementar UI/API y está basada en las tablas y módulos reales del repositorio. Identifica explícitamente qué métricas existen y qué datos faltan.",
"checks": [
{"item":"Existing models and relations inventoried","ok":true,"evidence":"REPORTING_ARCHITECTURE.md sections 2-3 cover orders/items, payments, inventory, POS, catalog, prices, promotions, taxes, customers and cash"},
{"item":"Available vs unavailable metrics separated","ok":true,"evidence":"Section 4 explicitly excludes payment method, detailed refunds, VAT rate, historical margin, shipping split and cash movements until data exists"},
{"item":"No parallel source of truth proposed","ok":true,"evidence":"Architecture uses orders_orders/orders_items and existing transactional modules; aggregation deferred until measured"},
{"item":"API/filter/query design present","ok":true,"evidence":"Common filter contract, response envelope, CTE strategy, grouping and pagination documented"},
{"item":"Performance and cache risks addressed","ok":true,"evidence":"EXPLAIN/ANALYZE-first policy, measured indexes, 30-60s cache proposal, N+1 prohibition and export limits"},
{"item":"Frontend/RBAC evolution documented","ok":true,"evidence":"Reusable Admin components, URL filters, backend permissions and financial-data scope documented"},
{"item":"P0-P3 task plan created","ok":true,"evidence":"REPORTING_TASKS.md contains dependency graph, P0, P1, P2 and P3 tasks; F-143..F-150 opened for P0"}
],
"issues": [],
"notes":"This feature intentionally stops at architecture/tasks. Dashboard implementation must start with F-143 and must not bypass the missing-data prerequisites in F-144/F-145."
}

View File

@@ -0,0 +1,19 @@
{
"feature_id": "F-142",
"agent": "security",
"stage": "security_gate",
"verdict": "APPROVED",
"reviewed_at": "2026-08-21T20:07:00Z",
"summary": "El diseño incorpora los controles necesarios para reporting: autorización backend, aislamiento de tienda, protección de PII/finanzas, SQL parametrizado y exportaciones limitadas. No se ha añadido código ni almacenamiento de datos.",
"checks": [
{"item":"PII scope identified","ok":true,"evidence":"Customer report requires permission and architecture limits emails/addresses to authorized reports"},
{"item":"Financial data scope identified","ok":true,"evidence":"REPORTING_FINANCIAL protects cost/margin, tax detail and cash differences"},
{"item":"Cross-store isolation planned","ok":true,"evidence":"Store scope is required in SQL and authorization; negative tests included in task Definition of Done"},
{"item":"SQL injection risk addressed","ok":true,"evidence":"Contract requires parameterized values, bounded filters and rejects invalid IDs; groupBy/order are allowlisted concepts"},
{"item":"Payment data safety addressed","ok":true,"evidence":"Payment-line task explicitly forbids PAN/CVV and stores only provider references"},
{"item":"Export abuse addressed","ok":true,"evidence":"REPORTING_EXPORT, server-side limits/streaming and audit metadata are required"},
{"item":"No security behavior changed in F-142","ok":true,"evidence":"Only docs and pending backlog tickets were added"}
],
"issues": [],
"notes":"Implementation must treat the architecture as a security contract: frontend visibility is not authorization; each report/export/filter must be checked in backend."
}

View File

@@ -1,12 +1,12 @@
{ {
"feature_id": "F-141", "feature_id": "F-142",
"stage": "close", "stage": "close",
"agent": "leader", "agent": "leader",
"action": "Close: Next workspace roots explicit and API-dependent catalog pages runtime rendered", "action": "Close: Reporting architecture, data-gap analysis and P0-P3 task plan approved",
"state": "running", "state": "running",
"next_agent": "leader", "next_agent": "leader",
"waiting_for": "commit", "waiting_for": "promote_F-143",
"updated_at": "2026-08-21T20:01:53Z", "updated_at": "2026-08-21T20:06:45Z",
"timeline": [ "timeline": [
{ {
"ts": "2026-08-21T19:58:45Z", "ts": "2026-08-21T19:58:45Z",
@@ -56,6 +56,48 @@
"stage": "close", "stage": "close",
"state": "running", "state": "running",
"message": "Close: Next workspace roots explicit and API-dependent catalog pages runtime rendered" "message": "Close: Next workspace roots explicit and API-dependent catalog pages runtime rendered"
},
{
"ts": "2026-08-21T20:02:06Z",
"agent": "architect",
"stage": "design",
"state": "running",
"message": "Design: inspect current transactional models and produce reporting architecture plus phased tasks before code"
},
{
"ts": "2026-08-21T20:05:44Z",
"agent": "implementer",
"stage": "build",
"state": "running",
"message": "Build: persist reporting architecture/task artifacts and open P0 backlog tickets; no dashboard code"
},
{
"ts": "2026-08-21T20:05:59Z",
"agent": "reviewer",
"stage": "review_gate",
"state": "running",
"message": "Review: reporting architecture grounded in existing models and phased tasks before implementation"
},
{
"ts": "2026-08-21T20:06:14Z",
"agent": "security",
"stage": "security_gate",
"state": "running",
"message": "Security: reporting data scope, PII/financial permissions, SQL and export risks reviewed"
},
{
"ts": "2026-08-21T20:06:27Z",
"agent": "qa",
"stage": "qa_gate",
"state": "running",
"message": "QA: documentation completeness, backlog dependencies, verify harness and no implementation scope leak"
},
{
"ts": "2026-08-21T20:06:45Z",
"agent": "leader",
"stage": "close",
"state": "running",
"message": "Close: Reporting architecture, data-gap analysis and P0-P3 task plan approved"
} }
] ]
} }