Ir al contenido

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.

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"
}
]
}
}
  • component es la ruta del archivo del componente, relativa a la raíz del plugin.
  • size mapea a una grid de 4 columnas: sm=1, md=2, lg=3, full=4 (default md).
  • order (opcional, default 100) controla la posición: menor = antes.

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,
});
ui/TopClientes.tsx
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.

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, user y apiBase.
  • Devuelve el resultado de tool({...}) del paquete ai (Vercel AI SDK), con zod para el inputSchema.
  • 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: true a tool({...}): el chat mostrará una tarjeta de confirmación antes de ejecutarla.

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"
}
]
}
}
  1. Refresca el ERP: en el Dashboard debería aparecer el widget “Top clientes por puntos” ocupando media fila.
  2. 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.