Ir al contenido

API del plugin

interface PluginContext {
readonly manifest: PluginManifest
products: {
get(id: string): Promise<PublicProduct> // products:read
getFields(ids: string[]): Promise<Record<string, Record<string, FieldValue>>> // products.fields:read
setFields(id: string, values: Record<string, unknown>): Promise<…> // products.fields:write
}
actions: { handle(actionId: string, handler: (ctx: ActionContext) => void | Promise<void>): void }
ui: { notify(message, kind?): void; showInfo(title, lines: string[]): void }
}

El módulo de servidor (server.ts) exporta un PluginServerModule con activate(ctx), deactivate() opcional y, si declara migrations, sus funciones de migración. Cada miembro del contexto exige su capacidad:

Miembro Capacidad Qué hace
ctx.events.on(evento, handler) events:product, events:stock Eventos de dominio tras el commit, payload de solo lectura
ctx.print.provide(blockId, proveedor) print:blocks Texto de un bloque de impresión declarado
ctx.fields.product(ids), findProducts, setProduct products.fields:* Sus propios campos de producto
ctx.storage.collection(n) storage insert, get, update, delete, find (filtros, búsqueda, orden, paginación), findOne, count
ctx.storage.transaction(tx => …) storage (+ stock:write para tx.stock.move) Una transacción: todo o nada. tx.stock.move registra entradas (PURCHASE), mermas (WASTE) o ajustes, validados por el núcleo
ctx.settings.get() — Su configuración, con los valores por defecto
ctx.data.provide(fuente, proveedor) — Filas para tablas, métricas, tarjetas y secciones
ctx.commands.handle(orden, handler) — Órdenes de formularios y botones; el handler recibe la entrada validada y quién llama (caller.can(permiso))
ctx.sales.onValidate(handler) sales:validate Revisar la venta antes de emitirla: solo puede rechazarla con un mensaje
ctx.sales.onRecorded / onCorrected / lines({ from, to }) sales:read Ventas y correcciones tras el commit; líneas emitidas entre dos fechas (máx. 20 000)
ctx.catalog (get, findByCode, search, list, create) products:read / catalog:write Productos públicos; create da de alta productos (variantes)
ctx.customers customers:read Clientes públicos
ctx.vouchers.issue(…) vouchers:issue Emite un vale del TPV
ctx.repairs repairs:read / repairs:write Reparaciones del núcleo
ctx.documents.pdf(doc) / labels(hoja) documents:render PDF para revisar o descargar; nunca imprime solo
ctx.barcode.provide(id, parser) pos:barcode Interpreta códigos escaneados que el núcleo no conoce
ctx.users.list() users:read Nombres de los usuarios
ctx.discounts.provide(id, () => DiscountRule[]) (0.2.1) sales:discounts Reglas de descuento declarativas del proveedor id (declarado en contributes.discountProviders). Las evalúa el núcleo; el plugin nunca ve ni cambia un importe
ctx.log(...) — Registro del plugin

Un error pensado para el usuario se lanza con PluginUserError(mensaje). Ayudas puras del SDK: toScaled/fromScaled/fixed (decimales exactos), ean13/gs1CheckDigit/parseVariableMeasure (EAN-13 de peso o precio), todayMadrid.

El contexto no contiene Prisma, SQL, ficheros, red ni el proceso: ctx.storage es un objeto congelado con solo la API de colecciones, y el host pone el pluginId en cada consulta.

interface PublicProduct {
id: string // uuid público; nunca el id interno
name: string
sku: string | null
priceWithTax: number // solo para mostrar
taxRate: number // solo para mostrar
priceWithTaxText?: string // 0.1.3: valor exacto, p. ej. "12.50"
taxRateText?: string // 0.1.3: p. ej. "21.00"
stock: number | null // en la unidad del producto; null sin control de stock
active: boolean
unit?: string // 0.1.1: UNIT, KILOGRAM, LITER, METER…
quantityScale?: number // 0.1.1: decimales que admite una cantidad
}
Método Ruta Capacidad Estado requerido
GET /api/plugin-api/v1/products/:uuid products:read active
GET /api/plugin-api/v1/fields/product?ids=… (máx. 200) products.fields:read active
PUT /api/plugin-api/v1/fields/product/:uuid { values } products.fields:write (y permiso products del usuario) active
GET /api/plugins — —
POST /api/plugins/:id/{install,enable,disable,uninstall,update,purge-data} permiso de usuario plugins según la transición
GET /api/plugins/:id/export permiso de usuario plugins instalado
GET / PUT /api/plugins/:id/settings plugins para escribir instalado
GET /api/plugins/:id/history plugins —
GET /api/plugin-permissions sesión —
POST /api/plugins/entitlements { payload, signature } plugins; firma Ed25519 del servicio de cuentas —
POST /api/plugin-api/v2/:plugin/data/:source { query } fuente declarada y permiso del usuario active
POST /api/plugin-api/v2/:plugin/commands/:command { formId, input, context } orden declarada, formulario validado por el host y permiso del usuario active
GET /api/plugin-api/v2/:plugin/filters/:filter?value= ui:filters, campo declarado active
GET /api/plugin-api/v2/barcode?code= lectores de plugins activos active

Las rutas v2 las usa el propio programa para pintar las contribuciones declarativas; el código de un plugin no las llama.

La autorización tiene tres ejes: capacidad declarada en el manifiesto, usuario (sesión y permisos de quien maneja el TPV) y establecimiento (hoy uno implícito; la cabecera X-SG-Store queda reservada). Las escrituras pasan por validación declarativa y nunca tocan atributos del núcleo. No hay acceso SQL.

Código Causa
400 Falta X-SG-Plugin, payload inválido o demasiados ids
401 Sin sesión de usuario
403 Capacidad no declarada, campo no declarado o usuario sin permiso
404 Plugin o producto inexistente
402 Licencia necesaria (plugin de pago sin licencia válida)
409 Plugin no activo (desactivado, desinstalado, incompatible o en error), o transición no permitida
422 Validación de campos ({ fields: { clave: mensaje } })