EJERCICIO 08 · TIPOS DE PAGES

Tipos de Pages

Dificultad: 3/10
Ejercicios anteriores

¿Qué tienes construido hasta ahora?

  • Tablas completas: "Maquinaria", "Cabecera Alquiler", "Linea Alquiler" y "Historial Estado Maquina" con triggers, FlowFields, FlowFilters y TableRelations.
  • Pages básicas: "Maquinaria List", "Maquinaria Card" y "Cabecera Alquiler Card".
  • Codeunit "Maquinaria Stats" con la comparativa trimestral.
  • Las pages actuales son funcionales pero incompletas: les falta vinculación entre List y Card, vistas filtradas, subformularios y FactBoxes.

Con las tablas y los triggers listos, el proyecto tiene toda la lógica de negocio en su sitio. Ahora toca construir la interfaz completa: vincular las pages, crear vistas especializadas con SourceTableView, añadir el historial como ListPart embebido en la ficha de maquinaria y terminar con un CardPart de resumen.

Este es el mapa de pages que tendrás al terminar el ejercicio:

List · existente → mejorar
"Maquinaria List"
Añadir CardPageId, Editable = false
Card · existente → mejorar
"Maquinaria Card"
Añadir grupos, Importance, ListPart de historial
List · nueva
"Cabecera Alquiler List"
Con CardPageId y columnas clave
List · nueva · SourceTableView
"Alquileres Activos List"
Solo alquileres con estado ≠ Retirado
ListPart · nueva
"Historial Maquina Part"
Embebida en Maquinaria Card
CardPart · nueva
"Maquinaria Stats Part"
FactBox con FlowFields de la máquina
Páginas nuevas Páginas existentes a mejorar
Ejercicio 01
List · Card · CardPageId

Vincula y mejora las pages existentes de Maquinaria

Objetivo: Completar "Maquinaria List" con CardPageId y Editable = false, y reestructurar "Maquinaria Card" en grupos (General, Estadísticas) usando Importance = Promoted en los campos clave.

Instrucciones

  • En MaquinariaList.Page.al añade las propiedades CardPageId = "Maquinaria Card" y Editable = false al nivel raíz de la page.
  • En MaquinariaCard.Page.al, divide el layout en dos grupos: group(General) con "No.", "Descripcion", "Categoria", "Fecha Alta" y "Estado"; y group(Estadisticas) con "Date Filter", "No. Alquileres", "Tiene Historial" y "Ultimo Cambio Estado".
  • En el grupo General, marca con Importance = Promoted los campos "No.", "Descripcion" y "Estado" para que aparezcan visibles aunque el grupo esté contraído.

Pista de Código

AL — MaquinariaList.Page.al (propiedades raíz)
page 50100 "Maquinaria List"
{
    PageType        = List;
    ApplicationArea = All;
    UsageCategory   = Lists;
    SourceTable     = "Maquinaria";
    CardPageId      = "Maquinaria Card";  // doble clic abre la ficha
    Editable        = false;              // solo lectura desde la lista
    // ... resto de la page
}
AL — MaquinariaCard.Page.al (layout reestructurado)
    layout
    {
        area(Content)
        {
            group(General)
            {
                field("No.";        Rec."No.")        { ApplicationArea = All; Importance = Promoted; }
                field("Descripcion"; Rec."Descripcion") { ApplicationArea = All; Importance = Promoted; }
                field("Categoria";   Rec."Categoria")   { ApplicationArea = All; }
                field("Fecha Alta";  Rec."Fecha Alta")  { ApplicationArea = All; }
                field("Estado";      Rec."Estado")
                {
                    ApplicationArea = All;
                    Importance      = Promoted;
                    trigger OnValidate()
                    begin
                        if Rec.Estado = Rec.Estado::Retirado then
                            Message('Atención: esta máquina ha sido retirada del parque.');
                    end;
                }
            }
            group(Estadisticas)
            {
                Caption = 'Estadísticas';
                field("Date Filter";         Rec."Date Filter")         { ApplicationArea = All; }
                field("No. Alquileres";    Rec."No. Alquileres")    { ApplicationArea = All; }
                field("Tiene Historial";   Rec."Tiene Historial")   { ApplicationArea = All; }
                field("Ultimo Cambio Estado"; Rec."Ultimo Cambio Estado") { ApplicationArea = All; }
            }
        }
    }
