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, falseindican: 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
localy 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:
- OData v4: el más moderno. Se usa para leer y escribir datos desde Power BI, Excel, aplicaciones externas o integraciones REST.
- SOAP: el clásico. Usado para integraciones legacy o sistemas más antiguos.
| 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
FlowFieldno se calculan automáticamente al exponer datos por OData. Debes añadirCalcFieldsen el trigger de la página antes de que los datos salgan al servicio. - Usa
SetAutoCalcFieldsen 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
RecordRefyFieldReftienen un coste de rendimiento mayor que trabajar con variables de tabla tipadas. Úsalos solo cuando la genericidad sea imprescindible.Variantanula 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?
- Migración inicial de datos desde un sistema anterior (clientes, artículos, proveedores…).
- Carga de tablas de configuración propias que has creado en tu extensión.
- Actualización masiva de un campo en miles de registros sin código.
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
- Orden de las tablas: si la tabla A tiene una
TableRelationa la tabla B, debes cargar primero la tabla B. Por ejemplo, primero cargas artículos y luego las líneas de pedido que los referencian. - Validación de campos: por defecto BC aplica las validaciones de campo (
OnValidate). Puedes desactivar esto por tabla en el paquete si los datos ya vienen limpios y quieres acelerar la carga. - Errores: si un registro falla, BC lo marca en rojo en la vista del paquete y te indica el motivo. El resto de registros válidos se siguen aplicando.
- Reutilización: un paquete puede exportarse como fichero
.rapidstarty compartirse entre entornos (útil para configurar una demo o un nuevo cliente).
💡 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
Codecon numeración automática, desactiva la validación para ese campo en el paquete y proporciona el valor directamente en el Excel.