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…
Registro de hooks
Sección titulada «Registro de hooks»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…).
export const init = async ({ documents }: PluginContext) => { documents.onBeforeCreate('SalesInvoice', async (ctx) => { // se ejecuta antes de crear cualquier factura de venta }); documents.onAfterCreate('SalesInvoice', async (ctx) => { // ... y después });};Solo cubre beforeCreate / afterCreate de documentos, y recibe el nombre de la tabla en PascalCase (SalesInvoice), no el evento en camelCase.
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);HookContext
Sección titulada «HookContext»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. datacontiene el objeto del evento: el documento en eventos de documento, el pago enpayment.*, el asiento enjournalEntry.*, etc.
Documentos (18 eventos)
Sección titulada «Documentos (18 eventos)»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'); }});Listados (2 eventos)
Sección titulada «Listados (2 eventos)»| 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); }});Contabilidad (4 eventos)
Sección titulada «Contabilidad (4 eventos)»| 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 |
Pagos (2 eventos)
Sección titulada «Pagos (2 eventos)»| Evento | Cuándo |
|---|---|
payment.created |
Al registrar un cobro o pago |
payment.deleted |
Al eliminar un cobro o pago |
RRHH (1 evento)
Sección titulada «RRHH (1 evento)»| Evento | Cuándo |
|---|---|
payroll.approved |
Al aprobar una nómina |
Logística (12 eventos)
Sección titulada «Logística (12 eventos)»| 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 |
Equivalencias con documents.on*
Sección titulada «Equivalencias con documents.on*»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.