merge: reconciliar historia local (project.md+commands+vault) con remote dataforge/aurgi (reports)
This commit is contained in:
@@ -0,0 +1,183 @@
|
|||||||
|
# Report 0001 — Plan: Dashboard de progresión de Tickets, Venta y Ticket Medio (total y por categorías)
|
||||||
|
|
||||||
|
- **Fecha:** 10/06/2026
|
||||||
|
- **Autor:** Claude (sesión meta_bigq)
|
||||||
|
- **Ámbito:** Metabase `reports.autingo.es` (DB 6 `DCBigQuery`) + BigQuery `autingo-159109.psql_dcpublic`
|
||||||
|
- **Estado:** plan (no ejecutado — pendiente de validar decisiones abiertas)
|
||||||
|
|
||||||
|
## Resumen
|
||||||
|
|
||||||
|
Dashboard para vigilar la **evolución temporal** de tres KPIs de venta del negocio aurgi/autingo
|
||||||
|
(recambios y servicios de automoción, multi-centro + ecommerce + call center) y, sobre todo, para
|
||||||
|
**detectar caídas**: cuándo baja la venta, cuándo baja el número de tickets y cuándo baja el ticket
|
||||||
|
medio, tanto a nivel total como desglosado por categoría de producto. El objetivo no es solo "ver
|
||||||
|
los números" sino responder *dónde y cuándo se está cayendo y por qué* (menos tickets vs. ticket más
|
||||||
|
bajo vs. una categoría concreta arrastrando al total).
|
||||||
|
|
||||||
|
## Contexto descubierto (fuente de verdad del modelo)
|
||||||
|
|
||||||
|
El contexto se ha reconstruido clonando los análisis del proyecto aurgi alojados en Gitea
|
||||||
|
(`dataforge/venta_web`, `dataforge/sale_prices_comprobation`, `dataforge/presupuestos_callcenter`).
|
||||||
|
No existe un repo paraguas `dataforge/aurgi`; cada análisis es su propio sub-repo. El código de la
|
||||||
|
web del negocio (GitHub) no es necesario: el modelo analítico vive en BigQuery y en las cards de
|
||||||
|
Metabase ya construidas.
|
||||||
|
|
||||||
|
**Plataforma:**
|
||||||
|
- Metabase: `https://reports.autingo.es`, API key en `pass metabase/aurgi-api-key`.
|
||||||
|
- Database analítica: id **6** (`DCBigQuery`, engine bigquery-cloud-sdk).
|
||||||
|
- Proyecto/Dataset BigQuery: `autingo-159109.psql_dcpublic` (réplica del Postgres del TPV/ERP).
|
||||||
|
|
||||||
|
**Tablas núcleo de venta (dataset `psql_dcpublic`):**
|
||||||
|
|
||||||
|
| Tabla | Rol en el modelo |
|
||||||
|
|---|---|
|
||||||
|
| `tpv_orders_order` | Cabecera de pedido: `id`, `customer_id`, `vehicle_id`, `terminal_id`, `total_cost`, `created_at` |
|
||||||
|
| `tpv_orders_invoice` | Factura (venta consumada). Match con order por `order_id` |
|
||||||
|
| `tpv_orders_orderitem` | Líneas de pedido: `order_id`, `product_id`, importe, `tax` → base del desglose por categoría |
|
||||||
|
| `tpv_orders_quote` | Presupuesto (no es venta; relevante para conversión, fuera de alcance v1) |
|
||||||
|
| `products` | Catálogo de producto: `id`, `nav_id`, `channel_id`, `show_on_ecommerce`, referencia normalizada |
|
||||||
|
| `ecommerce_categories` | Catálogo de categorías (candidato a fuente de "categoría") |
|
||||||
|
| `channels` | Canal de venta (`id`, `name`) — p. ej. ecommerce vs. TPV |
|
||||||
|
| `centers` + `tpv_terminals` | Centro físico (terminal → center). **159 y 162 = CALL CENTER** (excluir o segmentar) |
|
||||||
|
| `companies`, `tpv_customers`, `tpv_vehicles_vehicle` | Cliente / empresa / vehículo (matrícula) |
|
||||||
|
|
||||||
|
**Patrón de dashboard ya probado en este Metabase** (`presupuestos_callcenter/create_dashboard_v2.py`):
|
||||||
|
- Cards SQL nativo con **field-filters parametrizados** `[[AND {{filtro}}]]` compartidos por todo el tab.
|
||||||
|
- Colección de trabajo (presupuestos usó `collection_id 559`); dashboard de KPIs ya existente (nº 999).
|
||||||
|
- Field-ids ya identificados que reutilizaremos donde apliquen: `quote.created_at=16588`,
|
||||||
|
`center.id=17327`, `tpvuser.id=17965`, `company.id=17157`, `product.id=16698`.
|
||||||
|
|
||||||
|
## Definiciones de métricas (propuesta — a confirmar)
|
||||||
|
|
||||||
|
- **Ticket** = una **venta facturada** = 1 fila de `tpv_orders_invoice` (o `tpv_orders_order` con
|
||||||
|
factura asociada). Nº de tickets = `COUNT(DISTINCT invoice.id)`.
|
||||||
|
- **Venta (€)** = importe facturado. Propuesta: `SUM(tpv_orders_order.total_cost)` a nivel total;
|
||||||
|
para el desglose por categoría se suma a nivel **línea** (`tpv_orders_orderitem`). Hay que fijar si
|
||||||
|
el importe es **con o sin IVA** (existe campo `tax`) — debe ser coherente entre total y categorías.
|
||||||
|
- **Ticket medio (€)** = `Venta / Nº tickets`.
|
||||||
|
- **Caída** = variación negativa de cualquiera de los tres KPIs frente al **periodo anterior
|
||||||
|
comparable** (mes vs. mes anterior, y mismo mes año anterior — interanual, por la estacionalidad
|
||||||
|
fuerte del recambio: ITV, neumáticos por temporada, balizas V16, etc.).
|
||||||
|
|
||||||
|
## Arquitectura del dashboard
|
||||||
|
|
||||||
|
Un único dashboard, **3 secciones** (filtros globales compartidos arriba):
|
||||||
|
|
||||||
|
**Filtros globales (field-filters):** Rango de fechas (sobre la fecha de venta), Centro, Canal,
|
||||||
|
Categoría, Compañía. Todos mapeados a todas las cards vía el patrón `[[AND {{...}}]]`.
|
||||||
|
|
||||||
|
### Sección A — KPIs cabecera (fila de scalars con variación)
|
||||||
|
Tres `smartscalar` (o `scalar` + comparación): **Venta total**, **Nº tickets**, **Ticket medio**,
|
||||||
|
cada uno con su **variación % vs. periodo anterior** y un mini-sparkline. Aquí se ve "de un vistazo"
|
||||||
|
si hay caída global.
|
||||||
|
|
||||||
|
### Sección B — Progresión temporal (series)
|
||||||
|
- **Línea/área**: Venta por mes (con línea del año anterior superpuesta para comparar).
|
||||||
|
- **Línea**: Nº tickets por mes.
|
||||||
|
- **Línea**: Ticket medio por mes.
|
||||||
|
- **Barras + línea combinada**: Venta (barras) y Ticket medio (línea) en el mismo eje temporal —
|
||||||
|
permite ver si una caída de venta viene de menos tickets o de ticket más bajo.
|
||||||
|
|
||||||
|
### Sección C — Desglose por categoría (dónde está la caída)
|
||||||
|
- **Barras horizontales**: Venta por categoría en el periodo, ordenada desc.
|
||||||
|
- **Tabla con variación**: por categoría → Venta, Nº tickets, Ticket medio, **Δ% vs. periodo
|
||||||
|
anterior**, con formato condicional (rojo = caída). Esta es la tabla clave para "analizar caídas".
|
||||||
|
- **Heatmap / pivot** (mes × categoría) de la venta o del Δ% — detecta qué categoría empezó a caer y
|
||||||
|
cuándo.
|
||||||
|
- **Opcional**: mismo desglose por **centro** y por **canal** (misma estructura), si se quiere cruzar
|
||||||
|
la caída por ubicación/canal.
|
||||||
|
|
||||||
|
## SQL base (esqueleto parametrizado)
|
||||||
|
|
||||||
|
Una sola CTE de "ventas" reutilizada por casi todas las cards, con los field-filters inyectados. La
|
||||||
|
tabla cuya columna alimenta un field-filter va **sin alias** (convención del patrón existente):
|
||||||
|
|
||||||
|
```sql
|
||||||
|
WITH ventas AS (
|
||||||
|
SELECT
|
||||||
|
`psql_dcpublic.tpv_orders_invoice`.id AS ticket_id,
|
||||||
|
`psql_dcpublic.tpv_orders_order`.total_cost AS importe,
|
||||||
|
DATE_TRUNC(DATE(`psql_dcpublic.tpv_orders_order`.created_at), MONTH) AS mes,
|
||||||
|
`psql_dcpublic.centers`.id AS centro_id
|
||||||
|
-- categoria_id / canal_id segun la fuente que se confirme
|
||||||
|
FROM `psql_dcpublic.tpv_orders_order`
|
||||||
|
JOIN `psql_dcpublic.tpv_orders_invoice`
|
||||||
|
ON `psql_dcpublic.tpv_orders_invoice`.order_id = `psql_dcpublic.tpv_orders_order`.id
|
||||||
|
LEFT JOIN `psql_dcpublic.tpv_terminals`
|
||||||
|
ON `psql_dcpublic.tpv_orders_order`.terminal_id = `psql_dcpublic.tpv_terminals`.id
|
||||||
|
LEFT JOIN `psql_dcpublic.centers`
|
||||||
|
ON `psql_dcpublic.tpv_terminals`.center_id = `psql_dcpublic.centers`.id
|
||||||
|
WHERE 1=1
|
||||||
|
-- AND COALESCE(`psql_dcpublic.centers`.id,0) NOT IN (159,162) -- excluir call center si procede
|
||||||
|
[[AND {{fecha}}]]
|
||||||
|
[[AND {{centro}}]]
|
||||||
|
[[AND {{canal}}]]
|
||||||
|
[[AND {{categoria}}]]
|
||||||
|
)
|
||||||
|
SELECT mes, SUM(importe) AS venta, COUNT(DISTINCT ticket_id) AS tickets,
|
||||||
|
SAFE_DIVIDE(SUM(importe), COUNT(DISTINCT ticket_id)) AS ticket_medio
|
||||||
|
FROM ventas
|
||||||
|
GROUP BY mes ORDER BY mes;
|
||||||
|
```
|
||||||
|
|
||||||
|
Para el desglose por categoría se baja el grano a `tpv_orders_orderitem` (JOIN a `products` y a la
|
||||||
|
fuente de categoría) y se agrupa por `categoria`. **Gotcha de doble conteo**: al unir líneas, el
|
||||||
|
`COUNT(DISTINCT ticket_id)` sigue siendo correcto, pero la venta por categoría debe sumarse a nivel
|
||||||
|
línea, no de cabecera, para no duplicar `total_cost`.
|
||||||
|
|
||||||
|
## Detección de caídas (lo que de verdad pide el encargo)
|
||||||
|
|
||||||
|
1. **Variación vs. periodo anterior** en cada KPI (smartscalar con `previousValue` sobre breakout
|
||||||
|
mensual; ver gotcha smartscalar más abajo).
|
||||||
|
2. **Tabla por categoría con Δ%** y formato condicional rojo para Δ% < 0 → ranking de "quién cae".
|
||||||
|
3. **Comparativa interanual** (mismo mes del año pasado) como segunda serie en las líneas, por la
|
||||||
|
estacionalidad del sector.
|
||||||
|
4. **Descomposición de la caída**: al poner Venta, Tickets y Ticket medio juntos en el tiempo, se
|
||||||
|
distingue si la venta cae por *menos clientes* (tickets) o por *gastan menos* (ticket medio).
|
||||||
|
5. *(Futuro, opcional)* alerta por email/Slack de Metabase cuando Δ% mensual de venta o de una
|
||||||
|
categoría top supere un umbral negativo.
|
||||||
|
|
||||||
|
## Plan de ejecución por fases
|
||||||
|
|
||||||
|
1. **Exploración (barata, sin coste BQ relevante)** — confirmar contra `INFORMATION_SCHEMA` y el
|
||||||
|
metadata de Metabase DB6: (a) campo de fecha de venta correcto (`order.created_at` vs.
|
||||||
|
`invoice.created_at`), (b) si importe lleva IVA, (c) fuente real de "categoría" (`ecommerce_categories`
|
||||||
|
vs. familia NAV vs. `CASE` manual ya usado en `venta_web`), (d) field-ids de los filtros nuevos.
|
||||||
|
2. **Cards base** — crear las cards SQL parametrizadas (KPIs, series, desglose) en una colección
|
||||||
|
propia del dashboard. Validar cada query con `dry_run`/límite antes de guardar.
|
||||||
|
3. **Dashboard** — crear dashboard, añadir filtros globales, colocar cards en las 3 secciones,
|
||||||
|
copiar los `parameter_mappings` (patrón `metabase_dashboard_append_row` / donante).
|
||||||
|
4. **Variaciones y formato** — smartscalars con comparación, formato condicional de la tabla por
|
||||||
|
categoría, formato moneda EUR y porcentaje.
|
||||||
|
5. **Validación** — cuadrar el total del dashboard contra un número de control conocido (p. ej. venta
|
||||||
|
de un mes ya cerrado) y revisar visualmente las caídas detectadas.
|
||||||
|
|
||||||
|
## Decisiones abiertas (necesito tu confirmación antes de construir)
|
||||||
|
|
||||||
|
1. **¿Ticket = factura (`invoice`) o pedido (`order`)?** ¿Cuentan los pedidos sin factura?
|
||||||
|
2. **¿Importe con o sin IVA?** ¿`total_cost` es el bueno o hay que sumar líneas/base imponible?
|
||||||
|
3. **¿Qué es "categoría"?** ¿`ecommerce_categories`, la familia NAV del producto, o la clasificación
|
||||||
|
manual tipo `CASE` que ya aparece en `venta_web` (Balizas V16, etc.)?
|
||||||
|
4. **Call center (centros 159/162):** ¿se excluyen, se incluyen, o van como segmento aparte?
|
||||||
|
5. **Canales:** ¿separar ecommerce vs. TPV físico, o todo junto con filtro?
|
||||||
|
6. **Granularidad temporal:** ¿mensual (propuesto), o también semanal/diaria?
|
||||||
|
7. **¿Dashboard nuevo o ampliar uno existente** (p. ej. el 999 de presupuestos)? Recomiendo nuevo.
|
||||||
|
|
||||||
|
## Gaps / riesgos / gotchas
|
||||||
|
|
||||||
|
- **Sin confirmar todavía**: el join exacto producto→categoría y el campo de fecha/IVA (fase 1). El
|
||||||
|
SQL de arriba es esqueleto, no definitivo.
|
||||||
|
- **Coste BigQuery**: las tablas TPV son grandes. Usar `dry_run` antes de cada query nueva,
|
||||||
|
particionar por fecha en el `WHERE`, y evitar `SELECT *`. Metabase cachea, pero el primer render de
|
||||||
|
cada card paga su escaneo.
|
||||||
|
- **Smartscalar v0.59**: la comparación `anotherColumn` falla si la query devuelve varias filas;
|
||||||
|
para "vs. periodo anterior" hace falta breakout temporal + `previousValue` (documentado en el skill
|
||||||
|
meta_bigq). Alternativa: card SQL de 2 filas (actual/anterior) con `metabase_smartscalar_kpi_*`.
|
||||||
|
- **Doble conteo** al desglosar por categoría (líneas vs. cabecera) — sumar venta a nivel línea.
|
||||||
|
- **Definición de "caída"**: sin acordar el periodo de comparación (MoM vs. interanual) el dashboard
|
||||||
|
puede dar falsas alarmas por estacionalidad. Propuesta: mostrar ambas.
|
||||||
|
|
||||||
|
## Próximo paso
|
||||||
|
|
||||||
|
En cuanto confirmes las **decisiones abiertas** (sobre todo 1, 2 y 3), arranco la **fase 1
|
||||||
|
(exploración barata)** para fijar campos exactos y luego construyo cards + dashboard.
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
# Report 0002 — Bug de conteo de tickets en dashboard 734 (bruto vs neto) y plan de tests
|
||||||
|
|
||||||
|
- **Fecha:** 15/06/2026
|
||||||
|
- **Autor:** Claude (sesión meta_bigq)
|
||||||
|
- **Ámbito:** Metabase `reports.autingo.es` (DB 6), dashboard **734** ("BI - VENTAS - PORTFOLIO PRODUCTO"), pestaña **"Evolución Tickets"**. Fuente BigQuery `autingo-159109.bi_ventas_mart.cubo_ventas` + `autingo-159109.claude_bi.superficie_comparable_mat`.
|
||||||
|
- **Estado:** diagnóstico cerrado con evidencia; parche preparado y validado, **pendiente de aplicar**.
|
||||||
|
|
||||||
|
## Resumen
|
||||||
|
|
||||||
|
El KPI "Nº de tickets" del dashboard 734 **no cuenta lo mismo que el ticket medio del mismo dashboard**. El número de tickets se calcula con criterio **bruto** (cualquier documento que tenga al menos una línea con `PrecioVenta > 0`), mientras que el ticket medio, el detalle por centro y la evolución diaria usan criterio **neto** (solo documentos cuyo importe neto del ticket es `> 0`). Resultado: el N que se muestra es mayor que el N que realmente sustenta el ticket medio, la progresión interanual sale distorsionada, y no cuadra con el conteo que hace el equipo (Simón) en BigQuery.
|
||||||
|
|
||||||
|
La regla de negocio acordada es **"ticket = documento con neto > 0"** (se excluyen los documentos que quedan en cero o negativo tras abonos/devoluciones). Tres cards no cumplen esa regla y hay que corregirlas.
|
||||||
|
|
||||||
|
## Qué falla exactamente
|
||||||
|
|
||||||
|
En la pestaña "Evolución Tickets" conviven dos definiciones de "ticket" (un ticket = `numeroDocumento` único):
|
||||||
|
|
||||||
|
| Cards | Criterio aplicado | Qué cuenta |
|
||||||
|
|---|---|---|
|
||||||
|
| **10640** (N tickets), **10641** (N tickets N-1), **10642** (progresión N) | `WHERE PrecioVenta > 0` a nivel de **línea**, `COUNT(DISTINCT numeroDocumento)` | **BRUTO**: cualquier documento con ≥1 línea positiva, aunque el neto del ticket sea ≤ 0 |
|
||||||
|
| 9791/9792/9793 (ticket medio), 9794/9795 (evolución diaria), 9796 (detalle por centro) | `GROUP BY numeroDocumento HAVING SUM(PrecioVenta) > 0` | **NETO**: solo documentos con importe neto del ticket > 0 |
|
||||||
|
|
||||||
|
La card 10640 incluso se llama "N tickets (PV>0)", lo que induce a pensar que excluye los documentos en cero/negativo. No es así: el `PV>0` está a nivel de línea, no a nivel de documento neto.
|
||||||
|
|
||||||
|
### Evidencia (ejecución real, semana 24 — 8-14 jun 2026, ámbito Aurgi, superficie comparable)
|
||||||
|
|
||||||
|
Ejecutando las cards reales con los mismos parámetros:
|
||||||
|
|
||||||
|
| Métrica | Valor |
|
||||||
|
|---|---|
|
||||||
|
| Card 10640 — N tickets (lo que se muestra hoy) | **22.505** |
|
||||||
|
| N neto real (suma del detalle por centro 9796, criterio `HAVING SUM>0`) | **21.607** |
|
||||||
|
| Diferencia | **898 documentos (+4,16%)** contados de más |
|
||||||
|
| Venta neta total | 1.529.005 € |
|
||||||
|
| Ticket medio mostrado (card 9791) | **70,76 €** |
|
||||||
|
| Venta neta / N **neto** (21.607) | **70,76 €** → coincide exacto |
|
||||||
|
| Venta neta / N **bruto** (22.505) | 67,94 € → no coincide |
|
||||||
|
|
||||||
|
La identidad contable `Ticket medio = Venta / Nº tickets` solo se cumple con el N **neto** (21.607). Con el N que muestra el dashboard (22.505) no cuadra. Los 898 documentos de diferencia son tickets con alguna línea positiva pero neto del documento ≤ 0 (venta compensada con abono/devolución en la misma factura).
|
||||||
|
|
||||||
|
### Impacto en la progresión interanual
|
||||||
|
|
||||||
|
El sesgo bruto no es constante entre periodos: en la semana 24 el extra bruto sobre neto fue mayor en N-1 que en N. Eso infla más el año anterior y hace que la caída del dashboard (−10,6%) salga peor que la real (en torno a −5/−7% con criterio neto). Por eso el número de tickets y su progresión descuadran, mientras que el ticket medio y su progresión sí cuadran (ese sí usa neto).
|
||||||
|
|
||||||
|
## Causa raíz
|
||||||
|
|
||||||
|
Cada una de las 9 cards de tickets tiene su **propia copia** del SQL. La definición de "ticket" está duplicada card a card en lugar de vivir en un único sitio. Al editar las cards en momentos distintos, las de conteo (10640/10641/10642) quedaron con el criterio bruto y el resto con el neto. No hay ningún mecanismo que detecte que dos cards que deberían contar lo mismo divergen.
|
||||||
|
|
||||||
|
Alcance confirmado: las 9 cards tienen `dashboard_count = 1`, es decir, **solo se usan en el dashboard 734**. Corregir las 3 cards de conteo no afecta a ningún otro dashboard. (Las cards "Tickets por categoría/tipo/subcategoría/producto" de las pestañas *Foto Categorías* y *Portafolio Producto* miden otra cosa —cuántos tickets tocan cada segmento— y usan su propia lógica; quedan fuera de este parche y deben revisarse por separado si se quiere coherencia total del concepto "ticket".)
|
||||||
|
|
||||||
|
## Fix preparado (pendiente de aplicar)
|
||||||
|
|
||||||
|
Reescribir el SQL de las 3 cards de conteo para que apliquen el mismo criterio neto que el resto: `GROUP BY numeroDocumento HAVING SUM(PrecioVenta) > 0` y luego `COUNT(*)`. Solo cambia el texto del SQL; se preservan los template-tags, el tipo de visualización (scalar) y el formato.
|
||||||
|
|
||||||
|
Validación del parche contra Metabase (sin tocar producción), semana 24 / Aurgi / comparable:
|
||||||
|
|
||||||
|
- SQL actual (bruto) → 22.505 (idéntico a la card en producción).
|
||||||
|
- SQL del parche (neto) → 21.607 (idéntico a la suma del detalle por centro y a la base del ticket medio).
|
||||||
|
|
||||||
|
Tras aplicar, el N mostrado pasará de 22.505 a 21.607 y la progresión se recalculará. Es el comportamiento correcto, pero el número se moverve a la vista de todos: conviene avisar al equipo antes de aplicar.
|
||||||
|
|
||||||
|
## Plan de tests para que no vuelva a fallar
|
||||||
|
|
||||||
|
El objetivo es que cualquier divergencia futura entre las cards de tickets se detecte automáticamente, y a poder ser que sea imposible introducirla. Tres capas, de la más barata a la más sólida.
|
||||||
|
|
||||||
|
### Test 1 — Consistencia interna del N (el más importante)
|
||||||
|
|
||||||
|
Dentro de la pestaña, las tres vistas del número de tickets deben dar el mismo valor con los mismos filtros:
|
||||||
|
|
||||||
|
- `N_tickets (10640)` == `SUM(tickets) del detalle por centro (9796)` == `SUM(tickets) de la evolución diaria (9794)`.
|
||||||
|
- Lo mismo para N-1: `N_tickets_N1 (10641)` == `SUM(tickets_n1) de 9796`.
|
||||||
|
|
||||||
|
Implementación: una función que ejecuta las cards vía API Metabase (`metabase_execute_card`) con un set fijo de parámetros y compara. Tolerancia 0 (deben ser idénticos). Hoy este test **falla** (22.505 ≠ 21.607); tras el parche debe pasar.
|
||||||
|
|
||||||
|
### Test 2 — Identidad contable del ticket medio
|
||||||
|
|
||||||
|
El ticket medio debe ser exactamente la venta neta dividida por el número de tickets que se muestra:
|
||||||
|
|
||||||
|
- `ticket_medio (9791)` == `venta_neta_total / N_tickets (10640)`, con tolerancia de redondeo (±0,01 € o ±0,1%).
|
||||||
|
|
||||||
|
Hoy **falla** (70,76 ≠ 67,94 = 1.529.005 / 22.505). Tras el parche pasa porque numerador y denominador usan el mismo criterio. Este test es el que mejor captura la clase de bug "el N no es el denominador del TM".
|
||||||
|
|
||||||
|
### Test 3 — Auditoría estática anti-regresión
|
||||||
|
|
||||||
|
Revisar el `dataset_query` de toda card de la pestaña que cuente `numeroDocumento`: debe contener `HAVING SUM(... PrecioVenta ...) > 0` y **no** debe usar `WHERE PrecioVenta > 0` a nivel de línea como criterio de ticket. Se ejecuta leyendo el SQL de las cards por API (`metabase_get_card`), sin tocar BigQuery (coste cero). Detecta si alguien edita una card y reintroduce el patrón bruto.
|
||||||
|
|
||||||
|
### Prevención de fondo (elimina la causa raíz, mejor que cualquier test)
|
||||||
|
|
||||||
|
Definir "ticket" en **un solo sitio** en lugar de repetir el SQL en 9 cards:
|
||||||
|
|
||||||
|
- **Opción A (recomendada):** una vista o tabla en BigQuery —p. ej. `claude_bi.tickets_validos`— que materialice la lista de documentos válidos (doc con `SUM(PrecioVenta) > 0` dentro del perímetro comparable), con su fecha, centro, ámbito e importe neto. Todas las cards cuentan y suman sobre esa fuente. La definición vive en un único lugar y es imposible que dos cards divergan.
|
||||||
|
- **Opción B:** un *model* de Metabase (card `type=model`) con la definición de ticket, usado como `source-table` por las cards de la pestaña.
|
||||||
|
|
||||||
|
Con la opción A o B, el Test 3 deja de ser necesario (no hay copias que auditar) y los Tests 1 y 2 se vuelven verificaciones de cordura en lugar de detectores de bug.
|
||||||
|
|
||||||
|
### Dónde y cuándo corren los tests
|
||||||
|
|
||||||
|
- Empaquetar los Tests 1-3 como una función del registry (p. ej. `audit_ticket_consistency_metabase`), grupo `metabase`, que devuelve pass/fail por test con los valores concretos.
|
||||||
|
- Programarla a diario con el `dag_engine` (no cron) y alertar si algún test falla.
|
||||||
|
- Usarla además como **gate manual**: antes de aplicar cualquier cambio a las cards de tickets, correr la función; si no está verde, no se aplica.
|
||||||
|
|
||||||
|
## Cómo reproducir (para revisión)
|
||||||
|
|
||||||
|
```
|
||||||
|
# Token y cliente
|
||||||
|
pass metabase/aurgi-api-key # X-API-KEY de reports.autingo.es
|
||||||
|
|
||||||
|
# Parámetros usados en la evidencia (semana 24)
|
||||||
|
fecha = 2026-06-08~2026-06-14 ; compania = Aurgi ; superficie_comparable = Comparable ; comparativa = "Mismo dia semana"
|
||||||
|
|
||||||
|
# Cards: 10640 (N bruto), 9796 (detalle neto -> SUM tickets), 9791 (ticket medio)
|
||||||
|
# Esperado hoy: 10640 = 22.505 ; SUM(9796.tickets) = 21.607 ; 9791 = 70,76 = 1.529.005 / 21.607
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gaps / pendientes
|
||||||
|
|
||||||
|
- **Residuo de perímetro:** el conteo de Simón (~21.552) y la card neto (21.607) difieren en 55 documentos (0,25%). Probablemente diferencia en la definición de "comparable" (el dashboard une `superficie_comparable_mat` por **mes** natural) o en el ámbito exacto. No afecta a la conclusión, pero conviene cerrarlo si se quiere cuadre al documento.
|
||||||
|
- **Cards de otras pestañas:** "Tickets por categoría/tipo/subcategoría/producto" (11706/11735/11736/11737) usan su propia lógica de conteo; no se han auditado a fondo. Si se quiere un único concepto de "ticket" en todo el dashboard, hay que revisarlas.
|
||||||
|
- **Tests aún no implementados:** este report describe el plan; falta construir la función de auditoría y programarla en el `dag_engine`.
|
||||||
|
- **Parche sin aplicar:** las 3 cards siguen en producción con criterio bruto hasta que se confirme la aplicación.
|
||||||
Reference in New Issue
Block a user