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:
- Cliente en
REG_CUENTA(subsistema 02). - Vendedor en
REG_CUENTA(subsistema 22). - 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
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:
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.