Ir al contenido

Catálogo de hooks

Los hooks permiten a un plugin ejecutar lógica cuando ocurre algo en el ERP: se crea una factura, se asienta, se registra un pago, se cierra un período…

Hay dos estilos, ambos válidos:

export const init = async ({ hooks }: PluginContext) => {
hooks.register('salesInvoice.posted', async (ctx) => {
// se ejecuta al asentar cualquier factura de venta
});
};

Acepta cualquier evento del catálogo (documentos, listados, contabilidad, pagos, RRHH, logística). El nombre del evento usa el tipo de documento en camelCase (salesInvoice, purchaseOrder…).

En la firma de hooks.register el evento es un string sin restringir. Para tener autocompletado, anota con el tipo HookEvent:

import type { HookEvent } from '@openfactu/plugin-sdk';
const evento: HookEvent = 'salesInvoice.posted';
hooks.register(evento, handler);

Todo handler recibe un HookContext:

interface HookContext {
tenantId: string; // tenant donde ocurre el evento
db: any; // cliente Drizzle del tenant
data: any; // payload del evento (documento, pago, asiento...)
user?: any; // usuario que dispara la acción (si aplica)
[key: string]: any; // campos extra según el evento
}
type HookHandler = (ctx: HookContext) => Promise<void> | void;
  • En eventos beforeCreate, lanzar una excepción aborta la operación: throw new Error('...') impide que el documento se cree.
  • data contiene el objeto del evento: el documento en eventos de documento, el pago en payment.*, el asiento en journalEntry.*, etc.

Cada uno de los 6 tipos de documento emite 3 fases:

Evento Cuándo Notas
<tipo>.beforeCreate Antes de insertar el documento Lanzar excepción aborta la creación
<tipo>.afterCreate Justo después de insertarlo data incluye el documento creado
<tipo>.posted Al asentar el documento Fase contable

Tipos disponibles (camelCase): salesInvoice, purchaseInvoice, salesOrder, purchaseOrder, salesDeliveryNote, purchaseDeliveryNote.

hooks.register('salesInvoice.beforeCreate', async (ctx) => {
if (ctx.data.total > 10000) {
throw new Error('Límite de factura excedido');
}
});
Evento Cuándo
items.list.afterFetch Tras cargar el listado de artículos
partners.list.afterFetch Tras cargar el listado de socios de negocio

Estos handlers reciben un ListFetchContext, que extiende HookContext:

interface ListFetchContext<T = any> extends HookContext {
entity: 'items' | 'partners' | string;
filters: Record<string, any>; // filtros aplicados a la consulta
rows: T[]; // filas devueltas
}

El handler puede mutar rows (añadir columnas calculadas, filtrar, reordenar) o devolver un array nuevo:

hooks.register('partners.list.afterFetch', async (ctx) => {
const listCtx = ctx as ListFetchContext;
for (const row of listCtx.rows) {
row.saldo_puntos = await calcularPuntos(ctx, row.id);
}
});
Evento Cuándo
journalEntry.posted Al asentar un asiento contable
journalEntry.reversed Al revertir un asiento
period.closed Al cerrar un período contable
period.opened Al abrir un período contable
Evento Cuándo
payment.created Al registrar un cobro o pago
payment.deleted Al eliminar un cobro o pago
Evento Cuándo
payroll.approved Al aprobar una nómina
Evento Cuándo
shipment.created Se crea un envío
shipment.packed Envío embalado
shipment.dispatched Envío despachado
shipment.delivered Envío entregado
shipment.received Envío recibido
shipment.exception Incidencia en el envío
picking.started Comienza una tarea de picking
picking.taskUpdated Se actualiza una tarea de picking
picking.completed Picking completado
route.started Comienza una ruta de reparto
route.stopVisited Se visita una parada de la ruta
route.completed Ruta completada

documents.onBeforeCreate / onAfterCreate cubren únicamente las fases before/after de documentos, usando el nombre de tabla:

documents.on*(tabla, handler) Equivale a hooks.register(evento, handler)
onBeforeCreate('SalesInvoice', h) register('salesInvoice.beforeCreate', h)
onAfterCreate('SalesInvoice', h) register('salesInvoice.afterCreate', h)
onBeforeCreate('PurchaseInvoice', h) register('purchaseInvoice.beforeCreate', h)
onBeforeCreate('SalesOrder', h) register('salesOrder.beforeCreate', h)
onBeforeCreate('PurchaseOrder', h) register('purchaseOrder.beforeCreate', h)
onBeforeCreate('SalesDeliveryNote', h) register('salesDeliveryNote.beforeCreate', h)
onBeforeCreate('PurchaseDeliveryNote', h) register('purchaseDeliveryNote.beforeCreate', h)

Para la fase posted y todos los demás eventos no hay atajo: usa hooks.register.