feat(F-142): completed feature
This commit is contained in:
295
docs/reporting/REPORTING_ARCHITECTURE.md
Normal file
295
docs/reporting/REPORTING_ARCHITECTURE.md
Normal 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 30–60 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.
|
||||
241
docs/reporting/REPORTING_TASKS.md
Normal file
241
docs/reporting/REPORTING_TASKS.md
Normal 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.
|
||||
Reference in New Issue
Block a user