Tu primer proyecto en 20 minutos

En el capítulo anterior dijimos que Observable Framework procesa los datos al construir el sitio y deja la interactividad al navegador del lector. Es momento de verificarlo con las manos. Este capítulo es una guía cronometrada: si lo sigues de principio a fin, en alrededor de veinte minutos tendrás un proyecto funcionando en tu computadora, con una página propia, un data loader en Python y un gráfico reactivo alimentado por esos datos.

No asumimos más conocimientos que los del capítulo 1. Tampoco nos detendremos aún a explicar cada detalle de la reactividad o del Markdown extendido; eso llega en los próximos dos capítulos. Aquí la meta es otra: sentir el flujo de trabajo completo, de la terminal al navegador, antes de teorizar sobre él.


Lo que vas a construir

Al terminar, tendrás en tu equipo un proyecto Observable Framework que incluye:

La Figura 2.1 resume la trayectoria que recorreremos: cinco fases encadenadas, cada una con un entregable verificable.

Figura 2.1. Las cinco fases del capítulo. Cada una agrupa tareas más finas y deja un entregable verificable antes de pasar a la siguiente.

Paso 1. Requisitos previos

Antes de escribir nada, verifica que tu equipo tenga las tres herramientas de la Tabla 2.1. Observable Framework se apoya en Node.js para servir y construir el sitio (Node.js Project, 2026); nosotros, además, usaremos uv en lugar de pip o venv para administrar el entorno de Python, porque resuelve dependencias en segundos y produce entornos reproducibles con un único comando (Astral Software Inc., 2024).

Herramienta Versión mínima Verificar con Cómo instalar
Node.js 18 LTS (se recomienda 20 o 22) node -v Descarga desde nodejs.org. En macOS, brew install node también funciona.
Git cualquiera git --version git-scm.com o, en macOS, xcode-select --install.
uv cualquiera reciente uv --version macOS/Linux: curl -LsSf https://astral.sh/uv/install.sh | sh
Windows (PowerShell): powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Tabla 2.1. Herramientas mínimas para seguir el capítulo. Node.js sirve el sitio y compila los bundles; Git versiona el proyecto; uv ejecuta los data loaders escritos en Python.
Usuarios de Windows

Abre una ventana de PowerShell, no el Command Prompt clásico. El instalador de uv y varios comandos posteriores (irm, activación de entornos) asumen PowerShell. Si tu terminal no reconoce irm, estás en la consola equivocada.

Usuarios de macOS y Linux

Si instalaste uv con el script oficial y la terminal no lo encuentra, abre una ventana nueva o recarga tu perfil con source ~/.zshrc (o ~/.bashrc). El instalador añade $HOME/.local/bin al PATH, pero el cambio solo surte efecto en sesiones nuevas.

Cuando los tres comandos de verificación respondan sin errores, continúa.


Paso 2. Crear el proyecto con el scaffolder

Observable Framework incluye un asistente oficial que genera un proyecto completo a partir de una plantilla (Observable, Inc., 2026). Sitúate en la carpeta donde quieras guardar tu proyecto y ejecuta:

npm init @observablehq@latest

El asistente lanza cinco preguntas, en este orden:

  1. ¿Dónde creamos el proyecto? Ruta relativa; el valor por defecto es ./hello-framework. Para seguir el capítulo, escribe ./mi-primer-proyecto.
  2. ¿Qué título le ponemos a la aplicación? Texto libre que se muestra en el encabezado del sitio.
  3. ¿Incluir archivos de ejemplo? Acepta la opción recomendada (Yes). El asistente copiará dos páginas de muestra (example-dashboard.md, example-report.md) y un data loader en JavaScript (launches.csv.js) que nos servirán de referencia.
  4. ¿Instalar dependencias? Elige npm para que ejecute npm install al terminar y ahorrarte un paso.
  5. ¿Inicializar un repositorio Git? Acepta (Yes) si piensas versionar el proyecto.
Scaffolder vs. clonar un repositorio

