# 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.