true, 'dispatch_operations' => true, ]); } } public function can_access() { return is_user_logged_in() && ( current_user_can('manage_autobooking') || current_user_can('dispatch_operations') || current_user_can('manage_options') ); } /* ── REST endpoints ──────────────────────────────────────── */ public function register_endpoints() { $ns = 'autobooking/v1'; $auth = [$this, 'can_access']; register_rest_route($ns, '/command/stats', ['methods' => 'GET', 'callback' => [$this, 'ep_stats'], 'permission_callback' => $auth]); register_rest_route($ns, '/command/live-map', ['methods' => 'GET', 'callback' => [$this, 'ep_live_map'], 'permission_callback' => $auth]); register_rest_route($ns, '/command/alerts', ['methods' => 'GET', 'callback' => [$this, 'ep_alerts'], 'permission_callback' => $auth]); register_rest_route($ns, '/command/trip/(?P\d+)', ['methods' => 'GET', 'callback' => [$this, 'ep_trip'], 'permission_callback' => $auth]); register_rest_route($ns, '/command/incident', ['methods' => 'POST', 'callback' => [$this, 'ep_incident'], 'permission_callback' => $auth]); register_rest_route($ns, '/command/resolve/(?P\d+)',['methods' => 'POST', 'callback' => [$this, 'ep_resolve'], 'permission_callback' => $auth]); register_rest_route($ns, '/command/block-driver', ['methods' => 'POST', 'callback' => [$this, 'ep_block_driver'], 'permission_callback' => $auth]); register_rest_route($ns, '/command/message', ['methods' => 'POST', 'callback' => [$this, 'ep_message'], 'permission_callback' => $auth]); } public function ep_stats() { global $wpdb; return rest_ensure_response([ 'active_trips' => (int)$wpdb->get_var("SELECT COUNT(*) FROM wp_ab_trips WHERE status IN ('active','en_route','picking_up','in_progress')"), 'available_drivers' => (int)$wpdb->get_var("SELECT COUNT(*) FROM wp_ab_driver_status WHERE online=1 AND last_seen > DATE_SUB(NOW(), INTERVAL 5 MINUTE)"), 'open_sos' => (int)$wpdb->get_var("SELECT COUNT(*) FROM wp_ab_incidents WHERE status IN ('open','active','pending')"), 'trips_today' => (int)$wpdb->get_var("SELECT COUNT(*) FROM wp_ab_trips WHERE DATE(created_at) = CURDATE()"), ]); } public function ep_live_map() { global $wpdb; $trips = $wpdb->get_results(" SELECT t.id, t.status, t.driver_id, t.passenger_id, t.driver_name, t.passenger_name, t.pickup_time AS started_at, t.dropoff_lat, t.dropoff_lng, p.lat AS driver_lat, p.lng AS driver_lng FROM wp_ab_trips t LEFT JOIN ( SELECT trip_id, lat, lng FROM wp_ab_trip_positions WHERE id IN (SELECT MAX(id) FROM wp_ab_trip_positions GROUP BY trip_id) ) p ON t.id = p.trip_id WHERE t.status IN ('active','en_route','picking_up','in_progress') "); $drivers = $wpdb->get_results(" SELECT ds.driver_id AS user_id, ds.last_lat AS lat, ds.last_lng AS lng, u.display_name FROM wp_ab_driver_status ds JOIN wp_users u ON ds.driver_id = u.ID WHERE ds.online = 1 AND ds.last_seen > DATE_SUB(NOW(), INTERVAL 5 MINUTE) "); return rest_ensure_response(['trips' => $trips, 'drivers' => $drivers]); } public function ep_alerts() { global $wpdb; $sos = $wpdb->get_results(" SELECT i.id, i.trip_id, i.user_id, i.type, i.description, i.lat, i.lng, i.status, i.created_at, u.display_name AS user_name, t.driver_id, t.passenger_id FROM wp_ab_incidents i LEFT JOIN wp_users u ON i.user_id = u.ID LEFT JOIN wp_ab_trips t ON i.trip_id = t.id WHERE i.status IN ('open','active','pending') ORDER BY i.created_at DESC LIMIT 30 "); return rest_ensure_response(['sos' => $sos, 'count' => count($sos)]); } public function ep_trip($req) { global $wpdb; $id = (int)$req['id']; $trip = $wpdb->get_row($wpdb->prepare(" SELECT t.*, du.display_name AS driver_name, pu.display_name AS passenger_name, dm.meta_value AS driver_phone, pm.meta_value AS passenger_phone FROM wp_ab_trips t LEFT JOIN wp_users du ON t.driver_id = du.ID LEFT JOIN wp_users pu ON t.passenger_id = pu.ID LEFT JOIN wp_usermeta dm ON t.driver_id = dm.user_id AND dm.meta_key = 'phone' LEFT JOIN wp_usermeta pm ON t.passenger_id = pm.user_id AND pm.meta_key = 'phone' WHERE t.id = %d ", $id)); if (!$trip) return new WP_Error('not_found', 'Trip not found', ['status' => 404]); // Posición actual del conductor $cur = $wpdb->get_row($wpdb->prepare( "SELECT lat, lng FROM wp_ab_trip_positions WHERE trip_id = %d ORDER BY id DESC LIMIT 1", $id )); $trip->current_lat = $cur ? $cur->lat : null; $trip->current_lng = $cur ? $cur->lng : null; // Enmascarar teléfonos según rol $trip->driver_phone = ab_mask_phone($trip->driver_phone ?? ''); $trip->passenger_phone = ab_mask_phone($trip->passenger_phone ?? ''); $incidents = $wpdb->get_results($wpdb->prepare( "SELECT * FROM wp_ab_incidents WHERE trip_id = %d ORDER BY created_at DESC LIMIT 10", $id )); return rest_ensure_response(['trip' => $trip, 'incidents' => $incidents]); } public function ep_incident($req) { global $wpdb; $p = $req->get_params(); $trip_id = (int)($p['trip_id'] ?? 0); $type = sanitize_text_field($p['type'] ?? 'operator_action'); $desc = sanitize_textarea_field($p['description'] ?? ''); $protocol = sanitize_text_field($p['protocol'] ?? ''); $wpdb->insert('wp_ab_incidents', [ 'trip_id' => $trip_id, 'user_id' => get_current_user_id(), 'type' => $type, 'description' => $desc . ($protocol ? " [PROTOCOLO: {$protocol}]" : ''), 'status' => 'active', 'created_at' => current_time('mysql'), ]); $this->audit('command_incident', compact('trip_id', 'type', 'protocol')); return rest_ensure_response(['success' => true, 'id' => $wpdb->insert_id]); } public function ep_resolve($req) { global $wpdb; $id = (int)$req['id']; $notes = sanitize_textarea_field($req->get_param('notes') ?? ''); $wpdb->update('wp_ab_incidents', [ 'status' => 'resolved', 'resolved_at' => current_time('mysql'), 'resolution_notes' => $notes, ], ['id' => $id]); $this->audit('command_resolve', ['alert_id' => $id, 'notes' => $notes]); return rest_ensure_response(['success' => true]); } public function ep_block_driver($req) { global $wpdb; $driver_id = (int)($req->get_param('driver_id') ?? 0); $reason = sanitize_text_field($req->get_param('reason') ?? ''); if (!$driver_id) return new WP_Error('invalid', 'Driver ID required', ['status' => 400]); $wpdb->update('wp_ab_driver_status', ['online' => 0], ['driver_id' => $driver_id]); $this->audit('command_block_driver', compact('driver_id', 'reason')); return rest_ensure_response(['success' => true]); } public function ep_message($req) { global $wpdb; $trip_id = (int)($req->get_param('trip_id') ?? 0); $to_user_id = (int)($req->get_param('to_user_id') ?? 0); $message = sanitize_textarea_field($req->get_param('message') ?? ''); if (!$message) return new WP_Error('invalid', 'Message required', ['status' => 400]); $wpdb->insert('wp_autobooking_chat', [ 'trip_id' => $trip_id, 'from_user_id' => get_current_user_id(), 'to_user_id' => $to_user_id, 'message' => '[OPERADOR] ' . $message, 'created_at' => current_time('mysql'), ]); return rest_ensure_response(['success' => true]); } private function audit($action, $details) { global $wpdb; $wpdb->insert('wp_ab_admin_audit', [ 'admin_id' => get_current_user_id(), 'action' => $action, 'details' => wp_json_encode($details), 'created_at' => current_time('mysql'), ]); } /* ── Assets ──────────────────────────────────────────────── */ public function enqueue() { global $post; if (!$post || !has_shortcode($post->post_content, 'autobooking_command_center')) return; if (!$this->can_access()) return; $url = plugin_dir_url(__FILE__); wp_enqueue_style('cc-style', $url . 'command-center.css', [], '1.0.0'); wp_enqueue_script('cc-script', $url . 'command-center.js', ['jquery'], '1.0.0', true ); wp_localize_script('cc-script', 'ccConfig', [ 'rest' => rest_url('autobooking/v1/'), 'nonce' => wp_create_nonce('wp_rest'), 'userId' => get_current_user_id(), ]); } /* ── Shortcode HTML ──────────────────────────────────────── */ public function render() { if (!$this->can_access()) return '

Acceso no autorizado.

'; ob_start(); ?>
COMANDO CENTRAL
--Viajes activos
--Conductores libres
0Alertas SOS
--Viajes hoy
--:--:--
En viaje Disponible SOS
ALERTAS SOS 0
Sin alertas activas
VIAJES ACTIVOS
Sin viajes activos

Activar Protocolo de Seguridad

Enviar Mensaje del Operador