El manifiesto
El manifiesto (manifest.ts) es un contrato puramente declarativo que leen el servicio local y la interfaz. Se define con definePluginManifest y se valida al instalar, al activar y al arrancar, antes de ejecutar ningún código del plugin.
Un manifiesto mínimo (SDK 0.1)
Sección titulada «Un manifiesto mínimo (SDK 0.1)»import { definePluginManifest } from '@simplegest/plugin-sdk'
export const manifest = definePluginManifest({ id: 'simplegest.demo-product-extension', // <publisher>.<nombre> name: 'Garantía de productos (demo)', description: '…', version: '0.2.0', // semver publisher: { id: 'simplegest', name: 'SimpleGest' }, sdk: { range: '^0.1.0' }, // compatibilidad con el host permissions: ['products:read', 'products.fields:read', 'products.fields:write', 'ui:product-form', 'ui:product-list', 'ui:product-actions', 'print:blocks'], offline: 'full', // full | degraded | online-only contributes: { fields: [{ entity: 'product', definitions: [ { key: 'warrantyMonths', label: 'Garantía (meses)', type: 'integer', min: 0, max: 120, listable: true }, { key: 'origin', label: 'Origen', type: 'enum', options: [{ value: 'ES', label: 'España' }, /* … */] }, ]}], listColumns: [{ id: 'warranty', entity: 'product', field: 'warrantyMonths', header: 'Garantía', align: 'center', suffix: ' meses' }], actions: [{ id: 'warranty-end', entity: 'product', label: 'Fin de garantía', icon: 'ShieldCheck' }], printBlocks: [{ id: 'warranty', label: 'Garantía de lo vendido', documentKinds: ['SIMPLIFIED_INVOICE', 'INVOICE'] }], },})Lo que añade el SDK 0.2 (extracto de Alimentación)
Sección titulada «Lo que añade el SDK 0.2 (extracto de Alimentación)»export const manifest = definePluginManifest({ id: 'simplegest.food', sdk: { range: '^0.2.0' }, permissions: ['products:read', 'products.fields:read', 'products.fields:write', 'ui:product-form', 'ui:product-list', 'storage', 'ui:pages', 'ui:dashboard', 'ui:detail', 'ui:filters', 'sales:validate', 'sales:read', 'stock:write', 'pos:barcode'], userPermissions: [ { key: 'receive', label: 'Dar entrada a lotes', inherits: ['products'] }, // … ], configuration: [ { key: 'alertDays', label: 'Avisar de caducidades con (días)', type: 'integer', min: 0, max: 90, defaultValue: 7 }, // … ], storage: { collections: [{ name: 'lots', unique: [['productId', 'lotCode']] }, { name: 'movements' }, { name: 'wastes' }] }, migrations: [{ id: '0001_initial', description: '…' }, { id: '0002_lot_status', description: '…' }], contributes: { commands: [{ id: 'receive', label: 'Entrada de lote', permission: 'receive' }], dataSources: [{ id: 'lots', permission: 'reports' }], forms: [{ id: 'receiveLot', title: 'Entrada de lote', submitLabel: 'Dar entrada', fields: [/* … */] }], pages: [{ id: 'lots', title: 'Lotes y caducidades', tabs: [/* tablas, métricas, acciones */] }], navigation: [{ id: 'lots', label: 'Lotes y caducidad', icon: 'Calendar', page: 'lots' }], widgets: [/* Dashboard */], detailSections: [/* ficha de producto */], saleCaptures: [{ id: 'lots', label: 'Lote', kind: 'lots', whenField: 'lotTracked', required: false, suggestSource: 'fefo' }], barcodeParsers: [{ id: 'scale', label: 'Etiquetas de balanza (EAN 20-29)' }], }, catalog: { category: 'Sector', icon: 'Calendar', summary: '…', price: { type: 'free' /* … */ }, manual: '…', dataPolicy: '…', changelog: [{ version: '1.0.0', date: '2026-10-20', notes: ['…'] }] },})| Campo | Para qué |
|---|---|
storage.collections |
Colecciones propias del plugin, con claves unique que garantiza la base de datos |
migrations |
Migraciones NNNN_nombre, aplicadas una vez, con copia previa y verificación de que el núcleo no cambia |
userPermissions |
Permisos de usuario propios, heredados de un permiso del programa hasta que se cambian en Ajustes › Usuarios |
configuration |
Opciones con valor por defecto, editables en la pestaña Configuración de la Shop. Si falta una obligatoria, el plugin queda en pending_configuration |
catalog |
Ficha de la tienda: categoría, icono, resumen, modelo comercial (price), capturas, novedades (changelog, que debe empezar por la versión actual), manual y política de datos (dataPolicy, obligatoria) |
replaces |
Módulo anterior al que sustituye (por ejemplo, loyalty-system), cuyos datos puede importar en una migración |
dependencies / conflicts |
Otros plugins que necesita o con los que no puede convivir |
catalog.price admite free, one-off, subscription, included y trial. Cualquier modelo distinto de free exige una licencia firmada para instalar y activar el plugin. Mientras las condiciones comerciales no estén aprobadas, la Shop no muestra el modelo: dice «Requiere tu cuenta SimpleGest · condiciones por confirmar» (COMMERCIAL_TERMS_APPROVED).
Capacidades
Sección titulada «Capacidades»| Capacidad | Permite |
|---|---|
products:read |
Modelo público del producto |
products.fields:read / products.fields:write |
Leer y escribir sus propios campos de producto |
ui:product-form / ui:product-list / ui:product-actions |
Sección en la ficha, columnas y acciones en el listado de productos |
events:product / events:stock |
Eventos de producto y de stock (servidor) |
print:blocks |
Bloques de texto complementarios en los documentos impresos |
storage |
Colecciones propias y migraciones |
ui:pages / ui:dashboard / ui:detail / ui:pos-actions / ui:filters |
Pantallas y menú, tarjetas del Dashboard, secciones de ficha, botones del TPV, filtros del listado |
customers:read |
Modelo público del cliente |
sales:read / sales:validate |
Hechos de ventas emitidas y sus correcciones; revisar una venta antes de emitirla (solo rechazar) y capturar datos de línea |
catalog:write / stock:write |
Crear productos (variantes); entradas, mermas y ajustes de stock |
vouchers:issue |
Emitir vales del TPV |
repairs:read / repairs:write |
Reparaciones del módulo del núcleo |
documents:render |
PDF para revisar o descargar; nunca impresión silenciosa |
pos:barcode |
Leer códigos escaneados que el núcleo no conoce |
users:read |
Nombres de los usuarios del TPV |
legacy:loyalty |
Leer los datos del módulo de fidelización anterior para migrarlos |
sales:discounts (0.2.1) |
Proponer reglas de descuento declarativas que evalúa el motor de promociones del núcleo; exige declarar contributes.discountProviders |
Tipos de campo
Sección titulada «Tipos de campo»| Tipo | Restricciones | Control |
|---|---|---|
string |
maxLength, pattern |
Campo de texto |
text (0.2) |
maxLength |
Texto de varias líneas |
integer / number |
min, max |
Campo numérico |
decimal (0.2) |
scale, min, max |
Decimal exacto, guardado como texto |
boolean |
— | Casilla |
enum |
options u optionsSource (0.2: opciones de una fuente de datos del plugin) |
Desplegable |
date |
AAAA-MM-DD |
Fecha |
product / customer / user (0.2) |
— | Selector |
required, help y defaultValue son comunes. La validación es la misma en la interfaz y en el servicio local; decide siempre el servidor.
Ciclo de vida
Sección titulada «Ciclo de vida» install enableavailable ─────────► installed ──────────────────► active ◄───────┐ ▲ │ (config requerida) │ │ │ enable │ ▼ │ │ disable │ │ pending_configuration ──configurar────┘ ▼ │ │ disabled ──────┘ │ install │uninstalled ◄────────────── uninstall ──────────────────┘ └─ purge-data (confirmación explícita con el id; exporta antes de borrar)- Licencia: sin licencia válida, un plugin de pago no se instala ni se activa; si la pierde, al arrancar queda
disabledcon sus datos. - Desactivar no borra: oculta la interfaz y la API responde 409, pero los datos se conservan. No se puede desactivar si otro plugin activo depende de él.
- Desinstalar conserva los datos. Solo
purge-databorra, si el plugin está desinstalado y se confirma con su id. incompatible: SDK fuera de rango o manifiesto inválido; se marca antes de ejecutar su código.error: fallo al activar, en una migración o fallos repetidos en sus handlers.- Actualizar (
POST /api/plugins/:id/update, botón «Actualizar a vX»): una versión nueva llega con la aplicación; todas sus migraciones pendientes se aplican en una transacción, con copia previa (backups/pre-plugin-*.db) y comprobación de que ninguna tabla del núcleo cambió. Si falla, se deshace entera y el plugin quedaerrorcon la versión anterior. - Nunca hacia atrás: si la aplicación trae una versión anterior a la de los datos del plugin, queda
incompatiblesin ejecutar su código. Reinstalar conserva la versión de los datos; traspurge-data, se instala desde cero. - Todas las transiciones quedan en la auditoría y en la pestaña Historial de la Shop.
Compatibilidad
Sección titulada «Compatibilidad»- El host expone
HOST_SDK_VERSION(hoy0.2.1) ySUPPORTED_SDK_CONTRACTS(0.1.3,0.2.0y0.2.1). Se comprueba elsdk.rangedel plugin contra cada contrato antes de ejecutar su código: un plugin^0.1.0o^0.2.0sigue funcionando porque 0.2.0 y 0.2.1 solo añaden; uno^0.3.0quedaincompatible. - En 0.x cada versión menor puede romper compatibilidad; desde 1.0, solo las mayores.
- La suite
tests/sdk/plugin-sdk.test.tses el contrato: si se rompe, hay que subir la versión del SDK.