El comando npm init @observablehq es equivalente a clonar una plantilla y personalizarla. Lo recomendamos para aprender, porque produce un árbol mínimo y comentado. Cuando ya tengas un libro o tablero propio, clonar un repositorio existente (como el de este libro) será más rápido.

Al terminar el asistente, entra en la carpeta recién creada:

cd mi-primer-proyecto

Paso 3. Anatomía del proyecto

Antes de arrancar el servidor, vale la pena reconocer el terreno. El scaffolder dejó una estructura parecida a la de la Figura 2.2.

mi-primer-proyecto/
├── src/
│   ├── index.md           ← página de inicio
│   ├── example-dashboard.md
│   ├── example-report.md
│   ├── components/        ← módulos JS reutilizables
│   └── data/              ← data loaders (Python, JS, SQL, shell…)
├── observablehq.config.js ← configuración del sitio
├── package.json           ← dependencias de Node
├── .gitignore
└── README.md
Figura 2.2. Árbol de un proyecto recién generado. La carpeta src/ es el root del contenido: todo lo que quede dentro se convertirá en una página o en un recurso del sitio.

Cada archivo tiene un papel claro:

Si relacionas esta estructura con los cuatro pilares del capítulo 1, el mapa es directo: las páginas Markdown viven en src/, los data loaders en src/data/, el JavaScript reactivo habita dentro de cada Markdown y el sitio estático aparece, tras compilar, en dist/.


Paso 4. Levantar el servidor de desarrollo

Instala las dependencias de Node y arranca el modo desarrollo:

npm install
npm run dev

Abre http://localhost:3000 en el navegador. Verás la página de bienvenida que generó el scaffolder con dos ejemplos en el sidebar: un dashboard y un informe.

Recarga automática

Deja el servidor corriendo en segundo plano mientras editas. Cada vez que guardes un archivo .md, el navegador recargará solo la parte afectada: no hace falta refrescar ni reiniciar nada.

Si el puerto 3000 está ocupado (por ejemplo, porque otro proyecto ya usa ese número), detén el servidor con Ctrl+C y lanza npm run dev -- --port 3001.


Paso 5. Tu primera página

Crea el archivo src/hola.md con el siguiente contenido:

---
title: Hola, Observable
---

# Hola, Observable

Estoy escribiendo mi primera página del framework y quiero sentir la reactividad
desde el minuto uno.

```js
const repeticiones = view(Inputs.range([1, 20], {step: 1, value: 3, label: "¿Cuántas veces?"}));
```

```js
display(html`<p>${"Observable ".repeat(repeticiones)}</p>`);
```

Guarda el archivo y vuelve al navegador. Observarás dos cosas: primero, que la página apareció por sí sola en el sidebar sin tocar la configuración (el enrutamiento es automático: el archivo src/hola.md se publica en /hola); segundo, que al mover el deslizador la frase se repite el número de veces que marcas, sin recargar la página.

La Figura 2.3 reproduce ese mismo ejemplo aquí mismo para que compruebes el efecto sin cambiar de ventana. Es el tipo de demostración que vas a poder montar en segundos a lo largo del libro.

Figura 2.3. Mini-ejemplo reactivo idéntico al que acabas de crear en tu proyecto. La variable repeticiones se redefine al mover el deslizador y cualquier bloque que dependa de ella se reevalúa al instante.
Ver código
const repeticiones = view(Inputs.range([1, 20], {step: 1, value: 3, label: "¿Cuántas veces?"}));
display(html`<p>${"Observable ".repeat(repeticiones)}</p>`);

No hace falta importar Inputs, html ni display: todos están disponibles en cualquier página del framework. En el capítulo 3 veremos con calma cómo funciona esta reactividad y por qué los bloques se recalculan en el orden correcto aunque los escribas desordenados.


Paso 6. Tu primer data loader en Python

Hasta aquí los datos han vivido hardcodeados dentro de la página. Un data loader cambia las reglas: ahora el framework ejecutará un script al construir el sitio y pondrá el resultado a disposición del navegador como un archivo estático. Crea src/data/fuentes.csv.py con este contenido:

import csv
import sys

