> ## Documentation Index
> Fetch the complete documentation index at: https://docs.firecrawl.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Seguimiento de cambios

> Detecta y supervisa cambios en el contenido web entre ejecuciones de scraping

<img src="https://mintcdn.com/firecrawl/vlKm1oZYK3oSRVTM/images/launch-week/lw3d12.webp?fit=max&auto=format&n=vlKm1oZYK3oSRVTM&q=85&s=cc56c24d15e1b2ed4806ddb66d0f5c3f" alt="Seguimiento de cambios" width="2400" height="1350" data-path="images/launch-week/lw3d12.webp" />

El seguimiento de cambios compara el contenido actual de una página con el de la última vez que la extrajiste con scraping. Añade `changeTracking` a tu array de `formats` para detectar si una página es nueva, no ha cambiado o se ha modificado y, opcionalmente, obtener un diff estructurado de lo que cambió.

* Funciona con `/scrape`, `/crawl` y `/batch/scrape`
* Dos modos de diff: `git-diff` para cambios a nivel de línea y `json` para comparación a nivel de campo
* Acotado a tu equipo y, opcionalmente, a una etiqueta que especifiques

<div id="how-it-works">
  ## Cómo funciona
</div>

Cada extracción con `changeTracking` habilitado almacena una captura y la compara con la captura anterior para esa URL. Las capturas se almacenan de forma persistente y no caducan, por lo que las comparaciones se mantienen precisas sin importar cuánto tiempo haya pasado entre extracciones.

| Extracción            | Resultado                                              |
| --------------------- | ------------------------------------------------------ |
| Primera vez           | `changeStatus: "new"` (no existe una versión anterior) |
| Contenido sin cambios | `changeStatus: "same"`                                 |
| Contenido modificado  | `changeStatus: "changed"` (datos de diff disponibles)  |
| Página eliminada      | `changeStatus: "removed"`                              |

La respuesta incluye estos campos en el objeto `changeTracking`:

| Campo              | Tipo                  | Descripción                                                                                                  |
| ------------------ | --------------------- | ------------------------------------------------------------------------------------------------------------ |
| `previousScrapeAt` | `string \| null`      | Marca de tiempo de la extracción anterior (`null` en la primera extracción)                                  |
| `changeStatus`     | `string`              | `"new"`, `"same"`, `"changed"` o `"removed"`                                                                 |
| `visibility`       | `string`              | `"visible"` (detectable a través de enlaces/sitemap) o `"hidden"` (la URL funciona pero ya no está enlazada) |
| `diff`             | `object \| undefined` | Diff a nivel de línea (solo presente en modo `git-diff` cuando el estado es `"changed"`)                     |
| `json`             | `object \| undefined` | Comparación a nivel de campo (solo presente en modo `json` cuando el estado es `"changed"`)                  |

<div id="basic-usage">
  ## Uso básico
</div>

Incluye tanto `markdown` como `seguimientoDeCambios` en el array `formats`. El formato `markdown` es obligatorio porque seguimientoDeCambios compara las páginas según su contenido en markdown.

<CodeGroup>
  ```python Python theme={null}
  from firecrawl import Firecrawl

  firecrawl = Firecrawl(api_key="fc-YOUR-API-KEY")

  result = firecrawl.scrape(
      "https://example.com/pricing",
      formats=["markdown", "changeTracking"]
  )

  print(result.changeTracking)
  ```

  ```js Node theme={null}
  import { Firecrawl } from 'firecrawl';

  const firecrawl = new Firecrawl({ apiKey: "fc-YOUR-API-KEY" });

  const result = await firecrawl.scrape('https://example.com/pricing', {
    formats: ['markdown', 'changeTracking']
  });

  console.log(result.changeTracking);
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \
    -H "Authorization: Bearer $FIRECRAWL_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://example.com/pricing",
      "formats": ["markdown", "changeTracking"]
    }'
  ```
</CodeGroup>

<div id="response">
  ### Respuesta
