Dif. 6/10

Tipos de Datos Avanzados

Esta sección cubre cuatro conceptos que amplían lo que ya sabes sobre campos y tipos de datos en Business Central: cómo suscribirse a eventos de campo sin tocar la tabla original, qué tipos de datos son compatibles con los servicios web, cómo usar referencias genéricas con RecordRef y FieldRef, y cómo cargar datos masivamente mediante Paquetes de Configuración.

1. Eventos de Campo

Los triggers de campo (OnValidate, OnLookup…) son código que escribes dentro de la propia tabla. Pero ¿qué pasa cuando quieres reaccionar a la modificación de un campo de una tabla estándar de BC sin tocarla? Para eso existen los eventos de campo, que funcionan con el modelo publisher/subscriber de AL.

¿Cómo funciona el modelo publisher/subscriber?

BC publica eventos en momentos clave del ciclo de vida de un campo. Tu extensión se suscribe a esos eventos con un EventSubscriber. Cuando el evento se dispara, BC ejecuta tu código automáticamente, sin que hayas modificado ningún objeto base.

Evento de campo Cuándo se dispara Uso típico
OnBeforeValidateEvent Justo antes de ejecutar el OnValidate del campo Interceptar y modificar el valor antes de que se valide
OnAfterValidateEvent Justo después de que el OnValidate del campo termina Reaccionar al nuevo valor ya validado (el más usado)
OnBeforeLookupEvent Antes de abrir la ventana de búsqueda del campo Filtrar la lista que se mostrará al usuario
OnAfterLookupEvent Después de que el usuario selecciona un valor en la búsqueda Procesar el valor seleccionado

Ejemplo práctico

Queremos que, cada vez que alguien cambie el campo "Sell-to Customer No." en una cabecera de venta, nuestra extensión registre un mensaje en un log propio. Sin tocar la tabla Sales Header:

codeunit 50100 "Suscriptor Ventas"
{
    // Este codeunit no hace nada por sí solo.
    // Solo reacciona cuando BC dispara el evento.

    [EventSubscriber(ObjectType::Table,
                     Database::"Sales Header",
                     'OnAfterValidateEvent',
                     'Sell-to Customer No.',
                     false, false)]
    local procedure AlCambiarCliente(var Rec: Record "Sales Header")
    var
        MiLog: Record "Mi Tabla Log";
    begin
        MiLog.Init();
        MiLog."Fecha"      := Today;
        MiLog."Descripcion" := 'Cliente cambiado a: ' + Rec."Sell-to Customer No.";
        MiLog.Insert(true);
    end;
}

💡 Claves del EventSubscriber

  • Los dos últimos parámetros false, false indican: el primero si el suscriptor puede omitirse en caso de error, el segundo si se omite cuando el objeto está inactivo.
  • El procedimiento debe ser local y su firma debe coincidir exactamente con la del evento.
  • Puedes tener tantos suscriptores como necesites al mismo evento, en distintos codeunits.
  • Esta es la forma correcta y recomendada de extender la lógica de tablas base sin crear dependencias frágiles.

2. Servicios Web — Compatibilidad de Tipos de Datos

Cuando expones una página o query de BC como servicio web (OData o SOAP), no todos los tipos de datos AL se traducen de la misma forma al mundo externo. Es importante saber qué tipos son compatibles, cuáles se transforman y cuáles no se pueden exponer directamente.

¿Qué es un servicio web en BC?

BC permite publicar objetos (páginas, queries, codeunits) como endpoints accesibles desde el exterior. Hay dos protocolos principales:

Tipo AL Compatible con OData Compatible con SOAP Observación
Integer, Decimal ✅ Sí ✅ Sí Se mapean a tipos numéricos estándar
Text, Code ✅ Sí ✅ Sí Se exponen como string
Boolean ✅ Sí ✅ Sí Se expone como true/false
Date, DateTime ✅ Sí ✅ Sí Formato ISO 8601 en OData
Enum ✅ Sí ⚠️ Parcial OData expone el valor entero y el nombre
Blob ⚠️ Parcial ⚠️ Parcial Solo si se convierte a Base64 explícitamente
RecordId ❌ No directo ❌ No directo Hay que convertirlo a texto antes de exponerlo
Variant ❌ No ❌ No No se puede exponer en servicios web

Cómo publicar un servicio web desde BC

En Business Central, ve a Servicios Web (búscalo en el menú de búsqueda). Crea una nueva línea indicando el tipo de objeto (Page o Query), el número de objeto y el nombre del servicio. Marca la casilla Publicado y BC generará automáticamente la URL OData.

// Ejemplo: URL OData generada por BC para una página publicada
// Formato base:
https://[servidor]/[instancia]/ODataV4/Company('[empresa]')/[NombreServicio]

// Ejemplo real con BC en la nube (SaaS):
https://api.businesscentral.dynamics.com/v2.0/[tenantId]/production/ODataV4/Company('CRONUS')/Clientes

