Files
agenticSystem/docs/hooks-and-api.md
Jordan Diaz a9fbd01b5d Fix Claude adapter: convertir mensajes OpenAI→Claude format
- role=tool → role=user con tool_result blocks
- assistant con tool_calls → assistant con tool_use blocks
- Merge mensajes consecutivos del mismo role (Claude requiere alternancia)
- Capturar input_tokens del evento message_start

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:22:35 +00:00

11 KiB

Hooks & Server-Side API

Hooks

Hooks son archivos PHP que ejecutan lógica server-side. Pueden existir en dos sitios:

  • Hooks globales en hooks/hooks.<hook-id>.php
  • Hooks propios de módulo en template/estandar/modulos/<module-id>/hook.php

Tipos de hooks

1. Hook global

  • Archivo: hooks/hooks.<hook-id>.php
  • Endpoint: /hooks/<hook-id>/
  • Úsalo cuando la lógica se reutiliza entre módulos, páginas o formularios

2. Hook propio de módulo

  • Archivo: template/estandar/modulos/<module-id>/hook.php
  • Endpoint: /hooks/<module-id>/
  • Úsalo cuando la lógica pertenece solo a ese módulo

Ejemplos:

  • hooks/hooks.buscar_barcos.php -> /hooks/buscar_barcos/
  • template/estandar/modulos/hero_banner/hook.php -> /hooks/hero_banner/

Regla práctica:

  • Si el hook solo sirve a un módulo, créalo dentro del módulo
  • Si varias piezas del proyecto lo van a consumir, créalo como hook global

Reglas obligatorias para hooks

  • Un hook debe devolver datos con return [...]
  • No uses echo json_encode(...)
  • No uses exit
  • Para leer parámetros, usa $_REQUEST[...] o las variables ya inyectadas por el sistema
  • En hooks, usa CmsApi::get() o CocoDB::get() como primera opción
  • No uses CocoDB::getInstance() salvo necesidad real muy excepcional
  • No escribas SQL manual con prepare()/bind_param() salvo que no exista forma razonable de resolverlo con CmsApi o CocoDB

Estructura de un Hook

<?php
// Los parámetros se reciben como variables directamente
// Ejemplo: Si llamas hook con {param1: 100}, tendrás $param1 = 100

$resultado = $param1 * 2;

// Retornar un array (se convierte a JSON)
return [
    "success" => true,
    "message" => "Valor procesado: " . $resultado,
    "value" => $resultado
];
?>

Testing Hooks

El Docker debe estar corriendo. Hacer curl al endpoint del hook:

curl http://localhost:8080/hooks/example_hook/

No usar X-Hooks-Token en desarrollo local.

Cómo Llamar Hooks

La referencia siempre se hace con el endpoint /hooks/<id>/:

  • Para hooks globales, <id> es el nombre lógico del hook sin hooks. ni .php
  • Para hooks de módulo, <id> es el module-id de la carpeta del módulo

Desde HTML (recomendado para módulos):

<hook result="myVar" endpoint="/hooks/module_id/" :param1="value1" :param2="'string'"></hook>
<p>{{ myVar.message }}</p>

Desde Twig:

{% set resultado = 'hooks/mimodulo/' | hook({param1: 100, param2: 'texto'}) %}
<p>{{ resultado.message }}</p>

Desde JavaScript:

CmsApi.hook('/hooks/mimodulo/', {param1: 100, param2: 'texto'}, (data) => {
  console.log(data.message);
});

Ejemplo real para hook de módulo:

// Módulo: template/estandar/modulos/buscadorapartados_hjd8s/
CmsApi.hook('/hooks/buscadorapartados_hjd8s/', { termino: 'vela' }, (data) => {
  console.log(data);
});

Desde otro Hook PHP:

<?php
$result = hook("/hooks/mimodulo/", ["param1" => 100, "param2" => "texto"]);
$mensaje = $result["message"];
?>

Desde c-form: Los hooks se ejecutan automáticamente al enviar el formulario si están configurados.


CmsApi (PHP)

