Por qué este plugin es la referencia canónica
La mayoría de los plugins en producción son pequeños. Un driver de envío es una clase más una blade de conexión. Una pasarela de pago es un servicio más un controlador de redirección. Un complemento de barra lateral simple es una sola llamada a Hook::add. Ninguno de esos ejercita todo el SDK de plugins, y los plugins pequeños no son el ejercicio de lectura adecuado para un autor que intenta entender hasta dónde puede crecer un plugin con responsabilidad.
acelle/ai existe en el otro extremo del espectro. Es un subsistema de IA autónomo: un chatbox de agente, componentes universales de reescritura de texto, asistentes coach fundamentados en la KB y un panel de observabilidad de administración. Activar el plugin en una instalación de AcelleMail añade toda la superficie de IA sin tocar el código del core; desactivarlo lo retira limpiamente. El plugin usa cada concepto que cubren el resto de estas docs, en producción. Leerlo es la forma más rápida de ver cómo se combinan los patrones cuando la funcionalidad no es trivial.
El árbol de archivos de un vistazo
Del propio README del plugin:
| Carpeta | De qué es dueña |
src/AIHandler/ | Motor de IA en runtime: motores, bucle del agente, herramientas, resolver de settings, escritor/lector de observabilidad, lookup de la KB, sanitizador de URLs. |
src/Models/ | Ocho modelos Eloquent para el sustrato de auditoría (AIConversation, AIMessage, AIRequest, AIToolCall, AIFeedback, AIRawBlob, AIDailyRollup, AIToolUndoRecord). |
src/Controllers/ | Controladores de administración (/rui/admin/ai-*) + controladores de API pública (/api/v1/ai/*) + el PluginDashboardController de la landing del plugin. |
src/Services/ | PluginStatusReport, AISettingsService, AutomationService, AIObservabilityPolicy, etc. |
src/ServiceProvider.php | Único punto de entrada: registra PSR-4, rutas, vistas, archivos de idioma, hooks, alias de middleware y listeners del ciclo de vida. |
database/migrations/ | Catorce migraciones del sustrato de auditoría. Se ejecutan al activar, se revierten al eliminar. |
resources/views/ | Más de sesenta plantillas Blade de administración + tres componentes anónimos universales (<x-mc-ai-chatbox>, <x-mc-ai-rewrite>, <x-mc-ai-subject-ab-generator>) + partials JS de chatbox / Sparkle. |
resources/assets/ | CSS (~14 archivos) + JS (~21 archivos) publicados en public/plugins/acelle/ai/ vía vendor:publish --tag=plugin --force al instalar el plugin. |
resources/lang/ | Dieciocho locales × nueve archivos de idioma = toda la superficie del módulo de IA traducida. |
tests/ | Más de cien tests Pest (Feature + Unit) + la clase base Acelle\Ai\Tests\PluginTestCase. |
routes.php | Rutas del plugin (administración + API pública + el dashboard de landing del plugin en /plugins/acelle/ai/dashboard). |
composer.json | Metadatos del plugin; extra.setting-route apunta a PluginDashboardController@index para que el botón «Settings» de la página de Plugins de administración enlace directamente al dashboard propio del plugin. |
Ninguna de esas carpetas es a medida. Cada una se mapea directamente a una sección del resto de estas docs. Leer el plugin es un proceso de reconocer el mismo patrón entregado a escala.
Ocho modelos Eloquent — el sustrato de auditoría
La capa de datos del módulo de IA está moldeada en torno a la auditabilidad: cada conversación, cada invocación de modelo, cada llamada a herramienta, cada valoración de usuario y cada blob de salida bruta del proveedor se captura para replay y observabilidad. Ocho modelos cubren ese sustrato:
| Modelo | Tabla | Qué representa |
AIConversation | ai_conversations | Una fila por sesión de agente / soporte de varios turnos. Lleva las FKs de customer + user, la clave de la tarea, la ruta de la pantalla y los totales acumulados de tokens / coste. |
AIMessage | ai_messages | Una fila por turno de usuario / agente. Rol, JSON de contenido, FK a una llamada de herramienta, latencia, modelo usado. |
AIRequest | ai_requests | Una fila por llamada a la API upstream. Motor, hash del prompt, latencia, coste, estado de error. Puentea AIMessage con el tráfico HTTP real. |
AIToolCall | ai_tool_calls | Invocaciones de function-call generadas por un turno del agente. Nombre de la herramienta, JSON de entrada/salida, flag de origen. |
AIFeedback | ai_feedback | Valoraciones de pulgar arriba/abajo + texto libre por mensaje y por conversación. |
AIRawBlob | ai_raw_blobs | Respuestas brutas originales del proveedor, conservadas para replay / auditoría. Tabla aparte porque la tabla de rollup tiene que mantenerse pequeña. |
AIDailyRollup | ai_daily_rollup | Agregado por día para el panel de observabilidad de administración: totales de tokens, coste, tasa de error. Pre-agregado para que el panel lea barato. |
AIToolUndoRecord | ai_tool_undo_records | Rastrea acciones de herramienta reversibles para la funcionalidad de «deshacer último». |
Tres patrones de esta lista se trasladan directamente a otros plugins. Separar «respuestas brutas del proveedor» en una tabla aparte de «resumen acumulado» permite que la tabla de rollup se mantenga lo bastante pequeña como para escanearla. Las FKs anulables a customers y users permiten que la misma fila funcione para tráfico autenticado y anónimo. Los rollups de una fila por día dan al panel de administración lecturas baratas sin un JOIN pesado contra las tablas de actividad.
Catorce migraciones de una línea cada una
Los nombres de archivo de las migraciones en storage/app/plugins/acelle/ai/database/migrations/ cuentan su propia historia: aditivos en el tiempo, inmediatamente reversibles, nunca un movimiento de esquema destructivo:
| Nombre del archivo | Qué hace |
2026_04_28_000001_create_ai_conversations_table.php | Sesiones de chat de varios turnos — uid, FK de customer_id, enum de estado, rollups de tokens / coste |
2026_04_28_000002_create_ai_messages_table.php | Turno único de usuario / agente — rol, JSON de contenido, FK a tool-call, latencia, modelo usado |
2026_04_28_000003_create_ai_requests_table.php | Una fila por llamada a la API upstream — motor, hash del prompt, latencia, coste, error |
2026_04_28_000004_create_ai_tool_calls_table.php | Invocaciones de function-call generadas por un turno del agente — JSON de entrada / salida |
2026_04_28_000005_create_ai_feedback_table.php | Valoraciones de pulgar arriba/abajo + texto libre por mensaje y por conversación |
2026_04_28_000006_create_ai_raw_blobs_table.php | Respuestas brutas originales del proveedor, conservadas para replay / auditoría |
2026_04_28_000007_create_ai_daily_rollup_table.php | Agregado por día para el panel de administración — totales de tokens, coste, tasa de error |
2026_04_29_000001_add_client_message_id_to_ai_messages.php | Columna de dedupe entre pestañas — aditiva, sin valores por defecto |
2026_04_30_000002_add_source_to_ai_tool_calls.php | Rastrea si una llamada a herramienta vino de la ruta de agente o de la ruta de soporte |
2026_05_02_180000_widen_ai_conversations_client_session_uid.php | Arreglo del ancho de ULID / UUID: migración que altera columnas, totalmente reversible |
2026_05_02_200000_create_ai_tool_undo_records_table.php | Rastrea acciones de herramienta reversibles para la funcionalidad de «deshacer último» |
2026_05_03_000001_add_url_sanitization_to_ai_requests.php | Añade una columna JSON para la telemetría de URLs saneadas |
2026_05_04_000001_create_ai_settings_table.php | Settings de administración a nivel de plugin — separados de plugins.data para que cada fila pueda indexarse |
Lea las migraciones de arriba abajo y tendrá la evolución del esquema de todo el módulo de IA. Cada migración aditiva es un envío de feature: la forma aditiva es lo que hace que el rollback de delete_plugin_* del plugin siempre funcione, incluso cuando un admin lo desinstala después de un año de acumulación de features.
Todos los hooks que usa el plugin
El ServiceProvider del plugin ejercita todos los patrones de hook excepto FILTER. Un grep de Hook::* contra storage/app/plugins/acelle/ai/src/ServiceProvider.php:
REGISTRY (Hook::add) — seis contribuciones
- Nueve entradas de
add_translation_file en register() líneas 175-197: una por cada superficie de traducción (rewrite, chatbox, prompts, wait, subject AB, settings, admin usage, audit, permissions). Cada superficie es un archivo editable por separado en la UI de Idiomas de administración.
layout.head.assets en boot() línea 688: aporta el CSS + JS del chatbox a cada página que extiende los layouts maestros app / admin.
layout.body.before_close en boot() línea 699: aporta el HTML de la burbuja del chatbox y el popover de Sparkle antes del </body> de cada página.
admin.sidebar.groups en boot() línea 718: añade el grupo de IA con sus tres o cuatro enlaces hijos a la barra lateral de administración.
Las seis se protegen con aiPluginAvailable(): un helper que en última instancia resuelve a Plugin::getByName('acelle/ai')->isActive(). Devolver null cuando el plugin está bloqueado es la forma convencional de desaparecer limpiamente sin descargar el service provider.
EVENT (Hook::on) — dos listeners del ciclo de vida
activate_plugin_acelle/ai en la línea 472: ejecuta artisan migrate contra la carpeta de migraciones del plugin.
delete_plugin_acelle/ai en la línea 546: acepta el flag $keepData, revierte las migraciones cuando no está puesto y preserva las tablas de auditoría cuando sí lo está.
BEHAVIOR (Hook::set) — una sobrescritura de URL de icono
icon_url_acelle/ai en la línea 522: sobrescribe el hook BEHAVIOR por plugin para que la página de Plugins de administración renderice el icono propio del módulo de IA en lugar del plugin.svg por defecto del host.
Juntos son la mayor parte de la superficie de hook del plugin. Leer ServiceProvider.php de arriba abajo es la forma más rápida de ver cómo se combinan los patrones en producción.
La superficie de UI del chatbox
El plugin aporta tres componentes Blade universales en resources/views/components/: ninguno de ellos requiere que la aplicación host sepa que existen:
<x-mc-ai-chatbox>: la burbuja flotante del chatbox que se abre en una conversación de agente de varios turnos. Montada vía el hook REGISTRY layout.body.before_close para que aparezca en cada página app + admin.
<x-mc-ai-rewrite>: capacidad universal de «reescribe este texto» que se puede colocar junto a cualquier textarea del host. Con namespace del plugin, sin registro central.
<x-mc-ai-subject-ab-generator>: genera variantes A/B de línea de asunto a partir de un prompt. Se usa en el editor de campañas.
Estos tres componentes muestran el patrón para «el plugin aporta UI a varias páginas del host sin bifurcar cada página»: entregue el componente como un componente Blade anónimo bajo el namespace de vistas del plugin, regístrelo a través de los hooks REGISTRY de layout para un montaje global, o haga que las páginas del host hagan opt-in incluyéndolo directamente. Ambos patrones funcionan; el plugin de IA usa los dos.
Nueve archivos × dieciocho locales
El register() del plugin registra nueve archivos de traducción separados. La maquinaria de Language::dump() luego materializa cada uno en diecisiete carpetas de runtime de locales no ingleses bajo storage/app/data/plugins/acelle/ai/lang/. El resultado en disco: 153 archivos de traducción en runtime (9 archivos × 17 locales no ingleses + 9 originales en inglés = 162, menos los 9 maestros en inglés = 153 clones volcados), cada uno editable por separado a través de la UI de Idiomas de administración del host.
Los nueve archivos de superficie (registrados vía el bucle $aiLangFiles en ServiceProvider::register()):
ai_rewrite — el componente universal de reescritura de texto
ai_chatbox — la UI del chatbox
ai_chatbox_prompts — prompts predefinidos mostrados en el chatbox
ai_chatbox_wait — la UI de smart-wait («buscando… ejecutando herramienta… redactando respuesta»)
ai_subject_ab — el generador A/B de asuntos
ai_settings — etiquetas de la página de settings de administración
admin_ai_usage — panel de uso / coste de administración
admin_ai_audit — UI de auditoría / replay de administración
admin_ai_permissions — toggles de permisos por funcionalidad en administración
Esta división es la respuesta práctica a «cómo mantengo los archivos de traducción lo bastante pequeños como para que un traductor pueda editar uno en una sola sesión»: un maestro por superficie lógica, registrado por separado, materializado por locale, editado de forma independiente a través de la UI de administración.
Archivos de configuración propios del plugin
El plugin es dueño de dos archivos de configuración bajo config/: registrados en ServiceProvider::boot() vía $this->mergeConfigFrom() y alcanzables a través de los helpers estándar config('ai.*') y config('ai-navigation-hints.*'). La configuración propia del plugin es el sitio correcto para metadatos estáticos que no cambian por instalación (catálogo de motores, plantillas de prompt, valores por defecto de navegación); los ajustes editables por el admin viven en la tabla ai_settings, sembrada por migración.
La división (config para los valores por defecto inmutables que entrega el plugin, filas de BD para los ajustes mutables editables por el admin) es un patrón que se traslada limpiamente a otros plugins. Meter ambos en la columna JSON plugins.data es tentador, pero castiga el rendimiento de la UI de administración: una tabla dedicada e indexada fue la decisión correcta.
La infraestructura de tests
El directorio de tests del plugin sigue la forma de tests/ del host: carpetas Unit + Feature más una clase base PluginTestCase en la raíz:
storage/app/plugins/acelle/ai/tests/
├── PluginTestCase.php ← seeds the plugin row as active before every test
├── Feature/
│ ├── AIHandler/ ← engines, agent loop, tools, observability writer
│ └── PluginLifecycle/ ← lifecycle integration tests
├── Unit/ ← isolated unit tests (no Laravel boot for some)
├── Fixtures/ ← test fixtures + factories
├── Snapshots/ ← Pest snapshot artefacts
└── Support/ ← test-only helpers
El phpunit.xml del host registra el testsuite del plugin como <testsuite name="Plugin: acelle/ai">. ./vendor/bin/pest --testsuite="Plugin: acelle/ai" ejecuta la suite completa en paralelo con las suites Unit + Feature del propio host.
El PluginTestCase en la raíz demuestra la trampa del reseteo de caché de gate por petición que todo plugin que entrega middleware debería adoptar: vea Testing § trampa de caché de gate para el patrón completo. Sin él, el segundo test de la suite en adelante observa estado de caché obsoleto del arranque del primero.
Cómo aprender de él
Leer cada línea de un plugin de cien mil tokens no es el ejercicio correcto. Una receta de cuatro pasos es más útil:
-
Clone o symlinkee el plugin en una instalación host. Actívelo. Abra la barra lateral de administración: debería aparecer el grupo de IA. Haga clic en «Settings» en la entrada de la página de Plugins de administración: debería cargarse el dashboard de landing del plugin. Esto prueba que la superficie de UI del plugin está conectada con su host local.
-
Lea
src/ServiceProvider.php de arriba abajo. Cuarenta minutos. Cada concepto cubierto en Arquitectura de plugins + El sistema de Hooks + Inyección de UI + Traducciones aparece en este único archivo a escala de producción. Vaya cruzando referencias con las páginas de análisis a fondo a medida que avanza.
-
Rastree una funcionalidad de principio a fin. Elija la burbuja del chatbox. Encuentre el punto de entrada (el hook
layout.body.before_close en ServiceProvider), siga el partial que devuelve (ai::partials.body_assets), encuentre el componente anónimo que renderiza la burbuja, encuentre el JS que la monta, encuentre la ruta API que llama el JS, encuentre el controlador, sígalo hasta el motor en runtime AIHandler y observe cómo se insertan las filas de AIConversation + AIMessage + AIRequest. Dos o tres horas, de principio a fin. Después de esto sabrá qué patrones usa el plugin de IA y de cuáles prescinde.
-
Adapte un patrón a su propio plugin. Elija el más pequeño que encaje: normalmente el bucle de registro por superficie de traducción, o el grupo de barra lateral de administración, o el enfoque de tabla de rollup por día. Quite el código específico de IA; conserve el patrón estructural. Esa es la ganancia de productividad del autor del plugin en el primer día.
A dónde ir después
Este es el último de los once análisis a fondo para desarrolladores. Desde aquí, el hub del índice es el recap natural: Índice de documentación muestra todas las páginas del cluster organizadas en Fundamentos / Construcción / Calidad / Referencia. La landing para desarrolladores es el punto de entrada con forma de marketing para los visitantes nuevos que llegan desde buscadores.
Dos próximos pasos prácticos cuando esté listo para entregar un plugin real: el ciclo activar → testear → eliminar del análisis a fondo de Testing prueba que el listener del hook delete_plugin_* del plugin limpia correctamente. Arquitectura de plugins § Recuperación de un estado roto es la página que conviene marcar para incidencias de runtime en producción: tres modos de fallo más la vía exacta de arreglo para cada uno.
Más allá de los patrones específicos de AcelleMail, aplica el ecosistema Laravel más amplio. El código del plugin es Laravel puro; el loader en tiempo de ejecución del host es la única pieza no estándar. Cualquier cosa que sepa sobre Eloquent, Blade, Pest, colas, scheduling o middleware funciona dentro de la carpeta del plugin sin cambios.