fix: los lotes para abrir ciclo salian en orden alfabetico

El pedido es mostrar los 10 con los ciclos mas largos —los que toca abrir— y
una opcion para ver el resto. La apertura ofrecia todos los lotes de la finca
por nombre, sin señal de cual estaba mas atrasado.

Ahora apertura y cierre traen 10 ordenados por dias, con los dias en la
etiqueta, y "Otro lote" cambia al listado completo sin perder lo ya marcado.
Si ningun lote tiene historial la consulta cae sola en alfabetico, porque el
DATEDIFF queda nulo y desempata por nombre.

Solo toca el flujo de carga; los informes quedan como estaban.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-11 21:09:51 -05:00
co-authored by Claude Sonnet 4.6
parent d6bedfba54
commit 673c15dae3
3 changed files with 63 additions and 4 deletions
+24 -3
View File
@@ -623,7 +623,7 @@ class NormalBot
$lf = $cfg['label_field'] ?? 'label';
$pagina = max(0, (int)($cfg['__page'] ?? 0));
$porPagina = self::LISTA_MAX_FILAS - 2;
$porPagina = self::LISTA_MAX_FILAS - (empty($cfg['__ver_todos']) ? 2 : 3);
$hayMas = count($items) > $porPagina;
$visibles = $hayMas ? array_slice($items, $pagina * $porPagina, $porPagina) : $items;
@@ -640,6 +640,10 @@ class NormalBot
$rows[] = ['id' => '__mas', 'title' => ' Ver más', 'description' => "Faltan {$restantes}"];
}
if (!empty($cfg['__ver_todos'])) {
$rows[] = ['id' => '__todos', 'title' => '🔎 Otro lote', 'description' => 'Ver el listado completo'];
}
$n = count($elegidos);
$rows[] = [
'id' => '__listo',
@@ -1145,6 +1149,14 @@ class NormalBot
$key = $field['key'];
$elegidos = $cap['__multi'][$key] ?? [];
if ($value === '__todos') {
$cap['__todos'][$key] = true;
$cap['__page'][$key] = 0;
$meta['__cap'] = $cap;
ConversationContext::updateMetadata($ctxId, $meta);
return self::capAskNext($cap, $context['from'], $company, $ctxId);
}
if ($value === '__listo') {
if (empty($elegidos)) {
WhatsAppSender::sendText($context['from'], '⚠️ Elige al menos una opción.', (string)($context['phone_number_id'] ?? ''));
@@ -1153,7 +1165,7 @@ class NormalBot
// strval explícito: PHP convierte a int las claves numéricas
$cap['collected'][$key] = array_map('strval', array_keys($elegidos));
$cap['collected_labels'][$key] = implode(', ', array_values($elegidos));
unset($cap['__multi'][$key], $cap['__page'][$key]);
unset($cap['__multi'][$key], $cap['__page'][$key], $cap['__todos'][$key]);
$cap['index'] = $idx + 1;
while ($cap['index'] < count($fields) && self::capShouldSkip($fields[$cap['index']], $cap['collected'])) { $cap['index']++; }
$meta['__cap'] = $cap;
@@ -1375,8 +1387,16 @@ class NormalBot
// ── multi_select: acumula varias opciones antes de continuar ─────────
if ($type === 'multi_select') {
// Por defecto llega una lista corta con los mas relevantes; "Otro"
// cambia al catalogo completo sin perder lo ya marcado.
$verTodos = !empty($cap['__todos'][$field['key']]);
$claveSrc = $verTodos && !empty($field['full_endpoint_key'])
? $field['full_endpoint_key']
: ($field['source_endpoint_key'] ?? '');
$items = self::fetchDynamicList(
self::resolverPorCampos($field['source_endpoint_key'] ?? '', $cap['collected'] ?? []),
self::resolverPorCampos($claveSrc,
$cap['collected'] ?? []),
$company,
['__ep_vars_combined' => $cap['collected'] ?? []],
$to
@@ -1402,6 +1422,7 @@ class NormalBot
'body' => $field['prompt'] ?? 'Puedes elegir varios:',
'section_title' => $field['label'] ?? $field['key'],
'__page' => (int)($cap['__page'][$field['key']] ?? 0),
'__ver_todos' => !$verTodos && !empty($field['full_endpoint_key']),
], $cap['__multi'][$field['key']] ?? [], $to, $company);
}
+6 -1
View File
@@ -541,6 +541,8 @@ $configJson = [
'prompt' => 'Elige los lotes (puedes marcar varios):',
// Apertura ofrece los lotes de la finca; cierre, solo los abiertos
'source_endpoint_key' => 'lotes_{accion}_dn',
// "Otro lote" cambia al listado completo
'full_endpoint_key' => 'lotes_{accion}_todos_dn',
'value_field' => 'id',
'label_field' => 'label',
'empty_text' => '⚠️ No hay lotes disponibles para esta combinación.',
@@ -690,8 +692,11 @@ $endpoints = [
// ── Carga de ciclos ─────────────────────────────────────────────────────
// {ciclo} y {accion} se resuelven con lo que el usuario ya eligió, así los
// seis tipos y las dos acciones comparten estas tres entradas.
['key' => 'lotes_apertura_dn', 'dir' => 'download', 'url' => $BASE . '?peticion=lotes_x_finca&finca_id={finca_id}'],
// Los 10 mas atrasados; con todos=1, el listado completo para "Otro lote"
['key' => 'lotes_apertura_dn', 'dir' => 'download', 'url' => $BASE . '?peticion=lotes_apertura_dn&ciclo={ciclo}&finca_id={finca_id}'],
['key' => 'lotes_cierre_dn', 'dir' => 'download', 'url' => $BASE . '?peticion=lotes_abiertos_dn&ciclo={ciclo}&finca_id={finca_id}'],
['key' => 'lotes_apertura_todos_dn', 'dir' => 'download', 'url' => $BASE . '?peticion=lotes_apertura_dn&ciclo={ciclo}&finca_id={finca_id}&todos=1'],
['key' => 'lotes_cierre_todos_dn', 'dir' => 'download', 'url' => $BASE . '?peticion=lotes_abiertos_dn&ciclo={ciclo}&finca_id={finca_id}&todos=1'],
['key' => 'ciclos_up', 'dir' => 'upload', 'url' => $BASE . '?peticion=ciclos_{ciclo}_up'],
// Todos los lotes (PDF) por ciclo
['key' => 'cosecha_todos_dn', 'dir' => 'download', 'url' => $BASE . '?peticion=ciclos_cosecha_dn&finca_id={finca_id}'],
+33
View File
@@ -509,6 +509,39 @@ foreach (['show_main_menu', 'descargar_informes', 'enviar_informacion'] as $k) {
check('el bot ya no reordena por su cuenta',
isset(flowsDe($config, '3')['registrar_ausentismo']['fields'][1]['frecuentes']), false);
echo "\nLotes para abrir y cerrar ciclos\n";
$cfl = array_column($flows['registrar_ciclo']['fields'], null, 'key')['lote_ids'];
check('apertura y cierre usan catálogos distintos',
$cfl['source_endpoint_key'], 'lotes_{accion}_dn');
check('y "Otro lote" trae el listado completo',
$cfl['full_endpoint_key'], 'lotes_{accion}_todos_dn');
$resolver = function (string $t, array $c): string {
foreach ($c as $k => $v) $t = str_replace('{' . $k . '}', (string)$v, $t);
return $t;
};
foreach (['apertura', 'cierre'] as $acc) {
check("{$acc}: catálogo corto", $resolver($cfl['source_endpoint_key'], ['accion' => $acc]), "lotes_{$acc}_dn");
check("{$acc}: catálogo completo", $resolver($cfl['full_endpoint_key'], ['accion' => $acc]), "lotes_{$acc}_todos_dn");
foreach ([$acc . '_dn', $acc . '_todos_dn'] as $sufijo) {
check("endpoint lotes_{$sufijo} registrado", isset($eps['lotes_' . $sufijo]), true);
}
}
// Con "Otro" visible se reservan tres filas: paginar, ver todos y listo
$filas = function (int $total, bool $verTodos): int {
$max = 10;
$porPagina = $max - ($verTodos ? 3 : 2);
$hayMas = $total > $porPagina;
$n = min($total, $porPagina);
if ($hayMas) $n++;
if ($verTodos) $n++;
return $n + 1;
};
check('la lista corta entra en el límite de WhatsApp', $filas(10, true) <= 10, true);
check('el listado completo también', $filas(40, false) <= 10, true);
echo "\nAlcance de fincas por número\n";
/**