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

# Transformaciones de datos con código

> Transforma y calcula campos con JavaScript dentro del pipeline de ingesta de woku, sin middleware externo

<Info>
  **Disponible en el plan Corporate.** Esta funcionalidad forma parte de las capacidades empresariales de woku. [Conversa con nuestro equipo comercial](https://woku.app/pricing).
</Info>

El motor de ingesta de **woku** incluye una etapa de **transformación con
código** que se ejecuta dentro del mismo pipeline. Esto significa que puedes
limpiar, derivar y calcular campos con **JavaScript** sobre cada registro que
entra, **sin necesidad de un middleware externo** (ni Zapier, ni una Lambda
propia, ni un proceso ETL aparte). El código corre como parte de la ingesta
y su salida continúa hacia el resto del flujo.

<Note>
  La transformación con código está disponible en el plan **Corporate**. Se
  configura por fuente de ingesta y se versiona junto con el resto de la
  configuración del pipeline.
</Note>

## Dónde corre en el pipeline

Cada registro que ingresa atraviesa el pipeline en este orden:

<Steps>
  <Step title="Ingesta">
    El registro entra por evento (un webhook o llamada a la API) o por lote
    (un archivo o carga batch). woku lo normaliza a un objeto JSON.
  </Step>

  <Step title="Validación de entrada">
    Se valida la forma del registro contra el esquema de la fuente. Lo que no
    valida se rechaza o se enruta a la cola de errores antes de transformar.
  </Step>

  <Step title="Transformación con código">
    Tu función JavaScript recibe el registro validado, puede derivar nuevos
    campos, normalizar fechas/zonas horarias, calcular porcentajes dentro de
    un segmento y aplicar lógica condicional. Devuelve el registro
    transformado.
  </Step>

  <Step title="Persistencia y distribución">
    El registro transformado se almacena y queda disponible para reportes,
    alertas y extracción por API.
  </Step>
</Steps>

La transformación corre **siempre del lado del servidor**, dentro del límite
de la ingesta. No expones credenciales ni mantienes infraestructura propia:
escribes la función, la guardas y woku la ejecuta por cada registro.

## Runtime soportado

* **Lenguaje:** JavaScript (sintaxis ECMAScript moderna, ES2022).
* **Forma de la función:** exportas una función `transform(record, context)`
  que devuelve el registro transformado (o `null` para descartarlo).
* **Aislamiento:** cada ejecución corre en un *sandbox* sin acceso a red ni
  al sistema de archivos. Es una transformación pura sobre el dato de entrada.
* **Determinismo:** la función no debe depender de estado externo. Para la
  fecha de proceso usa `context.now` (provisto por woku) en lugar de
  `Date.now()`, de modo que reprocesar un lote dé el mismo resultado.
* **Utilidades disponibles:** `context` expone helpers de fecha/zona horaria
  y metadatos del registro (fuente, segmento, marca de tiempo de ingesta).

### Forma de entrada y salida

La función recibe dos argumentos y devuelve un objeto:

<ParamField path="record" type="object">
  El registro ya validado, como objeto JSON plano. Es el dato tal como entró
  a la ingesta.
</ParamField>

<ParamField path="context" type="object">
  Metadatos y utilidades de ejecución:
  `context.now` (ISO 8601 del momento de proceso), `context.timezone` (zona
  horaria de la empresa), `context.segment` (segmento actual, con su total
  para cálculos de porcentaje) y `context.source` (identificador de la
  fuente de ingesta).
</ParamField>

La **salida** debe ser un objeto JSON. Devuelve el `record` con los campos
nuevos agregados, o `null` si quieres descartar ese registro del flujo.

<h2 id="transformacion-de-campos">
  Transformación de campos
</h2>

Esta sección documenta cómo **transformar campos con JavaScript** para
generar nuevos campos, aplicar condiciones, normalizar fechas y zonas
horarias, y calcular porcentajes dentro de un segmento.

<CodeGroup>
  ```js Derivar un campo nuevo theme={null}
  // Genera un campo "fullName" combinando nombre y apellido.
  export function transform(record, context) {
    record.fullName = `${record.firstName ?? ''} ${record.lastName ?? ''}`.trim();
    return record;
  }
  ```

  ```js Lógica condicional theme={null}
  // Clasifica un NPS (0..10) en promotor / pasivo / detractor.
  export function transform(record, context) {
    const score = Number(record.npsScore);

    if (score >= 9) {
      record.npsCategory = 'promotor';
    } else if (score >= 7) {
      record.npsCategory = 'pasivo';
    } else {
      record.npsCategory = 'detractor';
    }

    return record;
  }
  ```

  ```js Fechas y zona horaria theme={null}
  // Normaliza una fecha a la zona horaria de la empresa
  // y deriva la fecha local en formato YYYY-MM-DD.
  export function transform(record, context) {
    const tz = context.timezone; // p. ej. "America/Santiago"
    const local = new Intl.DateTimeFormat('en-CA', {
      timeZone: tz,
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
    }).format(new Date(record.createdAt));

    record.localDate = local;     // "2026-05-29"
    record.timezone = tz;
    return record;
  }
  ```

  ```js Porcentaje dentro de un segmento theme={null}
  // Calcula qué porcentaje del total del segmento representa este registro.
  export function transform(record, context) {
    const total = context.segment.total; // total de registros del segmento
    const value = Number(record.amount);

    record.segmentPercentage = total > 0
      ? Math.round((value / total) * 10000) / 100 // 2 decimales
      : 0;

    return record;
  }
  ```
</CodeGroup>

### Encadenar varias transformaciones

Una sola función puede combinar todas las operaciones anteriores: derivar
campos, aplicar condiciones, normalizar la fecha y calcular el porcentaje en
un único `transform`.

```js theme={null}
export function transform(record, context) {
  // 1. Campo derivado
  record.fullName = `${record.firstName ?? ''} ${record.lastName ?? ''}`.trim();

  // 2. Condición
  const score = Number(record.npsScore);
  record.npsCategory = score >= 9 ? 'promotor' : score >= 7 ? 'pasivo' : 'detractor';

  // 3. Fecha en zona horaria de la empresa
  record.localDate = new Intl.DateTimeFormat('en-CA', {
    timeZone: context.timezone,
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
  }).format(new Date(record.createdAt ?? context.now));

  // 4. Porcentaje dentro del segmento
  const total = context.segment.total;
  record.segmentPercentage = total > 0
    ? Math.round((Number(record.amount) / total) * 10000) / 100
    : 0;

  return record;
}
```

<Warning>
  Si la función lanza una excepción para un registro, ese registro se enruta a
  la **cola de errores** con el detalle del fallo, sin detener la ingesta del
  resto del lote. Revisa la cola de errores para corregir datos o ajustar la
  función.
</Warning>

## Buenas prácticas

* **Idempotencia:** escribe transformaciones que den el mismo resultado si se
  reprocesa el mismo registro. Usa `context.now`, no relojes del sistema.
* **Tolerancia a nulos:** los datos de origen pueden venir incompletos; usa
  `??` y validaciones antes de calcular.
* **Una responsabilidad clara:** prefiere derivar campos explícitos
  (`localDate`, `npsCategory`, `segmentPercentage`) en lugar de mutar los
  originales, para que los reportes y la extracción sean predecibles.
* **Pruébala primero en sandbox:** valida la función en el
  [entorno sandbox](/desarrollo/sandbox) antes de activarla en producción.
