Saltar a contenido

Transforms

Los transforms son funciones que procesan datos entre el source y el destination. Se ejecutan durante el polling, antes de calcular hashes.

Flujo de Transforms

flowchart LR
    A[Source Data] --> B[Transform 1]
    B --> C[Transform 2]
    C --> D[Transform N]
    D --> E[Hash + Queue]

Configuración

Los transforms se definen en el flow:

// Transform único
{
  name: 'pedidos',
  transform: 'agruparPedidos',
  // ...
}

// Pipeline de transforms
{
  name: 'articulos',
  transform: ['validar', 'mapear', 'formatear'],
  // ...
}

Registro de Transforms

Todos los transforms se registran en src/transforms/index.ts:

import { agruparPedidos } from './pedidos.js';
import { agruparOrdenesCompra } from './ordenCompra.js';
import { procesarPedidoPreparado } from './pedidosPreparados.js';
import { procesarOrdenPreparado } from './ordenesPreparadas.js';
import { procesarArticulosTranscaden } from './articulosTranscaden.js';
import { clientesMysql } from './clientMysql.js';

export const transforms: Record<string, (rows: RowData[]) => RowData[]> = {
  agruparPedidos,
  agruparOrdenesCompra,
  procesarPedidoPreparado,
  procesarOrdenPreparado,
  procesarArticulosTranscaden,
  clientesMysql,
};

Transforms Implementados

agruparPedidos

Agrupa líneas de pedido en objetos de pedido con detalles.

flowchart LR
    subgraph Input
        A1[Línea 1 - Pedido 001]
        A2[Línea 2 - Pedido 001]
        A3[Línea 3 - Pedido 002]
    end

    subgraph Output
        B1[Pedido 001<br/>detalles: 2 items]
        B2[Pedido 002<br/>detalles: 1 item]
    end

    A1 --> B1
    A2 --> B1
    A3 --> B2

Input:

[
  { "Nro. Pedido": "001", "Cod Articulo": "A1", "Cantidad": 10 },
  { "Nro. Pedido": "001", "Cod Articulo": "A2", "Cantidad": 5 },
  { "Nro. Pedido": "002", "Cod Articulo": "A3", "Cantidad": 3 }
]

Output:

[
  {
    "_pk": "001",
    "idERP": "001",
    "numero": "001",
    "detalles": [
      { "codigoArticulo": "A1", "cantidadSolicitada": 10 },
      { "codigoArticulo": "A2", "cantidadSolicitada": 5 }
    ]
  },
  {
    "_pk": "002",
    "idERP": "002",
    "numero": "002",
    "detalles": [
      { "codigoArticulo": "A3", "cantidadSolicitada": 3 }
    ]
  }
]

agruparOrdenesCompra

Similar a agruparPedidos, pero para órdenes de compra.

procesarPedidoPreparado

Procesa pedidos preparados del WMS para escribir en Tango.

export function procesarPedidoPreparado(rows: RowData[]): RowData[] {
  return rows.map(row => {
    const now = new Date();
    return {
      ...row,
      _pk: String(row.idPedidoERP || row.idERP),
      idERP: row.idPedidoERP || row.idERP,
      fechaSQL: now.toISOString().slice(0, 10).replace(/-/g, ''),
      hora: now.toISOString().slice(11, 19).replace(/:/g, ''),
    };
  });
}

procesarOrdenPreparado

Análogo a procesarPedidoPreparado pero para órdenes preparadas (ordenesPreparados-dev).

procesarArticulosTranscaden

Adapta filas leídas de la base BAS Transcaden para escribirlas en MySQL Transcaden Web (articulos-transcaden).

clientesMysql

Transform específico del destino MySQL Web. De cada fila de WEBCLIENTE (Tango) prepara los campos necesarios para las tres escrituras del flow clientes en MySQL Web:

  1. Cliente en REG_CUENTA (subsistema 02).
  2. Vendedor en REG_CUENTA (subsistema 22).
  3. Relación cliente↔vendedor en REG_CUENTA_RELACIONES.