Ejercicio 02
List · SourceTableView

Lista de alquileres y vista filtrada de activos

Objetivo: Crear la page de lista general de "Cabecera Alquiler" y una segunda page con SourceTableView que muestre solo los alquileres cuyo estado no sea Retirado, sin que el usuario vea el filtro en la barra.

Instrucciones

  • Crea CabeceraAlquilerList.Page.al (ID 50105): PageType = List, SourceTable = "Cabecera Alquiler", CardPageId = "Cabecera Alquiler Card", Editable = false. El repeater debe mostrar "No.", "Descripcion", "No. Maquina Principal", "Descripcion Maquina", "Fecha Inicio", "Fecha Fin", "Estado" y "No. Lineas".
  • Crea AlquileresActivosList.Page.al (ID 50106): misma estructura pero añade SourceTableView = where("Estado" = filter(<> Retirado)) para excluir las máquinas retiradas. Añade también ordenación descendente por "Fecha Inicio".

Pista de Código

AL — AlquileresActivosList.Page.al
page 50106 "Alquileres Activos List"
{
    PageType        = List;
    ApplicationArea = All;
    UsageCategory   = Lists;
    SourceTable     = "Cabecera Alquiler";
    CardPageId      = "Cabecera Alquiler Card";
    Editable        = false;
    Caption         = 'Alquileres Activos';

    SourceTableView = sorting("Fecha Inicio") order(descending)
                      where("Estado" = filter(<> Retirado));

    layout
    {
        area(Content)
        {
            repeater(GroupName)
            {
                field("No.";                  Rec."No.")                  { ApplicationArea = All; }
                field("Descripcion";         Rec."Descripcion")         { ApplicationArea = All; }
                field("Descripcion Maquina";  Rec."Descripcion Maquina")  { ApplicationArea = All; }
                field("Fecha Inicio";         Rec."Fecha Inicio")         { ApplicationArea = All; }
                field("Fecha Fin";            Rec."Fecha Fin")            { ApplicationArea = All; }
                field("Estado";              Rec."Estado")              { ApplicationArea = All; }
                field("No. Lineas";          Rec."No. Lineas")          { ApplicationArea = All; }
            }
        }
    }
}
💡 Recuerda: SourceTableView aplica un filtro invisible para el usuario. Si necesitas una vista con filtro visible y modificable, usa una acción con SetRange como la del ejercicio de Enums. Ambos enfoques son válidos según el caso de uso.
Ejercicio 03
ListPart · Subformulario

Historial de estado embebido en la ficha de Maquinaria

Objetivo: Crear una page de tipo ListPart que muestre el historial de cambios de estado de la máquina activa, y embeberla en "Maquinaria Card" como subformulario.

Instrucciones

  • Crea HistorialMaquinaPart.Page.al (ID 50107): PageType = ListPart, SourceTable = "Historial Estado Maquina", sin UsageCategory ni repeater. Muestra los campos "Entry No.", "Estado Anterior", "Estado Nuevo", "Fecha Cambio" y "Tipo Origen".
  • Vuelve a MaquinariaCard.Page.al y añade un área area(FactBoxes) después del layout. Dentro, declara un part que apunte a "Historial Maquina Part" y que aplique el SubPageLink "No. Maquina" = field("No.") para que el subformulario filtre automáticamente por la máquina abierta.

Pista de Código

