Files
aurgi/reports/0002-2026-06-15-bug-conteo-tickets-734-bruto-vs-neto.md
T
egutierrez 6318a90021 chore: auto-commit (1 archivos)
- reports/0002-2026-06-15-bug-conteo-tickets-734-bruto-vs-neto.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-17 00:04:57 +02:00

121 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.