From 42adeb64d0ec68275f1af69ca83358f09a61ac55 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Wed, 19 Aug 2026 21:01:38 -0500 Subject: [PATCH] feat: Fase 3 con campos condicionales; fix del monitor del ERP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cosecha, polinizacion y fertilizacion en un solo flujo en vez de tres: skip_if muestra el producto solo a las dos que lo llevan y el pase solo a polinizacion. El lote se pregunta antes que el producto porque la dosis depende de los dos — sale de la fertilizacion que se autorizo en su modulo, no la elige el usuario. ErpMonitor construia api_base_url + /health, que no existe en el ERP: la ruta correcta es ?peticion=ping y la URL sale de company_endpoints como el resto. Por eso el panel mostraba el ERP caido. Tampoco mandaba el Bearer. Co-Authored-By: Claude Sonnet 4.6 --- services/ErpMonitor.php | 22 +++++++-- setup/seed_palmas.php | 95 +++++++++++++++++++++++++++++++++++++++ setup/test_navegacion.php | 42 +++++++++++++++++ 3 files changed, 156 insertions(+), 3 deletions(-) diff --git a/services/ErpMonitor.php b/services/ErpMonitor.php index ab0c44e..e1240c6 100644 --- a/services/ErpMonitor.php +++ b/services/ErpMonitor.php @@ -17,9 +17,24 @@ class ErpMonitor public static function check(array $company): array { - $baseUrl = rtrim($company['api_base_url'] ?? '', '/'); - $healthUrl = $baseUrl !== '' ? $baseUrl . '/health' : ''; + // El ERP no expone /health sino ?peticion=ping, y la URL sale de + // company_endpoints como el resto de las llamadas: api_base_url no + // sirve para esto. Sin esto el panel mostraba el ERP siempre caído. $apiKey = $company['api_key'] ?? ''; + $healthUrl = ''; + try { + $stmt = db()->prepare( + "SELECT url FROM company_endpoints WHERE company_id = ? AND endpoint_key = ? AND is_active = 1 LIMIT 1" + ); + $stmt->execute([(int)$company['id'], 'numeros_dn']); + $url = (string)($stmt->fetchColumn() ?: ''); + if ($url !== '') { + // Cualquier endpoint sirve de referencia; se reemplaza por ping + $healthUrl = preg_replace('/peticion=[^&]*/', 'peticion=ping', $url); + } + } catch (\PDOException $e) { + $healthUrl = ''; + } if ($healthUrl === '') { return [ @@ -27,7 +42,7 @@ class ErpMonitor 'company_name' => $company['name'] ?? '', 'status' => 'unknown', 'latency_ms' => null, - 'error' => 'Sin URL configurada', + 'error' => 'Sin endpoint registrado para esta empresa', 'last_check' => date('Y-m-d H:i:s'), ]; } @@ -40,6 +55,7 @@ class ErpMonitor CURLOPT_TIMEOUT => 10, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_HTTPHEADER => [ + 'Authorization: Bearer ' . $apiKey, 'X-API-Key: ' . $apiKey, 'User-Agent: bot-palmas360-monitor/1.0', ], diff --git a/setup/seed_palmas.php b/setup/seed_palmas.php index 4a6c521..b2b2e9f 100644 --- a/setup/seed_palmas.php +++ b/setup/seed_palmas.php @@ -251,6 +251,7 @@ $configJson = [ ['id' => 'registrar_ciclo', 'modulo' => 'ciclos', 'title' => '🔄 Ciclos', 'description' => 'Abrir o cerrar lotes'], ['id' => 'registrar_mantenimiento','modulo' => 'mantenimiento', 'title' => '🔧 Mantenimiento', 'description' => 'Avance sobre lotes pendientes'], ['id' => 'registrar_labor', 'modulo' => 'labores', 'title' => '💪 Labor diaria', 'description' => 'Tractorista, transporte, horas extra'], + ['id' => 'registrar_labor_validada','modulo' => 'labores', 'title' => '🌾 Cosecha y otros','description' => 'Cosecha, polinización, fertilización'], ], ], ], @@ -652,6 +653,96 @@ $configJson = [ ], ], + // Fase 3: labores con validacion previa. Un solo flujo con campos + // condicionales en vez de tres: cosecha no lleva producto, y el pase + // es solo de polinizacion. El ERP valida inventario y autorizaciones, + // y si rechaza algo lo dice en la respuesta. + 'registrar_labor_validada' => [ + 'type' => 'collect_and_post', + 'endpoint_key' => 'labores_up', + 'header' => '🌾 Cosecha, polinización y fertilización', + 'confirm' => true, + 'success_text' => '✅ Enviado a Palmas360.', + 'back' => 'enviar_informacion', + 'nlu_description' => 'Registrar labor de cosecha, polinización o fertilización', + 'fields' => [ + [ + 'key' => 'tipo', + 'label' => 'Tipo', + 'type' => 'static_select', + 'prompt' => '¿Qué vas a registrar?', + 'options' => [ + ['id' => 'cosecha', 'label' => '🌾 Cosecha'], + ['id' => 'polinizacion', 'label' => '🌸 Polinización'], + ['id' => 'fertilizacion','label' => '🧪 Fertilización'], + ], + ], + [ + 'key' => 'novedad_id', + 'label' => 'Labor', + 'type' => 'select', + 'prompt' => '¿Qué labor se hizo?', + 'source_endpoint_key' => 'novedades_labor3_dn', + 'value_field' => 'id', + 'label_field' => 'nombre', + ], + [ + 'key' => 'fecha', + 'label' => 'Fecha', + 'type' => 'date_quick', + 'prompt' => '📅 ¿De qué fecha?', + 'other_validate' => 'date', + 'other_prompt' => 'Escribe la fecha (YYYY-MM-DD):', + ], + [ + 'key' => 'lote_id', + 'label' => 'Lote', + 'type' => 'select', + 'prompt' => '¿En qué lote?', + 'source_endpoint_key' => 'lotes_finca_dn', + 'value_field' => 'id', + 'label_field' => 'label', + ], + [ + // Cosecha no lleva producto. La dosis sale de la + // fertilizacion autorizada para ese lote, por eso se + // pregunta despues del lote y no antes. + 'key' => 'producto_id', + 'label' => 'Producto', + 'type' => 'select', + 'prompt' => '¿Qué producto se aplicó?', + 'source_endpoint_key' => 'productos_fertilizacion_dn', + 'value_field' => 'id', + 'label_field' => 'nombre', + 'skip_if' => ['field' => 'tipo', 'not_in' => ['polinizacion', 'fertilizacion']], + 'empty_text' => '⚠️ Ese lote no tiene fertilización autorizada pendiente.', + ], + [ + 'key' => 'empleados', + 'label' => 'Trabajadores', + 'type' => 'multi_select', + 'prompt' => '¿Quiénes la hicieron? (puedes marcar varios)', + 'source_endpoint_key' => 'empleados_labor_dn', + 'value_field' => 'id', + 'label_field' => 'nombre', + 'from_phone' => 'tercero_id', + ], + [ + 'key' => 'cantidad', + 'label' => 'Cantidad', + 'type' => 'text', + 'prompt' => '¿Qué cantidad?', + ], + [ + 'key' => 'fase_polinizacion', + 'label' => 'Pase', + 'type' => 'text', + 'prompt' => '¿Qué pase de polinización?', + 'skip_if' => ['field' => 'tipo', 'equals' => 'polinizacion'], + ], + ], + ], + // Upload 'enviar_informacion' => ['type' => 'menu', 'menu' => 'submenu_subir_ciclos', 'nlu_skip' => true, 'back' => 'show_main_menu'], @@ -686,6 +777,7 @@ $configJson = [ ['id' => 'registrar_ciclo', 'modulo' => 'ciclos', 'title' => '🔄 Ciclos', 'description' => 'Abrir o cerrar lotes'], ['id' => 'registrar_mantenimiento','modulo' => 'mantenimiento', 'title' => '🔧 Mantenimiento', 'description' => 'Avance sobre lotes pendientes'], ['id' => 'registrar_labor', 'modulo' => 'labores', 'title' => '💪 Labor diaria', 'description' => 'Tractorista, transporte, horas extra'], + ['id' => 'registrar_labor_validada','modulo' => 'labores', 'title' => '🌾 Cosecha y otros','description' => 'Cosecha, polinización, fertilización'], ], ], ], @@ -808,6 +900,9 @@ $endpoints = [ ['key' => 'lotes_finca_dn', 'dir' => 'download', 'url' => $BASE . '?peticion=lotes_x_finca&finca_id={finca_id}'], ['key' => 'empleados_labor_dn', 'dir' => 'download', 'url' => $BASE . '?peticion=empleados&estado=1&items=200'], ['key' => 'labores_up', 'dir' => 'upload', 'url' => $BASE . '?peticion=labores_up'], + // Fase 3: las que arrastran inventario o autorizacion previa + ['key' => 'novedades_labor3_dn', 'dir' => 'download', 'url' => $BASE . '?peticion=novedades_labor_dn&fase=3'], + ['key' => 'productos_fertilizacion_dn', 'dir' => 'download', 'url' => $BASE . '?peticion=productos_fertilizacion_dn&lote_id={lote_id}'], ['key' => 'lotes_mantenimiento_dn', 'dir' => 'download', 'url' => $BASE . '?peticion=lotes_mantenimiento_dn&grupo={grupo_id}&finca_id={finca_id}'], ['key' => 'lotes_mantenimiento_todos_dn', 'dir' => 'download', 'url' => $BASE . '?peticion=lotes_mantenimiento_dn&grupo={grupo_id}&finca_id={finca_id}&todos=1'], // Todos los lotes (PDF) por ciclo diff --git a/setup/test_navegacion.php b/setup/test_navegacion.php index 0834e67..459dde9 100644 --- a/setup/test_navegacion.php +++ b/setup/test_navegacion.php @@ -580,6 +580,48 @@ foreach (['novedades_labor_dn', 'lotes_finca_dn', 'empleados_labor_dn', 'labores check("endpoint {$k} registrado", isset($eps[$k]), true); } +echo "\nFase 3 — campos según el tipo\n"; + +$f3 = $flows['registrar_labor_validada'] ?? []; +$c3 = array_column($f3['fields'] ?? [], null, 'key'); + +check('ofrece los tres tipos', + array_column($c3['tipo']['options'], 'id'), ['cosecha', 'polinizacion', 'fertilizacion']); +check('el producto sale de la fertilización autorizada del lote', + $c3['producto_id']['source_endpoint_key'] ?? null, 'productos_fertilizacion_dn'); + +/** Espejo de capShouldSkip() */ +$saltear = function (array $campo, array $col): bool { + $si = $campo['skip_if'] ?? null; + if (!$si) return false; + $v = (string)($col[$si['field']] ?? ''); + if (isset($si['not_in'])) return !in_array($v, array_map('strval', $si['not_in']), true); + if (isset($si['equals'])) return $v !== (string)$si['equals']; + return false; +}; +$pedidos = function (array $campos, string $tipo) use ($saltear): array { + $col = ['tipo' => $tipo]; + return array_values(array_filter(array_keys($campos), + fn($k) => !$saltear($campos[$k], $col))); +}; + +// Los tres formularios del pedido, sin escribir tres flujos +check('cosecha: sin producto ni pase', + $pedidos($c3, 'cosecha'), ['tipo', 'novedad_id', 'fecha', 'lote_id', 'empleados', 'cantidad']); +check('fertilización: con producto, sin pase', + $pedidos($c3, 'fertilizacion'), ['tipo', 'novedad_id', 'fecha', 'lote_id', 'producto_id', 'empleados', 'cantidad']); +check('polinización: con producto y pase', + $pedidos($c3, 'polinizacion'), ['tipo', 'novedad_id', 'fecha', 'lote_id', 'producto_id', 'empleados', 'cantidad', 'fase_polinizacion']); + +// El producto se pregunta despues del lote: la dosis depende de los dos +$orden = array_keys($c3); +check('el lote se pregunta antes que el producto', + array_search('lote_id', $orden) < array_search('producto_id', $orden), true); + +foreach (['novedades_labor3_dn', 'productos_fertilizacion_dn'] as $k) { + check("endpoint {$k} registrado", isset($eps[$k]), true); +} + echo "\nAlcance de fincas por número\n"; /**