- 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>
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()oCocoDB::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 conCmsApioCocoDB
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 sinhooks.ni.php - Para hooks de módulo,
<id>es elmodule-idde 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, nuncaid - Foreign keys:
categoria_num, nocategoria_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:
CmsApi::get()CocoDB::get()- 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
- Consultar el esquema de la tabla (leer
cms/data/schema/{tabla}.ini.php) - Revisar los tipos de campo
- Rellenar según el tipo de dato
- 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];
?>