$l->ID ?? null, 'name' => $l->name ?? null, 'fullname' => $l->fullname ?? null, 'address' => $l->address ?? null, 'address1' => $l->address1 ?? null, 'address2' => $l->address2 ?? null, 'mapslink' => $l->mapslink ?? null, ]; } if (!empty($locations)) { return $locations; } } catch (Exception $e) { // fallback below } } try { $db = ensure_efisio_db(); $stmt = $db->query("SELECT * FROM efisio_locations ORDER BY `order`, ID"); $rows = $stmt ? $stmt->fetchAll(PDO::FETCH_ASSOC) : []; foreach ($rows as $r) { $locations[] = [ 'id' => isset($r['ID']) ? intval($r['ID']) : null, 'name' => $r['name'] ?? null, 'fullname' => $r['fullname'] ?? null, 'address' => $r['address'] ?? null, 'address1' => $r['address1'] ?? null, 'address2' => $r['address2'] ?? null, 'mapslink' => $r['mapslink'] ?? null, ]; } } catch (Exception $e) { return []; } return $locations; } function get_location_details($clinica_id = null, $clinica_name = null) { if ($clinica_id !== null) { $clinica_id = intval($clinica_id); } if (class_exists('App_Location')) { $loc = null; if ($clinica_id !== null && $clinica_id >= 0) { $loc = App_Location::get($clinica_id); } else if (!empty($clinica_name)) { $loc = App_Location::get($clinica_name); } if ($loc) { return [ 'id' => $loc->ID ?? null, 'name' => $loc->name ?? null, 'fullname' => $loc->fullname ?? null, 'address' => $loc->address ?? null, 'address1' => $loc->address1 ?? null, 'address2' => $loc->address2 ?? null, 'mapslink' => $loc->mapslink ?? null, 'address_note' => $loc->address_note ?? null, ]; } } try { $db = ensure_efisio_db(); if ($clinica_id !== null && $clinica_id >= 0) { $stmt = $db->prepare("SELECT * FROM efisio_locations WHERE ID = ? LIMIT 1"); $stmt->execute([$clinica_id]); } else if (!empty($clinica_name)) { $stmt = $db->prepare("SELECT * FROM efisio_locations WHERE name LIKE ? OR fullname LIKE ? LIMIT 1"); $like = '%' . $clinica_name . '%'; $stmt->execute([$like, $like]); } else { return null; } $r = $stmt->fetch(PDO::FETCH_ASSOC); if ($r) { return [ 'id' => isset($r['ID']) ? intval($r['ID']) : null, 'name' => $r['name'] ?? null, 'fullname' => $r['fullname'] ?? null, 'address' => $r['address'] ?? null, 'address1' => $r['address1'] ?? null, 'address2' => $r['address2'] ?? null, 'mapslink' => $r['mapslink'] ?? null, 'address_note' => $r['address_note'] ?? null, ]; } } catch (Exception $e) { return null; } return null; } function recommend_workers_for_symptoms($symptoms, $especialidad = null, $clinica_id = null, $limit = 5) { $symptoms = trim((string)$symptoms); $especialidad = trim((string)($especialidad ?? '')); if ($symptoms === '' && $especialidad === '') return []; try { require_once __DIR__ . '/../workers/expertise.php'; $db = ensure_efisio_db(); $query = $symptoms; if ($especialidad !== '') { $query = trim($query . ' ' . $especialidad); } $results = recommend_workers($db, $query, $limit); if ($clinica_id !== null) { $clinica_id = intval($clinica_id); $results = array_values(array_filter($results, function($r) use ($clinica_id) { return isset($r['location_id']) && intval($r['location_id']) === $clinica_id; })); } return $results; } catch (Exception $e) { return []; } } function get_favorite_worker_info($user_id) { $user_id = intval($user_id); if ($user_id <= 0) return null; try { require_once __DIR__ . '/../appointments/favorites.php'; require_once __DIR__ . '/../appointments/worker.php'; require_once __DIR__ . '/../../libwp/appointments/includes/model/class_app.php'; $fav = get_favorite_worker($user_id); if ($fav > 0 && class_exists('App_Worker')) { $worker = App_Worker::get($fav); if ($worker) { return [ 'worker_id' => $worker->ID ?? $fav, 'worker_name' => $worker->name ?? '', 'worker_fullname' => method_exists($worker, 'getFullName') ? $worker->getFullName() : '', 'location_id' => $worker->location ?? null, 'location_name' => $worker->location_name ?? '', 'role' => $worker->role ?? '', ]; } } } catch (Exception $e) { return null; } return null; } function get_user_future_appointments($user_id, $limit = 5) { $user_id = intval($user_id); if ($user_id <= 0) return []; try { $db = ensure_efisio_db(); $stmt = $db->prepare(" SELECT ID, start, end, status, service, worker, location FROM wp_app_appointments WHERE user = ? AND start >= NOW() AND status != 'removed' ORDER BY start ASC LIMIT $limit "); $stmt->execute([$user_id]); return $stmt->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Exception $e) { return []; } } function get_user_past_appointments($user_id, $limit = 5) { $user_id = intval($user_id); if ($user_id <= 0) return []; try { $db = ensure_efisio_db(); $stmt = $db->prepare(" SELECT ID, start, end, status, service, worker, location FROM wp_app_appointments WHERE user = ? AND start < NOW() AND status != 'removed' ORDER BY start DESC LIMIT $limit "); $stmt->execute([$user_id]); return $stmt->fetchAll(PDO::FETCH_ASSOC) ?: []; } catch (Exception $e) { return []; } } function get_chat_system_prompt($user_context = []) { $fecha = date("Y-m-d"); if (class_exists('IntlDateFormatter')) { setlocale(LC_TIME, "es_ES.UTF-8", "es_ES", "spanish"); $formatter = new IntlDateFormatter("es_ES", IntlDateFormatter::FULL, IntlDateFormatter::NONE, null, null, "EEEE"); $dia_semana = $formatter->format(strtotime($fecha)); } else { $dias_semana = ['domingo','lunes','martes','miércoles','jueves','viernes','sábado']; $dia_semana = $dias_semana[intval(date('w'))]; } // Ubicaciones desde App_Location / DB $locations = get_locations_for_prompt(); // Servicios principales $services = [ ['id' => 545, 'name' => 'Fisioterapia', 'price' => 50, 'duration' => 50], ['id' => 2509, 'name' => 'Drenaje Linfático', 'price' => 55, 'duration' => 60], ['id' => 29811, 'name' => 'Suelo Pélvico', 'price' => 60, 'duration' => 60], ['id' => 2082, 'name' => 'Osteopatía', 'price' => 60, 'duration' => 60], ]; $user_info = ""; if (!empty($user_context['name'])) { $user_info = "El usuario se llama {$user_context['name']}."; } if (!empty($user_context['phone'])) { $user_info .= " Su teléfono es {$user_context['phone']}."; } if (!empty($user_context['favfisio_name'])) { $user_info .= " Fisio habitual: {$user_context['favfisio_name']}."; } if (!empty($user_context['upcoming_appointments'])) { $user_info .= " Citas futuras: " . json_encode($user_context['upcoming_appointments']) . "."; } if (!empty($user_context['recent_appointments'])) { $user_info .= " Historial reciente: " . json_encode($user_context['recent_appointments']) . "."; } return "Eres el asistente virtual de eFISIO, una red de clínicas de fisioterapia en Madrid. PERSONALIDAD: - Cercano y profesional (tutea al usuario) - Conciso (respuestas cortas y claras) - Empático con quienes tienen dolor - Proactivo para ayudar a reservar cita FECHA ACTUAL: $fecha ($dia_semana) CLÍNICAS: " . json_encode($locations) . " SERVICIOS PRINCIPALES: " . json_encode($services) . " BONOS: - 5 sesiones: 225€ (45€/sesión) - 10 sesiones: 400€ (40€/sesión) CONTACTO: - Teléfono: 910 052 363 - WhatsApp: 644 054 800 $user_info REGLAS: - NO des diagnósticos médicos - Si el usuario quiere reservar, guíale preguntando: servicio, clínica preferida, y disponibilidad - Ofrece siempre opciones claras - Cuando muestres fechas, incluye el día de la semana - Responde en español - Si el usuario es existente (hay user_id) y pide cita o cancelación, primero consulta \"citas_futuras\" y, si no tiene, consulta \"fisio_habitual\" para recomendar con su fisio de confianza. - Si el usuario pide CAMBIAR la cita y aporta una fecha/hora (o franja), debes consultar disponibilidad real con la herramienta \"ver_disponibilidad\" usando: - \"clinica_id\" y \"servicio_id\" de la próxima cita (si existen) - \"worker_id\" del fisio de esa cita (si existe) o su fisio habitual - \"desde\" y \"hasta\" alrededor de la fecha sugerida Responde directamente con opciones de horas libres (máx 3) o indica que no hay y ofrece alternativa. FORMATO DE RESPUESTA: Responde SIEMPRE en este formato JSON: { \"respuesta\": \"tu mensaje aquí\", \"acciones\": [\"opción 1\", \"opción 2\"] } Las acciones son botones que el usuario puede pulsar para continuar la conversación."; } function ejecutar_agente_v3($mensaje, $history = [], $user_context = []) { $system_prompt = get_chat_system_prompt($user_context); $messages = [ ["role" => "system", "content" => $system_prompt] ]; // Añadir historial (máximo 6 mensajes) foreach (array_slice($history, -6) as $h) { $messages[] = [ "role" => $h["role"], "content" => $h["content"] ]; } // Añadir mensaje actual $messages[] = ["role" => "user", "content" => $mensaje]; // Usar Claude Sonnet via OpenRouter (rápido y económico) $response = ask_ia($messages, "google/gemini-2.0-flash-001"); if (!$response) { return [ "respuesta" => "Disculpa, estoy teniendo problemas técnicos. ¿Puedes intentarlo de nuevo o llamarnos al 910 052 363?", "acciones" => ["Llamar al 910 052 363", "Intentar de nuevo"] ]; } // Intentar parsear JSON $parsed = extract_json_from_agent_response($response); if ($parsed && isset($parsed['respuesta'])) { return $parsed; } // Si no es JSON válido, devolver como texto return [ "respuesta" => $response, "acciones" => [] ]; } /** * Versión con herramientas para consultas de disponibilidad */ function ejecutar_agente_v3_tools($mensaje, $history = [], $user_context = []) { $system_prompt = get_chat_system_prompt($user_context); // Añadir instrucciones de herramientas $system_prompt .= " HERRAMIENTAS DISPONIBLES: Si necesitas consultar disponibilidad real, responde con: { \"herramienta\": \"ver_disponibilidad\", \"parametros\": { \"clinica_id\": 2, \"desde\": \"2024-01-20\", \"hasta\": \"2024-01-27\", \"servicio_id\": 545, \"worker_id\": 123 } } Si necesitas citas futuras del usuario, responde con: { \"herramienta\": \"citas_futuras\", \"parametros\": { \"user_id\": 12345 } } Si necesitas historial de citas del usuario, responde con: { \"herramienta\": \"citas_historial\", \"parametros\": { \"user_id\": 12345 } } Si necesitas detalles de una clínica, responde con: { \"herramienta\": \"detalle_clinica\", \"parametros\": { \"clinica_id\": 2, \"clinica_nombre\": \"Chamberí\" } } Si necesitas recomendar un fisio por patología/síntomas y clínica, responde con: { \"herramienta\": \"recomendar_fisio\", \"parametros\": { \"sintomas\": \"dolor lumbar, ciática\", \"especialidad\": \"suelo pélvico\", \"clinica_id\": 2 } } Si necesitas el fisio habitual del usuario, responde con: { \"herramienta\": \"fisio_habitual\", \"parametros\": { \"user_id\": 12345 } } PAUSA Te responderé con los resultados de la herramienta."; $messages = [ ["role" => "system", "content" => $system_prompt] ]; foreach (array_slice($history, -6) as $h) { $messages[] = ["role" => $h["role"], "content" => $h["content"]]; } $messages[] = ["role" => "user", "content" => $mensaje]; $max_turnos = 3; $turno = 0; $tool_trace = []; while ($turno < $max_turnos) { $response = ask_ia($messages, "google/gemini-2.0-flash-001"); if (!$response) { return [ "respuesta" => "Disculpa, estoy teniendo problemas. Llámanos al 910 052 363.", "acciones" => ["Llamar"] ]; } $parsed = extract_json_from_agent_response($response); // Si tiene respuesta final, devolverla if ($parsed && isset($parsed['respuesta'])) { if (!empty($user_context['debug_tools'])) { $parsed['debug_tools'] = $tool_trace; } return $parsed; } // Si pide herramienta if ($parsed && isset($parsed['herramienta'])) { $herramienta = $parsed['herramienta']; $params = $parsed['parametros'] ?? []; $result = null; if ($herramienta == 'ver_disponibilidad') { $location_id = $params['clinica_id'] ?? null; $service_id = $params['servicio_id'] ?? 545; $worker_id = $params['worker_id'] ?? 0; $desde = $params['desde'] ?? date('Y-m-d'); $hasta = $params['hasta'] ?? date('Y-m-d', strtotime('+7 days')); $result = null; if (class_exists('App_WeeklyAdm')) { $raw = App_WeeklyAdm::apps_free($location_id, strtotime($desde), strtotime($hasta), $service_id, $worker_id); $result = $raw; } else { $query = http_build_query([ 'from' => $desde, 'to' => $hasta, 'l' => $location_id, 's' => $service_id, 'w' => $worker_id ]); $url = "https://www.efisio.es/api/appsfree?" . $query; $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 15); $resp = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($resp && $http >= 200 && $http < 300) { $json = json_decode($resp, true); if (is_array($json) && isset($json['apps'])) { $result = $json['apps']; } } } if (!$result) { $result = ['error' => 'tool_unavailable']; } else { $simplified = []; if (!is_array($result) && !is_object($result)) $result = []; foreach ($result as $key => $slots) { $date = is_numeric($key) ? date('Y-m-d', intval($key)) : $key; if (!isset($simplified[$date])) $simplified[$date] = []; foreach ($slots as $s) { $status = $s['status'] ?? ($s->status ?? ''); if ($status && $status !== 'available') continue; $wkr = $s['worker'] ?? ($s->worker ?? null); if ($worker_id && intval($wkr) !== intval($worker_id)) continue; $start = $s['start'] ?? ($s->start ?? null); if (!$start) continue; $simplified[$date][] = [ 'hora' => date('H:i', strtotime($start)), 'inicio' => $start, 'fisio' => $s['worker_name'] ?? ($s->worker_name ?? ''), 'fisio_id' => $wkr, 'clinica' => $s['location'] ?? ($s->location ?? $location_id) ]; if (count($simplified[$date]) >= 5) break; } } $result = $simplified; } } else if ($herramienta == 'citas_futuras') { $user_id = $params['user_id'] ?? ($user_context['user_id'] ?? null); $result = get_user_future_appointments($user_id, 5); } else if ($herramienta == 'citas_historial') { $user_id = $params['user_id'] ?? ($user_context['user_id'] ?? null); $result = get_user_past_appointments($user_id, 5); } else if ($herramienta == 'detalle_clinica') { $clinica_id = $params['clinica_id'] ?? null; $clinica_name = $params['clinica_nombre'] ?? null; $result = get_location_details($clinica_id, $clinica_name); } else if ($herramienta == 'recomendar_fisio') { $sintomas = $params['sintomas'] ?? ''; $especialidad = $params['especialidad'] ?? ''; $clinica_id = $params['clinica_id'] ?? null; $result = recommend_workers_for_symptoms($sintomas, $especialidad, $clinica_id, 5); } else if ($herramienta == 'fisio_habitual') { $user_id = $params['user_id'] ?? ($user_context['user_id'] ?? null); $result = get_favorite_worker_info($user_id); } if ($result) { if (!empty($user_context['debug_tools'])) { $tool_trace[] = [ 'tool' => $herramienta, 'params' => $params, 'result' => $result ]; } $messages[] = ["role" => "assistant", "content" => $response]; $messages[] = ["role" => "user", "content" => "Resultado: " . json_encode($result, JSON_PRETTY_PRINT)]; } } else { // Respuesta en texto plano $res = [ "respuesta" => $response, "acciones" => [] ]; if (!empty($user_context['debug_tools'])) { $res['debug_tools'] = $tool_trace; } return $res; } $turno++; } $res = [ "respuesta" => "No pude completar tu consulta. ¿Prefieres que te llamemos?", "acciones" => ["Sí, llamadme", "Intentar de nuevo"] ]; if (!empty($user_context['debug_tools'])) { $res['debug_tools'] = $tool_trace; } return $res; } /** * Helper para extraer JSON de texto (local a este archivo) */ function extract_json_from_agent_response($text) { if (!$text) return null; // Buscar JSON en code blocks if (preg_match('/```(?:json)?\s*(\{.*?\})\s*```/s', $text, $matches)) { $json = json_decode($matches[1], true); if ($json) return $json; } // Buscar JSON directo if (preg_match('/\{[^{}]*"respuesta"[^{}]*\}/s', $text, $matches)) { $json = json_decode($matches[0], true); if ($json) return $json; } // Intentar parsear todo el texto $json = json_decode($text, true); if ($json) return $json; return null; }
Si tienes alguna duda o necesitas ayuda, no dudes en contactar con nosotros.
Página 1 de 0 (0 entradas en total)
¿Es tu primera vez en eFISIO?
Ahora tienes 10€ de descuento en tu primera reserva
Exclusivo para tienda online. Un único cupón por paciente. Válido para la primera cita del paciente. No compatible con otras ofertas de fisioterapia. Oferta válida hasta el 30 de abril de 2026
Encuentra la clínica eFISIO más cercana a tu ubicación.