Buenas prácticas #5: dbt, la herramienta detrás de Prophecy

Sesión 5

Mauro Loprete

2026-09-14

Buenas practicas DBX

dbt

La tecnología detrás de Prophecy

Taller semanal · Sesión 5

Mauro Loprete

Cómo funciona Prophecy: el camino feliz

Lo que dibujás en el lienzo nunca corre tal cual: se compila dos veces antes de tocar un dato.

Gems visualesse persisten como JSON en .prophecy/
Compilador de Prophecytraduce cada gem a SQL + YAML
Proyecto dbtlo que se comitea y se revisa en la PR
Compilación dbtresuelve Jinja, ref() y el orden
WarehouseDatabricks ejecuta el SQL final
  • El editor visual es una vista: la verdad que corre en producción es el proyecto del repo
  • De la caja naranja en adelante ya no hay nada de Prophecy: es dbt estándar, el tema del resto de la sesión

Todo lo que mergeás y todo lo que corre en producción sale de la caja naranja, no del lienzo.

El desvío: gems que no compilan a SQL

Las gems con conexiones externas (Excel en SharePoint, archivos en Volumes, otras bases) no se pueden traducir a un SELECT: toman otro camino.

Gem con conexión externaSharePoint · Volumes · bases externas
Orquestador de Prophecycódigo propietario, fuera de dbt
Tabla temporalescribe el dato en el warehouse
Proyecto dbtla referencia como una tabla más
  • El tramo rojo levanta el orquestador y abre la conexión antes de mover un solo dato: el costo es de arranque, no de volumen. Leer un Excel de 5 filas puede tardar más de 2 minutos
  • Prophecy resuelve esas dependencias por su cuenta: el dato entra al proyecto dbt ya materializado como tabla
  • Es el tramo que job_generator todavía no cubre: para generar esos jobs hay que parsear esa lógica propietaria

Pipeline lento: antes de mirar el SQL, preguntate en qué camino están sus gems. El rojo cuesta caro aunque el dato sea chico.

Los mensajes del Prophecy IDE

Los carteles que el Studio muestra mientras editás son estas mismas etapas, contadas en vivo:

Initializing projectcarga los archivos del proyecto y prepara el editorStudio
Processing changesdetecta qué archivos dependen de tu cambio y los actualizaProphecy
Generating codetraduce las gems a SQL + YAML: acá se escribe el proyecto dbtProphecy
Compilingcalcula el esquema de salida de cada gemProphecy
DBT initializationprepara el sandbox de dbt y compila los modelosdbt
Runningorquesta la corrida y sus dependenciasAutomate
Running query in warehouseel SQL final corre en Databricks: visible en el Query HistoryWarehouse
  • Lo que no es SQL corre por Prophecy Automate: es el orquestador del camino rojo
  • Cuanto más grande el proyecto (más gems, más dependencias), más duran estos carteles

El problema: la PR que se aprueba sin leer

Un diff típico de Prophecy: seis archivos que nadie escribió a mano.

Mprophecy/PROJECT_1/models/PIPELINE_1/mov_diarios.sqlModelo · SQL
Mprophecy/PROJECT_1/models/PIPELINE_1/schema.ymlTests y docs
Mprophecy/PROJECT_1/dbt_project.ymlConfig global
Mprophecy/PROJECT_1/.prophecy/models/PIPELINE_1/mov_diarios.jsonMetadata visual
Mprophecy/PROJECT_1/.prophecy/pipeline/PIPELINE_1.jsonMetadata visual
Nprophecy/PROJECT_1/macros/limpiar_monto.sqlMacro

Todo esto corre en producción igual que el código escrito a mano, y la PR es el único filtro antes de que corra.

La regla: el diff tiene dos mitades

Prophecy no inventa un formato propio: comitea un proyecto dbt estándar más la metadata del lienzo.