filas = [
    ("Datos abiertos",    82),
    ("Encuestas",         65),
    ("Redes sociales",    54),
    ("Sensores IoT",      41),
    ("Registros admin.",  37),
    ("Web scraping",      29),
    ("APIs públicas",     23),
    ("Bases internas",    18),
]

escritor = csv.writer(sys.stdout)
escritor.writerow(["recurso", "menciones"])
escritor.writerows(filas)

Presta atención al nombre del archivo: fuentes.csv.py. La doble extensión no es un capricho estético. Le dice al framework que el script produce un CSV por la salida estándar y que debe publicarlo como /data/fuentes.csv. Si lo hubieras llamado fuentes.json.py, se esperaría JSON. Este patrón, políglota por diseño, es el mismo que usan loaders escritos en R (*.csv.R), shell (*.json.sh) o SQL (*.parquet.sql).

Configurar el intérprete de Python

Si intentas construir ahora el sitio, el framework fallará con spawn python3 ENOENT. Por defecto invoca el comando python3 del sistema, que en Windows no existe y en otros sistemas puede apuntar a un intérprete sin las dependencias que necesitas. La solución es declarar, una sola vez, qué comando debe ejecutar cuando vea un .py. Abre observablehq.config.js y añade la propiedad interpreters:

export default {
  title: "Mi primer proyecto",
  interpreters: {
    ".py": ["uv", "run", "python", "-u"]
  }
  // …el resto de la configuración se queda igual
};

A partir de ahora, cada vez que el framework detecte un archivo .py, invocará uv run python -u. La herramienta uv creará un entorno virtual al vuelo y ejecutará el script. Para el loader de este capítulo no hace falta nada más: fuentes.csv.py solo usa la librería estándar de Python, así que uv run se basta solo.

Cuando toque instalar tu primera dependencia

En cuanto un loader necesite un paquete externo (Pandas, Polars, Requests…) tendrás que convertir el proyecto en un entorno gestionado por uv. La secuencia es: ejecuta uv init una sola vez en la raíz del proyecto para crear el pyproject.toml, y luego uv add nombre-paquete cada vez que sumes una dependencia. Si te saltas uv init, uv add responderá con error: No pyproject.toml found. En el capítulo 5 profundizaremos en este flujo.

La configuración se lee solo al arrancar

Si el servidor npm run dev ya estaba corriendo cuando editaste observablehq.config.js, deténlo con Ctrl+C y vuelve a lanzarlo. El framework solo lee ese archivo una vez al iniciar.

Guarda el archivo. La primera vez que una página pida data/fuentes.csv, el framework ejecutará el script, guardará el resultado en una caché (src/.observablehq/cache/) y lo servirá desde ahí hasta que el script cambie.


Paso 7. Consumir los datos, graficar y construir

Vuelve a src/hola.md y añade al final los tres bloques siguientes:

```js
const fuentes = FileAttachment("./data/fuentes.csv").csv({typed: true});
```

```js
const umbral = view(Inputs.range([0, 90], {step: 1, value: 30, label: "Umbral mínimo"}));
```

```js
Plot.plot({
  width: 640,
  marginLeft: 140,
  x: {grid: true, label: "Menciones →"},
  y: {label: null},
  marks: [
    Plot.barX(
      fuentes.filter(d => d.menciones >= umbral),
      {y: "recurso", x: "menciones", fill: "#E30A18", sort: {y: "-x"}}
    ),
    Plot.ruleX([umbral], {stroke: "#1D1D1B", strokeDasharray: "3,3"}),
    Plot.ruleX([0])
  ]
})
```

Tres piezas convergen aquí. La función FileAttachment resuelve la ruta del CSV y lo entrega ya parseado (con {typed: true} los números llegan como Number, no como cadenas). El deslizador define la variable umbral. Y el bloque de Plot filtra las filas, pinta las barras y dibuja una línea discontinua en el valor seleccionado. Como el gráfico depende de fuentes y de umbral, cualquier cambio en el CSV (tras un rebuild) o en el deslizador vuelve a dibujarlo automáticamente.

