# 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..php` - Hooks propios de módulo en `template/estandar/modulos//hook.php` ### Tipos de hooks **1. Hook global** - Archivo: `hooks/hooks..php` - Endpoint: `/hooks//` - Úsalo cuando la lógica se reutiliza entre módulos, páginas o formularios **2. Hook propio de módulo** - Archivo: `template/estandar/modulos//hook.php` - Endpoint: `/hooks//` - Ú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 true, "message" => "Valor procesado: " . $resultado, "value" => $resultado ]; ?> ``` ### Testing Hooks El Docker debe estar corriendo. Hacer curl al endpoint del hook: ```bash curl {ACAI_WEB_URL}/hooks/example_hook/ ``` (Use the project's actual URL, never localhost:8080) No usar X-Hooks-Token en desarrollo local. ### Cómo Llamar Hooks La referencia siempre se hace con el endpoint `/hooks//`: - Para hooks globales, `` es el nombre lógico del hook sin `hooks.` ni `.php` - Para hooks de módulo, `` es el `module-id` de la carpeta del módulo **Desde HTML (recomendado para módulos):** ```html

{{ myVar.message }}

``` **Desde Twig:** ```twig {% set resultado = 'hooks/mimodulo/' | hook({param1: 100, param2: 'texto'}) %}

{{ resultado.message }}

``` **Desde JavaScript:** ```js CmsApi.hook('/hooks/mimodulo/', {param1: 100, param2: 'texto'}, (data) => { console.log(data.message); }); ``` **Ejemplo real para hook de módulo:** ```js // Módulo: template/estandar/modulos/buscadorapartados_hjd8s/ CmsApi.hook('/hooks/buscadorapartados_hjd8s/', { termino: 'vela' }, (data) => { console.log(data); }); ``` **Desde otro Hook PHP:** ```php 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/.ini.php ```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/.ini.php ```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/.ini.php ```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/.ini.php ```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) ```js // 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 | `"

Texto

"` | | **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 10) { $precioUnitario *= 0.85; // 15% descuento } return [ "success" => true, "precioUnitario" => round($precioUnitario, 2), "total" => round($precioUnitario * $cantidad, 2), "descuento" => $tipo === 'mayoreo' ? 15 : 0 ]; ?> ``` ```html

Total: ${{ precio.total }}

``` 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 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]; ?> ```