Lógica y Eventos
Triggers de Tabla
Dificultad: 6/10Los 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.
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.
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.
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.
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.
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:
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:
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
OnInsertpara lógica de inicialización yOnValidatepara 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 unOnValidatepermite 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.