Saltar a contenido

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 ResourceBuilder o 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:

npm run dev:web-shadcn   # http://localhost:5175

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