2. Campos y tablas
El plugin necesita tres piezas de datos:
- Un campo Puntos de fidelidad en los clientes (
BusinessPartner) — el saldo actual. - Un campo Puntos canjeados en las facturas de venta (
SalesInvoice) — cuántos puntos se canjean en esa factura. - Una tabla propia,
fidelizacion_movimientos— el historial de cada suma y resta de puntos.
Añadir campos a tablas core
Sección titulada «Añadir campos a tablas core»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`);};tableNamees unCoreTableNameen PascalCase ('BusinessPartner','SalesInvoice'…). Hay 35 tablas extensibles.typepuede serTEXT,INTEGER,DECIMAL,BOOLEANoJSONB.labeles la etiqueta que se muestra en los formularios del ERP.
Crear la tabla de movimientos
Sección titulada «Crear la tabla de movimientos»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.
Leer y escribir con context.db
Sección titulada «Leer y escribir con context.db»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 helpersqldedrizzle-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.
Checkpoint
Sección titulada «Checkpoint»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.