</div>

En el primer scraping, `changeStatus` es `"new"` y `previousScrapeAt` es `null`:

```json theme={null}
{
  "success": true,
  "data": {
    "markdown": "# Pricing\n\nStarter: $9/mo\nPro: $29/mo...",
    "changeTracking": {
      "previousScrapeAt": null,
      "changeStatus": "new",
      "visibility": "visible"
    }
  }
}
```

En scrapes posteriores, `changeStatus` indica si el contenido ha cambiado:

```json theme={null}
{
  "success": true,
  "data": {
    "markdown": "# Pricing\n\nStarter: $12/mo\nPro: $39/mo...",
    "changeTracking": {
      "previousScrapeAt": "2025-06-01T10:00:00.000+00:00",
      "changeStatus": "changed",
      "visibility": "visible"
    }
  }
}
```

<div id="git-diff-mode">
  ## Modo git-diff
</div>

El modo `git-diff` retorna cambios línea por línea en un formato similar a `git diff`. Pasa un objeto dentro del array `formats` con `modes: ["git-diff"]`:

<CodeGroup>
  ```python Python theme={null}
  result = firecrawl.scrape(
      "https://example.com/pricing",
      formats=[
          "markdown",
          {
              "type": "changeTracking",
              "modes": ["git-diff"]
          }
      ]
  )

  if result.changeTracking.changeStatus == "changed":
      print(result.changeTracking.diff.text)
  ```

  ```js Node theme={null}
  const result = await firecrawl.scrape('https://example.com/pricing', {
    formats: [
      'markdown',
      { type: 'changeTracking', modes: ['git-diff'] }
    ]
  });

  if (result.changeTracking.changeStatus === 'changed') {
    console.log(result.changeTracking.diff.text);
  }
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \
    -H "Authorization: Bearer $FIRECRAWL_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://example.com/pricing",
      "formats": [
        "markdown",
        { "type": "changeTracking", "modes": ["git-diff"] }
      ]
    }'
  ```
</CodeGroup>

<div id="response">
  ### Respuesta
</div>

El objeto `diff` contiene tanto un diff en texto plano como una representación JSON estructurada:

```json theme={null}
{
  "changeTracking": {
    "previousScrapeAt": "2025-06-01T10:00:00.000+00:00",
    "changeStatus": "changed",
    "visibility": "visible",
    "diff": {
      "text": "@@ -1,3 +1,3 @@\n # Pricing\n-Starter: $9/mo\n-Pro: $29/mo\n+Starter: $12/mo\n+Pro: $39/mo",
      "json": {
        "files": [{
          "chunks": [{
            "content": "@@ -1,3 +1,3 @@",
            "changes": [
              { "type": "normal", "content": "# Pricing" },
              { "type": "del", "ln": 2, "content": "Starter: $9/mo" },
              { "type": "del", "ln": 3, "content": "Pro: $29/mo" },
              { "type": "add", "ln": 2, "content": "Starter: $12/mo" },
              { "type": "add", "ln": 3, "content": "Pro: $39/mo" }
            ]
          }]
        }]
      }
    }
  }
}
```

El objeto estructurado `diff.json` contiene:

* `files`: matriz de archivos modificados (generalmente uno por página web)
* `chunks`: secciones de cambios dentro de un archivo
* `changes`: cambios de líneas individuales con `type` (`"add"`, `"del"` o `"normal"`), número de línea (`ln`) y `content`

<div id="json-mode">
  ## modo JSON
</div>

El modo `json` extrae campos específicos tanto de la versión actual como de la versión anterior de la página usando un esquema que defines. Esto es útil para hacer un seguimiento de cambios en datos estructurados como precios, niveles de stock o metadatos sin tener que analizar un diff completo.

Pasa `modes: ["json"]` con un `schema` que defina los campos a extraer:

<CodeGroup>
  ```python Python theme={null}
  result = firecrawl.scrape(
      "https://example.com/product/widget",
      formats=[
          "markdown",
          {
              "type": "changeTracking",
              "modes": ["json"],
              "schema": {
                  "type": "object",
                  "properties": {
                      "price": { "type": "string" },
                      "availability": { "type": "string" }
                  }
              }
          }
      ]
  )

  if result.changeTracking.changeStatus == "changed":
      changes = result.changeTracking.json
      print(f"Price: {changes['price']['previous']} → {changes['price']['current']}")
  ```

  ```js Node theme={null}
  const result = await firecrawl.scrape('https://example.com/product/widget', {
    formats: [
      'markdown',
      {
        type: 'changeTracking',
        modes: ['json'],
        schema: {
          type: 'object',
          properties: {
            price: { type: 'string' },
            availability: { type: 'string' }
          }
        }
      }
    ]
  });

  if (result.changeTracking.changeStatus === 'changed') {
    const changes = result.changeTracking.json;
    console.log(`Price: ${changes.price.previous} → ${changes.price.current}`);
  }
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \
    -H "Authorization: Bearer $FIRECRAWL_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://example.com/product/widget",
      "formats": [
        "markdown",
        {
          "type": "changeTracking",
          "modes": ["json"],
          "schema": {
            "type": "object",
            "properties": {
              "price": { "type": "string" },
              "availability": { "type": "string" }
            }
          }
        }
      ]
    }'
  ```
</CodeGroup>

<div id="response">
  ### Respuesta
</div>

Cada campo del esquema se devuelve con valores `previous` y `current`:

```json theme={null}
{
  "changeTracking": {
    "previousScrapeAt": "2025-06-05T08:00:00.000+00:00",
    "changeStatus": "changed",
    "visibility": "visible",
    "json": {
      "price": {
        "previous": "$19.99",
        "current": "$24.99"
      },
      "availability": {
        "previous": "In Stock",
        "current": "In Stock"
      }
    }
  }
}
```

También puedes proporcionar un `prompt` opcional para guiar la extracción mediante el LLM junto con el esquema.

<Info>
  El modo JSON usa extracción con LLM y cuesta **5 créditos por página**. El seguimiento básico de cambios y el modo `git-diff` no tienen costo adicional.
</Info>

<div id="using-tags">
  ## Uso de etiquetas
</div>

De forma predeterminada, el seguimiento de cambios se compara con el scrape más reciente de la misma URL realizado por tu equipo. Las etiquetas te permiten mantener **historiales de seguimiento independientes** para la misma URL, lo cual es útil cuando supervisas la misma página en diferentes intervalos o en distintos contextos.

<CodeGroup>
  ```python Python theme={null}
  # Monitoreo por hora (comparado con el último scrape con etiqueta "hourly")
  result = firecrawl.scrape(
      "https://example.com/pricing",
      formats=[
          "markdown",
          { "type": "changeTracking", "tag": "hourly" }
      ]
  )

  # Resumen diario (comparado con el último scrape con etiqueta "daily")
  result = firecrawl.scrape(
      "https://example.com/pricing",
      formats=[
          "markdown",
          { "type": "changeTracking", "tag": "daily" }
      ]
  )
  ```

  ```js Node theme={null}
  // Monitoreo por hora (comparado con el último scrape con etiqueta "hourly")
  const result = await firecrawl.scrape('https://example.com/pricing', {
    formats: [
      'markdown',
      { type: 'changeTracking', tag: 'hourly' }
    ]
  });

  // Resumen diario (comparado con el último scrape con etiqueta "daily")
  const result2 = await firecrawl.scrape('https://example.com/pricing', {
    formats: [
      'markdown',
      { type: 'changeTracking', tag: 'daily' }
    ]
  });
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \
    -H "Authorization: Bearer $FIRECRAWL_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://example.com/pricing",
      "formats": [
        "markdown",
        { "type": "changeTracking", "tag": "hourly" }
      ]
    }'
  ```
</CodeGroup>