API server-side para operaciones de base de datos. Disponible en todos los hooks.

Read — CmsApi::get()

IMPORTANTE : Las tablas y nombres de campos puedes extraerlas de los esquemas en cms/data/schema/<nombre_de_tabla>.ini.php

// Todos los registros
$products = CmsApi::get("productos");

// Con condición WHERE en string
$active = CmsApi::get("productos", "active=1");

// Con orden y límite
$latest = CmsApi::get("noticias", "", "fecha DESC", 5);

// Con condición string
$activos = CmsApi::get("productos", "activo=1");

// Condición compleja 
$caros = CmsApi::get("productos", "precio > 100");

// Múltiples condiciones (AND)
$resultados = CmsApi::get("productos", "activo = 1 AND stock > 0");

// Con operadores
$expensive = CmsApi::get("productos", "precio >= 100");
$search = CmsApi::get("productos", "nombre LIKE '%keyword%'");
$inList = CmsApi::get("productos", "categoria_num IN (1, 2, 3)");

#### Opciones de `get()`

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `uploads` | bool | `true` | Incluir datos de upload fields |
| `relations` | bool/array | `true` | Resolver foreign keys. Array para limitar: `['category']` |
| `relationsDepth` | int | 2 | Profundidad de relaciones anidadas |
| `translates` | string | current lang | Código de idioma |
| `groupBy` | string | null | GROUP BY clause |
| `aggregates` | array | `[]` | Funciones de agregación |
| `onlyFields` | array | null | Seleccionar solo campos específicos |
| `debug` | bool | false | Mostrar SQL query |
| `redis` | bool | null | Forzar cache Redis |
| `redis_expire` | int | 60 | TTL de cache Redis (segundos) |

// Con opciones
$datos = CmsApi::get("productos", "", "", "", [
    'translates' => true,
    'uploads' => true,
    'relations' => true,
    'relationsDepth' => 2
]);

Insert — CmsApi::insert()

IMPORTANTE : Las tablas y nombres de campos puedes extraerlas de los esquemas en cms/data/schema/<nombre_de_tabla>.ini.php

// Un registro
CmsApi::insert('contacto', [
    ["nombre" => "John", "email" => "john@example.com", "mensaje" => "Hello"]
]);

// Múltiples registros
CmsApi::insert('productos', [
    ["nombre" => "Producto A", "precio" => 100],
    ["nombre" => "Producto B", "precio" => 200]
]);

// Con retorno del último ID
CmsApi::insert('productos',
    [["nombre" => "Nuevo", "precio" => 150]],
    [],
    ['return_last_id' => true]
);

Opciones de insert

Option Description
forceNum Permite setear el campo num manualmente
ignoreSchema Saltar validación de schema
ignoreFields Array de campos a ignorar

Update — CmsApi::update()

IMPORTANTE : Las tablas y nombres de campos puedes extraerlas de los esquemas en cms/data/schema/<nombre_de_tabla>.ini.php

// Con condición string
CmsApi::update('productos', ["precio" => 150], "num=1");

// Con condición array
CmsApi::update('productos',
    ["activo" => 1],
    [["column" => "num", "operator" => "=", "value" => 1]]
);

// Múltiples registros
CmsApi::update('productos', ["activo" => 0], "precio < 50");

Opciones de update

Option Description
forceNum Permite setear el campo num manualmente
ignoreSchema Saltar validación de schema
ignoreFields Array de campos a ignorar

Delete — CmsApi::delete()

IMPORTANTE : Las tablas y nombres de campos puedes extraerlas de los esquemas en cms/data/schema/<nombre_de_tabla>.ini.php

CmsApi::delete('productos', "num=5");

CmsApi::delete('productos',
    [["column" => "activo", "operator" => "=", "value" => 0]]
);

Reglas importantes

  • Nombres de tabla sin prefijo cms_
  • Primary key siempre es num, nunca id
  • Foreign keys: categoria_num, no categoria_id
  • Upload fields: no se manejan via insert/update
  • Operadores: =, !=, >, >=, <, <=, LIKE, IN

CmsApi (JavaScript — Client-Side)

