# 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 ### 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 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//`: - 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); }); ``` **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()` ```php // Todos los registros $products = CmsApi::get('productos'); // Con condición WHERE $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 como array $caros = CmsApi::get('productos', [ ["column" => "precio", "operator" => ">", "value" => 100] ]); // Múltiples condiciones (AND) $resultados = CmsApi::get('productos', [ ["column" => "activo", "operator" => "=", "value" => 1], ["column" => "stock", "operator" => ">", "value" => 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]]]); // Con opciones $datos = CmsApi::get('productos', '', '', '', [ 'translates' => true, 'uploads' => true, 'relations' => true, 'relationsDepth' => 2 ]); ``` ### Insert — `CmsApi::insert()` ```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] ); ``` ### Update — `CmsApi::update()` ```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"); ``` ### Delete — `CmsApi::delete()` ```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. ### `CocoDB::get($table, $where, $order, $limit, $options)` ```php // Básico $records = CocoDB::get('productos', ['activo' => 1], 'orden ASC', 10); // Where con operadores avanzados $records = CocoDB::get('productos', [ ['column' => 'precio', 'value' => 100, 'operator' => '>='], ['column' => 'categoria_num', 'value' => [1, 2, 3], 'operator' => 'IN'], ]); // Condiciones OR $records = CocoDB::get('productos', [ ['column' => 'nombre', 'value' => '%keyword%', 'operator' => 'LIKE'], ['column' => 'descripcion', 'value' => '%keyword%', 'operator' => 'LIKE', 'or' => true], ]); // NOT $records = CocoDB::get('productos', [ ['column' => 'estado', 'value' => 'borrador', 'operator' => '=', 'not' => true], ]); // IS NULL $records = CocoDB::get('productos', [ ['column' => 'fecha_baja', 'value' => '', 'operator' => 'IS NULL'], ]); // Limit con offset $records = CocoDB::get('productos', [], 'num DESC', ['limit' => 10, 'offset' => 20]); ``` #### 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) | ### `CocoDB::insertRecords($table, $records, $functions, $options)` ```php // Un registro $count = CocoDB::insertRecords('contacto', [ 'nombre' => 'John', 'email' => 'john@example.com', ]); // Usar mysql_insert_id() para obtener el nuevo num // Múltiples $count = CocoDB::insertRecords('productos', [ ['nombre' => 'Product A', 'precio' => 10], ['nombre' => 'Product B', 'precio' => 20], ]); ``` #### Opciones de insert/update | Option | Description | |--------|-------------| | `forceNum` | Permite setear el campo `num` manualmente | | `ignoreSchema` | Saltar validación de schema | | `ignoreFields` | Array de campos a ignorar | ### `CocoDB::updateRecords($table, $records, $where, $functions, $options)` ```php CocoDB::updateRecords('productos', ['precio' => 29.99, 'activo' => 1], ['num' => 42] ); // Con operador en where CocoDB::updateRecords('productos', ['activo' => 0], [['column' => 'stock', 'value' => 0, 'operator' => '<=']] ); ``` ### `CocoDB::deleteRecords($table, $where, $options)` ```php CocoDB::deleteRecords('productos', ['num' => 42]); CocoDB::deleteRecords('logs', [ ['column' => 'fecha', 'value' => '2024-01-01', 'operator' => '<'] ]); ``` ### Parámetro `$functions` Permite aplicar funciones MySQL a valores durante insert/update: ```php CocoDB::insertRecords('logs', [ 'mensaje' => 'Login exitoso', 'fecha' => '', ], [ 'fecha' => 'NOW()', ]); ``` ### Where Clause — Formatos **Simple (key-value):** ```php ['campo' => 'valor'] // campo = 'valor' ``` **Avanzado (array de condiciones):** ```php [ 'column' => 'field_name', 'value' => 'match_value', 'operator' => '=', // =, !=, <, >, <=, >=, LIKE, IN, IS NULL 'or' => false, // OR en vez de AND 'not' => false, // Negar la condición 'raw_key' => false, // Saltar check de existencia de columna ] ``` --- ## 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]; ?> ```