Ir al contenido

2. Campos y tablas

El plugin necesita tres piezas de datos:

  1. Un campo Puntos de fidelidad en los clientes (BusinessPartner) — el saldo actual.
  2. Un campo Puntos canjeados en las facturas de venta (SalesInvoice) — cuántos puntos se canjean en esa factura.
  3. Una tabla propia, fidelizacion_movimientos — el historial de cada suma y resta de puntos.

migration.addCustomField añade un campo a una tabla del ERP. En index.ts:

import type { PluginContext } from '@openfactu/plugin-sdk';
const PLUGIN_ID = 'fidelizacion';
export const init = async ({ migration }: PluginContext) => {
await migration.addCustomField({
pluginId: PLUGIN_ID,
tableName: 'BusinessPartner',
fieldName: 'loyalty_points',
type: 'INTEGER',
label: 'Puntos de fidelidad',
});
await migration.addCustomField({
pluginId: PLUGIN_ID,
tableName: 'SalesInvoice',
fieldName: 'puntos_canjeados',
type: 'INTEGER',
label: 'Puntos canjeados',
});
console.log(`[${PLUGIN_ID}] Plugin inicializado`);
};
  • tableName es un CoreTableName en PascalCase ('BusinessPartner', 'SalesInvoice'…). Hay 35 tablas extensibles.
  • type puede ser TEXT, INTEGER, DECIMAL, BOOLEAN o JSONB.
  • label es la etiqueta que se muestra en los formularios del ERP.

migration.createTable crea una tabla propia del plugin en el schema del tenant:

await migration.createTable({
pluginId: PLUGIN_ID,
tableName: 'fidelizacion_movimientos',
columns: [
{ name: 'id', type: 'UUID', primaryKey: true },
{ name: 'partner_id', type: 'UUID' },
{ name: 'invoice_id', type: 'UUID', nullable: true },
{ name: 'points', type: 'INTEGER' },
{ name: 'reason', type: 'TEXT' },
{ name: 'created_at', type: 'TIMESTAMP', default: 'now()' },
],
});

Además de los tipos de addCustomField, aquí dispones de UUID y TIMESTAMP, y de los flags primaryKey, nullable y default por columna. invoice_id es nullable porque los canjes manuales no van ligados a una factura.

Para trabajar con datos usa db.forTenant(tenantId), que devuelve el cliente Drizzle del tenant:

export const init = async ({ migration, db }: PluginContext) => {
// ... migraciones ...
// Ejemplo: leer datos de una tabla core con Drizzle
// const tenantDb = await db.forTenant(tenantId);
// const partners = await tenantDb.select().from(db.schema.businessPartners).limit(5);
};

Dos patrones que usaremos en la parte 3:

  • Tablas core → queries Drizzle con db.schema.* (db.schema.businessPartners, db.schema.items…).
  • Tu tabla propia → no está en db.schema, así que se consulta con SQL parametrizado usando el helper sql de drizzle-orm.

db.forTenant es la única vía de acceso a datos de un tenant: resuelve el schema físico a través de la tabla Tenant y nunca acepta un nombre de schema arbitrario, lo que evita fugas entre empresas.

Guarda el archivo (con plugin watch o plugin dev activo el servidor recarga solo). En el ERP:

  • Abre la ficha de un cliente: debería aparecer el campo Puntos de fidelidad.
  • Abre una factura de venta: debería aparecer Puntos canjeados.