Para que la op 2 y la op 3 puedan referenciar el vendedor, el transform agrega al row los campos _vend_cod y _vend_nombre (con prefijo _ por convención de "campo derivado"). La configuración de operations del adapter MySQL los lee con nro_cuenta: '_vend_cod', etc.

Validación obligatoria: si Cod Vendedor o Vendedor vienen vacíos, el transform lanza error (Cliente XXX sin vendedor asignado...). El job se reintenta con backoff y, si agota los 5 intentos, dispara la alerta onJobFailed configurada en el flow.

export function clientesMysql(rows: RowData[]): RowData[] {
  return rows.map((row) => {
    const codCliente = String(row['Cod Cliente'] || '').trim();
    const codVendedor = String(row['Cod Vendedor'] || '').trim();
    const nombreVendedor = String(row['Vendedor'] || '').trim();

    if (!codVendedor || !nombreVendedor) {
      throw new Error(`Cliente ${codCliente} sin vendedor asignado`);
    }

    return limpiarVacios({
      _pk: codCliente,
      subsistema_id: '02',
      nombre_cuenta: row['Razon Social'],
      nro_cuenta: codCliente,
      // ... resto de campos del cliente
      _vend_cod: codVendedor,
      _vend_nombre: nombreVendedor,
    });
  });
}

Ver el detalle de cómo se consume este row en Destinations · Operaciones Múltiples.

Crear Nuevo Transform

1. Crear archivo

// src/transforms/mi-transform.ts
import type { RowData } from '../types/index.js';

export function miTransform(rows: RowData[]): RowData[] {
  return rows.map(row => ({
    ...row,
    _pk: String(row.id),  // Siempre incluir _pk
    // ... transformaciones
  }));
}

2. Registrar

// src/transforms/index.ts
import { miTransform } from './mi-transform.js';

export const transforms = {
  // ...
  miTransform,
};

3. Usar en flow

{
  name: 'miEntidad',
  transform: 'miTransform',
  // ...
}

Consideraciones

Campo _pk

Siempre incluir _pk

El transform debe establecer _pk para identificar cada registro. El poller usa este campo para el hash store.

// ✅ Correcto
return rows.map(row => ({
  ...row,
  _pk: String(row.id),
}));

// ❌ Incorrecto (falta _pk)
return rows.map(row => ({
  nombre: row.NOMBRE,
}));

Agrupación

Para transforms que agrupan (como pedidos), usar Map:

export function agrupar(rows: RowData[]): RowData[] {
  const grupos = new Map<string, RowData>();

  for (const row of rows) {
    const pk = String(row.id);

    if (!grupos.has(pk)) {
      grupos.set(pk, {
        _pk: pk,
        id: pk,
        detalles: [],
      });
    }

    grupos.get(pk)!.detalles.push({
      linea: row.linea,
      cantidad: row.cantidad,
    });
  }

  return Array.from(grupos.values());
}

Validación

Filtrar datos inválidos:

export function validar(rows: RowData[]): RowData[] {
  return rows.filter(row => {
    if (!row.codigo) {
      console.warn('Row sin código, ignorando');
      return false;
    }
    return true;
  });
}

Formateo de Fechas

// Formato SQL Server: YYYYMMDD
const fechaSQL = new Date().toISOString().slice(0, 10).replace(/-/g, '');

// Formato MySQL: YYYY-MM-DD HH:mm:ss
const fechaMySQL = new Date().toISOString().slice(0, 19).replace('T', ' ');

// Formato Argentina
const fechaArg = new Date().toLocaleString('es-AR', {
  day: '2-digit',
  month: '2-digit',
  year: 'numeric',
  hour: '2-digit',
  minute: '2-digit',
  second: '2-digit',
  hour12: false,
});

Pipeline de Transforms

Los transforms se ejecutan en orden:

{
  transform: ['validar', 'mapear', 'agrupar', 'formatear'],
}
flowchart LR
    A[Raw Data] --> B[validar]
    B --> C[mapear]
    C --> D[agrupar]
    D --> E[formatear]
    E --> F[Final Data]

Cada transform recibe el output del anterior.