<div id="crawl-with-change-tracking">
  ## Rastreo con seguimiento de cambios
</div>

Añade seguimiento de cambios a las operaciones de rastreo para supervisar un sitio completo en busca de cambios. Pasa el formato `changeTracking` dentro de `scrapeOptions`:

<CodeGroup>
  ```python Python theme={null}
  result = firecrawl.crawl(
      "https://example.com",
      limit=50,
      scrape_options={
          "formats": ["markdown", "changeTracking"]
      }
  )

  for page in result.data:
      status = page.changeTracking.changeStatus
      url = page.metadata.url
      print(f"{url}: {status}")
  ```

  ```js Node theme={null}
  const result = await firecrawl.crawl('https://example.com', {
    limit: 50,
    scrapeOptions: {
      formats: ['markdown', 'changeTracking']
    }
  });

  for (const page of result.data) {
    console.log(`${page.metadata.url}: ${page.changeTracking.changeStatus}`);
  }
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://api.firecrawl.dev/v2/crawl" \
    -H "Authorization: Bearer $FIRECRAWL_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://example.com",
      "limit": 50,
      "scrapeOptions": {
        "formats": ["markdown", "changeTracking"]
      }
    }'
  ```
</CodeGroup>

<div id="batch-scrape-with-change-tracking">
  ## Extracción por lotes con seguimiento de cambios
</div>

Usa [batch scrape](/es/features/batch-scrape) para supervisar un conjunto específico de URL:

<CodeGroup>
  ```python Python theme={null}
  result = firecrawl.batch_scrape(
      [
          "https://example.com/pricing",
          "https://example.com/product/widget",
          "https://example.com/blog/latest"
      ],
      formats=["markdown", {"type": "changeTracking", "modes": ["git-diff"]}]
  )
  ```

  ```js Node theme={null}
  const result = await firecrawl.batchScrape([
    'https://example.com/pricing',
    'https://example.com/product/widget',
    'https://example.com/blog/latest'
  ], {
    options: {
      formats: ['markdown', { type: 'changeTracking', modes: ['git-diff'] }]
    }
  });
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://api.firecrawl.dev/v2/batch/scrape" \
    -H "Authorization: Bearer $FIRECRAWL_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "urls": [
        "https://example.com/pricing",
        "https://example.com/product/widget",
        "https://example.com/blog/latest"
      ],
      "formats": [
        "markdown",
        { "type": "changeTracking", "modes": ["git-diff"] }
      ]
    }'
  ```
</CodeGroup>

<div id="scheduling-change-tracking">
  ## Programar el seguimiento de cambios
</div>

El seguimiento de cambios es más útil cuando haces scraping a intervalos regulares. Puedes automatizarlo con cron, planificadores en la nube o herramientas de orquestación de flujos de trabajo.

<div id="cron-job">
  ### Tarea cron
</div>

Crea un script que haga scraping de una URL y notifique cuando haya cambios:

```bash check-pricing.sh theme={null}
#!/bin/bash
RESPONSE=$(curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \
  -H "Authorization: Bearer $FIRECRAWL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://competitor.com/pricing",
    "formats": [
      "markdown",
      {
        "type": "changeTracking",
        "modes": ["json"],
        "schema": {
          "type": "object",
          "properties": {
            "starter_price": { "type": "string" },
            "pro_price": { "type": "string" }
          }
        }
      }
    ]
  }')

STATUS=$(echo "$RESPONSE" | jq -r '.data.changeTracking.changeStatus')

if [ "$STATUS" = "changed" ]; then
  echo "$RESPONSE" | jq '.data.changeTracking.json'
  # Envía alerta por correo electrónico, Slack, etc.
fi
```

Programa la tarea con `crontab -e`:

```bash theme={null}
0 */6 * * * /path/to/check-pricing.sh >> /var/log/price-monitor.log 2>&1
```