// Llamar hook
CmsApi.hook('/hooks/module_id/', { param: 'value' }, function(response) {
  // response es la salida del hook
});

// Leer registros (si está expuesto via hooks)
CmsApi.get('tableName', { where: conditions }, function(records) {
  // records array
});

CocoDB

Capa de abstracción de BD de bajo nivel usada internamente por CmsApi. Usar directamente desde hooks cuando necesites más control.

Para búsquedas y lecturas habituales, prioriza:

  1. CmsApi::get()
  2. CocoDB::get()
  3. SQL manual solo si de verdad no hay alternativa razonable

CocoDB::get($table, $where, $order, $limit, $options)

Funcionalidad exactamente igual a CmsApi::get ( ver referencia CmsApi::get )

CocoDB::insertRecords($table, $records, $functions, $options)

Funcionalidad exactamente igual a CmsApi::insert ( ver referencia CmsApi::insert )

CocoDB::updateRecords($table, $records, $where, $functions, $options)

Funcionalidad exactamente igual a CmsApi::update ( ver referencia CmsApi::update )

CocoDB::deleteRecords($table, $where, $options)

Funcionalidad exactamente igual a CmsApi::delete ( ver referencia CmsApi::delete )


Creación y Actualización de Registros

Flujo correcto

  1. Consultar el esquema de la tabla (leer cms/data/schema/{tabla}.ini.php)
  2. Revisar los tipos de campo
  3. Rellenar según el tipo de dato
  4. Enviar con la estructura correcta

Tipos de campo y formato

Tipo Formato Ejemplo
Text field String "Texto"
Text box String multilínea "Línea 1\nLínea 2"
Date/time YYYY-MM-DD HH:mm:ss "2025-12-03 10:30:00"
Wysiwyg String HTML "<p class=\"font-bold\">Texto</p>"
List String o número "activo" o "1" (num si es foreign key)
Checkbox Número 1/0 1 o 0
Multivalores String JSON "[{\"producto\":\"1\"}]"
Upload NO enviar — usar upload_record_image después de crear el registro

Table Schemas

Los schemas están en cms/data/schema/ como archivos .ini.php. Definen:

  • Nombres y tipos de campo
  • Reglas de validación
  • Relaciones (foreign keys)
  • Configuración de display

Ejemplos Prácticos

Hook de Cálculo de Precio

<?php
// hook.php del módulo "calcular_precio"
$precioUnitario = 50;

if ($tipo === 'mayoreo' && $cantidad > 10) {
    $precioUnitario *= 0.85; // 15% descuento
}

return [
    "success" => true,
    "precioUnitario" => round($precioUnitario, 2),
    "total" => round($precioUnitario * $cantidad, 2),
    "descuento" => $tipo === 'mayoreo' ? 15 : 0
];
?>
<hook result="precio" endpoint="/hooks/calcular_precio/" :cantidad="10" :tipo="'mayoreo'"></hook>
<p>Total: ${{ precio.total }}</p>

En este ejemplo, el endpoint usa calcular_precio porque el archivo vive en: template/estandar/modulos/calcular_precio/hook.php

Hook con Operaciones de BD

<?php
// hook.php del módulo "procesar_compra"
$producto = CmsApi::get("productos", "num=" . $producto_id);

if (empty($producto)) {
    return ["success" => false, "message" => "Producto no encontrado"];
}

$total = $producto[0]['precio'] * $cantidad;

// Crear venta
CmsApi::insert('ventas', [[
    "usuario_num" => $usuario_id,
    "producto_num" => $producto_id,
    "cantidad" => $cantidad,
    "total" => $total,
    "fecha" => date('Y-m-d H:i:s')
]], [], ['return_last_id' => true]);

// Actualizar stock
$stock = CmsApi::get("stocks", "producto_num=" . $producto_id);
if (!empty($stock)) {
    CmsApi::update('stocks',
        ["cantidad" => $stock[0]['cantidad'] - $cantidad],
        "producto_num=$producto_id"
    );
}

return ["success" => true, "total" => $total];
?>