API del plugin
En la interfaz: PluginContext
Sección titulada «En la interfaz: PluginContext»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 }}En el servidor: PluginServerContext
Sección titulada «En el servidor: PluginServerContext»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.
Modelo público de producto
Sección titulada «Modelo público de producto»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}HTTP del servicio local
Sección titulada «HTTP del servicio local»| 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.
Errores
Sección titulada «Errores»| 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 } }) |