> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-parallel-read-in-order-multi-part.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo de HackerNews Analyzer

> Instrumente una aplicación de Node.js con un agente y envíe logs, traces, métricas y session replay a ClickStack

export const AgentPrompt = ({prompt, title = "Configuración asistida por agente", description, outline, outlineLabel = "Lo que hará el agente", repositoryUrl, repositoryLabel = "ClickHouse/agent-skills"}) => {
  const [copied, setCopied] = useState(false);
  const handleCopy = async () => {
    const copyWithTextArea = () => {
      const textArea = document.createElement("textarea");
      textArea.value = prompt;
      textArea.style.position = "fixed";
      textArea.style.opacity = "0";
      document.body.appendChild(textArea);
      textArea.select();
      document.execCommand("copy");
      document.body.removeChild(textArea);
    };
    try {
      if (navigator?.clipboard?.writeText) {
        try {
          await navigator.clipboard.writeText(prompt);
        } catch {
          copyWithTextArea();
        }
      } else {
        copyWithTextArea();
      }
      setCopied(true);
      window.setTimeout(() => setCopied(false), 2000);
    } catch {}
  };
  return <div className="ch-agent-prompt-wrapper" data-mdast="ignore">
      <div className="ch-agent-prompt-main-row">
        <div className="ch-agent-prompt-left">
          <span className="ch-agent-prompt-title">{title}</span>
        </div>
        <div className="ch-agent-prompt-prompt-area" style={{
    overflow: "hidden"
  }}>
          <code className="ch-agent-prompt-prompt-text" style={{
    overflowX: "auto"
  }}>
            {prompt}
          </code>
        </div>
        <button type="button" className="ch-agent-prompt-copy-button" style={{
    boxSizing: "border-box",
    justifyContent: "center",
    minWidth: "8.25rem",
    whiteSpace: "nowrap"
  }} onClick={handleCopy} aria-label={copied ? "Copiado" : "Copiar prompt"}>
          {copied ? <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <polyline points="20 6 9 17 4 12" />
            </svg> : <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
              <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
            </svg>}
          <span style={{
    display: "grid",
    justifyItems: "center"
  }}>
            <span style={{
    gridArea: "1 / 1",
    visibility: copied ? "hidden" : "visible"
  }}>Copiar prompt</span>
            <span style={{
    gridArea: "1 / 1",
    visibility: copied ? "visible" : "hidden"
  }}>Copiado</span>
          </span>
        </button>
      </div>
      {(description || repositoryUrl) && <div className="ch-agent-prompt-sub-row">
          {description && <span className="ch-agent-prompt-description">{description}</span>}
          {repositoryUrl && <a className="ch-agent-prompt-repository-link" href={repositoryUrl} target="_blank" rel="noopener noreferrer">
              {repositoryLabel}
            </a>}
        </div>}
      {outline?.length > 0 && <details className="ch-agent-prompt-outline">
          <summary className="ch-agent-prompt-outline-summary">
            <svg width="12" height="12" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" className="ch-agent-prompt-outline-chevron" aria-hidden="true">
              <path d="M6.1584 3.13508C6.35985 2.94621 6.67627 2.95642 6.86514 3.15788L10.6151 7.15788C10.7954 7.3502 10.7954 7.64949 10.6151 7.84182L6.86514 11.8418C6.67627 12.0433 6.35985 12.0535 6.1584 11.8646C5.95694 11.6757 5.94673 11.3593 6.1356 11.1579L9.565 7.49985L6.1356 3.84182C5.94673 3.64036 5.95694 3.32394 6.1584 3.13508Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd" />
            </svg>
            <span>{outlineLabel}</span>
          </summary>
          <ol className="ch-agent-prompt-outline-list">
            {outline.map((item, index) => <li key={index}>{item}</li>)}
          </ol>
        </details>}
    </div>;
};