⚠️ FlowFields y servicios web

  • Los campos FlowField no se calculan automáticamente al exponer datos por OData. Debes añadir CalcFields en el trigger de la página antes de que los datos salgan al servicio.
  • Usa SetAutoCalcFields en el código de la página para forzar el cálculo antes de la exposición.

3. Referencias y Otros Tipos de Datos

AL dispone de tipos de datos especiales que permiten escribir código genérico capaz de trabajar con cualquier tabla o campo sin conocerlos en tiempo de compilación. Son los tipos de referencia: RecordRef, FieldRef, KeyRef y Variant.

RecordRef — Referencia genérica a cualquier tabla

Un RecordRef es una variable que puede apuntar a un registro de cualquier tabla en tiempo de ejecución. Se usa cuando quieres crear funciones reutilizables que operen sobre tablas distintas sin duplicar código.

// Procedimiento genérico que cuenta registros de cualquier tabla
procedure ContarRegistros(NumTabla: Integer): Integer
var
    RefRec: RecordRef;
begin
    RefRec.Open(NumTabla);        // Abre la tabla por su número
    exit(RefRec.Count);           // Devuelve el número de registros
    RefRec.Close();
end;

// Llamadas desde otro código:
// ContarRegistros(18)  → cuenta clientes (tabla Customer = 18)
// ContarRegistros(27)  → cuenta artículos (tabla Item = 27)

FieldRef — Referencia genérica a cualquier campo

Un FieldRef se obtiene a partir de un RecordRef y permite leer o escribir el valor de un campo de forma genérica, sin conocer su nombre en tiempo de compilación.

// Leer el valor del campo número 2 de cualquier registro
procedure LeerCampo(RefRec: RecordRef; NumeroCampo: Integer): Text
var
    RefCampo: FieldRef;
begin
    RefCampo := RefRec.Field(NumeroCampo);   // Obtiene el campo por número
    exit(Format(RefCampo.Value));             // Devuelve su valor como texto
end;

Variant — El tipo comodín

Una variable Variant puede contener un valor de cualquier tipo en tiempo de ejecución: un entero, un texto, una fecha, un record… Se usa principalmente en parámetros de procedimientos muy genéricos. Su contrapartida es que el compilador no puede verificar el tipo en tiempo de compilación, lo que lo hace más propenso a errores.

procedure MostrarValor(Valor: Variant)
begin
    // Variant acepta cualquier tipo como argumento
    Message('El valor es: %1', Valor);
end;

// Puede llamarse con cualquier tipo:
MostrarValor(42);
MostrarValor('Hola');
MostrarValor(Today);
Tipo Para qué sirve Cuándo usarlo
RecordRef Referenciar cualquier tabla en tiempo de ejecución Utilidades genéricas, exportadores, validadores universales
FieldRef Acceder a campos de un RecordRef por número Comparar, copiar o leer campos sin conocer la tabla concreta
KeyRef Acceder a la clave de un RecordRef Recorrer registros por clave de forma genérica
Variant Variable que acepta cualquier tipo Parámetros de procedimientos muy genéricos o interop con COM

⚠️ Usa estos tipos con moderación

  • RecordRef y FieldRef tienen un coste de rendimiento mayor que trabajar con variables de tabla tipadas. Úsalos solo cuando la genericidad sea imprescindible.
  • Variant anula la seguridad de tipos del compilador. Prefiere siempre parámetros tipados cuando el tipo sea conocido.

4. Carga de Datos con Paquetes de Configuración

Los Paquetes de Configuración (Configuration Packages) son la herramienta nativa de Business Central para importar datos masivamente desde ficheros Excel o CSV hacia las tablas de BC. No requieren código AL y son especialmente útiles en migraciones de datos iniciales o en la carga de catálogos.

¿Cuándo usarlos?

Flujo de trabajo

El proceso siempre sigue los mismos pasos: defines el paquete indicando qué tabla y qué campos quieres cargar, exportas la plantilla a Excel, rellenas los datos y la vuelves a importar para que BC valide e inserte los registros.

Paso Acción en BC Resultado
1 Ir a Paquetes de Configuración → Nuevo Se crea un paquete vacío con un código identificador
2 Añadir tablas al paquete y seleccionar campos BC registra qué columnas formarán parte de la importación
3 Acción: Exportar a Excel Se descarga una plantilla .xlsx con las columnas definidas
4 Rellenar datos en Excel y guardar El fichero queda listo con los datos a importar
5 Acción: Importar desde Excel BC carga los datos en la tabla de staging del paquete
6 Acción: Aplicar Paquete BC valida y aplica los datos a las tablas reales. Muestra errores si los hay.

Puntos clave a tener en cuenta

💡 Paquetes de Configuración y tus tablas propias

  • Las tablas que creas en tu extensión también aparecen disponibles en los Paquetes de Configuración. Es una forma cómoda de poblarlas sin escribir código de carga.
  • Para que funcione bien, asegúrate de que los campos obligatorios (NotBlank) estén incluidos en el paquete.
  • Si tu tabla tiene una clave primaria de tipo Code con numeración automática, desactiva la validación para ese campo en el paquete y proporciona el valor directamente en el Excel.
← Volver a Teoría