feat(metabase): expansion cards y documents — export, model, ProseMirror validation, copy nativo

Cards: export_card (CSV/XLSX/JSON), create_model (type=model para fuentes MBQL).
Documents: prosemirror_card_embed helper (resizeNode envolviendo cardEmbed),
validacion automatica contra whitelist TipTap antes de enviar, copy_document
refactorizado al endpoint nativo POST /api/document/:id/copy.
Docs: dataset_query legacy vs MBQL5, template-tags, whitelist de nodos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 19:03:19 +02:00
parent cb392a48ee
commit cf70385bca
9 changed files with 516 additions and 71 deletions
@@ -43,7 +43,58 @@ card = metabase_create_card(client, "Revenue", {
}, display="scalar")
```
## Ejemplo con template-tags (filtros)
```python
card = metabase_create_card(client, "Revenue filtrable", {
"database": 6, "type": "native",
"native": {
"query": "SELECT * FROM orders WHERE 1=1 [[AND date >= {{fecha_desde}}]]",
"template-tags": {
"fecha_desde": {
"name": "fecha_desde",
"display-name": "Fecha desde",
"id": "fecha_desde_tag",
"type": "text"
}
}
},
}, display="table")
```
## Formato dataset_query — IMPORTANTE
Hay DOS formatos y hay que saber cuál usar:
**Para CREAR cards (POST /api/card) — formato legacy:**
```python
{"database": id, "type": "native", "native": {"query": "SELECT ..."}}
```
**Lo que DEVUELVE Metabase al LEER cards (GET /api/card/:id) — formato MBQL5:**
```python
{"lib/type": "mbql/query", "database": id,
"stages": [{"lib/type": "mbql.stage/native", "native": "SELECT ..."}]}
```
Son representaciones distintas de lo mismo. Al leer una card existente y querer extraer el SQL:
```python
card = metabase_get_card(client, 42)
ds = card["dataset_query"]
# Formato MBQL5 (Metabase reciente)
stages = ds.get("stages", [])
if stages:
sql = stages[0].get("native", "")
# Formato legacy
else:
sql = ds.get("native", {}).get("query", "")
```
Al crear/actualizar cards, usar SIEMPRE el formato legacy — Metabase lo acepta y lo convierte internamente.
## Notas
dataset_query SQL nativo: `{"database": id, "type": "native", "native": {"query": "..."}}`
dataset_query MBQL: `{"database": id, "type": "query", "query": {"source-table": id, ...}}`
- `display` válidos: scalar, table, line, bar, pie, area, row, funnel, combo, pivot, map, scatter, waterfall, gauge, progress, smartscalar, sankey.
- Para queries que se repiten como base de otras cards, considerar crear un "model" (type="model") y referenciar desde otras cards con `source-table: "card__<id>"`.