| Programación                | Expresión     |
| --------------------------- | ------------- |
| Cada hora                   | `0 * * * *`   |
| Cada 6 horas                | `0 */6 * * *` |
| Todos los días a las 09:00  | `0 9 * * *`   |
| Todos los lunes a las 08:00 | `0 8 * * 1`   |

<div id="cloud-and-serverless-schedulers">
  ### Planificadores en la nube y sin servidor
</div>

* **AWS**: regla de EventBridge que invoca una función Lambda
* **GCP**: Cloud Scheduler que invoca una Cloud Function
* **Vercel / Netlify**: funciones serverless activadas mediante cron
* **GitHub Actions**: flujos de trabajo programados con los desencadenadores `schedule` y `cron`

<div id="workflow-automation">
  ### Automatización de flujos de trabajo
</div>

Plataformas sin código como **n8n**, **Zapier** y **Make** pueden llamar periódicamente a la API de Firecrawl y enrutar los resultados a Slack, correo electrónico o bases de datos. Consulta las [guías sobre automatización de flujos de trabajo](/es/developer-guides/workflow-automation/n8n).

<div id="webhooks">
  ## Webhooks
</div>

Para operaciones asíncronas como `crawl` y *batch scrape*, usa [webhooks](/es/webhooks/overview) para recibir resultados de seguimientoDeCambios a medida que llegan en lugar de hacer *polling*.

<CodeGroup>
  ```python Python theme={null}
  job = firecrawl.start_crawl(
      "https://example.com",
      limit=50,
      scrape_options={
          "formats": [
              "markdown",
              {"type": "changeTracking", "modes": ["git-diff"]}
          ]
      },
      webhook={
          "url": "https://your-server.com/firecrawl-webhook",
          "headers": {"Authorization": "Bearer your-webhook-secret"},
          "events": ["crawl.page", "crawl.completed"]
      }
  )
  ```

  ```js Node theme={null}
  const { id } = await firecrawl.startCrawl('https://example.com', {
    limit: 50,
    scrapeOptions: {
      formats: [
        'markdown',
        { type: 'changeTracking', modes: ['git-diff'] }
      ]
    },
    webhook: {
      url: 'https://your-server.com/firecrawl-webhook',
      headers: { Authorization: 'Bearer your-webhook-secret' },
      events: ['crawl.page', 'crawl.completed']
    }
  });
  ```

  ```bash cURL theme={null}
  curl -s -X POST "https://api.firecrawl.dev/v2/crawl" \
    -H "Authorization: Bearer $FIRECRAWL_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://example.com",
      "limit": 50,
      "scrapeOptions": {
        "formats": [
          "markdown",
          { "type": "changeTracking", "modes": ["git-diff"] }
        ]
      },
      "webhook": {
        "url": "https://your-server.com/firecrawl-webhook",
        "headers": { "Authorization": "Bearer your-webhook-secret" },
        "events": ["crawl.page", "crawl.completed"]
      }
    }'
  ```
</CodeGroup>

El payload del evento `crawl.page` incluye el objeto `changeTracking` para cada página:

```json theme={null}
{
  "success": true,
  "type": "crawl.page",
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "data": [{
    "markdown": "# Pricing\n\nStarter: $12/mo...",
    "metadata": {
      "title": "Pricing",
      "url": "https://example.com/pricing",
      "statusCode": 200
    },
    "changeTracking": {
      "previousScrapeAt": "2025-06-05T12:00:00.000+00:00",
      "changeStatus": "changed",
      "visibility": "visible",
      "diff": {
        "text": "@@ -2,1 +2,1 @@\n-Starter: $9/mo\n+Starter: $12/mo"
      }
    }
  }]
}
```

Para más detalles sobre la configuración de webhooks (encabezados, metadatos, eventos, reintentos, verificación de firma), consulta la [documentación de webhooks](/es/webhooks/overview).

<div id="configuration-reference">
  ## Referencia de configuración
</div>

El conjunto completo de opciones disponibles al pasar un objeto de formato `seguimientoDeCambios`:

