Páginas Avanzadas (II): Interacción y Control
Esta segunda parte cubre los mecanismos de interacción y control de las páginas de Business Central: la función Navigate para conectar registros relacionados, los gráficos integrados, el Diseñador en el cliente para personalizar sin código, los triggers de control de campo y la diferencia esencial entre páginas encuadernadas y sin encuadernar.
1. Navegar por la Página y Función Navigate
La función Navigate en Business Central permite al usuario ver todos los documentos y movimientos contables relacionados con un registro concreto desde una ventana unificada. Por ejemplo, desde una factura publicada puedes ver los movimientos de cliente, las entradas de IVA y los apuntes de libro mayor que se generaron al contabilizarla, todo en una sola pantalla.
¿Cómo funciona Navigate internamente?
BC mantiene un campo "Document No." y "Posting Date"
en la mayoría de sus tablas de movimientos. La función Navigate busca ese número
de documento en todas las tablas registradas y muestra un resumen con cuántos
registros encontró en cada una.
Añadir Navigate a tu propia página
// Acción Navigate en una página de documento publicado
page 50500 "Prestamo Publicado Card"
{
PageType = Card;
SourceTable = "Prestamo Publicado";
actions
{
area(Navigation)
{
action(Navigate)
{
Caption = '&Navegar';
ApplicationArea = All;
Image = Navigate;
trigger OnAction()
var
NavigatePage: Page Navigate;
begin
// Le pasamos el número de documento y la fecha de contabilización
NavigatePage.SetDoc(Rec."Posting Date", Rec."No.");
NavigatePage.Run();
end;
}
}
}
}
Registrar tu propia tabla en Navigate
Si has creado una tabla de movimientos propia y quieres que aparezca en los resultados
de Navigate, debes suscribirte al evento OnAfterNavigate del codeunit
de Navigate:
codeunit 50501 "Registro Navigate Prestamo"
{
[EventSubscriber(ObjectType::Page, Page::Navigate,
'OnAfterNavigate', '', false, false)]
local procedure RegistrarMovPrestamo(
var DocEntry: Record "Document Entry";
DocNoFilter: Text;
PostingDateFilter: Text)
var
MovPrestamo: Record "Movimiento Prestamo";
begin
MovPrestamo.SetFilter("Document No.", DocNoFilter);
MovPrestamo.SetFilter("Posting Date", PostingDateFilter);
if MovPrestamo.FindFirst() then begin
DocEntry.Init();
DocEntry."Table ID" := Database::"Movimiento Prestamo";
DocEntry."Table Name" := 'Movimiento Prestamo';
DocEntry."No. of Records" := MovPrestamo.Count;
DocEntry.Insert();
end;
end;
}
💡 Claves de Navigate
SetDoc(PostingDate, DocumentNo)es la forma estándar de inicializar Navigate con los datos del registro actual.- El usuario puede hacer doble clic en cualquier línea del resultado de Navigate para abrir directamente esa tabla filtrada.
- Registrar tu tabla en Navigate mediante el evento es la forma correcta: no modifica ningún objeto base de BC.
2. Gráficos en Páginas de Business Central
Business Central permite incrustar gráficos dentro de páginas usando el control BusinessChart. Este control renderiza datos de una query o de una tabla en forma de gráfico de barras, líneas o circular directamente en la interfaz.
Componentes necesarios
- Una Query que agrupe y calcule los datos a mostrar.
- Un codeunit que construya el objeto
BusinessChartBuffercon los datos. - Una página con un control de tipo
BusinessChartPartque muestre el gráfico.
// Página con gráfico de préstamos por mes
page 50510 "Grafico Prestamos Part"
{
PageType = CardPart;
ApplicationArea = All;
layout
{
area(Content)
{
// Control nativo de BC para gráficos
usercontrol(GraficoCtrl; "Microsoft.Dynamics.Nav.Client.BusinessChart")
{
ApplicationArea = All;
trigger DataPointClicked(Point: JsonObject)
begin
// Reaccionar cuando el usuario hace clic en un punto del gráfico
Message('Punto seleccionado en el gráfico.');
end;
trigger AddInReady()
begin
CargarDatosGrafico();
end;
}
}
}
var
ChartBuffer: Record "Business Chart Buffer";
local procedure CargarDatosGrafico()
begin
ChartBuffer.Initialize();
ChartBuffer.SetXAxis('Mes', ChartBuffer."Data Type"::String);
ChartBuffer.AddDecimalMeasure('Préstamos', 1, ChartBuffer."Chart Type"::Column);
// Añadir datos manualmente o desde una query
ChartBuffer.SetValueByIndex(0, 0, 'Enero');
ChartBuffer.SetValueByIndex(0, 1, 24);
ChartBuffer.SetValueByIndex(1, 0, 'Febrero');
ChartBuffer.SetValueByIndex(1, 1, 31);
ChartBuffer.Update(CurrPage.GraficoCtrl);
end;
}
⚠️ Gráficos y compatibilidad
- El control
BusinessChartsolo funciona en el cliente web de BC. No se renderiza en entornos headless ni en exportaciones. - Para dashboards más complejos, la alternativa recomendada por Microsoft es Power BI integrado directamente en el RoleCenter.
3. Diseñador en el Cliente
El Diseñador (Designer) de Business Central es una herramienta visual
integrada en el propio navegador que permite personalizar páginas sin escribir
código AL. Los cambios se guardan como una extensión de página implícita y
pueden exportarse como un fichero .app.
¿Cómo activarlo?
- Navega a cualquier página de BC en el navegador.
- Haz clic en el icono de Configuración (engranaje) en la esquina superior derecha.
- Selecciona Diseño o pulsa Shift + F7 en algunas versiones.
- La página entrará en modo diseño: los campos tendrán bordes azules y aparecerá una barra de herramientas de diseño.
Qué puedes hacer con el Diseñador
| Acción | Cómo |
|---|---|
| Mover campos | Arrastra y suelta cualquier campo a otra posición |
| Ocultar campos | Clic derecho sobre el campo → Ocultar |
| Añadir campos de la tabla | Botón "+" → lista de campos disponibles en la tabla fuente |
| Crear nuevos FastTabs | Botón "Añadir grupo" en la barra de herramientas |
| Añadir partes (FactBoxes) | Botón "Añadir parte" → selecciona la página a incrustar |
| Exportar como extensión | Botón "Detener diseño" → opción de descargar el .app generado |
💡 Diseñador vs. PageExtension en VS Code
- El Diseñador es perfecto para ajustes rápidos y personalizaciones visuales sencillas.
- Para lógica de negocio (triggers, validaciones, acciones) necesitas siempre VS Code.
- Puedes exportar lo que has hecho en el Diseñador como código AL y continuar refinándolo en VS Code.
- Los cambios del Diseñador se almacenan como una extensión de perfil, no modifican el objeto original.
4. Triggers de Control de Página
Además de los triggers de la propia página (OnOpenPage, OnAfterGetRecord…),
cada campo individual dentro de una página puede tener sus propios triggers
que responden a la interacción directa del usuario con ese control concreto.
| Trigger | Cuándo se dispara | Uso típico |
|---|---|---|
OnValidate |
El usuario sale del campo después de modificarlo | Validar el valor introducido y actualizar campos dependientes |
OnLookup |
El usuario hace clic en el botón de búsqueda del campo | Mostrar una lista filtrada personalizada en lugar de la estándar |
OnDrillDown |
El usuario hace clic en el valor de un campo (generalmente FlowField) | Abrir una lista con el detalle de los registros que forman el total |
OnAssistEdit |
El usuario hace clic en el botón "…" junto al campo | Abrir un asistente o diálogo auxiliar para rellenar el campo |
page 50520 "Prestamo Card"
{
PageType = Card;
SourceTable = Prestamo;
layout
{
area(Content)
{
group(General)
{
field("No. Socio"; Rec."No. Socio")
{
ApplicationArea = All;
// OnValidate: al cambiar el socio, actualizamos su nombre
trigger OnValidate()
var
Socio: Record Socio;
begin
if Socio.Get(Rec."No. Socio") then
Rec."Nombre Socio" := Socio.Nombre;
end;
// OnLookup: filtramos solo socios activos
trigger OnLookup(var Text: Text): Boolean
var
Socio: Record Socio;
SocioList: Page "Socio List";
begin
Socio.SetRange(Activo, true);
SocioList.SetTableView(Socio);
SocioList.LookupMode := true;
if SocioList.RunModal() = Action::LookupOK then begin
SocioList.GetRecord(Socio);
Rec."No. Socio" := Socio."No.";
exit(true);
end;
end;
}
field("Total Prestamos"; Rec."Total Prestamos")
{
ApplicationArea = All;
// OnDrillDown: al hacer clic en el total, mostramos el detalle
trigger OnDrillDown()
var
Prestamo: Record Prestamo;
PrestamoList: Page "Prestamo List";
begin
Prestamo.SetRange("No. Socio", Rec."No. Socio");
PrestamoList.SetTableView(Prestamo);
PrestamoList.Run();
end;
}
field("No. Libro"; Rec."No. Libro")
{
ApplicationArea = All;
// OnAssistEdit: abre un asistente para seleccionar el libro
trigger OnAssistEdit()
var
Libro: Record Libro;
begin
if Libro.Get(Rec."No. Libro") then;
if PAGE.RunModal(PAGE::"Libro List", Libro) = Action::LookupOK then
Rec."No. Libro" := Libro."No.";
end;
}
}
}
}
}
5. Páginas Encuadernadas y Sin Encuadernar
En Business Central, una página puede estar encuadernada (bound) a una tabla o sin encuadernar (unbound). Esta diferencia determina si la página lee y escribe datos de la base de datos directamente o si trabaja exclusivamente con variables en memoria.
| Característica | Encuadernada (Bound) | Sin encuadernar (Unbound) |
|---|---|---|
SourceTable |
Sí, apunta a una tabla | No se declara |
| Origen de los datos | Base de datos SQL | Variables AL en memoria |
| Guardado automático | Sí, BC gestiona Insert/Modify | No, el desarrollador controla todo |
| Usos típicos | Cards, Lists, Documents, Worksheets | Asistentes, diálogos de parámetros, formularios libres |
Ejemplo de página sin encuadernar (Unbound)
Una página sin encuadernar es perfecta para crear asistentes de configuración o formularios donde el usuario introduce parámetros que después se usan en un proceso, sin necesidad de guardar nada en ninguna tabla.
// Página sin SourceTable — completamente unbound
page 50530 "Asistente Importacion"
{
PageType = NavigatePage; // tipo para asistentes paso a paso
Caption = 'Asistente de Importación';
ApplicationArea = All;
// Sin SourceTable: la página no está ligada a ninguna tabla
layout
{
area(Content)
{
group(Paso1)
{
Visible = PasoActual = 1;
Caption = 'Paso 1: Configuración';
field(RutaFichero; RutaFichero)
{
Caption = 'Ruta del fichero';
ApplicationArea = All;
}
field(Separador; Separador)
{
Caption = 'Separador';
ApplicationArea = All;
}
}
group(Paso2)
{
Visible = PasoActual = 2;
Caption = 'Paso 2: Confirmar';
field(Resumen; GetResumen())
{
Caption = 'Resumen';
ApplicationArea = All;
Editable = false;
MultiLine = true;
}
}
}
}
actions
{
area(Processing)
{
action(Siguiente)
{
Caption = 'Siguiente';
ApplicationArea = All;
Visible = PasoActual < 2;
trigger OnAction()
begin
PasoActual += 1;
end;
}
action(Finalizar)
{
Caption = 'Importar';
ApplicationArea = All;
Visible = PasoActual = 2;
trigger OnAction()
begin
// Ejecutar la importación con los parámetros recogidos
Message('Importando desde: %1 con separador: %2', RutaFichero, Separador);
CurrPage.Close();
end;
}
}
}
// Variables en memoria — no se guardan en ninguna tabla
var
PasoActual: Integer;
RutaFichero: Text[250];
Separador: Text[1];
trigger OnOpenPage()
begin
PasoActual := 1;
Separador := ';';
end;
local procedure GetResumen(): Text
begin
exit('Fichero: ' + RutaFichero + ' | Separador: ' + Separador);
end;
}
💡 Cuándo usar cada tipo
- Usa páginas encuadernadas siempre que los datos deban persistir en la base de datos: maestros, documentos, movimientos.
- Usa páginas sin encuadernar para asistentes de configuración, diálogos de parámetros o cualquier flujo donde los datos sean temporales.
- En las páginas unbound, eres tú quien decide qué hacer con los datos que introduce el usuario: guardarlos, pasarlos a un codeunit o simplemente usarlos en el momento.