EJERCICIO 08 · TIPOS DE PAGES
Tipos de Pages
Dificultad: 3/10Ejercicios 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.alañade las propiedadesCardPageId = "Maquinaria Card"yEditable = falseal 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"; ygroup(Estadisticas)con"Date Filter","No. Alquileres","Tiene Historial"y"Ultimo Cambio Estado". - En el grupo General, marca con
Importance = Promotedlos 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. Elrepeaterdebe 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ñadeSourceTableView = 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", sinUsageCategorynirepeater. Muestra los campos"Entry No.","Estado Anterior","Estado Nuevo","Fecha Cambio"y"Tipo Origen". - Vuelve a
MaquinariaCard.Page.aly añade un áreaarea(FactBoxes)después del layout. Dentro, declara unpartque apunte a"Historial Maquina Part"y que aplique elSubPageLink"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 triggerOnAfterGetRecord()del CardPart, llama aRec.CalcFields(...)para los tres campos. - Añade este CardPart como FactBox en
"Maquinaria List"conSubPageLink = "No." = field("No."). - En
"Maquinaria Card", añade el triggerOnAfterGetRecord(): siRec.Estado = Rec.Estado::Retirado, asignaCurrPage.Caption := Rec."Descripcion" + ' — RETIRADA'; en caso contrario, asigna simplementeCurrPage.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.