679 lines
30 KiB
PHP
679 lines
30 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: AutoBooking Simulator
|
|
* Description: Entorno de simulación — 12 conductores, 20 usuarios, 2 clientes institucionales, rutas en Bogotá con desvío de ruta
|
|
* Version: 1.0.0
|
|
* Author: AutoBooking
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
define('AB_SIM_CRON', 'ab_sim_tick');
|
|
define('AB_SIM_OPT', 'ab_sim_state');
|
|
define('AB_SIM_MARKER', 'ab_sim_user');
|
|
|
|
/* ══════════════════════════════════════════════════════════════════════
|
|
* RUTAS — Miami / Hialeah, Florida
|
|
* Waypoints cada ~500-700m. Ruta C tiene desvío para probar alarmas.
|
|
* ══════════════════════════════════════════════════════════════════════ */
|
|
function ab_sim_routes() {
|
|
return [
|
|
'A' => [
|
|
'name' => 'Hialeah → Brickell (S por NW 27th Ave)',
|
|
'from' => 'Hialeah Market',
|
|
'to' => 'Brickell City Centre',
|
|
'wps' => [
|
|
[25.8576, -80.2781], [25.8450, -80.2700], [25.8320, -80.2620],
|
|
[25.8190, -80.2540], [25.8060, -80.2460], [25.7930, -80.2380],
|
|
[25.7800, -80.2300], [25.7680, -80.2220], [25.7617, -80.1918],
|
|
],
|
|
],
|
|
'B' => [
|
|
'name' => 'MIA Airport → Miami Beach (E por NW 36th St)',
|
|
'from' => 'Miami International Airport',
|
|
'to' => 'South Beach',
|
|
'wps' => [
|
|
[25.7959, -80.2870], [25.7959, -80.2650], [25.7940, -80.2430],
|
|
[25.7880, -80.2210], [25.7820, -80.1990], [25.7760, -80.1770],
|
|
[25.7700, -80.1550], [25.7640, -80.1330], [25.7617, -80.1320],
|
|
],
|
|
],
|
|
'C' => [
|
|
'name' => 'Kendall → Hialeah (N por FL Turnpike) — DESVÍO POSIBLE',
|
|
'from' => 'Kendall Drive',
|
|
'to' => 'Hialeah',
|
|
'wps' => [
|
|
[25.7090, -80.3560], [25.7230, -80.3420], [25.7370, -80.3280],
|
|
[25.7510, -80.3140], [25.7650, -80.3000], [25.7790, -80.2860],
|
|
[25.7930, -80.2720], [25.8070, -80.2580], [25.8210, -80.2440],
|
|
],
|
|
'deviation_start' => 3,
|
|
'deviation_path' => [
|
|
[25.7520, -80.2900], [25.7540, -80.2650],
|
|
[25.7560, -80.2400], [25.7580, -80.2150],
|
|
],
|
|
],
|
|
'D' => [
|
|
'name' => 'Aventura → Downtown Miami (S por Biscayne Blvd)',
|
|
'from' => 'Aventura Mall',
|
|
'to' => 'Downtown Miami',
|
|
'wps' => [
|
|
[25.9565, -80.1425], [25.9380, -80.1410], [25.9190, -80.1395],
|
|
[25.9000, -80.1380], [25.8810, -80.1365], [25.8620, -80.1350],
|
|
[25.8240, -80.1320], [25.7900, -80.1800], [25.7750, -80.1900],
|
|
],
|
|
],
|
|
'E' => [
|
|
'name' => 'Doral → Coral Gables (S por Palmetto Expy)',
|
|
'from' => 'Doral City Place',
|
|
'to' => 'Coral Gables Miracle Mile',
|
|
'wps' => [
|
|
[25.8196, -80.3556], [25.8100, -80.3400], [25.8000, -80.3250],
|
|
[25.7900, -80.3100], [25.7800, -80.2950], [25.7700, -80.2800],
|
|
[25.7600, -80.2650],
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
/* ══════════════════════════════════════════════════════════════════════
|
|
* CONFIGURACIÓN DE CONDUCTORES (12)
|
|
* ['login', 'nombre', ruta, step_inicial, empresa_corp|null]
|
|
* route FREE = disponible | route OFF = offline
|
|
* ══════════════════════════════════════════════════════════════════════ */
|
|
function ab_sim_driver_config() {
|
|
return [
|
|
['abtest_drv_a1', 'Andrés Pérez', 'A', 0, null],
|
|
['abtest_drv_a2', 'Beatriz Torres', 'A', 3, null],
|
|
['abtest_drv_b1', 'Carlos Méndez', 'B', 0, null],
|
|
['abtest_drv_b2', 'Diana López', 'B', 5, 'TechCorp'],
|
|
['abtest_drv_c1', 'Eduardo Ruiz', 'C', 0, null],
|
|
['abtest_drv_d1', 'Felipe Vargas', 'D', 0, 'LogiCorp'],
|
|
['abtest_drv_e1', 'Gabriela Suárez', 'E', 0, null],
|
|
['abtest_drv_f1', 'Hugo Ramírez', 'FREE', null, null],
|
|
['abtest_drv_f2', 'Isabel Castro', 'FREE', null, null],
|
|
['abtest_drv_f3', 'Julián Morales', 'FREE', null, null],
|
|
['abtest_drv_f4', 'Karen Reyes', 'FREE', null, null],
|
|
['abtest_drv_f5', 'Luis Herrera', 'OFF', null, null],
|
|
];
|
|
}
|
|
|
|
function ab_sim_haversine($lat1, $lng1, $lat2, $lng2) {
|
|
$R = 6371;
|
|
$dLat = deg2rad($lat2 - $lat1);
|
|
$dLng = deg2rad($lng2 - $lng1);
|
|
$a = sin($dLat/2)**2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng/2)**2;
|
|
return $R * 2 * atan2(sqrt($a), sqrt(1 - $a));
|
|
}
|
|
|
|
function ab_sim_free_positions() {
|
|
return [
|
|
[4.6700, -74.0550],
|
|
[4.6490, -74.0680],
|
|
[4.6120, -74.0760],
|
|
[4.6320, -74.1100],
|
|
];
|
|
}
|
|
|
|
/* ══════════════════════════════════════════════════════════════════════
|
|
* ESTADO EN wp_options
|
|
* ══════════════════════════════════════════════════════════════════════ */
|
|
function ab_sim_state() {
|
|
return get_option(AB_SIM_OPT, [
|
|
'running' => false,
|
|
'deviation' => false,
|
|
'drivers' => [],
|
|
'last_tick' => 0,
|
|
]);
|
|
}
|
|
function ab_sim_save($s) { update_option(AB_SIM_OPT, $s); }
|
|
|
|
/* ══════════════════════════════════════════════════════════════════════
|
|
* PÁGINA DE ADMINISTRACIÓN (Tools > AB Simulator)
|
|
* ══════════════════════════════════════════════════════════════════════ */
|
|
add_action('admin_menu', function () {
|
|
add_management_page('AB Simulator', 'AB Simulator', 'manage_options', 'ab-sim', 'ab_sim_page');
|
|
});
|
|
|
|
function ab_sim_page() {
|
|
if (isset($_POST['ab_sim_act']) && check_admin_referer('ab_sim')) {
|
|
$act = sanitize_key($_POST['ab_sim_act']);
|
|
if ($act === 'start') ab_sim_start();
|
|
if ($act === 'stop') ab_sim_stop();
|
|
if ($act === 'reset') ab_sim_reset();
|
|
if ($act === 'deviate') ab_sim_activate_deviation();
|
|
if ($act === 'tick') ab_sim_tick();
|
|
}
|
|
|
|
$s = ab_sim_state();
|
|
$routes = ab_sim_routes();
|
|
$running = $s['running'];
|
|
?>
|
|
<div class="wrap">
|
|
<h1>🚗 AutoBooking Simulator</h1>
|
|
|
|
<div style="background:#fff;padding:20px 24px;margin:16px 0;border:1px solid #ccc;border-radius:6px;">
|
|
<p style="font-size:15px;">
|
|
Estado: <?php echo $running
|
|
? '<strong style="color:#2e7d32">▶ CORRIENDO</strong>'
|
|
: '<strong style="color:#555">⏸ DETENIDO</strong>'; ?>
|
|
| Último tick: <code><?php echo $s['last_tick'] ? date('H:i:s', $s['last_tick']) : '—'; ?></code>
|
|
| Desvío ruta C: <?php echo $s['deviation']
|
|
? '<strong style="color:#c62828">⚠ ACTIVO</strong>'
|
|
: '<span style="color:#888">inactivo</span>'; ?>
|
|
</p>
|
|
|
|
<form method="post" style="display:inline-block;margin-right:8px;">
|
|
<?php wp_nonce_field('ab_sim'); ?>
|
|
<input type="hidden" name="ab_sim_act" value="<?php echo $running ? 'stop' : 'start'; ?>">
|
|
<button class="button <?php echo $running ? '' : 'button-primary'; ?> button-large">
|
|
<?php echo $running ? '⏸ Pausar' : '▶ Iniciar simulación'; ?>
|
|
</button>
|
|
</form>
|
|
|
|
<form method="post" style="display:inline-block;margin-right:8px;">
|
|
<?php wp_nonce_field('ab_sim'); ?>
|
|
<input type="hidden" name="ab_sim_act" value="deviate">
|
|
<button class="button button-large" <?php echo !$running ? 'disabled' : ''; ?>>
|
|
🔀 Activar desvío (conductor C)
|
|
</button>
|
|
</form>
|
|
|
|
<form method="post" style="display:inline-block;margin-right:8px;">
|
|
<?php wp_nonce_field('ab_sim'); ?>
|
|
<input type="hidden" name="ab_sim_act" value="tick">
|
|
<button class="button button-large" <?php echo !$running ? 'disabled' : ''; ?>>
|
|
⏭ Avanzar un paso
|
|
</button>
|
|
</form>
|
|
|
|
<form method="post" style="display:inline-block;"
|
|
onsubmit="return confirm('¿Eliminar todos los datos simulados?')">
|
|
<?php wp_nonce_field('ab_sim'); ?>
|
|
<input type="hidden" name="ab_sim_act" value="reset">
|
|
<button class="button button-large" style="color:#c62828;">🗑 Reset total</button>
|
|
</form>
|
|
</div>
|
|
|
|
<?php if (!empty($s['drivers'])): ?>
|
|
<h2>Conductores (<?php echo count($s['drivers']); ?>)</h2>
|
|
<table class="widefat striped">
|
|
<thead><tr><th>ID</th><th>Nombre</th><th>Ruta</th><th>Paso</th><th>Estado DB</th><th>Lat / Lng</th></tr></thead>
|
|
<tbody>
|
|
<?php foreach ($s['drivers'] as $d):
|
|
$u = get_userdata($d['user_id']);
|
|
$rkey = $d['route'];
|
|
if ($rkey === 'FREE') $rname = '📍 Disponible';
|
|
elseif ($rkey === 'OFF') $rname = '🔴 Offline';
|
|
else $rname = $routes[$rkey]['name'] ?? $rkey;
|
|
?>
|
|
<tr>
|
|
<td><?php echo $d['user_id']; ?></td>
|
|
<td><?php echo esc_html($u ? $u->display_name : '—'); ?></td>
|
|
<td><?php echo esc_html($rname); ?></td>
|
|
<td><?php echo $d['step'] ?? '—'; ?></td>
|
|
<td><?php echo esc_html($d['db_status'] ?? '—'); ?></td>
|
|
<td><code><?php echo isset($d['lat']) ? round($d['lat'], 5) . ', ' . round($d['lng'], 5) : '—'; ?></code></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
|
|
<h2 style="margin-top:24px;">Escenarios de prueba</h2>
|
|
<table class="widefat">
|
|
<thead><tr><th>Escenario</th><th>Qué prueba</th><th>Pasos</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><b>Mapa en vivo</b></td>
|
|
<td>Conductores disponibles y activos en el mapa del Command Center</td>
|
|
<td>Iniciar → abrir Command Center</td>
|
|
</tr>
|
|
<tr>
|
|
<td><b>Viajes activos</b></td>
|
|
<td>7 vehículos moviéndose en tiempo real (cada ~30s)</td>
|
|
<td>Iniciar → refrescar mapa cada 30s</td>
|
|
</tr>
|
|
<tr>
|
|
<td><b>Desvío de ruta</b></td>
|
|
<td>Conductor C abandona ruta Kennedy→Suba → alerta <code>route_deviation</code> en Command Center</td>
|
|
<td>Iniciar → 2 ticks → Activar desvío → ver alertas</td>
|
|
</tr>
|
|
<tr>
|
|
<td><b>Viaje corporativo</b></td>
|
|
<td>Diana López (TechCorp) y Felipe Vargas (LogiCorp) con company_id en sus viajes</td>
|
|
<td>Iniciar → ver viajes activos con empresa asignada</td>
|
|
</tr>
|
|
<tr>
|
|
<td><b>Avance manual</b></td>
|
|
<td>Control paso a paso para debugging de posiciones exactas</td>
|
|
<td>Usar "Avanzar un paso" repetidamente</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/* ══════════════════════════════════════════════════════════════════════
|
|
* CONTROL
|
|
* ══════════════════════════════════════════════════════════════════════ */
|
|
function ab_sim_start() {
|
|
$s = ab_sim_state();
|
|
if (empty($s['drivers'])) {
|
|
ab_sim_seed($s);
|
|
}
|
|
$s['running'] = true;
|
|
$s['last_tick'] = time();
|
|
ab_sim_save($s);
|
|
if (!wp_next_scheduled(AB_SIM_CRON)) {
|
|
wp_schedule_event(time(), 'ab_sim_30s', AB_SIM_CRON);
|
|
}
|
|
}
|
|
|
|
function ab_sim_stop() {
|
|
$s = ab_sim_state();
|
|
$s['running'] = false;
|
|
ab_sim_save($s);
|
|
wp_clear_scheduled_hook(AB_SIM_CRON);
|
|
}
|
|
|
|
function ab_sim_activate_deviation() {
|
|
$s = ab_sim_state();
|
|
$s['deviation'] = true;
|
|
ab_sim_save($s);
|
|
}
|
|
|
|
function ab_sim_reset() {
|
|
global $wpdb;
|
|
ab_sim_stop();
|
|
$s = ab_sim_state();
|
|
$uids = array_column($s['drivers'] ?? [], 'user_id');
|
|
$extra = get_option('ab_sim_extra_uids', []);
|
|
$all = array_unique(array_merge($uids, $extra));
|
|
|
|
foreach ($all as $uid) {
|
|
$trip_ids = $wpdb->get_col($wpdb->prepare(
|
|
"SELECT id FROM wp_ab_trips WHERE (driver_id=%d OR passenger_id=%d) AND trip_uuid LIKE 'sim\\_%'",
|
|
$uid, $uid
|
|
));
|
|
if ($trip_ids) {
|
|
$in = implode(',', array_map('intval', $trip_ids));
|
|
$wpdb->query("DELETE FROM wp_ab_trip_positions WHERE trip_id IN ($in)");
|
|
$wpdb->query("DELETE FROM wp_ab_incidents WHERE trip_id IN ($in)");
|
|
$wpdb->query("DELETE FROM wp_ab_trips WHERE id IN ($in)");
|
|
}
|
|
$wpdb->delete('wp_ab_driver_status', ['driver_id' => $uid]);
|
|
if (get_user_meta($uid, AB_SIM_MARKER, true) === '1') {
|
|
require_once ABSPATH . 'wp-admin/includes/user.php';
|
|
wp_delete_user($uid);
|
|
}
|
|
}
|
|
|
|
$wpdb->query("DELETE FROM wp_ab_companies WHERE name LIKE '%[SIM]%'");
|
|
delete_option(AB_SIM_OPT);
|
|
delete_option('ab_sim_extra_uids');
|
|
delete_option('ab_sim_company_ids');
|
|
}
|
|
|
|
/* ══════════════════════════════════════════════════════════════════════
|
|
* SEED — crear usuarios, empresas y viajes iniciales
|
|
* ══════════════════════════════════════════════════════════════════════ */
|
|
function ab_sim_seed(&$s) {
|
|
global $wpdb;
|
|
$routes = ab_sim_routes();
|
|
$drv_cfg = ab_sim_driver_config();
|
|
$free_pos = ab_sim_free_positions();
|
|
$now = current_time('mysql');
|
|
|
|
/* — 2 empresas institucionales — */
|
|
$company_ids = [];
|
|
foreach (['TechCorp [SIM]' => 'TechCorp', 'LogiCorp [SIM]' => 'LogiCorp'] as $cname => $ckey) {
|
|
$cid = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT id FROM wp_ab_companies WHERE name=%s LIMIT 1", $cname
|
|
));
|
|
if (!$cid) {
|
|
$wpdb->insert('wp_ab_companies', [
|
|
'name' => $cname,
|
|
'email' => strtolower($ckey) . '@sim.local',
|
|
'status' => 'active',
|
|
'created_at' => $now,
|
|
]);
|
|
$cid = $wpdb->insert_id;
|
|
}
|
|
$company_ids[$ckey] = (int) $cid;
|
|
}
|
|
update_option('ab_sim_company_ids', $company_ids);
|
|
|
|
/* — 20 pasajeros — */
|
|
$pass_names = [
|
|
'María García', 'José Rodríguez', 'Ana Martínez', 'Luis González',
|
|
'Carmen López', 'Pedro Sánchez', 'Laura Fernández', 'Miguel Torres',
|
|
'Isabel Ramírez', 'Carlos Flores', 'Sara Díaz', 'David Morales',
|
|
'Elena Jiménez', 'Roberto Ruiz', 'Patricia Herrera','Javier Medina',
|
|
'Sofía Delgado', 'Tomás Vargas', 'Valentina Cruz', 'Nicolás Ortiz',
|
|
];
|
|
$pass_ids = [];
|
|
foreach ($pass_names as $i => $pname) {
|
|
$login = 'abtest_pass_' . ($i + 1);
|
|
$existing = get_user_by('login', $login);
|
|
if ($existing) {
|
|
$pid = $existing->ID;
|
|
} else {
|
|
$pid = wp_insert_user([
|
|
'user_login' => $login,
|
|
'user_pass' => wp_generate_password(),
|
|
'display_name' => $pname,
|
|
'role' => 'subscriber',
|
|
]);
|
|
if (is_wp_error($pid)) continue;
|
|
update_user_meta($pid, AB_SIM_MARKER, '1');
|
|
// Empleados corporativos: índices 16-17 TechCorp, 18-19 LogiCorp
|
|
if ($i >= 16 && $i <= 17) update_user_meta($pid, 'ab_company_id', $company_ids['TechCorp']);
|
|
if ($i >= 18 && $i <= 19) update_user_meta($pid, 'ab_company_id', $company_ids['LogiCorp']);
|
|
}
|
|
$pass_ids[] = $pid;
|
|
}
|
|
update_option('ab_sim_extra_uids', $pass_ids);
|
|
|
|
/* — 12 conductores — */
|
|
$s['drivers'] = [];
|
|
$free_idx = 0;
|
|
|
|
foreach ($drv_cfg as $pi => $cfg) {
|
|
[$login, $dname, $route, $step, $corp] = $cfg;
|
|
|
|
$existing = get_user_by('login', $login);
|
|
if ($existing) {
|
|
$uid = $existing->ID;
|
|
} else {
|
|
$uid = wp_insert_user([
|
|
'user_login' => $login,
|
|
'user_pass' => wp_generate_password(),
|
|
'display_name' => $dname,
|
|
'role' => 'driver',
|
|
]);
|
|
if (is_wp_error($uid)) continue;
|
|
update_user_meta($uid, AB_SIM_MARKER, '1');
|
|
}
|
|
|
|
// Posición inicial y estado DB
|
|
if ($route === 'FREE') {
|
|
[$lat, $lng] = $free_pos[$free_idx % count($free_pos)];
|
|
$free_idx++;
|
|
$db_status = 'available';
|
|
} elseif ($route === 'OFF') {
|
|
$lat = 4.6500; $lng = -74.0600;
|
|
$db_status = 'offline';
|
|
} else {
|
|
[$lat, $lng] = $routes[$route]['wps'][$step ?? 0];
|
|
$db_status = 'on_trip';
|
|
}
|
|
|
|
// Insertar/actualizar wp_ab_driver_status
|
|
$exists = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT driver_id FROM wp_ab_driver_status WHERE driver_id=%d", $uid
|
|
));
|
|
if ($exists) {
|
|
$wpdb->update('wp_ab_driver_status',
|
|
['last_lat' => $lat, 'last_lng' => $lng, 'online' => ($db_status === 'offline') ? 0 : 1, 'last_seen' => $now],
|
|
['driver_id' => $uid]
|
|
);
|
|
} else {
|
|
$wpdb->insert('wp_ab_driver_status', [
|
|
'driver_id' => $uid,
|
|
'last_lat' => $lat,
|
|
'last_lng' => $lng,
|
|
'online' => ($db_status === 'offline') ? 0 : 1,
|
|
'last_seen' => $now,
|
|
]);
|
|
}
|
|
|
|
// Crear viaje activo para conductores en ruta
|
|
$trip_id = null;
|
|
if ($route !== 'FREE' && $route !== 'OFF') {
|
|
$pass_id = $pass_ids[$pi] ?? $pass_ids[0];
|
|
$wps = $routes[$route]['wps'];
|
|
$last_wp = $wps[count($wps) - 1];
|
|
$company_id = ($corp && isset($company_ids[$corp])) ? $company_ids[$corp] : null;
|
|
|
|
$trip_id = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT id FROM wp_ab_trips WHERE driver_id=%d AND notes LIKE '%[SIM]%' LIMIT 1", $uid
|
|
));
|
|
if (!$trip_id) {
|
|
$wps = $routes[$route]['wps'];
|
|
$last = $wps[count($wps) - 1];
|
|
$drv_user = get_userdata($uid);
|
|
$pax_user = get_userdata($pass_id);
|
|
$wpdb->insert('wp_ab_trips', [
|
|
'trip_uuid' => uniqid('sim_', true),
|
|
'driver_id' => $uid,
|
|
'passenger_id' => $pass_id,
|
|
'driver_name' => $drv_user ? $drv_user->display_name : 'Conductor SIM',
|
|
'passenger_name' => $pax_user ? $pax_user->display_name : 'Pasajero SIM',
|
|
'status' => 'in_progress',
|
|
'pickup_lat' => $wps[0][0],
|
|
'pickup_lng' => $wps[0][1],
|
|
'dropoff_lat' => $last[0],
|
|
'dropoff_lng' => $last[1],
|
|
'pickup_time' => $now,
|
|
'created_at' => $now,
|
|
]);
|
|
$trip_id = $wpdb->insert_id;
|
|
}
|
|
if ($trip_id) {
|
|
$wpdb->insert('wp_ab_trip_positions', [
|
|
'trip_id' => $trip_id,
|
|
'lat' => $lat,
|
|
'lng' => $lng,
|
|
'ts' => $now,
|
|
]);
|
|
}
|
|
}
|
|
|
|
$s['drivers'][] = [
|
|
'user_id' => $uid,
|
|
'route' => $route,
|
|
'step' => $step ?? 0,
|
|
'trip_id' => $trip_id,
|
|
'db_status' => $db_status,
|
|
'lat' => $lat,
|
|
'lng' => $lng,
|
|
'deviated' => false,
|
|
];
|
|
}
|
|
}
|
|
|
|
/* ══════════════════════════════════════════════════════════════════════
|
|
* TICK — avanzar todos los vehículos un paso
|
|
* ══════════════════════════════════════════════════════════════════════ */
|
|
add_action(AB_SIM_CRON, 'ab_sim_tick');
|
|
|
|
function ab_sim_tick() {
|
|
global $wpdb;
|
|
$s = ab_sim_state();
|
|
if (!$s['running']) return;
|
|
|
|
$routes = ab_sim_routes();
|
|
$now = current_time('mysql');
|
|
|
|
foreach ($s['drivers'] as &$d) {
|
|
$route = $d['route'];
|
|
|
|
if ($route === 'FREE') {
|
|
// Micromovimiento para conductores disponibles
|
|
$d['lat'] += (mt_rand(-4, 4) * 0.0001);
|
|
$d['lng'] += (mt_rand(-4, 4) * 0.0001);
|
|
$wpdb->update('wp_ab_driver_status',
|
|
['lat' => $d['lat'], 'lng' => $d['lng'], 'updated_at' => $now],
|
|
['user_id' => $d['user_id']]
|
|
);
|
|
continue;
|
|
}
|
|
|
|
if ($route === 'OFF') continue;
|
|
|
|
$wps = $routes[$route]['wps'];
|
|
$dev_start = $routes[$route]['deviation_start'] ?? PHP_INT_MAX;
|
|
$dev_path = $routes[$route]['deviation_path'] ?? [];
|
|
|
|
$is_deviating = $s['deviation'] && $route === 'C' && $d['step'] >= $dev_start;
|
|
|
|
if ($is_deviating) {
|
|
$di = $d['step'] - $dev_start;
|
|
|
|
if (isset($dev_path[$di])) {
|
|
[$d['lat'], $d['lng']] = $dev_path[$di];
|
|
|
|
// Primera vez que desvía: registrar separación pasajero-conductor
|
|
if (!$d['deviated'] && $d['trip_id']) {
|
|
$sep = $wps[$dev_start - 1] ?? $wps[0]; // último punto en ruta normal
|
|
$trip_row = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT dropoff_lat, dropoff_lng, passenger_name FROM wp_ab_trips WHERE id = %d", $d['trip_id']
|
|
));
|
|
$dist_dest = ($trip_row && $trip_row->dropoff_lat)
|
|
? ab_sim_haversine($sep[0], $sep[1], $trip_row->dropoff_lat, $trip_row->dropoff_lng)
|
|
: 0;
|
|
$wpdb->insert('wp_ab_incidents', [
|
|
'trip_id' => $d['trip_id'],
|
|
'user_id' => $d['user_id'],
|
|
'type' => 'separacion_sospechosa',
|
|
'description' => sprintf(
|
|
'[SIM] Conductor abandonó ruta. Pasajero %s último punto conocido: %.5f, %.5f (a %.1f km del destino contratado). Conductor ahora en: %.5f, %.5f.',
|
|
$trip_row->passenger_name ?? 'desconocido',
|
|
$sep[0], $sep[1], $dist_dest,
|
|
$d['lat'], $d['lng']
|
|
),
|
|
'lat' => $d['lat'],
|
|
'lng' => $d['lng'],
|
|
'status' => 'open',
|
|
'created_at' => $now,
|
|
]);
|
|
$d['deviated'] = true;
|
|
}
|
|
$d['step']++;
|
|
|
|
} else {
|
|
// Agotó la ruta de desvío — cerrar viaje con alerta de separación fuera del destino
|
|
if ($d['trip_id']) {
|
|
$trip_row = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT dropoff_lat, dropoff_lng, passenger_name FROM wp_ab_trips WHERE id = %d", $d['trip_id']
|
|
));
|
|
$sep = $wps[$dev_start - 1] ?? $wps[0];
|
|
if ($trip_row && $trip_row->dropoff_lat) {
|
|
$dist = ab_sim_haversine($sep[0], $sep[1], $trip_row->dropoff_lat, $trip_row->dropoff_lng);
|
|
$wpdb->insert('wp_ab_incidents', [
|
|
'trip_id' => $d['trip_id'],
|
|
'user_id' => $d['user_id'],
|
|
'type' => 'separacion_destino_incorrecto',
|
|
'description' => sprintf(
|
|
'[SIM] Viaje cerrado fuera del destino. Pasajero %s separado a %.1f km del destino contratado (%.5f, %.5f). Punto de separación: %.5f, %.5f.',
|
|
$trip_row->passenger_name ?? 'desconocido',
|
|
$dist,
|
|
$trip_row->dropoff_lat, $trip_row->dropoff_lng,
|
|
$sep[0], $sep[1]
|
|
),
|
|
'lat' => $sep[0],
|
|
'lng' => $sep[1],
|
|
'status' => 'open',
|
|
'created_at' => $now,
|
|
]);
|
|
}
|
|
$wpdb->update('wp_ab_trips',
|
|
['status' => 'completed', 'completed_at' => $now],
|
|
['id' => $d['trip_id']]
|
|
);
|
|
$d['trip_id'] = null;
|
|
}
|
|
$d['step'] = 0;
|
|
$d['deviated'] = false;
|
|
$d['db_status'] = 'available';
|
|
[$d['lat'], $d['lng']] = $wps[0];
|
|
$wpdb->update('wp_ab_driver_status',
|
|
['last_lat' => $d['lat'], 'last_lng' => $d['lng'], 'online' => 1, 'last_seen' => $now],
|
|
['driver_id' => $d['user_id']]
|
|
);
|
|
continue;
|
|
}
|
|
|
|
} else {
|
|
$next = $d['step'] + 1;
|
|
if ($next >= count($wps)) {
|
|
// Llegó al destino — verificar si coincide con el punto contratado
|
|
if ($d['trip_id']) {
|
|
$trip_row = $wpdb->get_row($wpdb->prepare(
|
|
"SELECT dropoff_lat, dropoff_lng, passenger_name FROM wp_ab_trips WHERE id = %d", $d['trip_id']
|
|
));
|
|
if ($trip_row && $trip_row->dropoff_lat) {
|
|
$dist = ab_sim_haversine($d['lat'], $d['lng'], $trip_row->dropoff_lat, $trip_row->dropoff_lng);
|
|
if ($dist > 0.5) {
|
|
$wpdb->insert('wp_ab_incidents', [
|
|
'trip_id' => $d['trip_id'],
|
|
'user_id' => $d['user_id'],
|
|
'type' => 'separacion_fuera_destino',
|
|
'description' => sprintf(
|
|
'[SIM] Viaje completado a %.1f km del destino contratado. Pasajero %s separado en: %.5f, %.5f. Destino original: %.5f, %.5f.',
|
|
$dist,
|
|
$trip_row->passenger_name ?? 'desconocido',
|
|
$d['lat'], $d['lng'],
|
|
$trip_row->dropoff_lat, $trip_row->dropoff_lng
|
|
),
|
|
'lat' => $d['lat'],
|
|
'lng' => $d['lng'],
|
|
'status' => 'open',
|
|
'created_at' => $now,
|
|
]);
|
|
}
|
|
}
|
|
$wpdb->update('wp_ab_trips',
|
|
['status' => 'completed', 'completed_at' => $now],
|
|
['id' => $d['trip_id']]
|
|
);
|
|
$d['trip_id'] = null;
|
|
}
|
|
$d['step'] = 0;
|
|
$d['deviated'] = false;
|
|
$d['db_status'] = 'available';
|
|
[$d['lat'], $d['lng']] = $wps[0];
|
|
$wpdb->update('wp_ab_driver_status',
|
|
['last_lat' => $d['lat'], 'last_lng' => $d['lng'], 'online' => 1, 'last_seen' => $now],
|
|
['driver_id' => $d['user_id']]
|
|
);
|
|
continue;
|
|
}
|
|
$d['step'] = $next;
|
|
[$d['lat'], $d['lng']] = $wps[$next];
|
|
}
|
|
|
|
$wpdb->update('wp_ab_driver_status',
|
|
['last_lat' => $d['lat'], 'last_lng' => $d['lng'], 'last_seen' => $now],
|
|
['driver_id' => $d['user_id']]
|
|
);
|
|
|
|
if ($d['trip_id']) {
|
|
$wpdb->insert('wp_ab_trip_positions', [
|
|
'trip_id' => $d['trip_id'],
|
|
'lat' => $d['lat'],
|
|
'lng' => $d['lng'],
|
|
'ts' => $now,
|
|
]);
|
|
}
|
|
}
|
|
unset($d);
|
|
|
|
$s['last_tick'] = time();
|
|
ab_sim_save($s);
|
|
}
|
|
|
|
/* ══════════════════════════════════════════════════════════════════════
|
|
* CRON — intervalo de 30 segundos
|
|
* ══════════════════════════════════════════════════════════════════════ */
|
|
add_filter('cron_schedules', function ($sc) {
|
|
$sc['ab_sim_30s'] = ['interval' => 30, 'display' => 'Cada 30s (AutoBooking Sim)'];
|
|
return $sc;
|
|
});
|
|
|
|
register_deactivation_hook(__FILE__, function () {
|
|
wp_clear_scheduled_hook(AB_SIM_CRON);
|
|
});
|