feat: add collect_for_each flow type to bot-config UI

- Dropdown option '🌧 Registro por ítem' in both PHP-rendered cards
  and the addFlowCard() JS template
- Fields panel: endpoint lista, endpoint upload, campo ID, campo nombre,
  encabezado, pregunta por ítem, mensaje de éxito
- flowTabChange() now shows/hides ftg-foreach panel
- Save handler in index.php parses all flow_fe_* POST fields and
  builds the collect_for_each config object

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-06-29 10:40:15 -05:00
co-authored by Claude Sonnet 4.6
parent b45f0fbe51
commit fb61a6e027
2 changed files with 74 additions and 18 deletions
+58 -18
View File
@@ -2917,15 +2917,24 @@ HTML;
$showEp = ($ft === 'function' && $fFn === 'api_report')
? 'display:flex;flex-direction:column;gap:8px;margin-top:8px'
: 'display:none';
$showCollect = $ft === 'collect_input'
? 'display:flex;flex-direction:column;gap:8px'
: 'display:none';
$showDynList = $ft === 'dynamic_list'
? 'display:flex;flex-direction:column;gap:8px'
: 'display:none';
$showSubmit = $ft === 'submit_form'
? 'display:flex;flex-direction:column;gap:8px'
: 'display:none';
$showCollect = $ft === 'collect_input' ? 'display:flex;flex-direction:column;gap:8px' : 'display:none';
$showDynList = $ft === 'dynamic_list' ? 'display:flex;flex-direction:column;gap:8px' : 'display:none';
$showSubmit = $ft === 'submit_form' ? 'display:flex;flex-direction:column;gap:8px' : 'display:none';
$showForEach = $ft === 'collect_for_each' ? 'display:flex;flex-direction:column;gap:8px' : 'display:none';
$feEpSelHtml = '<option value="">— Selecciona endpoint lista —</option>';
foreach ($companyEndpoints as $ep) {
$sel = $srcEp === self::h($ep['endpoint_key']) ? ' selected' : '';
$feEpSelHtml .= '<option value="' . self::h($ep['endpoint_key']) . '"' . $sel . '>' . $epLabel($ep) . '</option>';
}
$feUpEpSelHtml = '<option value="">— Selecciona endpoint upload —</option>';
$feUpEpKey = self::h($flow['endpoint_key'] ?? '');
foreach ($companyEndpoints as $ep) {
$sel = $feUpEpKey === self::h($ep['endpoint_key']) ? ' selected' : '';
$feUpEpSelHtml .= '<option value="' . self::h($ep['endpoint_key']) . '"' . $sel . '>' . $epLabel($ep) . '</option>';
}
$feQuestion = self::h($flow['question'] ?? '');
$feSuccessText = self::h($flow['success_text'] ?? '');
$fnOpts = '';
foreach (['goodbye' => '↩️ Salir (goodbye)', 'api_report' => '📄 Descargar informe', 'forward_to_ai' => '🤖 Escalar a IA', 'forward_to_agent' => '👤 Escalar a agente'] as $fv => $fl) {
@@ -2956,9 +2965,10 @@ HTML;
<option value=\"function\"" . ($ft==='function'?' selected':'') . ">⚡ Función especial</option>
</optgroup>
<optgroup label=\"Formulario\">
<option value=\"collect_input\"" . ($ft==='collect_input'?' selected':'') . ">✏️ Pedir dato</option>
<option value=\"dynamic_list\"" . ($ft==='dynamic_list'?' selected':'') . ">📋 Lista dinámica</option>
<option value=\"submit_form\"" . ($ft==='submit_form'?' selected':'') . ">🚀 Enviar formulario</option>
<option value=\"collect_input\"" . ($ft==='collect_input' ?' selected':'') . ">✏️ Pedir dato</option>
<option value=\"dynamic_list\"" . ($ft==='dynamic_list' ?' selected':'') . ">📋 Lista dinámica</option>
<option value=\"collect_for_each\"" . ($ft==='collect_for_each'?' selected':'') . ">🌧 Registro por ítem</option>
<option value=\"submit_form\"" . ($ft==='submit_form' ?' selected':'') . ">🚀 Enviar formulario</option>
</optgroup>
</select>
</div>
@@ -3028,6 +3038,21 @@ HTML;
</div>
</div>
<!-- collect_for_each -->
<div class=\"ftg-foreach\" style=\"{$showForEach}\">
<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:8px\">
<div><label>Endpoint lista (fincas)</label><select name=\"flow_fe_src_ep[]\">{$feEpSelHtml}</select></div>
<div><label>Endpoint upload (POST)</label><select name=\"flow_fe_up_ep[]\">{$feUpEpSelHtml}</select></div>
</div>
<div style=\"display:grid;grid-template-columns:1fr 1fr;gap:8px\">
<div><label>Campo ID del ítem</label><input type=\"text\" name=\"flow_fe_val_field[]\" value=\"{$valF}\" placeholder=\"id\"></div>
<div><label>Campo nombre del ítem</label><input type=\"text\" name=\"flow_fe_lbl_field[]\" value=\"{$lblF}\" placeholder=\"nombre\"></div>
</div>
<div><label>Encabezado</label><input type=\"text\" name=\"flow_fe_header[]\" value=\"{$header}\" placeholder=\"🌧 Pluviometría\"></div>
<div><label>Pregunta por ítem</label><input type=\"text\" name=\"flow_fe_question[]\" value=\"{$feQuestion}\" placeholder=\"¿Cuál es el valor? (mm)\"></div>
<div><label>Mensaje de éxito</label><input type=\"text\" name=\"flow_fe_success[]\" value=\"{$feSuccessText}\" placeholder=\"✅ Datos registrados correctamente.\"></div>
</div>
</div>
</div>
</div>";
@@ -3284,12 +3309,13 @@ function flowTabChange(sel) {
const v = sel.value;
// flex sections need display:flex; block sections use '' (default block)
const sections = {
'ftg-text': {show: v==='text', flex: false},
'ftg-menu': {show: v==='menu', flex: false},
'ftg-fn': {show: v==='function', flex: false},
'ftg-collect': {show: v==='collect_input', flex: true},
'ftg-dynlist': {show: v==='dynamic_list', flex: true},
'ftg-submit': {show: v==='submit_form', flex: true},
'ftg-text': {show: v==='text', flex: false},
'ftg-menu': {show: v==='menu', flex: false},
'ftg-fn': {show: v==='function', flex: false},
'ftg-collect': {show: v==='collect_input', flex: true},
'ftg-dynlist': {show: v==='dynamic_list', flex: true},
'ftg-submit': {show: v==='submit_form', flex: true},
'ftg-foreach': {show: v==='collect_for_each', flex: true},
};
for (const [cls, cfg] of Object.entries(sections)) {
const el = card.querySelector('.' + cls);
@@ -3340,6 +3366,7 @@ function addFlowCard() {
' <optgroup label="Formulario">' +
' <option value="collect_input">✏️ Pedir dato</option>' +
' <option value="dynamic_list">📋 Lista dinámica</option>' +
' <option value="collect_for_each">🌧 Registro por ítem</option>' +
' <option value="submit_form">🚀 Enviar formulario</option>' +
' </optgroup>' +
' </select>' +
@@ -3397,6 +3424,19 @@ function addFlowCard() {
' <div><label>Mensaje</label><input type="text" name="flow_sf_caption[]" placeholder="Aquí tienes el informe."></div>' +
' </div>' +
' </div>' +
' <div class="ftg-foreach" style="display:none;flex-direction:column;gap:8px">' +
' <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">' +
' <div><label>Endpoint lista</label><select name="flow_fe_src_ep[]">' + epOpts + '</select></div>' +
' <div><label>Endpoint upload (POST)</label><select name="flow_fe_up_ep[]">' + epOpts + '</select></div>' +
' </div>' +
' <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">' +
' <div><label>Campo ID</label><input type="text" name="flow_fe_val_field[]" placeholder="id"></div>' +
' <div><label>Campo nombre</label><input type="text" name="flow_fe_lbl_field[]" placeholder="nombre"></div>' +
' </div>' +
' <div><label>Encabezado</label><input type="text" name="flow_fe_header[]" placeholder="🌧 Pluviometría"></div>' +
' <div><label>Pregunta por ítem</label><input type="text" name="flow_fe_question[]" placeholder="¿Cuál es el valor? (mm)"></div>' +
' <div><label>Mensaje de éxito</label><input type="text" name="flow_fe_success[]" placeholder="✅ Datos registrados."></div>' +
' </div>' +
' </div>' +
' </div>' +
'</div>'
+16
View File
@@ -414,6 +414,13 @@ $routes = [
$flowSfGroups = $_POST['flow_sf_meta_group']?? [];
$flowSfFnames = $_POST['flow_sf_filename'] ?? [];
$flowSfCaps = $_POST['flow_sf_caption'] ?? [];
$flowFeSrcEps = $_POST['flow_fe_src_ep'] ?? [];
$flowFeUpEps = $_POST['flow_fe_up_ep'] ?? [];
$flowFeValFlds = $_POST['flow_fe_val_field'] ?? [];
$flowFeLblFlds = $_POST['flow_fe_lbl_field'] ?? [];
$flowFeHeaders = $_POST['flow_fe_header'] ?? [];
$flowFeQs = $_POST['flow_fe_question'] ?? [];
$flowFeSucTxts = $_POST['flow_fe_success'] ?? [];
$flows = [];
foreach ($flowKeys as $i => $fk) {
@@ -465,6 +472,15 @@ $routes = [
$f['meta_group'] = trim($flowSfGroups[$i] ?? '');
$f['filename'] = trim($flowSfFnames[$i] ?? '');
$f['caption'] = trim($flowSfCaps[$i] ?? '');
} elseif ($ftype === 'collect_for_each') {
$f['source_endpoint_key'] = trim($flowFeSrcEps[$i] ?? '');
$f['endpoint_key'] = trim($flowFeUpEps[$i] ?? '');
$f['value_field'] = trim($flowFeValFlds[$i] ?? 'id');
$f['label_field'] = trim($flowFeLblFlds[$i] ?? 'nombre');
$f['header'] = trim($flowFeHeaders[$i] ?? '');
$f['question'] = trim($flowFeQs[$i] ?? '');
$f['success_text'] = trim($flowFeSucTxts[$i] ?? '');
}
$flows[$fk] = $f;