5. Widget y AI tool
Dos extensiones más, y el plugin queda completo: un widget en el dashboard principal del ERP y una tool que el chat de IA (Keiro) podrá invocar.
Widget de dashboard (vía manifest)
Sección titulada «Widget de dashboard (vía manifest)»Añade el bloque dashboardWidgets al ui del manifest:
{ "ui": { "dashboardWidgets": [ { "id": "fidelizacion-top", "title": "Top clientes por puntos", "component": "ui/TopClientes.tsx", "size": "md" } ] }}componentes la ruta del archivo del componente, relativa a la raíz del plugin.sizemapea a una grid de 4 columnas:sm=1,md=2,lg=3,full=4 (defaultmd).order(opcional, default 100) controla la posición: menor = antes.
Registro programático (alternativa)
Sección titulada «Registro programático (alternativa)»Si el widget depende de lógica (por ejemplo, mostrarlo solo bajo cierta condición), puedes registrarlo desde init() en vez del manifest:
widgets.registerDashboard({ id: 'fidelizacion-top', title: 'Top clientes por puntos', subtitle: 'Los 5 clientes con más puntos de fidelidad', component: 'ui/TopClientes.tsx', size: 'md', order: 50,});El componente del widget
Sección titulada «El componente del widget»import React, { useEffect, useState } from 'react';import { Loader } from '@openfactu/ui';
const TopClientes = () => { const [top, setTop] = useState<any[] | null>(null);
useEffect(() => { fetch('/api/plugins/fidelizacion/top') .then((r) => r.json()) .then(setTop); }, []);
if (!top) return <Loader />;
return ( <ol> {top.map((p) => ( <li key={p.id}> {p.name} — <strong>{p.loyalty_points ?? 0} pts</strong> </li> ))} </ol> );};
export default TopClientes;Consume el endpoint /top que creamos en la parte 4.
Una tool para el chat de IA
Sección titulada «Una tool para el chat de IA»Con aiTools.register, el asistente de IA del ERP puede consultar los puntos de un cliente cuando el usuario se lo pida en lenguaje natural (“¿cuántos puntos tiene el cliente C-042?”):
import { tool } from 'ai';import { z } from 'zod';
export const init = async ({ /* ... */, factuApi, aiTools }: PluginContext) => { // ... partes 2, 3 y 4 ...
aiTools.register('fidelizacion_consultar_puntos', (ctx) => tool({ description: 'Consulta el saldo de puntos de fidelidad de un cliente por su código o id.', inputSchema: z.object({ codigoOId: z.string() }), execute: async ({ codigoOId }) => { const api = factuApi.connect(ctx.tenantId, ctx.tenantClient, ctx.user); const partner = await api.getPartner(codigoOId); if (!partner) return { error: 'Cliente no encontrado' }; return { nombre: partner.name, puntos: Number(partner.loyalty_points ?? 0) }; }, }), );};Claves:
- La factory se invoca por petición con un
AiChatToolContext:tenantClient(Drizzle del tenant de la conversación),tenantId,tenantSchema,useryapiBase. - Devuelve el resultado de
tool({...})del paqueteai(Vercel AI SDK), conzodpara elinputSchema. - Si el nombre ya lo usa otra tool (del core o de otro plugin), el registro se ignora — prefija con el id de tu plugin.
- Si tu tool crea o modifica datos, pásale
needsApproval: trueatool({...}): el chat mostrará una tarjeta de confirmación antes de ejecutarla.
Bonus: un tema visual
Sección titulada «Bonus: un tema visual»Los plugins también pueden aportar presets al selector de Branding:
{ "ui": { "themes": [ { "id": "fidelizacion-gold", "label": "Gold", "description": "Tema dorado del programa de fidelidad", "colorPrimary": "#b45309", "colorAccent": "#fbbf24", "themeMode": "light" } ] }}Checkpoint
Sección titulada «Checkpoint»- Refresca el ERP: en el Dashboard debería aparecer el widget “Top clientes por puntos” ocupando media fila.
- Abre el chat de IA y pregunta por los puntos de un cliente usando su código: la tool debería ejecutarse y responder con el saldo.
Parte 6: PublicarRevisión final, prueba en servidor real y publicación en el marketplace.