El proyecto dbtSE REVISA
Modelo · SQLla lógica que corre
Tests y docslo que protege al dato
Config globalalcance: el proyecto entero
Macrouna función, muchos modelos
Dependenciascódigo de terceros
La metadata visualNO SE REVISA A MANO
models/*.jsoncada gem, editable en el lienzo
pipeline/*.jsonconexiones, dependencias, parámetros
El chequeo es de presencia: que ningún JSON falte ni se renombre. La lógica se revisa en el SQL de al lado.

Meta de hoy: que cada archivo del diff te diga qué es. La próxima sesión: qué preguntarle a cada uno, con un diff de ejemplo por cada color.

Qué es dbt

dbt viene de data build tool, y va en minúscula. La idea completa:

Cada transformación es un SELECT versionado en git. dbt lo convierte en tabla o vista, en el orden correcto, con tests.

Pieza Quién se ocupa
La lógica: un SELECT por modelo Vos, o Prophecy por vos
El DDL: CREATE TABLE AS, MERGE dbt lo escribe
El orden de ejecución dbt lo deduce de los ref()
Tests y documentación Los declarás en YAML, dbt los corre
  • dbt no procesa datos: compila SQL y se lo manda a Databricks. El que trabaja es el warehouse de siempre

Cómo se usa: Core, Cloud y nuestro caso

  • dbt Core: la herramienta open source de línea de comandos. Corre donde la invoques: tu máquina, un job, CI
  • dbt Cloud: la plataforma comercial de dbt Labs. IDE web, scheduler y ambientes, con Core adentro
  • Nuestro caso: Prophecy escribe el proyecto y lo comitea; los jobs lo ejecutan con Core. Nadie tipea dbt run a mano, pero los logs hablan este idioma:
Comando Qué hace
dbt run Construye los modelos: crea las tablas y vistas
dbt test Corre los tests declarados en los YAML
dbt build run + test, en orden de dependencias
dbt compile Genera el SQL final sin ejecutar nada (protagonista de la sesión 6)
dbt deps Instala los paquetes de packages.yml

Qué NO hace dbt

  • dbt solo genera SQL: no lee archivos, no abre conexiones, no mueve datos por su cuenta. Transforma lo que ya existe como tabla o lo que Databricks alcanza con SQL (Volumes, conexiones federadas)
  • La ingesta la resuelven otras herramientas. La clásica del stack es Fivetran: tan de la mano van que hoy son la misma empresa (dbt Labs y Fivetran se fusionaron en 2025)
  • Databricks tiene su propia apuesta: Lakeflow Connect (conectores propios, donde antes dependía de Fivetran) y Spark Declarative Pipelines (lo que antes era DLT, Delta Live Tables). Y en Beta, un editor visual estilo Prophecy: misma idea, JSON que compila a código SDP en SQL o PySpark

La regla: si el dato no se alcanza con un SELECT desde el warehouse, no es un problema de dbt. Es un problema de ingesta.

La conexión con Databricks: dbt-databricks

dbt-databricks es el adapter: el paquete que traduce lo que dbt quiere hacer al dialecto y las APIs de Databricks. La conexión vive en profiles.yml:

prophecy-profile:
  target: dev
  outputs:
    dev:
      type: databricks
      host: adb-xxxx.azuredatabricks.net
      http_path: /sql/1.0/warehouses/abc123   # un SQL warehouse
      catalog: dev
      schema: analytics
      token: "{{ env_var('DBT_TOKEN') }}"
  • Cada target apunta a un catálogo y esquema de Unity Catalog: eso decide dónde caen los modelos, y es lo que separa dev de prod
  • Las queries salen por un SQL warehouse: aparecen en el Query History como cualquier otra (sesión 1)

1

El proyecto, archivo por archivo

Lo que ves en el diff

dbt_project.yml: la cédula del proyecto

name: PROJECT_1
profile: databricks
model-paths: ["models"]

vars:
  fecha_inicio: "2020-01-01"

models:
  PROJECT_1:
    staging:
      +materialized: view
    marts:
      +materialized: table
  • Uno por proyecto, en la raíz: nombre, rutas y configuración por carpeta
  • El bloque models: fija defaults en cascada: todo staging/ sale como vista, todo marts/ como tabla. El + marca que es una config
  • vars: define valores globales (ya llegamos)

En una PR, una línea cambiada acá puede cambiar la materialización de cuarenta modelos. Pesa más que cualquier diff de un modelo suelto.

Un modelo: un archivo .sql con un SELECT

-- models/marts/mov_diarios.sql
{{ config(materialized='table') }}

SELECT cliente_id, fecha, SUM(monto) AS monto_dia
FROM {{ ref('stg_movimientos') }}
GROUP BY cliente_id, fecha
  • El nombre del archivo es el nombre del objeto en el catálogo: mov_diarios.sql crea catalogo.esquema.mov_diarios
  • Siempre es un SELECT: el CREATE TABLE AS lo escribe dbt. Si ves DDL a mano en un modelo, algo anda mal
  • materialized decide qué se crea: view (default), table, o incremental (tema de una próxima sesión)
  • El config() ya lo vimos en la sesión 4: ahí declaramos liquid_clustered_by

Una tabla = un modelo = un dueño. dbt reconstruye la tabla entera desde su SELECT en cada corrida: por eso dos pipelines no pueden escribir la misma tabla, el segundo pisaría al primero.

ref() y source(): así se arma el DAG

FROM {{ ref('stg_movimientos') }}         -- otro modelo del proyecto
FROM {{ source('core', 'movimientos') }}  -- tabla cruda que carga otro proceso
  • dbt reemplaza cada llamada por el nombre completo catalogo.esquema.tabla según el ambiente: el mismo modelo lee de dev en dev y de prod en prod
  • Con esas referencias dbt arma el DAG, el grafo de dependencias: de ahí saca el orden de ejecución y el lineage

El anti-patrón que hay que cazar en las PRs:

FROM prod.raw.movimientos    -- hardcodeado: corre, pero...
  • Queda fuera del DAG: el orden ya no lo protege, el lineage miente, y en dev sigue leyendo de prod

Un FROM sin llaves es una bandera roja en la PR.

sources.yml: las tablas que dbt no construye

version: 2
sources:
  - name: core
    database: prod        # en Databricks: el catálogo
    schema: raw
    tables:
      - name: movimientos
        loaded_at_field: fecha_carga
        freshness:
          warn_after: {count: 24, period: hour}
  • Declara las tablas crudas que ingesta otro proceso: la frontera entre nuestro proyecto y el resto del mundo
  • Es lo que source('core', 'movimientos') va a buscar
  • freshness: dbt source freshness avisa si la fuente lleva más de 24 horas sin datos nuevos

Una source nueva en el diff es una dependencia externa nueva: ¿quién carga esa tabla? ¿tiene dueño?

2

Jinja

Variables, macros y paquetes

Por qué el SQL tiene llaves

Jinja es un motor de plantillas: texto con huecos que se rellenan al compilar. dbt lo usa arriba del SQL.

SELECT * FROM {{ ref('stg_movimientos') }}
{% if target.name != 'prod' %}
LIMIT 1000
{% endif %}

Compilado contra dev, queda:

SELECT * FROM dev.analytics.stg_movimientos
LIMIT 1000
  • { ... } imprime un valor; {% ... %} es control de flujo: if, for
  • Se resuelve antes de ejecutar: Databricks nunca ve una llave, solo recibe el SQL final
  • target trae los datos del ambiente contra el que corrés: por eso el LIMIT existe solo fuera de prod

Variables: valores que viven fuera del código

# dbt_project.yml
vars:
  fecha_proceso: "2026-09-01"
-- en cualquier modelo
WHERE fecha >= '{{ var("fecha_proceso") }}'
# override puntual, sin tocar el repo
dbt run --vars '{fecha_proceso: "2026-01-01"}'
  • var("nombre", "default"): el segundo argumento evita el error si nadie la definió
  • Uso típico: fechas de reproceso, flags, umbrales que cambian entre corridas
  • El override de la línea de comandos gana sobre el dbt_project.yml

En la PR: si un modelo trae un valor mágico (una fecha, un umbral suelto), la pregunta es si debería ser una var.

Macros: funciones que generan SQL

-- macros/limpiar_monto.sql
{% macro limpiar_monto(col) %}
    CAST(REPLACE({{ col }}, ',', '.') AS DECIMAL(18, 2))
{% endmacro %}
-- en un modelo
SELECT {{ limpiar_monto('monto_txt') }} AS monto
  • Una macro se expande al compilar: el warehouse recibe el CAST, no la macro
  • La regla de negocio vive en un solo lugar; sin macro, ese CAST estaría copiado en veinte modelos con tres versiones distintas
  • Prophecy genera una en todos los proyectos SQL: generate_schema_name, que decide en qué esquema cae cada modelo. No se borra ni se toca sin charla previa

En la PR: un diff en macros/ alcanza a todos los modelos que la llaman. El radio de impacto no es el archivo, es el proyecto.

packages.yml: código de otros

packages:
  - package: dbt-labs/dbt_utils
    version: 1.3.0
  • Dependencias del proyecto, como un requirements.txt: dbt deps las instala en dbt_packages/
  • dbt_utils es el paquete estrella: tests genéricos y macros que evitan reinventar (deduplicación, pivots, comparar esquemas)
  • Lo instalado en dbt_packages/ es código de terceros: no se revisa, no se edita, y no debería estar comiteado
  • En Prophecy nadie edita este archivo a mano: se agrega desde Dependencies en los settings del proyecto, y lo elegido aterriza acá
  • Tres orígenes: otros proyectos Prophecy (Package Hub), repos de GitHub (fijados por tag, commit o branch) y paquetes del dbt Hub, cuyas macros quedan disponibles en el gem de Macro

En la PR: ¿la versión está fijada? Un paquete sin versión fija es un deploy distinto cada vez que corre dbt deps.

Cómo encaja todo: del modelo al warehouse

Macros propiasmacros/*.sql
Paquetesdbt_utils y cía
Modeloun SELECT con {{ llaves }}
Jinjaresuelve ref, source y var · expande macros
SQL finalpuro SQL, sin llaves
Adapterdbt-databricks: dialecto y APIs
Databricksel warehouse ejecuta
  • Las macros que expande Jinja vienen de dos lados: las del proyecto (macros/) y las que traen los paquetes de packages.yml
  • El adapter vive fuera del proyecto: es el dbt-databricks de profiles.yml, el que traduce al dialecto y las APIs de Databricks

Ningún dato pasa por dbt: el modelo termina siendo SQL final, y ese SQL entra al warehouse por el enchufe del adapter.

Para esta semana

  1. En el proyecto del pipeline que estas trabajando identifica los paquetes utilizados, ver si tiene alguna macro y como se referencian desde los modelos.
  2. Revisa en tus últimos PR con cambios “controlables” y fíjate si el cambio de lógica es entendible mirando el diff en los models/
  3. Si tenes ganas, instala dbt en tu entorno local y probá correr los modelos del proyecto. . . .

Para la próxima sesión, traé los hallazgos de los puntos 1 y 2.

Próxima sesión

La PR, a fondo

Un diff de ejemplo por cada tipo de archivo, el Job que corre en producción, el checklist completo y revisión de PRs reales del equipo

¿Preguntas? · Mauro Loprete

Anexo: dbt en tu máquina

# 1. Entorno de Python (3.9+)
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate

# 2. El adapter trae dbt Core adentro: una sola instalación
pip install dbt-databricks
dbt --version

# 3. Credenciales en ~/.dbt/profiles.yml (el de la slide del adapter)

# 4. Probar contra el proyecto clonado
dbt debug        # valida Python, profiles y conexión
dbt deps         # instala los paquetes de packages.yml
dbt compile      # compila todo sin ejecutar nada
  • host y http_path salen de Connection details del SQL warehouse; el token es un token personal de Databricks
  • dbt debug primero, siempre: valida la conexión antes de intentar cualquier otra cosa
  • Con dbt compile andando llegás con ventaja a la próxima sesión, que lo usa en vivo

Referencias