Forzar máximo 2 steps en plan: 1 coder + 1 reviewer opcional
El planner generaba 3+ steps para tareas simples causando que el coder repitiera acciones en cada step (creaba el módulo varias veces). Ahora el engine fusiona los steps en 1 coder con descripción combinada. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,13 @@
|
|||||||
|
|
||||||
El atributo `data-field-label` se convierte a variable removiendo espacios y caracteres especiales (minúsculas).
|
El atributo `data-field-label` se convierte a variable removiendo espacios y caracteres especiales (minúsculas).
|
||||||
|
|
||||||
|
Reglas obligatorias:
|
||||||
|
- Todo elemento editable con `data-field-type` debe incluir también `data-field-label`
|
||||||
|
- Si falta `data-field-label`, el builder puede generar variables temporales o incorrectas y el módulo queda mal configurado
|
||||||
|
- Usa labels descriptivos y estables; no dejes labels vacíos ni genéricos como "Campo" o "Texto"
|
||||||
|
|
||||||
|
Evita también en `index-base.tpl` las clases Tailwind con valores arbitrarios como `text-[44px]`, `font-['Cinzel']` o `leading-[1.1]`. En este stack pueden romper el parseo/compilación del template. Muévelas a `style.css`.
|
||||||
|
|
||||||
| Label | Variable |
|
| Label | Variable |
|
||||||
|-------|----------|
|
|-------|----------|
|
||||||
| Categoría Noticia | `categoranoticia` |
|
| Categoría Noticia | `categoranoticia` |
|
||||||
|
|||||||
@@ -72,12 +72,29 @@ Patrón para colores configurables por el usuario:
|
|||||||
|
|
||||||
JavaScript scopeado al módulo usando `section_id`:
|
JavaScript scopeado al módulo usando `section_id`:
|
||||||
|
|
||||||
|
This is the default and expected place for module JavaScript.
|
||||||
|
Do NOT embed `<script>` tags directly inside `index-base.tpl` for normal module behavior.
|
||||||
|
Keep `index-base.tpl` for HTML/Twig markup and put interactive logic in `script.js`.
|
||||||
|
`script.js` is a static file: do NOT use Twig syntax inside it.
|
||||||
|
Do NOT write `{{ section_id }}`, `{{ variable }}`, `{% if ... %}`, or builder attributes inside `script.js`.
|
||||||
|
|
||||||
|
If JavaScript needs dynamic values, pass them from `index-base.tpl` through `data-*` attributes:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<section
|
||||||
|
class="buscador-apartados"
|
||||||
|
data-section-id="{{ section_id }}"
|
||||||
|
data-hook-endpoint="/hooks/buscadorapartados_hjd8s/">
|
||||||
|
</section>
|
||||||
|
```
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const section = document.getElementById('{{ section_id }}');
|
document.querySelectorAll('.buscador-apartados').forEach((section) => {
|
||||||
if (section) {
|
const sectionId = section.dataset.sectionId;
|
||||||
|
const hookEndpoint = section.dataset.hookEndpoint;
|
||||||
const buttons = section.querySelectorAll('.btn');
|
const buttons = section.querySelectorAll('.btn');
|
||||||
// ...
|
// ...
|
||||||
}
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### CmsApi (Client-Side)
|
### CmsApi (Client-Side)
|
||||||
@@ -88,6 +105,25 @@ CmsApi.hook('/hooks/module_id/', { action: 'getData', id: 123 }, function(respon
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you are calling a hook that belongs to the current module, the endpoint must use the real module id:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Module folder: template/estandar/modulos/buscadorapartados_hjd8s/
|
||||||
|
CmsApi.hook('/hooks/buscadorapartados_hjd8s/', { termino: 'vela' }, function(response) {
|
||||||
|
console.log(response);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not try to build this endpoint with Twig inside `script.js`. Put the final endpoint in a `data-hook-endpoint` attribute in `index-base.tpl` if needed.
|
||||||
|
|
||||||
|
### Module Styles (`style.css`)
|
||||||
|
|
||||||
|
`style.css` is also a static file.
|
||||||
|
Do NOT use Twig syntax or builder attributes inside it.
|
||||||
|
Do NOT write selectors or values that depend on `{{ section_id }}`.
|
||||||
|
|
||||||
|
Scope styles with the module root class instead of dynamic Twig ids.
|
||||||
|
|
||||||
### Cuándo usar Vue 3
|
### Cuándo usar Vue 3
|
||||||
|
|
||||||
Usar Vue 3 CDN cuando la lógica requiera:
|
Usar Vue 3 CDN cuando la lógica requiera:
|
||||||
|
|||||||
@@ -2,7 +2,39 @@
|
|||||||
|
|
||||||
## Hooks
|
## Hooks
|
||||||
|
|
||||||
Hooks son archivos PHP en `hooks/` que ejecutan lógica server-side. También pueden estar dentro de un módulo en `template/estandar/modulos/<module-id>/hook.php`.
|
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
|
### Estructura de un Hook
|
||||||
|
|
||||||
@@ -34,6 +66,10 @@ No usar X-Hooks-Token en desarrollo local.
|
|||||||
|
|
||||||
### Cómo Llamar Hooks
|
### 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):**
|
**Desde HTML (recomendado para módulos):**
|
||||||
```html
|
```html
|
||||||
<hook result="myVar" endpoint="/hooks/module_id/" :param1="value1" :param2="'string'"></hook>
|
<hook result="myVar" endpoint="/hooks/module_id/" :param1="value1" :param2="'string'"></hook>
|
||||||
@@ -53,6 +89,14 @@ CmsApi.hook('/hooks/mimodulo/', {param1: 100, param2: 'texto'}, (data) => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**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:**
|
**Desde otro Hook PHP:**
|
||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
@@ -71,37 +115,49 @@ API server-side para operaciones de base de datos. Disponible en todos los hooks
|
|||||||
|
|
||||||
### Read — `CmsApi::get()`
|
### Read — `CmsApi::get()`
|
||||||
|
|
||||||
|
## IMPORTANTE : Las tablas y nombres de campos puedes extraerlas de los esquemas en cms/data/schemas/<nombre_de_tabla>.ini.php
|
||||||
|
|
||||||
```php
|
```php
|
||||||
// Todos los registros
|
// Todos los registros
|
||||||
$products = CmsApi::get('productos');
|
$products = CmsApi::get("productos");
|
||||||
|
|
||||||
// Con condición WHERE
|
// Con condición WHERE en string
|
||||||
$active = CmsApi::get('productos', ['active' => 1]);
|
$active = CmsApi::get("productos", "active=1");
|
||||||
|
|
||||||
// Con orden y límite
|
// Con orden y límite
|
||||||
$latest = CmsApi::get('noticias', [], 'fecha DESC', 5);
|
$latest = CmsApi::get("noticias", "", "fecha DESC", 5);
|
||||||
|
|
||||||
// Con condición string
|
// Con condición string
|
||||||
$activos = CmsApi::get('productos', 'activo=1');
|
$activos = CmsApi::get("productos", "activo=1");
|
||||||
|
|
||||||
// Condición compleja como array
|
// Condición compleja
|
||||||
$caros = CmsApi::get('productos', [
|
$caros = CmsApi::get("productos", "precio > 100");
|
||||||
["column" => "precio", "operator" => ">", "value" => 100]
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Múltiples condiciones (AND)
|
// Múltiples condiciones (AND)
|
||||||
$resultados = CmsApi::get('productos', [
|
$resultados = CmsApi::get("productos", "activo = 1 AND stock > 0");
|
||||||
["column" => "activo", "operator" => "=", "value" => 1],
|
|
||||||
["column" => "stock", "operator" => ">", "value" => 0]
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Con operadores
|
// Con operadores
|
||||||
$expensive = CmsApi::get('productos', ['precio' => ['>=' => 100]]);
|
$expensive = CmsApi::get("productos", "precio >= 100");
|
||||||
$search = CmsApi::get('productos', ['nombre' => ['LIKE' => '%keyword%']]);
|
$search = CmsApi::get("productos", "nombre LIKE '%keyword%'");
|
||||||
$inList = CmsApi::get('productos', ['categoria_num' => ['IN' => [1, 2, 3]]]);
|
$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
|
// Con opciones
|
||||||
$datos = CmsApi::get('productos', '', '', '', [
|
$datos = CmsApi::get("productos", "", "", "", [
|
||||||
'translates' => true,
|
'translates' => true,
|
||||||
'uploads' => true,
|
'uploads' => true,
|
||||||
'relations' => true,
|
'relations' => true,
|
||||||
@@ -111,6 +167,8 @@ $datos = CmsApi::get('productos', '', '', '', [
|
|||||||
|
|
||||||
### Insert — `CmsApi::insert()`
|
### Insert — `CmsApi::insert()`
|
||||||
|
|
||||||
|
## IMPORTANTE : Las tablas y nombres de campos puedes extraerlas de los esquemas en cms/data/schemas/<nombre_de_tabla>.ini.php
|
||||||
|
|
||||||
```php
|
```php
|
||||||
// Un registro
|
// Un registro
|
||||||
CmsApi::insert('contacto', [
|
CmsApi::insert('contacto', [
|
||||||
@@ -131,8 +189,19 @@ CmsApi::insert('productos',
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 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()`
|
### Update — `CmsApi::update()`
|
||||||
|
|
||||||
|
## IMPORTANTE : Las tablas y nombres de campos puedes extraerlas de los esquemas en cms/data/schemas/<nombre_de_tabla>.ini.php
|
||||||
|
|
||||||
```php
|
```php
|
||||||
// Con condición string
|
// Con condición string
|
||||||
CmsApi::update('productos', ["precio" => 150], "num=1");
|
CmsApi::update('productos', ["precio" => 150], "num=1");
|
||||||
@@ -147,8 +216,19 @@ CmsApi::update('productos',
|
|||||||
CmsApi::update('productos', ["activo" => 0], "precio < 50");
|
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()`
|
### Delete — `CmsApi::delete()`
|
||||||
|
|
||||||
|
## IMPORTANTE : Las tablas y nombres de campos puedes extraerlas de los esquemas en cms/data/schemas/<nombre_de_tabla>.ini.php
|
||||||
|
|
||||||
```php
|
```php
|
||||||
CmsApi::delete('productos', "num=5");
|
CmsApi::delete('productos', "num=5");
|
||||||
|
|
||||||
@@ -187,134 +267,24 @@ CmsApi.get('tableName', { where: conditions }, function(records) {
|
|||||||
|
|
||||||
Capa de abstracción de BD de bajo nivel usada internamente por CmsApi. Usar directamente desde hooks cuando necesites más control.
|
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)`
|
### `CocoDB::get($table, $where, $order, $limit, $options)`
|
||||||
|
## Funcionalidad exactamente igual a CmsApi::get ( ver referencia CmsApi::get )
|
||||||
|
|
||||||
```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)`
|
### `CocoDB::insertRecords($table, $records, $functions, $options)`
|
||||||
|
## Funcionalidad exactamente igual a CmsApi::insert ( ver referencia CmsApi::insert )
|
||||||
```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)`
|
### `CocoDB::updateRecords($table, $records, $where, $functions, $options)`
|
||||||
|
## Funcionalidad exactamente igual a CmsApi::update ( ver referencia CmsApi::update )
|
||||||
|
|
||||||
```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)`
|
### `CocoDB::deleteRecords($table, $where, $options)`
|
||||||
|
## Funcionalidad exactamente igual a CmsApi::delete ( ver referencia CmsApi::delete )
|
||||||
```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
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -379,12 +349,15 @@ return [
|
|||||||
<p>Total: ${{ precio.total }}</p>
|
<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
|
### Hook con Operaciones de BD
|
||||||
|
|
||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
// hook.php del módulo "procesar_compra"
|
// hook.php del módulo "procesar_compra"
|
||||||
$producto = CmsApi::get('productos', "num=$producto_id");
|
$producto = CmsApi::get("productos", "num=" . $producto_id);
|
||||||
|
|
||||||
if (empty($producto)) {
|
if (empty($producto)) {
|
||||||
return ["success" => false, "message" => "Producto no encontrado"];
|
return ["success" => false, "message" => "Producto no encontrado"];
|
||||||
@@ -402,7 +375,7 @@ CmsApi::insert('ventas', [[
|
|||||||
]], [], ['return_last_id' => true]);
|
]], [], ['return_last_id' => true]);
|
||||||
|
|
||||||
// Actualizar stock
|
// Actualizar stock
|
||||||
$stock = CmsApi::get('stocks', "producto_num=$producto_id");
|
$stock = CmsApi::get("stocks", "producto_num=" . $producto_id);
|
||||||
if (!empty($stock)) {
|
if (!empty($stock)) {
|
||||||
CmsApi::update('stocks',
|
CmsApi::update('stocks',
|
||||||
["cantidad" => $stock[0]['cantidad'] - $cantidad],
|
["cantidad" => $stock[0]['cantidad'] - $cantidad],
|
||||||
|
|||||||
@@ -73,6 +73,20 @@ PHP files that execute server-side logic. Triggered by:
|
|||||||
- JavaScript: `CmsApi.hook('/hooks/module_id/', {param: value}, callback)`
|
- JavaScript: `CmsApi.hook('/hooks/module_id/', {param: value}, callback)`
|
||||||
- Form action: via `c-form` attribute
|
- Form action: via `c-form` attribute
|
||||||
|
|
||||||
|
There are two valid hook locations:
|
||||||
|
- Global hooks in `hooks/hooks.<hook-id>.php` for reusable/shared server-side logic
|
||||||
|
- Module-specific hooks in `template/estandar/modulos/<module-id>/hook.php` for logic owned by a single module
|
||||||
|
|
||||||
|
How to reference them:
|
||||||
|
- Global hook `hooks/hooks.calcular_precio.php` -> endpoint `/hooks/calcular_precio/`
|
||||||
|
- Module hook `template/estandar/modulos/hero_banner/hook.php` -> endpoint `/hooks/hero_banner/`
|
||||||
|
- Module hook `template/estandar/modulos/buscadorapartados_hjd8s/hook.php` -> endpoint `/hooks/buscadorapartados_hjd8s/`
|
||||||
|
|
||||||
|
Rule of thumb:
|
||||||
|
- If the logic is only used by one module, prefer that module's `hook.php`
|
||||||
|
- If the logic will be reused by several modules/pages, create a global hook in `hooks/`
|
||||||
|
- Return arrays from hooks; do not use `echo json_encode(...)` or `exit`
|
||||||
|
|
||||||
See [docs/hooks-and-api.md](docs/hooks-and-api.md) for usage.
|
See [docs/hooks-and-api.md](docs/hooks-and-api.md) for usage.
|
||||||
|
|
||||||
## Database Access
|
## Database Access
|
||||||
@@ -103,17 +117,18 @@ Do NOT modify web-base files — they are shared across all projects.
|
|||||||
## Critical Rules
|
## Critical Rules
|
||||||
|
|
||||||
1. **Before working with any area (hooks, modules, templates, CSS/JS, etc.), read the corresponding documentation in `docs/` first.** Do not guess or assume — always consult the docs before taking action.
|
1. **Before working with any area (hooks, modules, templates, CSS/JS, etc.), read the corresponding documentation in `docs/` first.** Do not guess or assume — always consult the docs before taking action.
|
||||||
2. **NEVER use `mkdir` to create directories.** Instead, use the `Write` tool to create the first file inside the directory — this creates parent directories automatically. For example, to create a new module, directly write the `index-base.tpl` file.
|
2. **NEVER use `mkdir` to create directories.** Instead, use `acai-write` to create the first file inside the directory — this creates parent directories automatically. For example, to create a new module, directly write the `index-base.tpl` file.
|
||||||
3. Only edit `index-base.tpl` in modules — `index.tpl`, `index-twig.tpl`, and `builder.json` are auto-generated
|
3. Only edit `index-base.tpl` in modules — `index.tpl`, `index-twig.tpl`, and `builder.json` are auto-generated
|
||||||
3. **Edit `index-base.tpl` using `acai_write` or `acai_line_replace`** — the server compiles automatically when the file is saved. No need to call `compile_module` manually.
|
4. Editing or creating any `index-base.tpl` through `acai-write` or `acai-line-replace` triggers automatic compilation. `compile_module` is only for manual recovery when you need to force a recompile without changing the file.
|
||||||
4. Use Twig **filters** (with `|`), never Twig functions
|
5. `script.js` and `style.css` are static files — do NOT use Twig syntax inside them. Pass dynamic values from `index-base.tpl` via `data-*` attributes.
|
||||||
5. Table names without `cms_` prefix everywhere
|
6. Use Twig **filters** (with `|`), never Twig functions
|
||||||
6. Primary key is `num`, never `id`
|
7. Table names without `cms_` prefix everywhere
|
||||||
7. Upload fields are arrays — access with `[0].urlPath`
|
8. Primary key is `num`, never `id`
|
||||||
8. Tailwind CSS as primary styling, custom CSS scoped with BEM when needed
|
9. Upload fields are arrays — access with `[0].urlPath`
|
||||||
9. Twig concatenation uses `~` operator: `'value=' ~ variable`
|
10. Tailwind CSS as primary styling, custom CSS scoped with BEM when needed
|
||||||
10. `enlace` (link) fields already include slashes — **NEVER modify an existing enlace** unless explicitly asked
|
11. Twig concatenation uses `~` operator: `'value=' ~ variable`
|
||||||
11. **NEVER modify the `controlador` field** of existing records — it defines whether a page is Builder or Standard
|
12. `enlace` (link) fields already include slashes — **NEVER modify an existing enlace** unless explicitly asked
|
||||||
|
13. **NEVER modify the `controlador` field** of existing records — it defines whether a page is Builder or Standard
|
||||||
|
|
||||||
## MCP Tools
|
## MCP Tools
|
||||||
|
|
||||||
@@ -122,8 +137,8 @@ This project has MCP tools for managing modules, records, media, and more. **Bef
|
|||||||
See [docs/mcp-tools-reference.md](docs/mcp-tools-reference.md) for the complete list of available tools and step-by-step workflows.
|
See [docs/mcp-tools-reference.md](docs/mcp-tools-reference.md) for the complete list of available tools and step-by-step workflows.
|
||||||
|
|
||||||
Key workflows:
|
Key workflows:
|
||||||
- **Create module**: Read [docs/module-creation-guide.md](docs/module-creation-guide.md) first → Write `index-base.tpl` via `acai_write` → `add_module_to_record` (returns sectionId) → `set_module_config_vars` (returns uploadFields) → images via uploadFields
|
- **Create module**: Read [docs/module-creation-guide.md](docs/module-creation-guide.md) first → create files with `acai-write` / refine with `acai-line-replace` → automatic compile on `index-base.tpl` → `add_module_to_record` (returns sectionId) → `set_module_config_vars` (returns uploadFields) → images via uploadFields. Use `compile_module` only if you need a manual recompile without editing the file.
|
||||||
- **Edit module**: read vars → edit `index-base.tpl` with `acai_write` or `acai_line_replace` (server compiles automatically)
|
- **Edit module**: `acai-view` → `acai-line-replace` (or `acai-write` for full rewrites) → automatic compile on `index-base.tpl`
|
||||||
- **Add images**: use `uploadFields` from `set_module_config_vars` response → `upload_record_image`
|
- **Add images**: use `uploadFields` from `set_module_config_vars` response → `upload_record_image`
|
||||||
- **Generate images**: `generate_image` → `upload_record_image` with returned URL
|
- **Generate images**: `generate_image` → `upload_record_image` with returned URL
|
||||||
|
|
||||||
@@ -4,11 +4,15 @@
|
|||||||
|
|
||||||
| Tool | Categoría | Acción |
|
| Tool | Categoría | Acción |
|
||||||
|------|-----------|--------|
|
|------|-----------|--------|
|
||||||
| `create_module` | Módulos | (Legacy) Alternativa para crear módulo — preferir acai_write |
|
| `compile_module` | Módulos | Recompilación manual de rescate cuando necesitas forzar la compilación sin editar el archivo |
|
||||||
| `compile_module` | Módulos | Compila módulo tras editar index-base.tpl |
|
|
||||||
| `check_module` | Módulos | Preview de cómo renderiza un módulo |
|
| `check_module` | Módulos | Preview de cómo renderiza un módulo |
|
||||||
| `check_module_usage` | Módulos | Qué páginas usan un módulo |
|
| `check_module_usage` | Módulos | Qué páginas usan un módulo |
|
||||||
| `set_module_example_data` | Módulos | Datos de ejemplo para editor visual |
|
| `acai-view` | Archivos | Lee un archivo del proyecto por tramos |
|
||||||
|
| `acai-glob` | Archivos | Busca rutas de archivos por patrón |
|
||||||
|
| `acai-grep` | Archivos | Busca texto en archivos del proyecto |
|
||||||
|
| `acai-line-replace` | Archivos | Reemplaza un bloque concreto en un archivo existente |
|
||||||
|
| `acai-write` | Archivos | Crea o reescribe un archivo completo. Antes de usarlo, lee la doc correspondiente según el tipo de archivo (`module-creation-guide`, `builder-fields`, `css-js-conventions`, `hooks-and-api`) |
|
||||||
|
| `acai-delete` | Archivos | Borra un archivo del proyecto |
|
||||||
| `list_page_modules` | Registros | Lista módulos de una página |
|
| `list_page_modules` | Registros | Lista módulos de una página |
|
||||||
| `add_module_to_record` | Registros | Añade módulo a una página |
|
| `add_module_to_record` | Registros | Añade módulo a una página |
|
||||||
| `remove_module_from_record` | Registros | Elimina módulo de una página |
|
| `remove_module_from_record` | Registros | Elimina módulo de una página |
|
||||||
@@ -30,29 +34,43 @@
|
|||||||
| `refresh_acai_token` | Auth | Renovar token JWT expirado |
|
| `refresh_acai_token` | Auth | Renovar token JWT expirado |
|
||||||
| `navigate_browser` | Navegación | Navegar el browser del frontend a una URL |
|
| `navigate_browser` | Navegación | Navegar el browser del frontend a una URL |
|
||||||
| `save_project_styles` | Proyecto | Guardar resumen de estilos en docs/project-styles.md |
|
| `save_project_styles` | Proyecto | Guardar resumen de estilos en docs/project-styles.md |
|
||||||
| `orchestrate_task` | Orquestador | Guía paso a paso para tareas complejas |
|
|
||||||
| `rollback_git` | Git | Recuperar cambios de git remoto |
|
| `rollback_git` | Git | Recuperar cambios de git remoto |
|
||||||
|
|
||||||
## Flujos de trabajo
|
## Flujos de trabajo
|
||||||
|
|
||||||
### Crear un módulo nuevo desde cero
|
### Crear un módulo nuevo desde cero
|
||||||
|
|
||||||
1. `acai_write` — Escribe `index-base.tpl` en `template/estandar/modulos/NOMBRE/`. El server crea la carpeta si no existe, compila y genera todos los archivos derivados (index-twig.tpl, index.tpl, builder.json, screenshots)
|
1. `acai-write` — Crea `index-base.tpl`, `style.css`, `script.js` y cualquier hook necesario con rutas relativas al proyecto
|
||||||
2. `add_module_to_record` — Añade el módulo a una página (tabla padre, ej: `apartados`)
|
2. `acai-write` o `acai-line-replace` compilan automáticamente al tocar `index-base.tpl`
|
||||||
3. `set_module_config_vars` — Rellena las variables con contenido (textos, colores, opciones). **OBLIGATORIO** — sin esto el módulo no muestra nada. Devuelve:
|
3. `add_module_to_record` — Añade el módulo a una página (tabla padre, ej: `apartados`)
|
||||||
|
4. `set_module_config_vars` — Rellena las variables con contenido (textos, colores, opciones). **OBLIGATORIO** — sin esto el módulo no muestra nada. Devuelve:
|
||||||
- `configVars`: mapa de variables → recordNums
|
- `configVars`: mapa de variables → recordNums
|
||||||
- `uploadFields`: mapa de variables upload → `{ fieldName, recordNum }` — **usa estos directamente** para subir imágenes sin necesidad de leer builder.json
|
- `uploadFields`: mapa de variables upload → `{ fieldName, recordNum }` — **usa estos directamente** para subir imágenes sin necesidad de leer builder.json
|
||||||
- Para vars multi con uploads: `uploadFields["varName.subVarName"]` es un array con `[{ index, fieldName, recordNum }]`
|
- Para vars multi con uploads: `uploadFields["varName.subVarName"]` es un array con `[{ index, fieldName, recordNum }]`
|
||||||
4. Para imágenes: `generate_image` o `upload_record_image` usando el `recordNum` y `fieldName` del `uploadFields` devuelto en el paso 3
|
5. Para imágenes: `generate_image` o `upload_record_image` usando el `recordNum` y `fieldName` del `uploadFields` devuelto en el paso 4
|
||||||
5. Verificar con `check_module` o recargando la página
|
6. Verificar con `check_module` o recargando la página
|
||||||
|
|
||||||
> **Nota:** `create_module` es una alternativa legacy que hace lo mismo pero con menos control sobre el contenido del template.
|
|
||||||
|
|
||||||
### Editar un módulo existente
|
### Editar un módulo existente
|
||||||
|
|
||||||
1. `get_module_config_vars` — Leer el estado actual del módulo (variables, recordNums)
|
1. `get_module_config_vars` — Leer el estado actual del módulo (variables, recordNums)
|
||||||
2. Editar `index-base.tpl` con `acai_write` o `acai_line_replace` — el server compila automáticamente al guardar
|
2. `acai-view` — Leer solo el tramo de `index-base.tpl` que se va a modificar
|
||||||
3. Si cambias variables: `set_module_config_vars` para actualizar valores
|
3. `acai-line-replace` — Editar el bloque concreto. Usa `acai-write` solo si el archivo es nuevo o el cambio es masivo
|
||||||
|
4. La compilación es automática tras cada edición de `index-base.tpl`
|
||||||
|
5. Si cambias variables: `set_module_config_vars` para actualizar valores
|
||||||
|
|
||||||
|
### Editar archivos del proyecto con bajo consumo de tokens
|
||||||
|
|
||||||
|
1. `acai-view` — Leer el archivo o un rango de líneas
|
||||||
|
2. `acai-glob` — Encontrar archivos relevantes por ruta cuando no conoces el path exacto
|
||||||
|
3. `acai-grep` — Buscar texto o atributos concretos dentro de archivos del proyecto
|
||||||
|
4. `acai-line-replace` — Reemplazar el bloque exacto en archivos existentes
|
||||||
|
5. `acai-write` — Crear archivos nuevos o reescribirlos por completo si es necesario
|
||||||
|
6. `acai-delete` — Borrar archivos solo cuando sea explícitamente necesario
|
||||||
|
|
||||||
|
Reglas:
|
||||||
|
- Usa siempre rutas relativas al proyecto
|
||||||
|
- No edites `index.tpl`, `index-twig.tpl` ni `builder.json` — son auto-generados
|
||||||
|
- Tras editar cualquier `index-base.tpl` con las file tools, la compilación se ejecuta automáticamente
|
||||||
|
|
||||||
### Añadir/modificar imágenes de un módulo
|
### Añadir/modificar imágenes de un módulo
|
||||||
|
|
||||||
@@ -68,13 +86,13 @@
|
|||||||
- `tableName`: `"builder_custom"` (siempre sin cms_)
|
- `tableName`: `"builder_custom"` (siempre sin cms_)
|
||||||
- `recordId`: el recordNum del paso 1
|
- `recordId`: el recordNum del paso 1
|
||||||
- `fieldName`: el campo de relations del builder.json (ej: `image1`)
|
- `fieldName`: el campo de relations del builder.json (ej: `image1`)
|
||||||
- `imageUrl`: URL accesible desde Docker (ej: `http://localhost/cms/uploads/...`)
|
- `imageUrl`: usa la URL recomendada por la tool que generó/subió la imagen. En Forge, si `generate_image` devuelve `uploadUrl` o `fullUrl`, priorízala frente a `dockerUrl`
|
||||||
|
|
||||||
### Generar imagen con IA
|
### Generar imagen con IA
|
||||||
|
|
||||||
1. `generate_image` con prompt descriptivo + style (photographic, digital-art, minimalist...)
|
1. `generate_image` con prompt descriptivo + style (photographic, digital-art, minimalist...)
|
||||||
2. La imagen se guarda en `cms/uploads/generated/` y devuelve `dockerUrl`
|
2. La imagen se guarda en `cms/uploads/generated/` y devuelve una URL local de preview (`dockerUrl`) y, cuando aplica, una URL recomendada para subida (`uploadUrl` / `fullUrl`)
|
||||||
3. Usar esa `dockerUrl` con `upload_record_image` para asignarla a un módulo
|
3. Para `upload_record_image`, usa la URL recomendada por la tool. En Forge, prioriza `uploadUrl` o `fullUrl` si están presentes
|
||||||
|
|
||||||
### Gestionar registros de una tabla
|
### Gestionar registros de una tabla
|
||||||
|
|
||||||
@@ -85,10 +103,9 @@
|
|||||||
|
|
||||||
### Explorar el sitio
|
### Explorar el sitio
|
||||||
|
|
||||||
1. `orchestrate_task` con workflow `explore_site` — Guía para entender la estructura
|
1. `list_page_modules` — Ver qué módulos tiene cada página
|
||||||
2. `list_page_modules` — Ver qué módulos tiene cada página
|
2. `get_module_config_vars` — Ver los datos de cada módulo
|
||||||
3. `get_module_config_vars` — Ver los datos de cada módulo
|
3. `check_module` — Preview de cómo renderiza
|
||||||
4. `check_module` — Preview de cómo renderiza
|
|
||||||
|
|
||||||
## Reglas importantes para todas las tools
|
## Reglas importantes para todas las tools
|
||||||
|
|
||||||
|
|||||||
@@ -37,12 +37,35 @@ Each module lives in `template/estandar/modulos/<moduleId>/` with:
|
|||||||
|
|
||||||
## Creating a Module — Full Workflow
|
## Creating a Module — Full Workflow
|
||||||
|
|
||||||
|
If the module needs JavaScript, you MUST read `docs/css-js-conventions.md` before writing `index-base.tpl` or `script.js`.
|
||||||
|
If the module needs server-side logic, dynamic data processing, form handling, or reusable backend behavior, you MUST read `docs/hooks-and-api.md` before creating `hook.php` or any global hook.
|
||||||
|
If the module will call hooks from Twig, also review `docs/twig-filters.md` for the `hook` filter syntax.
|
||||||
|
If `index-base.tpl` contains builder fields (`data-field-type`), you MUST review `docs/builder-fields.md` before writing the template.
|
||||||
|
|
||||||
|
Hard rules for module files:
|
||||||
|
- `index-base.tpl` is for HTML/Twig only
|
||||||
|
- `script.js` is for module JavaScript
|
||||||
|
- `style.css` is for module CSS
|
||||||
|
- `hook.php` is for server-side logic
|
||||||
|
- Do NOT embed `<script>` tags in `index-base.tpl`
|
||||||
|
- Do NOT put PHP logic in `index-base.tpl`
|
||||||
|
- `script.js` and `style.css` are static files: do NOT use Twig syntax inside them
|
||||||
|
- Do NOT use `{{ ... }}`, `{% ... %}`, `c-if`, `c-for`, or builder attributes inside `script.js` or `style.css`
|
||||||
|
- If JavaScript needs dynamic values such as `section_id` or a hook endpoint, expose them from `index-base.tpl` via `data-*` attributes
|
||||||
|
- Every editable builder field with `data-field-type` MUST also define `data-field-label`
|
||||||
|
- Avoid Tailwind arbitrary-value syntax such as `text-[...]`, `font-[...]`, `leading-[...]`, `bg-[...]` inside `index-base.tpl`; move those styles to `style.css`
|
||||||
|
|
||||||
1. **Read style reference** (steps above)
|
1. **Read style reference** (steps above)
|
||||||
2. **`acai_write`** — Write `index-base.tpl` to `template/estandar/modulos/MODULE_ID/index-base.tpl`. The server automatically creates the directory, compiles and generates all derived files. `create_module` is a legacy alternative.
|
2. **`acai-write`** — Create the module files directly (`index-base.tpl`, `style.css`, `script.js`, optional `hook.php`) using project-relative paths and complete file contents.
|
||||||
3. **`add_module_to_record`** — Adds the module to a page. Response includes `sectionId` — use it directly in the next step.
|
- If the server-side logic belongs only to that module, create `template/estandar/modulos/<module-id>/hook.php`
|
||||||
4. **`set_module_config_vars`** — Fill variables with content. Response includes `uploadFields` with `{ fieldName, recordNum }` for each upload variable.
|
- If the logic should be reused across modules/pages, create a global hook in `hooks/hooks.<hook-id>.php`
|
||||||
5. **Upload images** — Use `generate_image` then `upload_record_image` with the `recordNum` and `fieldName` from step 4's `uploadFields`. No need to read builder.json or call get_module_config_vars.
|
- Inside the module, reference its own hook with `/hooks/<module-id>/`
|
||||||
6. **`navigate_browser`** — Navigate to the page so the user can see the result.
|
- Example: module folder `template/estandar/modulos/buscadorapartados_hjd8s/` -> hook endpoint `/hooks/buscadorapartados_hjd8s/`
|
||||||
|
3. **Automatic compile** — Writing `index-base.tpl` automatically creates the generated template placeholders and triggers compilation. `compile_module` is only a manual recovery tool if you need to force a recompile without changing the file.
|
||||||
|
4. **`add_module_to_record`** — Adds the module to a page. Response includes `sectionId` — use it directly in the next step.
|
||||||
|
5. **`set_module_config_vars`** — Fill variables with content. Response includes `uploadFields` with `{ fieldName, recordNum }` for each upload variable.
|
||||||
|
6. **Upload images** — Use `generate_image` then `upload_record_image` with the `recordNum` and `fieldName` from step 5's `uploadFields`. No need to read builder.json or call get_module_config_vars.
|
||||||
|
7. **`navigate_browser`** — Navigate to the page so the user can see the result.
|
||||||
|
|
||||||
## HTML Field Types
|
## HTML Field Types
|
||||||
|
|
||||||
@@ -72,6 +95,6 @@ Modules with `MJMLModule: true` in their schema are email modules:
|
|||||||
- Use `section_id` variable for unique anchors/scoping
|
- Use `section_id` variable for unique anchors/scoping
|
||||||
- Use `interno` variable to detect CMS editor vs public view
|
- Use `interno` variable to detect CMS editor vs public view
|
||||||
- Include other modules with: `<module_id :param1="value1"></module_id>`
|
- Include other modules with: `<module_id :param1="value1"></module_id>`
|
||||||
- After editing `index-base.tpl` with `acai_write` or `acai_line_replace`, the server compiles automatically — no need to call `compile_module`
|
- Editing `index-base.tpl` with `acai-write` or `acai-line-replace` compiles automatically
|
||||||
- Twig uses filters (with `|`), never functions
|
- Twig uses filters (with `|`), never functions
|
||||||
- Twig concatenation uses `~`: `'value=' ~ variable`
|
- Twig concatenation uses `~`: `'value=' ~ variable`
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ The controlador defines whether the page is Builder or Standard. Changing it bre
|
|||||||
1. List current modules: `list_page_modules`
|
1. List current modules: `list_page_modules`
|
||||||
2. Get module vars: `get_module_config_vars`
|
2. Get module vars: `get_module_config_vars`
|
||||||
3. Update vars: `set_module_config_vars`
|
3. Update vars: `set_module_config_vars`
|
||||||
4. Or edit the module template: edit `index-base.tpl` → `compile_module`
|
4. Or edit the module template: edit `index-base.tpl` with `acai-line-replace` or `acai-write` → compilation runs automatically
|
||||||
|
|
||||||
## Working with Standard Pages
|
## Working with Standard Pages
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from ..context.engine import ContextEngine
|
|||||||
from ..mcp.manager import MCPManager
|
from ..mcp.manager import MCPManager
|
||||||
from ..memory.store import MemoryStore
|
from ..memory.store import MemoryStore
|
||||||
from ..models.agent import AgentRole
|
from ..models.agent import AgentRole
|
||||||
from ..models.session import SessionState, SessionStatus, TaskState, TaskStatus
|
from ..models.session import SessionState, SessionStatus, TaskState, TaskStatus, TaskStep
|
||||||
from ..streaming.sse import SSEEmitter, EventType
|
from ..streaming.sse import SSEEmitter, EventType
|
||||||
from .agents.coder import CoderAgent, create_coder_profile
|
from .agents.coder import CoderAgent, create_coder_profile
|
||||||
from .agents.collector import CollectorAgent, create_collector_profile
|
from .agents.collector import CollectorAgent, create_collector_profile
|
||||||
@@ -182,6 +182,20 @@ class OrchestratorEngine:
|
|||||||
"total_cost_usd": round(cost_usd, 6),
|
"total_cost_usd": round(cost_usd, 6),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Enforce max 2 steps: 1 coder + 1 optional reviewer
|
||||||
|
# The coder is capable enough to do everything in 1 step
|
||||||
|
if len(plan_result) > 2:
|
||||||
|
logger.warning("Plan had %d steps, trimming to 2", len(plan_result))
|
||||||
|
# Keep first coder step + merge descriptions of remaining into it
|
||||||
|
merged_desc = "; ".join(s.description for s in plan_result)
|
||||||
|
plan_result[0].description = merged_desc
|
||||||
|
# Add reviewer if any step was reviewer
|
||||||
|
has_reviewer = any(s.agent_role == "reviewer" for s in plan_result)
|
||||||
|
if has_reviewer:
|
||||||
|
plan_result = [plan_result[0], TaskStep(description="Revisar el resultado", agent_role="reviewer")]
|
||||||
|
else:
|
||||||
|
plan_result = [plan_result[0]]
|
||||||
|
|
||||||
task.plan = plan_result
|
task.plan = plan_result
|
||||||
task.status = TaskStatus.EXECUTING
|
task.status = TaskStatus.EXECUTING
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user