Herramientas Avanzadas de AL
Esta sección cubre los elementos avanzados del desarrollo en Business Central: el flujo de procesos del sistema, el diseño de páginas RoleCenter con cues y actividades, la personalización de la cinta de acciones, y los XMLports para importar y exportar datos.
1. Flujo de Procesos de Business Central
Business Central organiza su funcionamiento alrededor de procesos de negocio que siguen un flujo estándar. Entender este flujo es fundamental para saber dónde encaja cada objeto AL que desarrollamos.
| Fase | Descripción | Objetos AL involucrados |
|---|---|---|
| 1. Configuración | Crear datos maestros (clientes, productos, socios, libros) y configurar el sistema. | Tablas, Páginas Card/List |
| 2. Entrada de datos | El usuario introduce documentos o transacciones (pedidos, préstamos, facturas). | Páginas Document, Worksheet |
| 3. Validación | El sistema valida los datos mediante triggers y lógica de negocio. | Triggers de tabla, Codeunits |
| 4. Contabilización (Posting) | El documento borrador se convierte en un documento publicado y genera movimientos contables. | Codeunits de posting, Reports ProcessingOnly |
| 5. Informes y análisis | El usuario consulta los datos mediante informes, queries y dashboards. | Reports, Queries, RoleCenter |
// Flujo típico de un proceso de préstamo en nuestra aplicación:
//
// 1. CONFIGURACIÓN → Crear Socios y Libros (tablas maestras)
// 2. ENTRADA → Crear un nuevo Préstamo (tabla de documento)
// 3. VALIDACIÓN → OnValidate verifica que el socio existe y el libro está disponible
// 4. PROCESO → Codeunit cambia el estado del libro a "Prestado"
// 5. INFORMES → Report muestra préstamos activos, vencidos, estadísticas
//
// Cada fase usa objetos AL diferentes, pero todos están conectados
// a través de TableRelation, Codeunits y Events.
2. Páginas del Centro de Roles (RoleCenter)
El RoleCenter es la página principal que ve el usuario al iniciar sesión en Business Central. Cada rol de usuario (contable, gerente, bibliotecario) tiene su propio RoleCenter personalizado con accesos directos, indicadores y actividades relevantes para su trabajo diario.
| Componente | Qué es | Tipo de Part |
|---|---|---|
| Headline | Título dinámico en la parte superior con información contextual o saludos | HeadlinePart |
| Activities (Cues) | Contadores visuales que muestran el número de elementos pendientes | CardPart con CueGroup |
| Acciones | Enlaces rápidos a las páginas y funciones más usadas del rol | Actions en el RoleCenter |
// Página RoleCenter básica para un bibliotecario
page 50500 "RC Bibliotecario"
{
PageType = RoleCenter;
Caption = 'Centro de Roles — Bibliotecario';
layout
{
area(RoleCenter)
{
// Headline dinámico
part(Titular; "Headline Biblioteca")
{
ApplicationArea = All;
}
// Cues — indicadores numéricos
part(Actividades; "Actividades Biblioteca")
{
ApplicationArea = All;
}
}
}
actions
{
area(Embedding)
{
action(Socios)
{
Caption = 'Socios';
ApplicationArea = All;
RunObject = page "Lista Socios";
Image = CustomerList;
}
action(Libros)
{
Caption = 'Catálogo de Libros';
ApplicationArea = All;
RunObject = page "Lista Libros";
Image = ItemList;
}
action(Prestamos)
{
Caption = 'Préstamos';
ApplicationArea = All;
RunObject = page "Lista Prestamos";
Image = Documents;
}
}
}
}
3. Estructura: Titulares, Actividades y Cues
HeadlinePart — El titular dinámico
Una HeadlinePart es una página especial que muestra un "titular de periódico" en la parte superior del RoleCenter. Es ideal para dar la bienvenida, mostrar datos clave del día o destacar alertas.
page 50501 "Headline Biblioteca"
{
PageType = HeadlinePart;
Caption = 'Titulares';
layout
{
area(Content)
{
field(Bienvenida; BienvenidaTxt)
{
ApplicationArea = All;
}
field(PrestamosHoy; PrestamosHoyTxt)
{
ApplicationArea = All;
}
}
}
trigger OnOpenPage()
var
Prestamo: Record Prestamo;
begin
BienvenidaTxt := 'Buenos días, ' + UserId;
Prestamo.SetRange("Fecha Prestamo", Today);
PrestamosHoyTxt := StrSubstNo('Hoy se han registrado %1 préstamos nuevos',
Prestamo.Count);
end;
var
BienvenidaTxt: Text[100];
PrestamosHoyTxt: Text[100];
}
Cues — Los indicadores numéricos
Los Cues son contadores visuales que aparecen como tarjetas de colores en el RoleCenter. Cada cue muestra un número (ej. "5 préstamos vencidos") y al hacer clic abre la página filtrada con esos registros.
Para implementar cues necesitas:
- Una tabla de cues con campos FlowField que calculan los contadores.
- Una página CardPart que muestra esos campos dentro de un
cuegroup.
// 1. Tabla de Cues — los FlowFields calculan los contadores
table 50501 "Cues Biblioteca"
{
Caption = 'Cues Biblioteca';
fields
{
field(1; "Clave"; Code[10]) { }
field(10; "Prestamos Activos"; Integer)
{
FieldClass = FlowField;
CalcFormula = count(Prestamo where(Estado = const(Activo)));
Caption = 'Préstamos Activos';
}
field(20; "Prestamos Vencidos"; Integer)
{
FieldClass = FlowField;
CalcFormula = count(Prestamo where(Estado = const(Vencido)));
Caption = 'Préstamos Vencidos';
}
field(30; "Total Socios"; Integer)
{
FieldClass = FlowField;
CalcFormula = count(Socio);
Caption = 'Total Socios';
}
}
keys
{
key(PK; "Clave") { Clustered = true; }
}
}
// 2. Página CardPart con CueGroup
page 50502 "Actividades Biblioteca"
{
PageType = CardPart;
Caption = 'Actividades';
SourceTable = "Cues Biblioteca";
layout
{
area(Content)
{
cuegroup(Prestamos)
{
Caption = 'Préstamos';
field(PrestActivos; Rec."Prestamos Activos")
{
ApplicationArea = All;
DrillDownPageId = "Lista Prestamos";
Caption = 'Activos';
}
field(PrestVencidos; Rec."Prestamos Vencidos")
{
ApplicationArea = All;
DrillDownPageId = "Lista Prestamos";
Caption = 'Vencidos';
// Cambia de color según el valor
Style = Unfavorable;
StyleExpr = Rec."Prestamos Vencidos" > 0;
}
}
cuegroup(General)
{
Caption = 'General';
field(Socios; Rec."Total Socios")
{
ApplicationArea = All;
DrillDownPageId = "Lista Socios";
Caption = 'Total Socios';
}
}
}
}
trigger OnOpenPage()
begin
Rec.Reset();
if not Rec.Get() then begin
Rec.Init();
Rec.Insert();
end;
end;
}
💡 Cómo funcionan los Cues
- La tabla de cues solo tiene un registro (singleton). Por eso el
OnOpenPagehaceGet()sin parámetros. - Los campos
FlowFieldcalculan los contadores automáticamente con SQL. DrillDownPageIddefine qué página se abre al hacer clic en el cue.- Usa
Style = Unfavorablepara que el número aparezca en rojo cuando hay problemas.
4. Diseñador de Acciones y Cinta de Opciones
Las acciones en Business Central son los botones y enlaces que el usuario ve en la barra superior de cada página. Se organizan en áreas y grupos, y cada una puede ejecutar código AL, abrir otra página o lanzar un informe.
| Área de acción | Dónde aparece | Uso típico |
|---|---|---|
area(Processing) |
Barra de acciones principal | Acciones que modifican datos (Registrar, Aprobar, Enviar) |
area(Navigation) |
Menú "Navegar" / "Relacionado" | Abrir páginas relacionadas (Historial, Movimientos) |
area(Reporting) |
Menú "Informes" | Lanzar informes y documentos |
area(Creation) |
Menú "Nuevo" (en listas) | Crear nuevos registros o documentos |
area(Promoted) |
Barra principal (destacado) | Acciones frecuentes que se quieren hacer muy visibles |
// Acciones en una página de tarjeta de socio
page 50110 "Tarjeta Socio"
{
PageType = Card;
SourceTable = Socio;
// ... layout omitido ...
actions
{
// Acciones principales (barra superior)
area(Processing)
{
action(RegistrarPrestamo)
{
Caption = 'Nuevo Préstamo';
ApplicationArea = All;
Image = NewDocument;
Promoted = true;
PromotedCategory = Process;
PromotedIsBig = true;
trigger OnAction()
var
GestPrest: Codeunit "Gestion Prestamos";
begin
GestPrest.RegistrarPrestamo(Rec."No.", '');
end;
}
}
// Acciones de navegación
area(Navigation)
{
group(Historial)
{
Caption = 'Historial';
action(VerPrestamos)
{
Caption = 'Préstamos del Socio';
ApplicationArea = All;
Image = Documents;
RunObject = page "Lista Prestamos";
RunPageLink = "No. Socio" = field("No.");
// Abre la lista filtrada por este socio
}
}
}
// Acciones de informes
area(Reporting)
{
action(ImprimirFicha)
{
Caption = 'Ficha del Socio';
ApplicationArea = All;
Image = Report;
RunObject = report "Ficha Socio Word";
}
}
}
}
💡 Promoted actions (acciones destacadas)
- Usa
Promoted = truepara que la acción aparezca directamente en la barra superior de la página. PromotedIsBig = truehace que el botón sea más grande y visible.PromotedCategoryagrupa las acciones destacadas:Process,Report,Category4aCategory10.- No abuses de las acciones destacadas. Solo pon las que el usuario usa a diario.
5. XMLports: Componentes, Propiedades y Triggers
Un XMLport es un objeto AL diseñado para importar y exportar datos entre Business Central y archivos externos (XML, CSV, texto plano). Es la herramienta estándar para migraciones de datos, integraciones con otros sistemas y cargas masivas.
| Componente | Descripción |
|---|---|
schema |
Define la estructura del fichero: qué elementos y atributos tendrá el XML/CSV de salida. |
requestpage |
Igual que en reports: pantalla previa para que el usuario configure opciones antes de ejecutar. |
Direction |
Define si el XMLport es de Import, Export o Both.
|
Format |
Formato del fichero: Xml, VariableText (CSV) o
FixedText. |
Propiedades principales
| Propiedad | Dónde | Qué hace |
|---|---|---|
Direction |
XMLport | Import, Export o Both |
Format |
XMLport | Xml, VariableText (CSV), FixedText |
FieldSeparator |
XMLport (CSV) | Carácter separador de campos (ej. ; o ,) |
TextEncoding |
XMLport | UTF8, UTF16, MSDos, Windows |
TableElementName |
tableelement | Nombre del nodo XML para cada registro |
Triggers del XMLport
| Trigger | Cuándo se ejecuta |
|---|---|
OnPreXmlPort |
Antes de procesar cualquier dato (inicialización) |
OnPostXmlPort |
Después de procesar todos los datos (resumen, mensaje final) |
OnBeforeInsertRecord |
Antes de insertar cada registro (validación, transformación) |
OnAfterInsertRecord |
Después de insertar cada registro (acciones post-inserción) |
OnBeforeModifyRecord |
Antes de modificar un registro existente |
OnAfterGetRecord |
Después de leer cada registro (en exportación) |
// XMLport para exportar socios a CSV
xmlport 50500 "Exportar Socios CSV"
{
Direction = Export;
Format = VariableText; // CSV
FieldSeparator = ';';
TextEncoding = UTF8;
Caption = 'Exportar Socios a CSV';
schema
{
textelement(Root)
{
tableelement(SocioElement; Socio)
{
fieldelement(No; Socio."No.") { }
fieldelement(Nombre; Socio.Nombre) { }
fieldelement(Email; Socio.Email) { }
fieldelement(Tipo; Socio.Tipo) { }
}
}
}
trigger OnPostXmlPort()
begin
Message('Exportación completada.');
end;
}
6. XMLport SourceType: Texto, Tabla y Campo
Los elementos del schema de un XMLport pueden tener diferentes SourceType que determinan de dónde vienen los datos.
| SourceType | Elemento | Descripción |
|---|---|---|
| Table | tableelement |
Representa una tabla de BC. Se itera sobre cada registro. Es el contenedor principal. |
| Field | fieldelement / fieldattribute |
Representa un campo de la tabla del tableelement padre. |
| Text | textelement / textattribute |
Texto fijo o variable no vinculado a ninguna tabla. Se gestiona con código AL. |
// XMLport para importar socios desde CSV
xmlport 50501 "Importar Socios CSV"
{
Direction = Import;
Format = VariableText;
FieldSeparator = ';';
TextEncoding = UTF8;
Caption = 'Importar Socios desde CSV';
schema
{
textelement(Root) // textelement = nodo raíz sin tabla
{
tableelement(SocioImport; Socio) // tableelement = vinculado a tabla Socio
{
// fieldelement = vinculados a campos de la tabla
fieldelement(No; Socio."No.") { }
fieldelement(Nombre; Socio.Nombre) { }
fieldelement(Email; Socio.Email) { }
// textelement dentro de tableelement = dato del CSV
// que no corresponde directamente a un campo
// (lo procesamos manualmente en un trigger)
textelement(TipoTexto) { }
trigger OnBeforeInsertRecord()
begin
// Convertir el texto del CSV al enum de tipo de socio
case UpperCase(TipoTexto) of
'NORMAL': Socio.Tipo := Socio.Tipo::Normal;
'PREMIUM': Socio.Tipo := Socio.Tipo::Premium;
else
Socio.Tipo := Socio.Tipo::Normal;
end;
ContadorImportados += 1;
end;
}
}
}
trigger OnPostXmlPort()
begin
Message('Importación completada. Socios importados: %1', ContadorImportados);
end;
var
ContadorImportados: Integer;
}
💡 Element vs. Attribute (XML)
- En formato XML, un
fieldelementgenera un nodo hijo:<Nombre>Ana</Nombre> - Un
fieldattributegenera un atributo en el nodo padre:<Socio Nombre="Ana"/> - En formato CSV (
VariableText), esta distinción no aplica — todo se serializa como columnas separadas.
⚠️ Buenas prácticas con XMLports
- Siempre valida los datos en
OnBeforeInsertRecordantes de insertarlos. Un CSV mal formado puede insertar basura. - Usa
TextEncoding = UTF8para evitar problemas con acentos y caracteres especiales en castellano. - Muestra un mensaje con el contador de registros procesados en
OnPostXmlPortpara que el usuario sepa el resultado. - Si el XMLport es
Direction = Both, asegúrate de que la estructura sea coherente para importación y exportación.