Cada punto de extensión es un contrato estable: un id, una capacidad requerida, una contribución declarada en el manifiesto y un host que la pinta con componentes oficiales. Los plugins nunca acceden al árbol React ni a ficheros internos.
Un bloque por plugin con su nombre en la ficha de producto. Carga los valores al abrir y valida antes de enviar; un error bloquea el guardado. Se guarda después del producto: si el plugin falla, el producto queda guardado y se avisa de qué extensión no guardó.
Los módulos anteriores (loyalty-system, company-lookup, repairs) siguen usando hooks internos de src/modules/moduleRegistry.ts y no forman parte del SDK. Su transición:
Descuentos automáticos en el carrito y en el ticket
El plugin propone reglas declarativas (SECOND_UNIT, PACK, CONDITIONAL, de shared/plugin-sdk/discounts.ts) con prioridad, acumulación, exclusividad, compatibilidad con códigos y cupones, franja horaria (hora de Madrid) y clientes. Nunca un importe calculado.
El host lee las reglas de los plugins activos con un límite de 2 segundos, descarta las no válidas (discountRuleProblems) y se las da al motor de promociones del núcleo, que calcula con el mismo código en el carrito y al emitir. Si el resultado no coincide con lo que mostró el carrito, el cobro se rechaza (ADVANCED_DISCOUNT_CHANGED).
El reparto queda en el documento emitido, línea a línea; devoluciones y rectificativas lo prorratean con el mecanismo del núcleo. Un plugin desactivado no aporta reglas.
Ejemplo completo: Promociones Avanzadas. Manual de usuario en Promociones Avanzadas.