Dif. 7/10

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

// 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 BusinessChart solo 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?

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.
← Volver a Teoría