| Parameter | Type       | Default    | Description                                                                      |
| --------- | ---------- | ---------- | -------------------------------------------------------------------------------- |
| `type`    | `string`   | (required) | Debe ser `"changeTracking"`                                                      |
| `modes`   | `string[]` | `[]`       | Modos de diff a habilitar: `"git-diff"`, `"json"` o ambos                        |
| `schema`  | `object`   | (none)     | JSON Schema para la comparación a nivel de campo (requerido para el modo `json`) |
| `prompt`  | `string`   | (none)     | Prompt personalizado para guiar la extracción del LLM (usado con el modo `json`) |
| `tag`     | `string`   | `null`     | Identificador independiente para el historial de seguimiento                     |

<div id="data-models">
  ### Modelos de datos
</div>

<CodeGroup>
  ```ts TypeScript theme={null}
  interface ChangeTrackingResult {
    previousScrapeAt: string | null;
    changeStatus: "new" | "same" | "changed" | "removed";
    visibility: "visible" | "hidden";
    diff?: {
      text: string;
      json: {
        files: Array<{
          from: string | null;
          to: string | null;
          chunks: Array<{
            content: string;
            changes: Array<{
              type: "add" | "del" | "normal";
              ln?: number;
              ln1?: number;
              ln2?: number;
              content: string;
            }>;
          }>;
        }>;
      };
    };
    json?: Record<string, { previous: any; current: any }>;
  }
  ```

  ```python Python theme={null}
  class ChangeTrackingData(BaseModel):
      previous_scrape_at: Optional[str] = None
      change_status: str  # "new" | "same" | "changed" | "removed"
      visibility: str     # "visible" | "hidden"
      diff: Optional[Dict[str, Any]] = None
      json: Optional[Dict[str, Any]] = None
  ```
</CodeGroup>

<div id="important-details">
  ## Detalles importantes
</div>

<Warning>
  El formato `markdown` siempre debe incluirse junto con `changeTracking`. El seguimiento de cambios compara las páginas mediante su contenido en markdown.
</Warning>

* **Retención de snapshots**: Los snapshots se almacenan de forma persistente y no caducan. Una extracción realizada meses después de la anterior seguirá comparándose correctamente con el snapshot anterior.
* **Alcance**: Las comparaciones se limitan a tu equipo. La primera extracción de cualquier URL devuelve `"new"`, incluso si otros usuarios ya la han extraído.
* **Coincidencia de URL**: Las extracciones anteriores se asocian por la URL de origen exacta, el ID de equipo, el formato `markdown` y la `tag`. Mantén las URLs consistentes entre extracciones.
* **Consistencia de parámetros**: Usar configuraciones diferentes de `includeTags`, `excludeTags` u `onlyMainContent` en extracciones de la misma URL produce comparaciones poco fiables.
* **Algoritmo de comparación**: El algoritmo es resistente a cambios en espacios en blanco y en el orden del contenido. Se ignoran las URLs de origen de iframes para manejar la aleatorización de captcha/antibot.
* **Caché**: Las solicitudes con `changeTracking` omiten la caché del índice. Se ignora el parámetro `maxAge`.
* **Manejo de errores**: Supervisa el campo `warning` en las respuestas y contempla el caso en que el objeto `changeTracking` pueda estar ausente (esto puede ocurrir si la consulta a la base de datos para la extracción previa excede el tiempo de espera).

<div id="billing">
  ## Facturación
</div>

| Modo                          | Costo                                               |
| ----------------------------- | --------------------------------------------------- |
| Seguimiento básico de cambios | Sin costo adicional (créditos de scraping estándar) |
| Modo `git-diff`               | Sin costo adicional                                 |
| Modo `json`                   | 5 créditos por página                               |

> ¿Eres un agente de IA que necesita una clave de API de Firecrawl? Consulta [firecrawl.dev/agent-onboarding/SKILL.md](https://www.firecrawl.dev/agent-onboarding/SKILL.md) para ver las instrucciones de onboarding automatizado.
