feat: editar desde el resumen sin rehacer el formulario
Cancelar obligaba a empezar de cero por una fecha mal puesta. El resumen suma un tercer boton —WhatsApp admite tres y usaba dos—: Editar lista los campos ya cargados con su valor, se elige uno, se corrige y se vuelve derecho al resumen. Los campos que alimentan a otros declaran 'invalida' en el seed: cambiar el tipo de ausentismo borra el motivo y lo vuelve a preguntar, acotado al tipo nuevo. Sin eso quedaba un motivo de Incapacidad dentro de un Permiso y el ERP recibia datos incoherentes sin que nadie lo notara. El motor engancha en un solo lugar, capAskNext: mientras queden campos sin valor los pregunta en cadena, y cuando no queda ninguno vuelve al resumen en vez de seguir el orden del formulario. Aplica a los cinco flujos de carga. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
6616b63683
commit
52dbd1e203
@@ -1374,6 +1374,7 @@ class NormalBot
|
|||||||
'body' => ['text' => $summary],
|
'body' => ['text' => $summary],
|
||||||
'action' => ['buttons' => [
|
'action' => ['buttons' => [
|
||||||
['type' => 'reply', 'reply' => ['id' => '__cap_confirm', 'title' => '✅ Confirmar']],
|
['type' => 'reply', 'reply' => ['id' => '__cap_confirm', 'title' => '✅ Confirmar']],
|
||||||
|
['type' => 'reply', 'reply' => ['id' => '__cap_edit', 'title' => '✏️ Editar']],
|
||||||
['type' => 'reply', 'reply' => ['id' => '__cap_cancel', 'title' => '❌ Cancelar']],
|
['type' => 'reply', 'reply' => ['id' => '__cap_cancel', 'title' => '❌ Cancelar']],
|
||||||
]],
|
]],
|
||||||
], $company);
|
], $company);
|
||||||
@@ -1422,6 +1423,32 @@ class NormalBot
|
|||||||
|
|
||||||
private static function capAskNext(array $cap, string $to, array $company, int $ctxId = 0): ?array
|
private static function capAskNext(array $cap, string $to, array $company, int $ctxId = 0): ?array
|
||||||
{
|
{
|
||||||
|
// Editando desde el resumen: se pregunta el primer campo que quedo sin
|
||||||
|
// valor —el que se eligio, mas los que dependian de el— y al completarse
|
||||||
|
// todos se vuelve al resumen en vez de seguir el orden del formulario.
|
||||||
|
if (!empty($cap['__editando'])) {
|
||||||
|
$pendiente = null;
|
||||||
|
foreach ($cap['fields'] as $i => $f) {
|
||||||
|
$k = $f['key'] ?? '';
|
||||||
|
if ($k === '' || isset($cap['collected'][$k])) continue;
|
||||||
|
if (self::capShouldSkip($f, $cap['collected'])) continue;
|
||||||
|
$pendiente = $i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
unset($cap['__editando']);
|
||||||
|
if ($pendiente !== null) {
|
||||||
|
$cap['__editando'] = true;
|
||||||
|
$cap['index'] = $pendiente;
|
||||||
|
}
|
||||||
|
$m = $ctxId > 0 ? ConversationContext::getMetadata($ctxId) : [];
|
||||||
|
$m['__cap'] = $cap;
|
||||||
|
if ($ctxId > 0) ConversationContext::updateMetadata($ctxId, $m);
|
||||||
|
|
||||||
|
if ($pendiente === null) {
|
||||||
|
return self::capShowConfirmOrSubmit($cap, $m, ['from' => $to], $company, $ctxId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Saltar campos ya pre-llenados (no contiguos)
|
// Saltar campos ya pre-llenados (no contiguos)
|
||||||
$prefilled = $cap['prefilled'] ?? [];
|
$prefilled = $cap['prefilled'] ?? [];
|
||||||
while ((int)$cap['index'] < count($cap['fields'])) {
|
while ((int)$cap['index'] < count($cap['fields'])) {
|
||||||
@@ -2163,6 +2190,58 @@ class NormalBot
|
|||||||
if ($input === '__cap_confirm') {
|
if ($input === '__cap_confirm') {
|
||||||
return self::submitCapPost($meta, $context, $company, $ctxId);
|
return self::submitCapPost($meta, $context, $company, $ctxId);
|
||||||
}
|
}
|
||||||
|
// Elegir que corregir sin rehacer todo el formulario
|
||||||
|
if ($input === '__cap_edit') {
|
||||||
|
$cap = $meta['__cap'];
|
||||||
|
$rows = [];
|
||||||
|
foreach ($cap['fields'] as $f) {
|
||||||
|
$k = $f['key'] ?? '';
|
||||||
|
if ($k === '' || !isset($cap['collected'][$k])) continue;
|
||||||
|
$valor = $cap['collected_labels'][$k] ?? $cap['collected'][$k];
|
||||||
|
if (is_array($valor)) $valor = implode(', ', $valor);
|
||||||
|
$rows[] = [
|
||||||
|
'id' => '__cap_ed:' . $k,
|
||||||
|
'title' => mb_substr(self::capFieldLabel($cap['fields'], $k), 0, 24),
|
||||||
|
'description' => mb_substr((string)$valor, 0, 72),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$rows[] = ['id' => '__cap_ed_volver', 'title' => '↩️ Volver', 'description' => 'Sin cambios'];
|
||||||
|
return self::enqueueInteractive($context['from'], [
|
||||||
|
'type' => 'list',
|
||||||
|
'header' => ['type' => 'text', 'text' => mb_substr($cap['header'], 0, 60)],
|
||||||
|
'body' => ['text' => '¿Qué quieres corregir?'],
|
||||||
|
'footer' => ['text' => ''],
|
||||||
|
'action' => ['button' => 'Ver campos', 'sections' => [[
|
||||||
|
'title' => 'Campos',
|
||||||
|
'rows' => array_slice($rows, 0, self::LISTA_MAX_FILAS),
|
||||||
|
]]],
|
||||||
|
], $company);
|
||||||
|
}
|
||||||
|
if ($input === '__cap_ed_volver') {
|
||||||
|
return self::capShowConfirmOrSubmit($meta['__cap'], $meta, $context, $company, $ctxId);
|
||||||
|
}
|
||||||
|
if (str_starts_with($input, '__cap_ed:')) {
|
||||||
|
$clave = substr($input, strlen('__cap_ed:'));
|
||||||
|
$cap = $meta['__cap'];
|
||||||
|
|
||||||
|
// Un campo alimenta a otros: cambiar el tipo invalida el motivo,
|
||||||
|
// cambiar el lote invalida el producto. Se borran y se vuelven a
|
||||||
|
// preguntar en cadena, si no el ERP recibiria datos incoherentes.
|
||||||
|
$borrar = [$clave];
|
||||||
|
foreach ($cap['fields'] as $f) {
|
||||||
|
if (($f['key'] ?? '') === $clave) {
|
||||||
|
$borrar = array_merge($borrar, (array)($f['invalida'] ?? []));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($borrar as $k) {
|
||||||
|
unset($cap['collected'][$k], $cap['collected_labels'][$k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cap['__editando'] = true;
|
||||||
|
$meta['__cap'] = $cap;
|
||||||
|
ConversationContext::updateMetadata($ctxId, $meta);
|
||||||
|
return self::capAskNext($cap, $context['from'], $company, $ctxId);
|
||||||
|
}
|
||||||
if ($input === '__cap_cancel') {
|
if ($input === '__cap_cancel') {
|
||||||
$m = ConversationContext::getMetadata($ctxId);
|
$m = ConversationContext::getMetadata($ctxId);
|
||||||
unset($m['__cap']);
|
unset($m['__cap']);
|
||||||
|
|||||||
@@ -448,6 +448,8 @@ $configJson = [
|
|||||||
// Primero el grupo general: 17 motivos en una sola lista
|
// Primero el grupo general: 17 motivos en una sola lista
|
||||||
// llegaban con el nombre cortado y costaban de entender
|
// llegaban con el nombre cortado y costaban de entender
|
||||||
'key' => 'grupo_novedad',
|
'key' => 'grupo_novedad',
|
||||||
|
// Al cambiarlo se vuelve a preguntar: el motivo pertenece al tipo elegido
|
||||||
|
'invalida' => ['novedad_id'],
|
||||||
'label' => 'Tipo',
|
'label' => 'Tipo',
|
||||||
'type' => 'select',
|
'type' => 'select',
|
||||||
'prompt' => '¿Qué tipo de ausentismo?',
|
'prompt' => '¿Qué tipo de ausentismo?',
|
||||||
@@ -592,6 +594,7 @@ $configJson = [
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
'key' => 'lote_id',
|
'key' => 'lote_id',
|
||||||
|
|
||||||
'label' => 'Lote',
|
'label' => 'Lote',
|
||||||
'type' => 'select',
|
'type' => 'select',
|
||||||
'prompt' => 'Elige el lote (entre paréntesis va lo que falta):',
|
'prompt' => 'Elige el lote (entre paréntesis va lo que falta):',
|
||||||
@@ -662,6 +665,7 @@ $configJson = [
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
'key' => 'lote_id',
|
'key' => 'lote_id',
|
||||||
|
|
||||||
'label' => 'Lote',
|
'label' => 'Lote',
|
||||||
'type' => 'select',
|
'type' => 'select',
|
||||||
'prompt' => '¿En qué lote?',
|
'prompt' => '¿En qué lote?',
|
||||||
@@ -704,6 +708,9 @@ $configJson = [
|
|||||||
'fields' => [
|
'fields' => [
|
||||||
[
|
[
|
||||||
'key' => 'tipo',
|
'key' => 'tipo',
|
||||||
|
// Al cambiarlo se vuelve a preguntar: el catálogo de labores
|
||||||
|
// y el producto dependen del tipo elegido
|
||||||
|
'invalida' => ['novedad_id', 'producto_id'],
|
||||||
'label' => 'Tipo',
|
'label' => 'Tipo',
|
||||||
'type' => 'static_select',
|
'type' => 'static_select',
|
||||||
'prompt' => '¿Qué vas a registrar?',
|
'prompt' => '¿Qué vas a registrar?',
|
||||||
@@ -732,6 +739,8 @@ $configJson = [
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
'key' => 'lote_id',
|
'key' => 'lote_id',
|
||||||
|
// Al cambiarlo se vuelve a preguntar: la dosis sale de la fertilizacion de ese lote
|
||||||
|
'invalida' => ['producto_id'],
|
||||||
'label' => 'Lote',
|
'label' => 'Lote',
|
||||||
'type' => 'select',
|
'type' => 'select',
|
||||||
'prompt' => '¿En qué lote?',
|
'prompt' => '¿En qué lote?',
|
||||||
|
|||||||
@@ -270,6 +270,56 @@ $r = NormalBot::process($co, $ctx, 'subir labores diarias');
|
|||||||
check('"subir labores diarias" arranca el flujo directo', str_contains(textoDe($r), 'De qué fecha es la labor'));
|
check('"subir labores diarias" arranca el flujo directo', str_contains(textoDe($r), 'De qué fecha es la labor'));
|
||||||
capturas();
|
capturas();
|
||||||
|
|
||||||
|
// ════ 9. Editar desde el resumen ══════════════════════════════════════════════
|
||||||
|
echo "\nEditar — corregir sin rehacer el formulario\n";
|
||||||
|
|
||||||
|
$ctx = contexto('57300EDITAR');
|
||||||
|
NormalBot::processInteractive($co, $ctx, 'registrar_ausentismo');
|
||||||
|
NormalBot::process($co, $ctx, 'julio');
|
||||||
|
NormalBot::processInteractive($co, $ctx, '412');
|
||||||
|
NormalBot::processInteractive($co, $ctx, 'Incapacidad');
|
||||||
|
NormalBot::processInteractive($co, $ctx, '46');
|
||||||
|
NormalBot::processInteractive($co, $ctx, $hoy);
|
||||||
|
$r = NormalBot::processInteractive($co, $ctx, $manana);
|
||||||
|
check('el resumen ofrece editar', isset(filasDe($r)['__cap_edit']));
|
||||||
|
|
||||||
|
$r = NormalBot::processInteractive($co, $ctx, '__cap_edit');
|
||||||
|
$campos = filasDe($r);
|
||||||
|
check('lista los campos ya cargados',
|
||||||
|
array_keys($campos),
|
||||||
|
['__cap_ed:empleado_id', '__cap_ed:grupo_novedad', '__cap_ed:novedad_id',
|
||||||
|
'__cap_ed:fecha_inicial', '__cap_ed:fecha_final', '__cap_ed_volver']);
|
||||||
|
|
||||||
|
// Un campo hoja: se corrige y vuelve derecho al resumen
|
||||||
|
$r = NormalBot::processInteractive($co, $ctx, '__cap_ed:fecha_final');
|
||||||
|
check('pregunta solo ese campo', str_contains(textoDe($r), 'Hasta qué fecha'));
|
||||||
|
$pasado = date('Y-m-d', strtotime('+2 days'));
|
||||||
|
$r = NormalBot::processInteractive($co, $ctx, $pasado);
|
||||||
|
check('y vuelve al resumen, no al siguiente campo', isset(filasDe($r)['__cap_confirm']));
|
||||||
|
check('con el valor corregido', str_contains(textoDe($r), $pasado));
|
||||||
|
|
||||||
|
// Un campo del que otro depende: se re-pregunta en cadena
|
||||||
|
$r = NormalBot::processInteractive($co, $ctx, '__cap_edit');
|
||||||
|
$r = NormalBot::processInteractive($co, $ctx, '__cap_ed:grupo_novedad');
|
||||||
|
check('editar el tipo vuelve a preguntar el tipo', str_contains(textoDe($r), 'tipo de ausentismo'));
|
||||||
|
$r = NormalBot::processInteractive($co, $ctx, 'Permiso');
|
||||||
|
check('y encadena el motivo, que quedó inválido', str_contains(textoDe($r), 'Cuál es el motivo'));
|
||||||
|
check('acotado al tipo nuevo', array_map('strval', array_keys(filasDe($r))), ['50']);
|
||||||
|
$r = NormalBot::processInteractive($co, $ctx, '50');
|
||||||
|
check('recién ahí vuelve al resumen', isset(filasDe($r)['__cap_confirm']));
|
||||||
|
|
||||||
|
// Salir sin tocar nada
|
||||||
|
$r = NormalBot::processInteractive($co, $ctx, '__cap_edit');
|
||||||
|
$r = NormalBot::processInteractive($co, $ctx, '__cap_ed_volver');
|
||||||
|
check('"Volver" deja todo como estaba', isset(filasDe($r)['__cap_confirm']));
|
||||||
|
|
||||||
|
capturas();
|
||||||
|
NormalBot::processInteractive($co, $ctx, '__cap_confirm');
|
||||||
|
$post = array_values(array_filter(capturas(), fn($c) => ($c['peticion'] ?? '') === 'ausentismos_up'))[0] ?? null;
|
||||||
|
check('el POST lleva lo editado, sin restos del valor viejo',
|
||||||
|
[$post['body']['novedad_id'] ?? null, $post['body']['fecha_final'] ?? null],
|
||||||
|
['50', $pasado]);
|
||||||
|
|
||||||
// ════ Resultado ═══════════════════════════════════════════════════════════════
|
// ════ Resultado ═══════════════════════════════════════════════════════════════
|
||||||
echo "\n" . ($GLOBALS['fallas'] ? "{$GLOBALS['fallas']} falla(s)\n" : "Todos los flujos ejecutan de punta a punta\n");
|
echo "\n" . ($GLOBALS['fallas'] ? "{$GLOBALS['fallas']} falla(s)\n" : "Todos los flujos ejecutan de punta a punta\n");
|
||||||
exit($GLOBALS['fallas'] ? 1 : 0);
|
exit($GLOBALS['fallas'] ? 1 : 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user