feat(auto_metabase): push-all + describe/sql + auto-inject de dashcards

- push_all(): pushea todos los YAMLs de un proyecto (cards primero,
  dashboards despues), solo CREATE/UPDATE, resiliente a fallos por item
- explore.py: comandos describe (schema de DB) y sql (query ad-hoc con
  limite, cap 5MB, bloqueo de escrituras destructivas)
- payload.py: auto-inyecta id:-N, visualization_settings:{} y
  parameter_mappings:[] en dashcards nuevas para evitar 500 en push
- test_local: 11 cards + 3 dashboards sobre Sample Database de Metabase
- registry.db regenerado con auto_metabase_py_analytics indexada

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-13 13:14:05 +02:00
parent 386a5471e0
commit 310b409ae0
32 changed files with 3116 additions and 0 deletions
@@ -0,0 +1,28 @@
_meta:
kind: card
id: 48
slug: clientes_nuevos_por_mes
synced_at: '2026-04-13T10:49:59Z'
remote_updated_at: '2026-04-13T10:49:59.096661Z'
_refs:
database: sample_database
collection: null
payload:
description: Altas mensuales en PEOPLE (CREATED_AT)
archived: false
enable_embedding: false
query_type: native
name: Clientes nuevos por mes
type: question
dataset_query:
lib/type: mbql/query
database: sample_database
stages:
- lib/type: mbql.stage/native
native: "SELECT FORMATDATETIME(CREATED_AT, 'yyyy-MM') AS mes,\n COUNT(*) AS nuevos\n FROM PEOPLE\n GROUP BY mes\n\
\ ORDER BY mes"
parameter_mappings: []
display: line
collection_preview: true
visualization_settings: {}
parameters: []
@@ -0,0 +1,31 @@
_meta:
kind: card
id: 49
slug: clientes_por_edad
synced_at: '2026-04-13T10:49:59Z'
remote_updated_at: '2026-04-13T10:49:59.154517Z'
_refs:
database: sample_database
collection: null
payload:
description: Distribucion calculada desde BIRTH_DATE a fecha actual
archived: false
enable_embedding: false
query_type: native
name: Clientes por rango de edad
type: question
dataset_query:
lib/type: mbql/query
database: sample_database
stages:
- lib/type: mbql.stage/native
native: "SELECT\n CASE\n WHEN DATEDIFF('year', BIRTH_DATE, CURRENT_DATE) < 25 THEN '1) <25'\n WHEN DATEDIFF('year',\
\ BIRTH_DATE, CURRENT_DATE) < 35 THEN '2) 25-34'\n WHEN DATEDIFF('year', BIRTH_DATE, CURRENT_DATE) < 45 THEN '3)\
\ 35-44'\n WHEN DATEDIFF('year', BIRTH_DATE, CURRENT_DATE) < 55 THEN '4) 45-54'\n WHEN DATEDIFF('year', BIRTH_DATE,\
\ CURRENT_DATE) < 65 THEN '5) 55-64'\n ELSE '6) 65+'\n END AS rango_edad,\n COUNT(*) AS clientes\nFROM PEOPLE\n\
WHERE BIRTH_DATE IS NOT NULL\nGROUP BY rango_edad\nORDER BY rango_edad"
parameter_mappings: []
display: bar
collection_preview: true
visualization_settings: {}
parameters: []
@@ -0,0 +1,27 @@
_meta:
kind: card
id: 46
slug: clientes_por_estado
synced_at: '2026-04-13T10:49:59Z'
remote_updated_at: '2026-04-13T10:49:59.215737Z'
_refs:
database: sample_database
collection: null
payload:
description: Top 10 estados (US) por numero de clientes
archived: false
enable_embedding: false
query_type: native
name: clientes_por_estado
type: question
dataset_query:
lib/type: mbql/query
database: sample_database
stages:
- lib/type: mbql.stage/native
native: "SELECT STATE, COUNT(*) AS clientes\n FROM PEOPLE\n GROUP BY STATE\n ORDER BY clientes DESC\n LIMIT 10"
parameter_mappings: []
display: bar
collection_preview: true
visualization_settings: {}
parameters: []
@@ -0,0 +1,28 @@
_meta:
kind: card
id: 50
slug: clientes_por_source
synced_at: '2026-04-13T10:49:59Z'
remote_updated_at: '2026-04-13T10:49:59.276556Z'
_refs:
database: sample_database
collection: null
payload:
description: Distribucion por SOURCE (Google, Twitter, Facebook, Organic, Affiliate)
archived: false
enable_embedding: false
query_type: native
name: Clientes por canal
type: question
dataset_query:
lib/type: mbql/query
database: sample_database
stages:
- lib/type: mbql.stage/native
native: "SELECT COALESCE(SOURCE, 'unknown') AS canal,\n COUNT(*) AS clientes\n FROM PEOPLE\n GROUP BY canal\n\
\ ORDER BY clientes DESC"
parameter_mappings: []
display: pie
collection_preview: true
visualization_settings: {}
parameters: []
@@ -0,0 +1,27 @@
_meta:
kind: card
id: 51
slug: clientes_recientes
synced_at: '2026-04-13T10:49:59Z'
remote_updated_at: '2026-04-13T10:49:59.325878Z'
_refs:
database: sample_database
collection: null
payload:
description: Tabla de las 20 altas mas recientes en PEOPLE
archived: false
enable_embedding: false
query_type: native
name: Ultimos 20 clientes registrados
type: question
dataset_query:
lib/type: mbql/query
database: sample_database
stages:
- lib/type: mbql.stage/native
native: "SELECT NAME, EMAIL, CITY, STATE, SOURCE, CREATED_AT\n FROM PEOPLE\n ORDER BY CREATED_AT DESC\n LIMIT 20"
parameter_mappings: []
display: table
collection_preview: true
visualization_settings: {}
parameters: []
@@ -0,0 +1,28 @@
_meta:
kind: card
id: 52
slug: clientes_top_ciudades
synced_at: '2026-04-13T10:49:59Z'
remote_updated_at: '2026-04-13T10:49:59.37711Z'
_refs:
database: sample_database
collection: null
payload:
description: Ciudades con mas clientes
archived: false
enable_embedding: false
query_type: native
name: Top 15 ciudades
type: question
dataset_query:
lib/type: mbql/query
database: sample_database
stages:
- lib/type: mbql.stage/native
native: "SELECT CITY, STATE, COUNT(*) AS clientes\n FROM PEOPLE\n GROUP BY CITY, STATE\n ORDER BY clientes DESC\n LIMIT\
\ 15"
parameter_mappings: []
display: bar
collection_preview: true
visualization_settings: {}
parameters: []
@@ -0,0 +1,27 @@
_meta:
kind: card
id: 42
slug: clientes_total
synced_at: '2026-04-13T10:49:59Z'
remote_updated_at: '2026-04-13T10:49:59.441467Z'
_refs:
database: sample_database
collection: null
payload:
description: Numero total de clientes en PEOPLE
archived: false
enable_embedding: false
query_type: native
name: clientes_total
type: question
dataset_query:
lib/type: mbql/query
database: sample_database
stages:
- lib/type: mbql.stage/native
native: SELECT COUNT(*) AS total FROM PEOPLE
parameter_mappings: []
display: scalar
collection_preview: true
visualization_settings: {}
parameters: []
@@ -0,0 +1,28 @@
_meta:
kind: card
id: 45
slug: compras_por_mes
synced_at: '2026-04-13T10:49:59Z'
remote_updated_at: '2026-04-13T10:49:59.516411Z'
_refs:
database: sample_database
collection: null
payload:
description: Numero de compras agrupadas por mes
archived: false
enable_embedding: false
query_type: native
name: compras_por_mes
type: question
dataset_query:
lib/type: mbql/query
database: sample_database
stages:
- lib/type: mbql.stage/native
native: "SELECT FORMATDATETIME(CREATED_AT, 'yyyy-MM') AS mes,\n COUNT(*) AS compras\n FROM ORDERS\n GROUP BY\
\ mes\n ORDER BY mes"
parameter_mappings: []
display: line
collection_preview: true
visualization_settings: {}
parameters: []
@@ -0,0 +1,27 @@
_meta:
kind: card
id: 43
slug: compras_total
synced_at: '2026-04-13T10:49:59Z'
remote_updated_at: '2026-04-13T10:49:59.586558Z'
_refs:
database: sample_database
collection: null
payload:
description: Numero total de compras en ORDERS
archived: false
enable_embedding: false
query_type: native
name: compras_total
type: question
dataset_query:
lib/type: mbql/query
database: sample_database
stages:
- lib/type: mbql.stage/native
native: SELECT COUNT(*) AS total FROM ORDERS
parameter_mappings: []
display: scalar
collection_preview: true
visualization_settings: {}
parameters: []
@@ -0,0 +1,27 @@
_meta:
kind: card
id: 44
slug: ingresos_totales
synced_at: '2026-04-13T10:49:59Z'
remote_updated_at: '2026-04-13T10:49:59.663393Z'
_refs:
database: sample_database
collection: null
payload:
description: Suma de TOTAL de todas las compras
archived: false
enable_embedding: false
query_type: native
name: ingresos_totales
type: question
dataset_query:
lib/type: mbql/query
database: sample_database
stages:
- lib/type: mbql.stage/native
native: SELECT ROUND(SUM(TOTAL), 2) AS ingresos FROM ORDERS
parameter_mappings: []
display: scalar
collection_preview: true
visualization_settings: {}
parameters: []
@@ -0,0 +1,27 @@
_meta:
kind: card
id: 40
slug: test_count_users
synced_at: '2026-04-13T10:49:59Z'
remote_updated_at: '2026-04-13T10:49:59.727051Z'
_refs:
database: metabase_internal_pg
collection: null
payload:
description: otro cambio externo
archived: false
enable_embedding: false
query_type: native
name: test_count_users
type: question
dataset_query:
lib/type: mbql/query
database: metabase_internal_pg
stages:
- lib/type: mbql.stage/native
native: SELECT COUNT(*) AS users FROM core_user
parameter_mappings: []
display: scalar
collection_preview: true
visualization_settings: {}
parameters: []
@@ -0,0 +1,26 @@
_meta:
kind: card
id: 41
slug: test_users_by_locale
synced_at: '2026-04-13T10:49:59Z'
remote_updated_at: '2026-04-13T10:49:59.796377Z'
_refs:
database: metabase_internal_pg
collection: null
payload:
archived: false
enable_embedding: false
query_type: native
name: test_users_by_locale
type: question
dataset_query:
lib/type: mbql/query
database: metabase_internal_pg
stages:
- lib/type: mbql.stage/native
native: SELECT COALESCE(locale, 'unknown') AS locale, COUNT(*) AS n FROM core_user GROUP BY locale ORDER BY n DESC
parameter_mappings: []
display: bar
collection_preview: true
visualization_settings: {}
parameters: []
@@ -0,0 +1,29 @@
_meta:
kind: card
id: 47
slug: top_clientes
synced_at: '2026-04-13T10:49:59Z'
remote_updated_at: '2026-04-13T10:49:59.882916Z'
_refs:
database: sample_database
collection: null
payload:
description: Top 10 clientes por gasto total
archived: false
enable_embedding: false
query_type: native
name: top_clientes
type: question
dataset_query:
lib/type: mbql/query
database: sample_database
stages:
- lib/type: mbql.stage/native
native: "SELECT P.NAME AS cliente,\n P.EMAIL AS email,\n COUNT(O.ID) AS num_compras,\n ROUND(SUM(O.TOTAL),\
\ 2) AS total_gastado\n FROM PEOPLE P\n JOIN ORDERS O ON O.USER_ID = P.ID\n GROUP BY P.NAME, P.EMAIL\n ORDER BY\
\ total_gastado DESC\n LIMIT 10"
parameter_mappings: []
display: table
collection_preview: true
visualization_settings: {}
parameters: []