AL — HistorialMaquinaPart.Page.al
page 50107 "Historial Maquina Part"
{
    PageType        = ListPart;
    ApplicationArea = All;
    SourceTable     = "Historial Estado Maquina";
    Editable        = false;

    layout
    {
        area(Content)
        {
            field("Entry No.";       Rec."Entry No.")       { ApplicationArea = All; }
            field("Estado Anterior"; Rec."Estado Anterior") { ApplicationArea = All; }
            field("Estado Nuevo";    Rec."Estado Nuevo")    { ApplicationArea = All; }
            field("Fecha Cambio";    Rec."Fecha Cambio")    { ApplicationArea = All; }
            field("Tipo Origen";     Rec."Tipo Origen")     { ApplicationArea = All; }
        }
    }
}
AL — MaquinariaCard.Page.al (área FactBoxes)
        area(FactBoxes)
        {
            part(HistorialPart; "Historial Maquina Part")
            {
                ApplicationArea = All;
                Caption         = 'Historial de Estado';
                SubPageLink     = "No. Maquina" = field("No.");
            }
        }
⚠️ Ojo: Las pages de tipo ListPart no llevan repeater en el layout. Si lo añades, el compilador lanzará un error. Los campos se declaran directamente dentro del area(Content).
Ejercicio 04
CardPart · OnAfterGetRecord · OnOpenPage

FactBox de resumen y título dinámico

Objetivo: Crear un CardPart de resumen estadístico que aparezca como FactBox en "Maquinaria List", y añadir un trigger OnAfterGetRecord() a "Maquinaria Card" que cambie el título de la página si la máquina está retirada.

Instrucciones

  • Crea MaquinariaStatsPart.Page.al (ID 50108): PageType = CardPart, SourceTable = "Maquinaria". Muestra los FlowFields "No. Alquileres", "Tiene Historial" y "Ultimo Cambio Estado". En el trigger OnAfterGetRecord() del CardPart, llama a Rec.CalcFields(...) para los tres campos.
  • Añade este CardPart como FactBox en "Maquinaria List" con SubPageLink = "No." = field("No.").
  • En "Maquinaria Card", añade el trigger OnAfterGetRecord(): si Rec.Estado = Rec.Estado::Retirado, asigna CurrPage.Caption := Rec."Descripcion" + ' — RETIRADA'; en caso contrario, asigna simplemente CurrPage.Caption := Rec."Descripcion".

Pista de Código

AL — MaquinariaStatsPart.Page.al
page 50108 "Maquinaria Stats Part"
{
    PageType        = CardPart;
    ApplicationArea = All;
    SourceTable     = "Maquinaria";

    layout
    {
        area(Content)
        {
            field("No. Alquileres";    Rec."No. Alquileres")    { ApplicationArea = All; }
            field("Tiene Historial";   Rec."Tiene Historial")   { ApplicationArea = All; }
            field("Ultimo Cambio Estado"; Rec."Ultimo Cambio Estado") { ApplicationArea = All; }
        }
    }

    trigger OnAfterGetRecord()
    begin
        Rec.CalcFields("No. Alquileres", "Tiene Historial", "Ultimo Cambio Estado");
    end;
}
AL — MaquinariaCard.Page.al (trigger OnAfterGetRecord)
    trigger OnAfterGetRecord()
    begin
        Rec.CalcFields("Ultimo Cambio Estado", "No. Alquileres", "Tiene Historial");

        if Rec.Estado = Rec.Estado::Retirado then
            CurrPage.Caption := Rec."Descripcion" + ' — RETIRADA'
        else
            CurrPage.Caption := Rec."Descripcion";
    end;
💡 Recuerda: OnAfterGetRecord() se ejecuta cada vez que BC carga un registro en la page: al abrir la ficha, al navegar con los botones anterior/siguiente y al refrescar. Es el lugar correcto para cálculos visuales que dependen del registro activo. Evita operaciones pesadas aquí para no ralentizar la navegación.
💡 Bonus: Ahora que tienes "Alquileres Activos List" y "Cabecera Alquiler List", podrías añadir una acción en "Maquinaria Card" que abra "Alquileres Activos List" pre-filtrada por Rec."No." usando SetRange, para ver de un vistazo todos los alquileres activos de esa máquina concreta.
← Volver a Ejercicios