<Note>
  **En resumen**

  Clone [HackerNews Analyzer](https://github.com/ClickHouse/hn-news-analyzer), complete `.env` con el endpoint y el token de OTLP, y luego pegue el prompt del agente. El backend no necesita importaciones de OpenTelemetry; el agente configura `@hyperdx/node-opentelemetry` al iniciar el proceso.

  Tiempo necesario: unos 10 minutos
</Note>

HackerNews Analyzer es una aplicación de Node.js que consulta el conjunto de datos de HackerNews alojado en la demo pública de ClickHouse. Cada gráfico, tabla y cuadro de búsqueda ejecuta una consulta real de ClickHouse, por lo que cada interacción genera una traza cuyo span principal es la llamada HTTPS del backend a ClickHouse.

Se trata de un caso distinto de la [demo de reproducción de sesiones](/es/clickstack/example-datasets/session-replay), que instrumenta una aplicación que se ejecuta únicamente en el navegador con ClickStack local mediante Docker. Aquí obtiene autoinstrumentación del backend, spans de consultas de ClickHouse y reproducción de sesiones desde la misma aplicación.

<h2 id="prerequisites">
  Requisitos previos
</h2>

* Node 18+ y npm
* Un endpoint OTLP/HTTP de ClickStack y un token de ingestión:
  * **ClickHouse Cloud:** abra el servicio y luego vaya a **ClickStack** → **Configure su exporter de OpenTelemetry** → **Variables de entorno**. El protocolo es `http/protobuf`. El header es `authorization=<ingestion token>`, sin el prefijo `Bearer`.
  * **Collector local:** use `http://localhost:4318`. Si el collector no está protegido, deje `authorization=` vacío.

<h2 id="clone-the-repository">
  Clona el repositorio
</h2>

Clona [HackerNews Analyzer](https://github.com/ClickHouse/hn-news-analyzer), instala las dependencias y copia la plantilla de variables de entorno:

```bash theme={null}
git clone https://github.com/ClickHouse/hn-news-analyzer.git
cd hn-news-analyzer
npm install
cp .env.example .env
```

Completarás el archivo `.env` en los siguientes pasos y, después, instrumentarás la aplicación desde este directorio.

<h2 id="instrument-the-application">
  Instrumentar la aplicación
</h2>

<Steps>
  <Step title="Ejecute la aplicación" id="run-the-application">
    Desde el directorio clonado `hn-news-analyzer`, inicia la aplicación. La fuente de datos de ClickHouse usa de forma predeterminada el clúster de demostración público de solo lectura, por lo que se ejecuta sin configuración adicional:

    ```bash theme={null}
    ./run.sh
    ```

    Abra [http://localhost:5001](http://localhost:5001). Verá un selector de año, estadísticas resumidas, un gráfico de actividad, tablas con los principales usuarios y dominios, y un cuadro de búsqueda. Explore la aplicación: cambie de año y consulte las historias en detalle.

    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99-parallel-read-in-order-multi-part/j4TqNPW6aWoq7zwy/images/clickstack/getting-started/hackernews_main.webp?fit=max&auto=format&n=j4TqNPW6aWoq7zwy&q=85&s=8893dcb341dbb8cffdf3d78821ce949c" alt="La aplicación HackerNews Analyzer ejecutándose localmente" width="2872" height="1474" data-path="images/clickstack/getting-started/hackernews_main.webp" />
    </Frame>

    En este punto, la aplicación está en ejecución, pero no está instrumentada. ClickStack no muestra datos: está a la espera de telemetría.
  </Step>

  <Step title="Configurar el entorno" id="configure-environment">
    Los SDKs leen las variables estándar del exporter de OpenTelemetry. No están codificadas de forma fija en el código fuente. Abra `.env` y configure:

    ```bash theme={null}
    OTEL_SERVICE_NAME=hn-analyzer-api
    OTEL_EXPORTER_OTLP_ENDPOINT=<your-otlp-http-endpoint>
    OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
    OTEL_EXPORTER_OTLP_HEADERS=authorization=<your-ingestion-token>
    OTEL_TRACES_EXPORTER=otlp
    OTEL_METRICS_EXPORTER=otlp
    OTEL_LOGS_EXPORTER=otlp
    ```

    `OTEL_EXPORTER_OTLP_ENDPOINT` es el endpoint OTLP/HTTP (puerto `4318`). `OTEL_EXPORTER_OTLP_HEADERS` es el encabezado de autorización, con el formato `authorization=<token>` y sin prefijo `Bearer`.

    Si el collector no exige autenticación, deje el token vacío (`OTEL_EXPORTER_OTLP_HEADERS=authorization=`). La variable debe seguir presente; el SDK omite la inicialización si no está definida o está completamente vacía.

    El SDK para navegador reutiliza estos mismos valores. `vite.config.ts` incorpora el endpoint y el token en el paquete público durante la compilación, así que use un token de ingestión desechable, no uno de producción.
  </Step>

  <Step title="## Instrumente la aplicación" id="instrument">
    Elige una opción. Las tres llevan a la misma aplicación instrumentada.

    <Tabs>
      <Tab title="Instrumentación con un agente" id="instrument-with-an-agent">
        Con el repositorio clonado y `.env` completado, pega esta instrucción en un agente de programación **desde ese directorio** para instrumentar la aplicación.

        <AgentPrompt
          prompt="Usa curl para descargar, leer y seguir las instrucciones de: github.com/ClickHouse/hn-news-analyzer/blob/main/agent.md"
          description="Después de clonar hn-news-analyzer y completar .env, ejecuta esta instrucción desde ese directorio. Funciona con Claude Code, Cursor, Codex y otros agentes de programación."
          outline={[
"Confirma que estás en el directorio clonado de hn-news-analyzer y que .env ya contiene valores para OTEL_EXPORTER_OTLP_*. Detente si falta alguno de ellos.",
"Instala @hyperdx/node-opentelemetry y cambia run.sh para usar opentelemetry-instrument.",
"Instala @hyperdx/browser y habilita HyperDX.init y HyperDX.addAction.",
"Inicia la aplicación, confirma que las comprobaciones de estado de OTLP se realizan correctamente e indícate que navegues por http://localhost:5001.",
]}
        />
      </Tab>

      <Tab title="Instrumentación manual" id="instrument-manually">
        La instrumentación consta de tres partes: instalar los SDK, cambiar el comando de inicio y habilitar el SDK para navegador. Nada de esto modifica la lógica de negocio de la aplicación.

        <h3 id="install-node-sdk">
          Instala el SDK de Node
        </h3>

        ```bash theme={null}
        npm install @hyperdx/node-opentelemetry
        ```

        <h3 id="enable-run-sh-wrapper">
          Habilita el envoltorio en run.sh
        </h3>

        Al final de `run.sh` hay dos líneas `exec`. Comenta la línea de `node` sin instrumentación y descomenta la instrumentada:

        ```diff theme={null}
         # ANTES: node sin instrumentación:
        -exec node scripts/entrypoint.js
        +# exec node scripts/entrypoint.js

         # DESPUÉS: mismo código fuente, envuelto por opentelemetry-instrument:
        -# exec npx opentelemetry-instrument scripts/entrypoint.js
        +exec npx opentelemetry-instrument scripts/entrypoint.js
        ```

        Sigue iniciando la aplicación mediante `scripts/entrypoint.js`. Este shim llama a `require('console')` para que la captura de consola envuelva `console.log`. Si apuntas `opentelemetry-instrument` directamente a `dist/server/index.js`, se enviarán traces, pero los logs se descartarán silenciosamente.

        <h3 id="enable-browser-sdk">
          Habilita el SDK para navegador
        </h3>

        ```bash theme={null}
        npm install @hyperdx/browser
        ```

        En `src/web/telemetry.ts`, descomenta la importación, el bloque `HyperDX.init({...})` y `HyperDX.addAction` en `recordAction()`:

        ```diff theme={null}
        -// import HyperDX from '@hyperdx/browser';
        +import HyperDX from '@hyperdx/browser';

         export function initTelemetry(): void {
        -  // HyperDX.init({
        -  //   url: __OTLP_ENDPOINT__,
        -  //   apiKey: __OTLP_AUTH_TOKEN__,
        -  //   service: 'hn-analyzer-web',
        -  //   tracePropagationTargets: [/localhost:5001/i, /\/api\//i],
        -  //   consoleCapture: true,
        -  //   advancedNetworkCapture: true,
        -  // });
        +  HyperDX.init({
        +    url: __OTLP_ENDPOINT__,
        +    apiKey: __OTLP_AUTH_TOKEN__,
        +    service: 'hn-analyzer-web',
        +    tracePropagationTargets: [/localhost:5001/i, /\/api\//i],
        +    consoleCapture: true,
        +    advancedNetworkCapture: true,
        +  });
         }
        ```

        `__OTLP_ENDPOINT__` y `__OTLP_AUTH_TOKEN__` son constantes de tiempo de compilación que `vite.config.ts` inyecta a partir de los mismos valores `OTEL_EXPORTER_OTLP_*` que usa el backend.

        <Warning>
          El token de ingestión se incluye en el paquete público del navegador y cualquiera que inspeccione la pestaña Network puede leerlo. Usa un token desechable.
        </Warning>
      </Tab>

      <Tab title="Usa una rama preinstrumentada" id="use-the-instrumented-branch">
        Para omitir la instrumentación y empezar con una aplicación ya instrumentada, cambia a la [rama `instrumented`](https://github.com/ClickHouse/hn-news-analyzer/tree/instrumented).

        ```bash theme={null}
        git checkout instrumented
        npm install
        ```

        No ejecutes `./reset.sh` en esta rama a menos que quieras eliminar los SDK.
      </Tab>
    </Tabs>
  </Step>

  <Step title="Genere tráfico y consulte los datos de telemetría" id="generate-traffic-and-view-telemetry">
    Reinicie la aplicación para que se apliquen el nuevo comando de inicio y el paquete del navegador recién compilado:

    ```bash theme={null}
    # Ctrl-C the previous run, then:
    ./run.sh
    ```

    Confirme que el banner de inicio muestre tres líneas "Health check passed" para `/v1/traces`, `/v1/metrics` y `/v1/logs`. Recargue la pestaña del navegador para que Vite sirva el paquete actualizado; después, cambie de año y abra historias para generar tráfico.

    Abra la UI de ClickStack:

    1. Vaya a **Búsqueda** y filtre por los últimos 5 minutos. Los logs de `hn-analyzer-api` comenzarán a llegar.

    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99-parallel-read-in-order-multi-part/j4TqNPW6aWoq7zwy/images/clickstack/getting-started/instrument_app_clickstack_logs.webp?fit=max&auto=format&n=j4TqNPW6aWoq7zwy&q=85&s=e93104dd5b9ee8d297a451510c1273b3" alt="Búsqueda de ClickStack que muestra logs de hn-analyzer-api de los últimos cinco minutos" width="3018" height="1578" data-path="images/clickstack/getting-started/instrument_app_clickstack_logs.webp" />
    </Frame>

    2. Haga clic en una solicitud y recorra el trace hacia arriba. Verá el span del handler de Express, un span HTTP hijo que apunta a `sql-clickhouse.clickhouse.com` con una duración de red real y registros de `console.log` correlacionados en el mismo trace.

    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99-parallel-read-in-order-multi-part/j4TqNPW6aWoq7zwy/images/clickstack/getting-started/instrument_app_clickstack_traces.webp?fit=max&auto=format&n=j4TqNPW6aWoq7zwy&q=85&s=a421c9aebd0d2e5966c5193f70c89667" alt="Trace de ClickStack con un span de handler de Express y un span HTTP hijo hacia ClickHouse" width="2398" height="1590" data-path="images/clickstack/getting-started/instrument_app_clickstack_traces.webp" />
    </Frame>

    3. Abra **Session Replay** para reproducir un video navegable de una sesión del navegador, sincronizado con la línea de tiempo del trace.

    <Frame>
      <img src="https://mintcdn.com/private-7c7dfe99-parallel-read-in-order-multi-part/j4TqNPW6aWoq7zwy/images/clickstack/getting-started/instrument_app_clickstack_sessions.webp?fit=max&auto=format&n=j4TqNPW6aWoq7zwy&q=85&s=6d9af2c26c59ee42f2df33f40fd40f62" alt="Reproducción de sesión de ClickStack sincronizada con la línea de tiempo del trace" width="2408" height="1580" data-path="images/clickstack/getting-started/instrument_app_clickstack_sessions.webp" />
    </Frame>

    Los logs, las métricas, los traces y las reproducciones de sesión llegan a la misma UI, comparten el mismo lenguaje de consulta y se correlacionan automáticamente.
  </Step>
</Steps>

<h2 id="learn-more">
  Más información
</h2>

* [HackerNews Analyzer](https://github.com/ClickHouse/hn-news-analyzer): el repositorio de demostración que instrumenta esta guía.
* [reproducción de sesiones](/es/clickstack/features/session-replay): descripción general de la funcionalidad, opciones de SDK y controles de privacidad.
* [reproducción de sesiones Demo](/es/clickstack/example-datasets/session-replay): una demo autónoma con una instancia local de ClickStack.
* [Primeros pasos con ClickStack](/es/clickstack/getting-started/index): despliegue de ClickStack e ingesta de tus primeros datos.
* [Todos los conjuntos de datos de ejemplo](/es/clickstack/example-datasets/index): otros conjuntos de datos de ejemplo y guías.
