Lógica y Eventos

Triggers de Tabla

Dificultad: 6/10

Los Triggers (Disparadores) son fragmentos de código AL que se ejecutan automáticamente por la plataforma cuando ciertos eventos físicos suceden con los registros de la base de datos.

Si escribimos código dentro de ciertas funciones pre-generadas en la raíz del objeto table, nos aseguramos de que ese código siempre se ejecute, sin importar desde qué pantalla ni cómo llegó la orden.

⚠️ Orden de ejecución

El código contenido en cada trigger se ejecuta antes de ejecutar el evento correspondiente. Es decir, tienes la oportunidad de validar, modificar o cancelar la operación antes de que BC la lleve a cabo.

Triggers principales de tabla

Trigger Cuándo se ejecuta Uso habitual
OnInsert() La primera vez que un registro nuevo se guarda en SQL Autocompletar números de serie, asignar fecha de creación, arrastrar campos
OnModify() Cada vez que un campo existente recibe un valor diferente Auditoría, guardar "Modificado Por", bloquear ediciones en registros contabilizados
OnDelete() Justo antes de destruir el registro Validar dependencias, impedir borrar registros con datos relacionados
OnRename() Al cambiar físicamente el valor de la Clave Primaria Actualizar tablas relacionadas para que nada se rompa al renombrar

OnInsert — Al crear un registro

Se dispara la primera vez que un registro en blanco (nuevo) es salvado en SQL. Se usa para autocompletar números de serie, asignar la fecha de hoy como "Create Date" o arrastrar campos principales.

AL
trigger OnInsert()
begin
    Rec."Created Date" := Today; // Asignación automática al crear
end;

OnModify — Al modificar un registro

Se dispara cada vez que una columna existente de un registro guardado recibe un valor diferente. Se usa para auditar cambios, guardar históricos de "Modificado Por" o bloquear ciertas ediciones.

AL
trigger OnModify()
begin
    Rec."Last Modified By" := UserId; // Auditoría de cambios
end;

OnDelete — Al borrar un registro

Se ejecuta justo antes de destruir el registro. Se usa comúnmente para validar dependencias, por ejemplo impedir que se borre un cliente que tiene facturas abiertas.

AL
trigger OnDelete()
begin
    if Rec.Status = Rec.Status::Released then
        Error('No se puede borrar un registro lanzado.');
end;

OnRename — Al cambiar la clave primaria

Es el más raro. Se activa solo si intentamos cambiar físicamente el valor de la Clave Primaria. Se usa mucho en BC para navegar por las tablas relacionadas y actualizar los viejos valores por el nuevo automáticamente para que nada se rompa.

AL
trigger OnRename()
begin
    Error('Los registros de esta tabla no pueden ser renombrados por integridad.');
end;

OnValidate — Trigger de campo

Puesto que OnModify es general para toda la fila, el trigger más usado en el día a día es el que posee cada campo por separado: OnValidate().

El usuario teclea en un campo de la pantalla, presiona el Tabulador, e inmediatamente OnValidate() salta. Se usa para calcular campos relacionados en vivo o para validar que el valor introducido es correcto.

Ejemplo clásico: el usuario cambia la Cantidad, el OnValidate() coge el Precio Unitario, los multiplica e inyecta el resultado en el Importe Total de forma automática.

AL
field(5; "Cantidad"; Decimal)
{
    trigger OnValidate()
    begin
        // Si cambias la cantidad, se recalcula el importe automáticamente
        Rec."Importe Total" := Rec."Cantidad" * Rec."Precio Unitario";
    end;
}

OnValidate con validación y cascada

El OnValidate también permite validar el dato antes de aceptarlo y disparar la validación en cascada de otros campos relacionados:

AL
field(50; "Discount %"; Decimal)
{
    trigger OnValidate()
    begin
        if (Rec."Discount %" > 50) then
            Error('Descuento máximo permitido: 50%.');

        // Validación en cascada: recalcula el importe neto automáticamente
        Rec.Validate("Net Amount", Rec."Gross Amount" * (1 - (Rec."Discount %" / 100)));
    end;
}

Ejemplo completo: tabla con todos los triggers

Una tabla de préstamos que usa todos los triggers para controlar el ciclo de vida del dato:

AL
table 50103 "Prestamo"
{
    fields
    {
        field(1; "No.";              Code[20])   { }
        field(2; "No. Socio";        Code[20])   { TableRelation = "Socio"; }
        field(3; "No. Libro";        Code[20])   { TableRelation = "Libro"; }
        field(4; "Fecha Prestamo";   Date)       { }
        field(5; "Fecha Devolucion"; Date)       { }
        field(6; "Dias Prestamo";    Integer)    { }
        field(7; "Devuelto";         Boolean)    { }
        field(8; "Modificado Por";   Code[50])   { }
        field(9; "Estado";           Enum "Estado Prestamo") { }

        field(10; "Fecha Devolucion Prevista"; Date)
        {
            trigger OnValidate()
            begin
                // No se puede devolver antes de que empiece el préstamo
                if Rec."Fecha Devolucion Prevista" < Rec."Fecha Prestamo" then
                    Error('La fecha de devolución no puede ser anterior a la de préstamo.');

                // Calculamos automáticamente los días de préstamo
                Rec."Dias Prestamo" := Rec."Fecha Devolucion Prevista" - Rec."Fecha Prestamo";
            end;
        }
    }

    keys
    {
        key(PK; "No.") { }
        key(SK1; "No. Socio") { }
        key(SK2; "No. Libro") { }
    }

    // Al crear: asignamos la fecha de hoy automáticamente
    trigger OnInsert()
    begin
        Rec."Fecha Prestamo" := Today;
        Rec."Estado"         := Rec."Estado"::Activo;
    end;

    // Al modificar: registramos quién hizo el último cambio
    trigger OnModify()
    begin
        Rec."Modificado Por" := UserId;
    end;

    // Al borrar: impedimos borrar préstamos que no han sido devueltos
    trigger OnDelete()
    begin
        if not Rec.Devuelto then
            Error('No se puede eliminar un préstamo que no ha sido devuelto.');
    end;

    // Al renombrar: bloqueamos el cambio de PK por integridad
    trigger OnRename()
    begin
        Error('Los préstamos no pueden ser renombrados.');
    end;
}

💡 Tips

  • Los triggers de tabla se ejecutan siempre, independientemente de desde qué pantalla o proceso llegue la operación.
  • Usa OnInsert para lógica de inicialización y OnValidate para lógica de negocio campo a campo.
  • Usa Error() dentro de cualquier trigger para cancelar la operación y mostrar un mensaje al usuario.
  • Rec.Validate() dentro de un OnValidate permite disparar la validación de otro campo en cascada, manteniendo la lógica centralizada.
  • Evita lógica pesada en OnModify: se dispara en cada cambio de campo y puede ralentizar la experiencia.
← Volver a Teoría