Dif. 7/10

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 OnOpenPage si no existe.
  • DrillDownPageId define 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 field del HeadlinePart es un titular independiente. BC los muestra en rotación o apilados.
  • Usa Visible con 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 del area(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

  • SubPageLink es 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 Document habilita 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:

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 = ConfirmationDialog cuando el diálogo necesite mostrar información adicional o campos.
  • RunModal() devuelve Action::OK o Action::Cancel según lo que pulse el usuario.
← Volver a Teoría