La Figura 2.4 reproduce el resultado dentro de este capítulo, con los mismos datos del data loader, para que veas a qué debes llegar.

Figura 2.4. El gráfico de tu primer proyecto, servido desde este capítulo con los mismos datos del loader. La línea discontinua marca el umbral; al moverlo, se recalcula el filtro sin tocar al servidor.

Compilar el sitio estático

Con todo en su sitio, detén el servidor de desarrollo y compila:

npm run build

El framework ejecutará cada data loader, almacenará sus salidas, renderizará cada página y escribirá en dist/ un conjunto de archivos HTML, CSS, JS y datos listos para publicarse. Puedes servir esa carpeta localmente para comprobar que no depende de Node ni de Python:

npx http-server dist
# o, si prefieres Python:
# python -m http.server -d dist
Lo que acaba de pasar

Durante el build, el framework ejecutó tu script de Python una sola vez y empaquetó el CSV resultante junto con el HTML. El sitio que estás sirviendo ya no necesita Python para funcionar: las barras se filtran en el navegador del lector con los datos precalculados. Es, en una frase, la promesa del capítulo 1 convertida en artefactos dentro de una carpeta.


Troubleshooting

Si algo sale mal, la Tabla 2.2 reúne los tropiezos más habituales al empezar. Casi todos aparecen por configuración del entorno y se resuelven sin tocar el código del capítulo.

Síntoma Causa probable Solución
EADDRINUSE: :::3000 al iniciar El puerto 3000 lo usa otro proceso. Lanza con npm run dev -- --port 3001 o cierra el proceso que lo ocupa.
command not found: uv El instalador añadió uv al PATH, pero tu terminal no la recargó. Abre una terminal nueva o recarga tu perfil (source ~/.zshrc, . $PROFILE en PowerShell).
ModuleNotFoundError en un loader Falta declarar la dependencia con uv. Ejecuta uv add nombre-paquete desde la raíz y reinicia el servidor.
error: No pyproject.toml found al ejecutar uv add El proyecto aún no está inicializado como entorno de uv. Ejecuta uv init una vez en la raíz del proyecto y reintenta uv add.
Cambios en observablehq.config.js que no aparecen El archivo de configuración solo se lee al arrancar. Detén npm run dev con Ctrl+C y vuelve a ejecutarlo.
Los datos siguen siendo los anteriores tras editar el loader Caché viva en src/.observablehq/cache/. Ejecuta npm run clean y recarga.
Error de sintaxis en un bloque js copiado Las comillas “curvas” que añaden algunos editores o PDFs no son válidas en JavaScript. Reemplázalas por comillas rectas (" o ').
Tabla 2.2. Errores comunes al montar el primer proyecto y cómo salir de ellos sin perder tiempo. Si tu problema no aparece aquí, revisa la salida de la terminal antes que los logs del navegador: el framework imprime allí casi todo.

Checklist final

Si llegaste hasta aquí en veinte o veinticinco minutos, ya puedes dar por cumplido el capítulo:

No es poco. Con esta base puedes construir, desde mañana, cualquier informe interactivo que se te ocurra. Lo que falta es profundizar: entender por qué la reactividad funciona como funciona, qué variantes de Markdown ofrece el framework y cómo organizar proyectos más grandes.

Ese es exactamente el recorrido del resto de la Parte I. En el capítulo 3, Markdown reactivo, despejaremos la mecánica de los bloques js, la diferencia entre variables ligadas y libres y por qué el orden en que los escribes no importa. Bret Victor imaginó hace más de una década documentos que fueran tanto ensayos como laboratorios (Victor, 2011); en los próximos capítulos veremos cómo Observable Framework convierte esa visión en una herramienta de uso diario.

Bibliografía

Astral Software Inc. (2024). uv: An extremely fast Python package and project manager [Https://docs.astral.sh/uv/].
Node.js Project. (2026). Node.js Documentation [Https://nodejs.org/en/docs].
Observable, Inc. (2026). Getting started with Observable Framework [Https://observablehq.com/framework/getting-started].
Victor, B. (2011). Explorable explanations [Http://worrydream.com/ExplorableExplanations/].