11 KiB
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 6DCBigQuery) + BigQueryautingo-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 enpass 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(otpv_orders_ordercon 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 campotax) — 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):
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)
- Variación vs. periodo anterior en cada KPI (smartscalar con
previousValuesobre breakout mensual; ver gotcha smartscalar más abajo). - Tabla por categoría con Δ% y formato condicional rojo para Δ% < 0 → ranking de "quién cae".
- Comparativa interanual (mismo mes del año pasado) como segunda serie en las líneas, por la estacionalidad del sector.
- 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).
- (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
- Exploración (barata, sin coste BQ relevante) — confirmar contra
INFORMATION_SCHEMAy el metadata de Metabase DB6: (a) campo de fecha de venta correcto (order.created_atvs.invoice.created_at), (b) si importe lleva IVA, (c) fuente real de "categoría" (ecommerce_categoriesvs. familia NAV vs.CASEmanual ya usado enventa_web), (d) field-ids de los filtros nuevos. - 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. - Dashboard — crear dashboard, añadir filtros globales, colocar cards en las 3 secciones,
copiar los
parameter_mappings(patrónmetabase_dashboard_append_row/ donante). - Variaciones y formato — smartscalars con comparación, formato condicional de la tabla por categoría, formato moneda EUR y porcentaje.
- 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)
- ¿Ticket = factura (
invoice) o pedido (order)? ¿Cuentan los pedidos sin factura? - ¿Importe con o sin IVA? ¿
total_costes el bueno o hay que sumar líneas/base imponible? - ¿Qué es "categoría"? ¿
ecommerce_categories, la familia NAV del producto, o la clasificación manual tipoCASEque ya aparece enventa_web(Balizas V16, etc.)? - Call center (centros 159/162): ¿se excluyen, se incluyen, o van como segmento aparte?
- Canales: ¿separar ecommerce vs. TPV físico, o todo junto con filtro?
- Granularidad temporal: ¿mensual (propuesto), o también semanal/diaria?
- ¿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_runantes de cada query nueva, particionar por fecha en elWHERE, y evitarSELECT *. Metabase cachea, pero el primer render de cada card paga su escaneo. - Smartscalar v0.59: la comparación
anotherColumnfalla 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) conmetabase_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.