Páginas Avanzadas (I)
Esta sección profundiza en los tipos de página de Business Central que van más allá de la List y la Card básicas: el RoleCenter como punto de entrada personalizado, las páginas de Documento con su estructura de cabecera y líneas, las FastTabs como herramienta de organización visual, y los tipos Worksheet y Dialog para casos de uso específicos.
1. Page tipo: RoleCenter
El RoleCenter es la página de inicio personalizada que BC muestra a cada usuario según su perfil. No es una lista ni una ficha: es un panel de control compuesto por bloques informativos llamados cues (mosaicos con contadores), activities (grupos de cues) y headlines (mensajes dinámicos en la parte superior).
Estructura de un RoleCenter
| Componente | Qué es | Control AL |
|---|---|---|
| Headline | Mensaje dinámico en la cabecera de la página (ej: "Tienes 3 pedidos pendientes") | HeadlinePart |
| Activities | Grupo de mosaicos (cues) agrupados por categoría | cuegroup |
| Cue | Mosaico individual con un número que al hacer clic abre una página filtrada | field sobre un FlowField |
| Parts | Subpáginas incrustadas (listas, gráficos, FactBoxes) | part |
Ejemplo: RoleCenter básico para un bibliotecario
// 1. Tabla de Cue Setup — guarda los contadores FlowField
table 50300 "Biblioteca Cue"
{
DataClassification = CustomerContent;
fields
{
field(1; "Primary Key"; Code[10]) { }
field(2; "Prestamos Activos"; Integer)
{
FieldClass = FlowField;
CalcFormula = count("Prestamo" where(Estado = const(Activo)));
Editable = false;
}
field(3; "Prestamos Vencidos"; Integer)
{
FieldClass = FlowField;
CalcFormula = count("Prestamo" where(Estado = const(Vencido)));
Editable = false;
}
}
keys
{
key(PK; "Primary Key") { Clustered = true; }
}
}
// 2. Página de actividades (los mosaicos)
page 50301 "Biblioteca Activities"
{
PageType = CardPart;
SourceTable = "Biblioteca Cue";
layout
{
area(Content)
{
cuegroup(Prestamos)
{
Caption = 'Préstamos';
field("Prestamos Activos"; Rec."Prestamos Activos")
{
DrillDownPageId = "Prestamo List";
ApplicationArea = All;
}
field("Prestamos Vencidos"; Rec."Prestamos Vencidos")
{
DrillDownPageId = "Prestamo List";
ApplicationArea = All;
StyleExpr = 'Unfavorable'; // lo pinta en rojo
}
}
}
}
trigger OnOpenPage()
begin
// Nos aseguramos de que siempre exista el único registro de cue
if not Rec.Get() then begin
Rec.Init();
Rec.Insert();
end;
Rec.CalcFields("Prestamos Activos", "Prestamos Vencidos");
end;
}
// 3. El RoleCenter propiamente dicho
page 50302 "Biblioteca Role Center"
{
PageType = RoleCenter;
Caption = 'Área de Trabajo Biblioteca';
ApplicationArea = All;
layout
{
area(RoleCenter)
{
// Incrustamos la página de actividades como part
part(Actividades; "Biblioteca Activities") { }
}
}
actions
{
area(Sections)
{
group(Catalogos)
{
Caption = 'Catálogos';
action(Libros)
{
RunObject = page "Libro List";
ApplicationArea = All;
}
action(Socios)
{
RunObject = page "Socio List";
ApplicationArea = All;
}
}
}
}
}
💡 Claves del RoleCenter
- La tabla de cues siempre tiene un único registro por usuario. Se crea en el
OnOpenPagesi no existe. DrillDownPageIddefine a qué página se navega al hacer clic en el mosaico.StyleExpr = 'Unfavorable'pinta el mosaico en rojo. Otros valores:'Favorable'(verde),'Ambiguous'(amarillo).- Para asignar el RoleCenter a un perfil, ve a Perfiles en BC y selecciona tu página como ID de centro de roles.
2. Page tipo: HeadlinePart
El HeadlinePart es la parte superior del RoleCenter que muestra
mensajes dinámicos y personalizados al usuario: bienvenidas, alertas del día,
métricas clave… Se define como una página independiente de tipo HeadlinePart
y se incrusta en el RoleCenter como un part.
page 50310 "Biblioteca Headline"
{
PageType = HeadlinePart;
ApplicationArea = All;
layout
{
area(Content)
{
// Cada field es una línea de titular en la cabecera
field(Bienvenida; GetBienvenida())
{
ApplicationArea = All;
ShowCaption = false;
}
field(AlertaVencidos; GetAlertaVencidos())
{
ApplicationArea = All;
ShowCaption = false;
Visible = HayVencidos; // solo se muestra si hay préstamos vencidos
}
}
}
var
HayVencidos: Boolean;
trigger OnOpenPage()
var
Prestamo: Record "Prestamo";
begin
Prestamo.SetRange(Estado, Prestamo.Estado::Vencido);
HayVencidos := not Prestamo.IsEmpty();
end;
// Procedimientos que devuelven el texto de cada titular
local procedure GetBienvenida(): Text
begin
exit('Bienvenido, ' + UserId + '. Hoy es ' + Format(Today) + '.');
end;
local procedure GetAlertaVencidos(): Text
var
Prestamo: Record "Prestamo";
begin
Prestamo.SetRange(Estado, Prestamo.Estado::Vencido);
exit('Atención: tienes ' + Format(Prestamo.Count) + ' préstamos vencidos.');
end;
}
💡 Claves del HeadlinePart
- Cada
fielddel HeadlinePart es un titular independiente. BC los muestra en rotación o apilados. - Usa
Visiblecon una variable booleana para mostrar u ocultar titulares según la situación. - El texto lo genera un procedimiento AL, así que puede ser completamente dinámico.
- Para incrustarlo en el RoleCenter añade:
part(Cabecera; "Biblioteca Headline") { }dentro delarea(RoleCenter).
3. Page tipo: Documento
El tipo Document representa documentos con una cabecera y sus líneas en la misma pantalla. La cabecera describe el documento completo (cliente, fecha, número) y las líneas detallan cada elemento (artículos, cantidades, precios). Es el tipo usado por los pedidos de venta, facturas o cualquier documento maestro-detalle.
Estructura: dos tablas, una página
La página de tipo Document tiene su SourceTable apuntando a la tabla de
cabecera. Las líneas se incrustan como una subpágina de tipo
ListPart ligada a la tabla de líneas.
// Tabla de cabecera del pedido de préstamo
table 50400 "Cabecera Pedido Prestamo"
{
DataClassification = CustomerContent;
fields
{
field(1; "No."; Code[20]) { }
field(2; "No. Socio"; Code[20]) { TableRelation = Socio; }
field(3; "Fecha Pedido"; Date) { }
}
keys
{
key(PK; "No.") { Clustered = true; }
}
}
// Tabla de líneas
table 50401 "Linea Pedido Prestamo"
{
DataClassification = CustomerContent;
fields
{
field(1; "No. Pedido"; Code[20]) { TableRelation = "Cabecera Pedido Prestamo"; }
field(2; "No. Linea"; Integer) { }
field(3; "No. Libro"; Code[20]) { TableRelation = Libro; }
field(4; "Dias Prestamo"; Integer) { }
}
keys
{
key(PK; "No. Pedido", "No. Linea") { Clustered = true; }
}
}
// Subpágina de líneas (ListPart)
page 50402 "Lineas Pedido Prestamo Part"
{
PageType = ListPart;
SourceTable = "Linea Pedido Prestamo";
layout
{
area(Content)
{
repeater(Lineas)
{
field("No. Linea"; Rec."No. Linea") { }
field("No. Libro"; Rec."No. Libro") { }
field("Dias Prestamo"; Rec."Dias Prestamo") { }
}
}
}
}
// Página de tipo Document (cabecera + líneas)
page 50403 "Pedido Prestamo"
{
PageType = Document;
SourceTable = "Cabecera Pedido Prestamo";
ApplicationArea = All;
UsageCategory = Documents;
Caption = 'Pedido de Préstamo';
layout
{
area(Content)
{
// Grupos de cabecera (FastTabs)
group(General)
{
Caption = 'General';
field("No."; Rec."No.") { }
field("No. Socio"; Rec."No. Socio") { }
field("Fecha Pedido"; Rec."Fecha Pedido") { }
}
// Subpágina de líneas incrustada
part(Lineas; "Lineas Pedido Prestamo Part")
{
SubPageLink = "No. Pedido" = field("No.");
ApplicationArea = All;
}
}
}
}
💡 Claves de la página Document
SubPageLinkes el nexo entre cabecera y líneas: le dice a la subpágina qué campo de la línea debe coincidir con qué campo de la cabecera.- La tabla de líneas siempre tiene una clave primaria compuesta: el número del documento padre + el número de línea.
- El tipo
Documenthabilita automáticamente la navegación entre documentos con las flechas de la barra de herramientas.
4. FastTabs — Organización visual en grupos
Las FastTabs son los paneles desplegables que organizan los campos
de una página Card o Document en secciones temáticas. En AL se definen simplemente
como controles group dentro del area(Content).
Su nombre viene de que el usuario puede desplegarlos y colapsarlos con un solo clic.
Propiedades clave de un grupo FastTab
| Propiedad | Qué hace | Ejemplo |
|---|---|---|
Caption |
Nombre visible del panel | Caption = 'Datos de Facturación' |
Importance = Additional |
El grupo aparece colapsado por defecto | Útil para datos secundarios |
Visible |
Muestra u oculta el grupo según una condición | Visible = EsEmpresa |
Editable |
Hace el grupo completo de solo lectura | Editable = false |
Propiedad Importance en campos individuales
Dentro de un FastTab, cada campo puede tener su propia propiedad Importance
que controla su visibilidad cuando el grupo está colapsado:
Importance = Promoted: el campo es visible incluso cuando el FastTab está colapsado.Importance = Standard: visible solo cuando el FastTab está expandido (valor por defecto).Importance = Additional: oculto por defecto. El usuario debe hacer clic en "Mostrar más" para verlo.
page 50410 "Socio Card Completa"
{
PageType = Card;
SourceTable = Socio;
layout
{
area(Content)
{
// FastTab 1: siempre visible con campos clave destacados
group(General)
{
Caption = 'General';
field("No."; Rec."No.") { Importance = Promoted; }
field(Nombre; Rec.Nombre) { Importance = Promoted; }
field(Email; Rec.Email) { }
field(Telefono; Rec.Telefono) { Importance = Additional; }
}
// FastTab 2: colapsado por defecto (datos secundarios)
group(Direccion)
{
Caption = 'Dirección';
Importance = Additional; // el grupo entero empieza colapsado
field(Calle; Rec.Calle) { }
field("Codigo Postal"; Rec."Codigo Postal") { }
field(Ciudad; Rec.Ciudad) { }
}
// FastTab 3: visible solo si el socio es de tipo Empresa
group(DatosEmpresa)
{
Caption = 'Datos Empresa';
Visible = EsEmpresa;
field(CIF; Rec.CIF) { }
field("Razon Social"; Rec."Razon Social") { }
}
}
}
var
EsEmpresa: Boolean;
trigger OnAfterGetRecord()
begin
EsEmpresa := (Rec.Tipo = Rec.Tipo::Empresa);
end;
}
5. Page tipo: Worksheet y Journal
El tipo Worksheet es una página optimizada para la entrada rápida y masiva de datos en formato de cuadrícula. A diferencia de una List normal, en un Worksheet el usuario puede editar directamente todas las filas a la vez sin necesidad de abrir una ficha. El caso más conocido en BC son los Diarios Contables.
Diferencias con List y Document
| Característica | List | Document | Worksheet |
|---|---|---|---|
| Edición inline | Limitada | Solo en líneas | Total — todas las columnas |
| Estructura | Solo registros | Cabecera + líneas | Solo líneas (sin cabecera) |
| Uso típico | Consulta y navegación | Pedidos, facturas | Diarios, hojas de trabajo |
| Destino de los datos | Tabla maestra | Tablas cabecera/línea | Tabla temporal → contabilización |
// Tabla de líneas del diario (datos temporales antes de contabilizar)
table 50420 "Diario Prestamo Line"
{
DataClassification = CustomerContent;
fields
{
field(1; "Journal Batch Name"; Code[10]) { }
field(2; "Line No."; Integer) { }
field(3; "No. Socio"; Code[20]) { TableRelation = Socio; }
field(4; "No. Libro"; Code[20]) { TableRelation = Libro; }
field(5; "Fecha"; Date) { }
}
keys
{
key(PK; "Journal Batch Name", "Line No.") { Clustered = true; }
}
}
// Página tipo Worksheet
page 50421 "Diario Prestamo"
{
PageType = Worksheet;
SourceTable = "Diario Prestamo Line";
ApplicationArea = All;
UsageCategory = Tasks;
Caption = 'Diario de Préstamos';
layout
{
area(Content)
{
repeater(Lineas)
{
field("No. Socio"; Rec."No. Socio") { }
field("No. Libro"; Rec."No. Libro") { }
field("Fecha"; Rec."Fecha") { }
}
}
}
actions
{
area(Processing)
{
action(Contabilizar)
{
Caption = 'Contabilizar';
ApplicationArea = All;
Image = PostOrder;
trigger OnAction()
begin
// Aquí llamarías al codeunit de posting
Message('Diario contabilizado correctamente.');
end;
}
}
}
}
6. Diálogos: ConfirmationDialog y StandardDialog
BC dispone de dos tipos especiales de página para interacciones modales con el usuario. No son páginas de datos al uso: su función es pedir confirmación o capturar parámetros simples antes de ejecutar una acción.
ConfirmationDialog
Muestra una ventana emergente con un mensaje y los botones Sí / No. Se usa para acciones destructivas o irreversibles donde necesitas confirmación explícita del usuario.
// Uso directo con la función Confirm() — sin necesidad de crear una página
trigger OnAction()
begin
if Confirm('¿Estás seguro de que quieres eliminar este préstamo?', false) then
Rec.Delete(true);
end;
// Página de tipo ConfirmationDialog (para diálogos más elaborados)
page 50430 "Confirmar Baja Socio"
{
PageType = ConfirmationDialog;
Caption = 'Confirmar Baja de Socio';
ApplicationArea = All;
layout
{
area(Content)
{
group(Mensaje)
{
ShowCaption = false;
field(TextoAviso; 'Esta acción eliminará el socio y todos sus préstamos.')
{
ApplicationArea = All;
ShowCaption = false;
Editable = false;
}
}
}
}
}
StandardDialog
Similar al ConfirmationDialog pero pensado para capturar información del usuario (no solo un sí/no). Se abre de forma modal y devuelve los valores introducidos cuando el usuario pulsa Aceptar.
// Página tipo StandardDialog para capturar un rango de fechas
page 50431 "Filtro Fechas Dialog"
{
PageType = StandardDialog;
Caption = 'Seleccionar Rango de Fechas';
ApplicationArea = All;
layout
{
area(Content)
{
group(Fechas)
{
field(FechaDesde; FechaDesde)
{
Caption = 'Fecha Desde';
ApplicationArea = All;
}
field(FechaHasta; FechaHasta)
{
Caption = 'Fecha Hasta';
ApplicationArea = All;
}
}
}
}
var
FechaDesde: Date;
FechaHasta: Date;
// Procedimientos para leer los valores desde el código que abrió el diálogo
procedure GetFechas(var pDesde: Date; var pHasta: Date)
begin
pDesde := FechaDesde;
pHasta := FechaHasta;
end;
}
// Cómo abrir el dialog desde otra página y leer sus valores
var
FiltroDialog: Page "Filtro Fechas Dialog";
Desde, Hasta: Date;
begin
if FiltroDialog.RunModal() = Action::OK then begin
FiltroDialog.GetFechas(Desde, Hasta);
Message('Rango: %1 — %2', Desde, Hasta);
end;
end;
💡 Confirm() vs ConfirmationDialog
- Para confirmaciones simples usa directamente la función
Confirm()de AL: es más sencillo y requiere menos código. - Usa
PageType = ConfirmationDialogcuando el diálogo necesite mostrar información adicional o campos. RunModal()devuelveAction::OKoAction::Cancelsegún lo que pulse el usuario.