Primer plugin¶
Tutorial paso a paso para crear un plugin de negocio con API REST y pantalla de listado en el namespace operations, sin escribir React.
Base recomendada: copia o extiende plugins/_template/ (repo cortex-plugin-template).
Antes de empezar¶
Clona cortex-hive con submódulos:
git clone --recurse-submodules git@github.com:cortex-ia-com-co/cortex-hive.git
cd cortex-hive && uv sync --all-packages
Detalle de repos y flujo git: Repositorios y submódulos.
Lee primero ¿Qué es un plugin? y Anatomía si es tu primera vez.
Cuándo usar esta guía¶
- Es tu primer plugin en Cortex/HIVE.
- Necesitas un listado con columnas y rutas REST bajo
/api/v1. - Quieres UI declarativa con
ResourceBuildero registro directo de manifest.
Estructura mínima¶
plugins/mi-modulo/
pyproject.toml
cortex_plugin_mi_modulo/
__init__.py
plugin.py # API REST + create_plugin()
resources.py # register_resources()
store.py # fixtures o persistencia
tests/
docs/
README.md
Paso 1 — Paquete y entry point¶
En pyproject.toml:
[project]
name = "cortex-plugin-mi-modulo"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"cortex-core",
"cortex-framework",
"fastapi>=0.115",
]
[tool.uv.sources]
cortex-core = { workspace = true }
cortex-framework = { workspace = true }
[project.entry-points."cortex.plugins"]
mi-modulo = "cortex_plugin_mi_modulo:create_plugin"
Añade "plugins/mi-modulo" al workspace en el pyproject.toml raíz (o enlázalo como submódulo) y ejecuta uv sync --all-packages.
Paso 2 — Plugin REST¶
plugin.py expone el contrato obligatorio del SPI:
from cortex_core.plugin import PluginDescriptor
from cortex_framework.api.list_response import list_endpoint
from fastapi import APIRouter, Request
class MiModuloPlugin:
descriptor = PluginDescriptor("mi-modulo", "Mi módulo", "0.1.0")
def api_router(self) -> APIRouter:
router = APIRouter(prefix="/mi-modulo", tags=["mi-modulo"])
@router.get("/items")
async def list_items(request: Request) -> dict:
items = [{"id": 1, "name": "Ejemplo"}]
return list_endpoint(items, request)
return router
def resource_paths(self) -> list[str]:
return ["mi-modulo"]
def create_plugin() -> MiModuloPlugin:
return MiModuloPlugin()
Referencia mínima: plugins/_template/cortex_plugin_template/plugin.py. Ejemplo con CRUD: plugins/clients/cortex_plugin_clients/plugin.py.
Paso 3 — UI en namespace operations¶
Referencia canónica del API interno (ResourceBuilder, FormBuilder, hooks): API interna del framework. Código ejecutable: plugins/helloworld/cortex_plugin_helloworld/resources.py y forms.py.
resources.py registra manifest y dashboards:
from cortex_framework.ui import ResourceBuilder, register_resource
from cortex_framework.ui.registry import ResourceRegistry
def register_resources(registry: ResourceRegistry) -> None:
def configure(builder: ResourceBuilder) -> None:
(
builder.id("items")
.title("Ítems")
.api_base("/api/v1/mi-modulo")
.list_path("/api/v1/mi-modulo/items")
.module_path("items")
.nav_group("catalog", nav_sort=20)
.pages(list=True, create=False, edit=False, view=False)
.table(
lambda t: t.columns(
lambda c: c.text("name", "Nombre", sortable=True)
)
)
)
register_resource(registry, "operations", "mi-modulo", configure)
Reexporta el hook en __init__.py:
from cortex_plugin_mi_modulo.plugin import create_plugin
from cortex_plugin_mi_modulo.resources import register_resources
__all__ = ["create_plugin", "register_resources"]
El framework materializa el namespace operations con ensure_panel() al primer registro.
Paso 4 — Activar el plugin¶
En desarrollo y producción, activa el plugin desde /control/plugins (scope sudo). El estado persiste en PostgreSQL.
En tests, usa create_test_app(..., enabled_plugins="mi-modulo") — ver Activación.
En otra terminal:
Paso 5 — Verificar¶
| Comprobación | Cómo |
|---|---|
| API | GET http://localhost:8000/api/v1/mi-modulo/items + header Authorization: default |
| OpenAPI | http://localhost:8000/api/docs |
| UI | Panel operations → módulo mi-modulo en la navegación |
| Manifest | GET /api/v1/panels/operations/modules/mi-modulo/manifest |
Flujo bootstrap → pantalla¶
sequenceDiagram
participant P as Plugin Python
participant API as FastAPI bootstrap
participant Shell as web-shadcn
participant W as Widgets
P->>API: register_resources
API->>API: ensure_panel operations
Shell->>API: GET panels/operations/modules/.../manifest
Shell->>API: GET ui/dashboards/{id}
Shell->>W: render data-table
W->>API: GET /api/v1/mi-modulo/items Errores frecuentes¶
| Síntoma | Causa habitual |
|---|---|
| Plugin no aparece en OpenAPI | Plugin no habilitado en /control/plugins o error en uv sync |
| Módulo no en navegación | panel_id incorrecto o hook no reexportado en __init__.py |
| Tabla vacía o 404 | list_path no coincide con la ruta del APIRouter |
register_dashboards ignorado | Si existe register_resources, el loader no ejecuta register_dashboards |
Siguiente paso¶
- Ciclo de vida — orden de bootstrap y hooks.
- Integración con el framework — namespaces y paneles generativos.
- Recursos — páginas create/edit/view.