<?php
// rcontrol/includes/adms_helpers.php
require_once dirname(__FILE__) . '/db.php';

define('RCONTROL_BASE_DIR', dirname(dirname(__FILE__)));
define('RCONTROL_LOG_DIR', RCONTROL_BASE_DIR . '/logs');

if (!is_dir(RCONTROL_LOG_DIR)) {
    mkdir(RCONTROL_LOG_DIR, 0775, true);
}

function rcontrol_text_response($body, $status_code)
{
    if ($status_code == 200) {
        header('HTTP/1.1 200 OK');
    } elseif ($status_code == 403) {
        header('HTTP/1.1 403 Forbidden');
    } elseif ($status_code == 500) {
        header('HTTP/1.1 500 Internal Server Error');
    }

    header('Content-Type: text/plain; charset=utf-8');
    echo $body;
    exit;
}

function rcontrol_get_daily_log_path()
{
    return RCONTROL_LOG_DIR . '/adms_raw_' . date('Y-m-d') . '.log';
}

function rcontrol_get_asistencia_log_path()
{
    return RCONTROL_LOG_DIR . '/asistencias_' . date('Y-m-d') . '.log';
}

function rcontrol_get_headers()
{
    $headers = array();

    foreach ($_SERVER as $key => $value) {
        if (substr($key, 0, 5) == 'HTTP_') {
            $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))));
            $headers[$name] = $value;
        }
    }

    if (isset($_SERVER['CONTENT_TYPE'])) {
        $headers['Content-Type'] = $_SERVER['CONTENT_TYPE'];
    }

    if (isset($_SERVER['CONTENT_LENGTH'])) {
        $headers['Content-Length'] = $_SERVER['CONTENT_LENGTH'];
    }

    return $headers;
}

function rcontrol_get_request_data($title)
{
    $raw_body = file_get_contents('php://input');

    $data = array(
        'time' => date('Y-m-d H:i:s'),
        'title' => $title,
        'remote_addr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '',
        'method' => isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : '',
        'path' => isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '',
        'query_string' => isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '',
        'args' => $_GET,
        'headers' => rcontrol_get_headers(),
        'body' => $raw_body
    );

    return $data;
}

function rcontrol_write_log($data)
{
    $line = json_encode($data) . "\n";
    file_put_contents(rcontrol_get_daily_log_path(), $line, FILE_APPEND);
}

function rcontrol_log_event($title)
{
    $data = rcontrol_get_request_data($title);
    rcontrol_write_log($data);

    error_log("\n========== ADMS REQUEST ==========");
    error_log(print_r($data, true));
    error_log("==================================\n");

    return $data;
}

function rcontrol_get_reloj_by_sn($sn)
{
    if ($sn === null || $sn === '') {
        return false;
    }

    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return false;
    }

    $sql = "
        SELECT 
            id,
            empresa_id,
            sede_id,
            nombre,
            serial_number,
            ip_esperada,
            modelo,
            user_agent,
            version_firmware,
            activo,
            ultimo_contacto_at
        FROM relojes
        WHERE serial_number = ?
          AND activo = 1
        LIMIT 1
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        error_log('RCONTROL DB PREPARE ERROR rcontrol_get_reloj_by_sn: ' . $mysqli->error);
        rcontrol_db_close($mysqli);
        return false;
    }

    $stmt->bind_param('s', $sn);
    $stmt->execute();

    $result = $stmt->get_result();

    if (!$result) {
        error_log('RCONTROL DB RESULT ERROR rcontrol_get_reloj_by_sn: ' . $stmt->error);
        $stmt->close();
        rcontrol_db_close($mysqli);
        return false;
    }

    $row = $result->fetch_assoc();

    $result->free();
    $stmt->close();
    rcontrol_db_close($mysqli);

    if (!$row) {
        return false;
    }

    return $row;
}

function rcontrol_update_ultimo_contacto($reloj_id)
{
    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return false;
    }

    $sql = "
        UPDATE relojes
        SET ultimo_contacto_at = NOW()
        WHERE id = ?
        LIMIT 1
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        error_log('RCONTROL DB PREPARE ERROR rcontrol_update_ultimo_contacto: ' . $mysqli->error);
        rcontrol_db_close($mysqli);
        return false;
    }

    $stmt->bind_param('i', $reloj_id);
    $ok = $stmt->execute();

    if (!$ok) {
        error_log('RCONTROL DB EXECUTE ERROR rcontrol_update_ultimo_contacto: ' . $stmt->error);
    }

    $stmt->close();
    rcontrol_db_close($mysqli);

    return $ok;
}

function rcontrol_parse_attlog_body($raw_body)
{
    $asistencias = array();
    $lines = preg_split('/\r\n|\r|\n/', $raw_body);

    foreach ($lines as $line) {
        $line = trim($line);

        if ($line === '') {
            continue;
        }

        $parts = explode("\t", $line);

        $pin = isset($parts[0]) ? $parts[0] : '';
        $fecha_hora = isset($parts[1]) ? $parts[1] : '';
        $estado = isset($parts[2]) ? $parts[2] : '';
        $verificacion = isset($parts[3]) ? $parts[3] : '';

        $fecha = '';
        $hora = '';

        if (strpos($fecha_hora, ' ') !== false) {
            $tmp = explode(' ', $fecha_hora, 2);
            $fecha = isset($tmp[0]) ? $tmp[0] : '';
            $hora = isset($tmp[1]) ? $tmp[1] : '';
        }

        $asistencias[] = array(
            'pin' => $pin,
            'fecha_hora' => $fecha_hora,
            'fecha' => $fecha,
            'hora' => $hora,
            'estado' => $estado,
            'verificacion' => $verificacion,
            'raw' => $line
        );
    }

    return $asistencias;
}

function rcontrol_get_first_pin_from_data($data)
{
    $args = isset($data['args']) ? $data['args'] : array();
    $table = isset($args['table']) ? $args['table'] : '';
    $raw_body = isset($data['body']) ? $data['body'] : '';

    if ($table != 'ATTLOG') {
        return null;
    }

    $asistencias = rcontrol_parse_attlog_body($raw_body);

    if (count($asistencias) == 0) {
        return null;
    }

    return $asistencias[0]['pin'];
}

function rcontrol_insert_asistencia_raw($data, $reloj)
{
    $args = isset($data['args']) ? $data['args'] : array();
    $headers = isset($data['headers']) ? $data['headers'] : array();

    $reloj_id = null;
    $empresa_id = null;
    $sede_id = null;

    if ($reloj && isset($reloj['id'])) {
        $reloj_id = (int)$reloj['id'];
        $empresa_id = (int)$reloj['empresa_id'];
        $sede_id = (int)$reloj['sede_id'];
    }

    $serial_number = isset($args['SN']) ? $args['SN'] : '';
    $pin_reloj = rcontrol_get_first_pin_from_data($data);

    $tabla = isset($args['table']) ? $args['table'] : null;
    $stamp = isset($args['Stamp']) ? $args['Stamp'] : null;

    $metodo_http = isset($data['method']) ? $data['method'] : '';
    $ruta = isset($data['path']) ? $data['path'] : '';
    $query_string = isset($data['query_string']) ? $data['query_string'] : '';

    $ip_origen = isset($data['remote_addr']) ? $data['remote_addr'] : '';
    $user_agent = isset($headers['User-Agent']) ? $headers['User-Agent'] : '';

    $body_raw = isset($data['body']) ? $data['body'] : '';
    $headers_json = json_encode($headers);

    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return false;
    }

    $sql = "
        INSERT INTO asistencias_raw (
            reloj_id,
            empresa_id,
            sede_id,
            serial_number,
            pin_reloj,
            tabla,
            stamp,
            metodo_http,
            ruta,
            query_string,
            ip_origen,
            user_agent,
            body_raw,
            headers_json
        ) VALUES (
            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
        )
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        error_log('RCONTROL DB PREPARE ERROR rcontrol_insert_asistencia_raw: ' . $mysqli->error);
        rcontrol_db_close($mysqli);
        return false;
    }

    $stmt->bind_param(
        'iiisssssssssss',
        $reloj_id,
        $empresa_id,
        $sede_id,
        $serial_number,
        $pin_reloj,
        $tabla,
        $stamp,
        $metodo_http,
        $ruta,
        $query_string,
        $ip_origen,
        $user_agent,
        $body_raw,
        $headers_json
    );

    $ok = $stmt->execute();

    if (!$ok) {
        error_log('RCONTROL DB EXECUTE ERROR rcontrol_insert_asistencia_raw: ' . $stmt->error);
        $stmt->close();
        rcontrol_db_close($mysqli);
        return false;
    }

    $raw_id = $stmt->insert_id;

    $stmt->close();
    rcontrol_db_close($mysqli);

    return $raw_id;
}

function rcontrol_get_trabajador_by_reloj_pin($reloj_id, $pin_reloj)
{
    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return false;
    }

    $sql = "
        SELECT
            rt.id AS reloj_trabajador_id,
            rt.trabajador_id,
            rt.pin_reloj,
            rt.nombre_reloj,
            t.nombres,
            t.apellidos
        FROM reloj_trabajadores rt
        INNER JOIN trabajadores t
            ON t.id = rt.trabajador_id
            AND t.activo = 1
        WHERE rt.reloj_id = ?
          AND rt.pin_reloj = ?
          AND rt.activo = 1
        LIMIT 1
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        error_log('RCONTROL DB PREPARE ERROR rcontrol_get_trabajador_by_reloj_pin: ' . $mysqli->error);
        rcontrol_db_close($mysqli);
        return false;
    }

    $stmt->bind_param('is', $reloj_id, $pin_reloj);
    $stmt->execute();

    $result = $stmt->get_result();

    if (!$result) {
        error_log('RCONTROL DB RESULT ERROR rcontrol_get_trabajador_by_reloj_pin: ' . $stmt->error);
        $stmt->close();
        rcontrol_db_close($mysqli);
        return false;
    }

    $row = $result->fetch_assoc();

    $result->free();
    $stmt->close();
    rcontrol_db_close($mysqli);

    if (!$row) {
        return false;
    }

    return $row;
}

function rcontrol_marcar_raw_procesado($raw_id, $procesado, $error_proceso)
{
    if (!$raw_id) {
        return false;
    }

    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return false;
    }

    $sql = "
        UPDATE asistencias_raw
        SET procesado = ?,
            procesado_at = NOW(),
            error_proceso = ?
        WHERE id = ?
        LIMIT 1
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        error_log('RCONTROL DB PREPARE ERROR rcontrol_marcar_raw_procesado: ' . $mysqli->error);
        rcontrol_db_close($mysqli);
        return false;
    }

    $stmt->bind_param('isi', $procesado, $error_proceso, $raw_id);
    $ok = $stmt->execute();

    if (!$ok) {
        error_log('RCONTROL DB EXECUTE ERROR rcontrol_marcar_raw_procesado: ' . $stmt->error);
    }

    $stmt->close();
    rcontrol_db_close($mysqli);

    return $ok;
}

function rcontrol_insert_asistencia_procesada($raw_id, $reloj, $asistencia)
{
    $pin_reloj = isset($asistencia['pin']) ? $asistencia['pin'] : '';
    $fecha_hora = isset($asistencia['fecha_hora']) ? $asistencia['fecha_hora'] : '';
    $fecha = isset($asistencia['fecha']) ? $asistencia['fecha'] : '';
    $hora = isset($asistencia['hora']) ? $asistencia['hora'] : '';
    $estado_reloj = isset($asistencia['estado']) ? $asistencia['estado'] : '';
    $metodo_verificacion = isset($asistencia['verificacion']) ? $asistencia['verificacion'] : '';

    if ($pin_reloj === '' || $pin_reloj === '0') {
        return array(
            'ok' => true,
            'ignorado' => true,
            'error' => null
        );
    }
    
    if ($fecha_hora === '' || $fecha === '' || $hora === '') {
        return array(
            'ok' => false,
            'error' => 'ATTLOG incompleto'
        );
    }

    $trabajador = rcontrol_get_trabajador_by_reloj_pin((int)$reloj['id'], $pin_reloj);

    $trabajador_id = null;
    $asociado = 0;
    $observacion = 'PIN no asociado';

    if ($trabajador && isset($trabajador['trabajador_id'])) {
        $trabajador_id = (int)$trabajador['trabajador_id'];
        $asociado = 1;
        $observacion = 'Marca procesada desde ATTLOG';
    }

    if ((string)$estado_reloj === '1') {
        $tipo_marca = 'Salida';
    } else {
        $tipo_marca = 'Entrada';
    }
    $hash_origen = null;

    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return array(
            'ok' => false,
            'error' => 'Error conexión BD'
        );
    }

    $sql = "
        INSERT IGNORE INTO asistencias (
            raw_id,
            empresa_id,
            sede_id,
            reloj_id,
            trabajador_id,
            serial_number,
            pin_reloj,
            fecha_hora,
            fecha,
            hora,
            tipo_marca,
            estado_reloj,
            metodo_verificacion,
            hash_origen,
            asociado,
            observacion
        ) VALUES (
            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
        )
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        $error = $mysqli->error;
        rcontrol_db_close($mysqli);

        return array(
            'ok' => false,
            'error' => 'Prepare asistencia: ' . $error
        );
    }

    $empresa_id = (int)$reloj['empresa_id'];
    $sede_id = (int)$reloj['sede_id'];
    $reloj_id = (int)$reloj['id'];
    $serial_number = $reloj['serial_number'];

    $stmt->bind_param(
        'iiiiisssssssssis',
        $raw_id,
        $empresa_id,
        $sede_id,
        $reloj_id,
        $trabajador_id,
        $serial_number,
        $pin_reloj,
        $fecha_hora,
        $fecha,
        $hora,
        $tipo_marca,
        $estado_reloj,
        $metodo_verificacion,
        $hash_origen,
        $asociado,
        $observacion
    );

    $ok = $stmt->execute();

    if (!$ok) {
        $error = $stmt->error;
        $stmt->close();
        rcontrol_db_close($mysqli);

        return array(
            'ok' => false,
            'error' => 'Execute asistencia: ' . $error
        );
    }

    $affected_rows = $stmt->affected_rows;

    $stmt->close();
    rcontrol_db_close($mysqli);

    return array(
        'ok' => true,
        'duplicado' => ($affected_rows == 0),
        'asociado' => $asociado,
        'trabajador_id' => $trabajador_id
    );
}

function rcontrol_procesar_attlog($raw_id, $data, $reloj)
{
    $raw_body = isset($data['body']) ? $data['body'] : '';
    $asistencias = rcontrol_parse_attlog_body($raw_body);

    if (count($asistencias) == 0) {
        rcontrol_marcar_raw_procesado($raw_id, 0, 'ATTLOG sin líneas válidas');
        return false;
    }

    $errores = array();

    foreach ($asistencias as $asistencia) {
        $resultado = rcontrol_insert_asistencia_procesada($raw_id, $reloj, $asistencia);

        if (!$resultado['ok']) {
            $errores[] = $resultado['error'];
        }
    }

    if (count($errores) > 0) {
        rcontrol_marcar_raw_procesado($raw_id, 0, implode(' | ', $errores));
        return false;
    }

    rcontrol_marcar_raw_procesado($raw_id, 1, null);
    return true;
}

function rcontrol_reject_unauthorized($sn)
{
    $data = rcontrol_log_event('UNAUTHORIZED SN=' . $sn);
    rcontrol_insert_asistencia_raw($data, false);

    error_log("\n========== ADMS REJECTED ==========");
    error_log('SN no autorizado: ' . $sn);
    error_log(print_r($data, true));
    error_log("===================================\n");

    rcontrol_text_response("UNAUTHORIZED\n", 403);
}

function rcontrol_write_asistencia_log($data)
{
    $args = isset($data['args']) ? $data['args'] : array();

    $sn = isset($args['SN']) ? $args['SN'] : '';
    $table = isset($args['table']) ? $args['table'] : '';
    $raw_body = isset($data['body']) ? $data['body'] : '';

    $asistencias = rcontrol_parse_attlog_body($raw_body);

    if (count($asistencias) == 0) {
        return;
    }

    $log_path = rcontrol_get_asistencia_log_path();

    foreach ($asistencias as $asistencia) {
        $txt = '';
        $txt .= str_repeat('=', 80) . "\n";
        $txt .= "ASISTENCIA RECIBIDA\n";
        $txt .= str_repeat('=', 80) . "\n";
        $txt .= "Recibido servidor : " . (isset($data['time']) ? $data['time'] : '') . "\n";
        $txt .= "Reloj SN          : " . $sn . "\n";
        $txt .= "IP reloj/origen   : " . (isset($data['remote_addr']) ? $data['remote_addr'] : '') . "\n";
        $txt .= "Usuario/PIN       : " . $asistencia['pin'] . "\n";
        $txt .= "Fecha asistencia  : " . $asistencia['fecha'] . "\n";
        $txt .= "Hora asistencia   : " . $asistencia['hora'] . "\n";
        $txt .= "Estado            : " . $asistencia['estado'] . "\n";
        $txt .= "Verificación      : " . $asistencia['verificacion'] . "\n";
        $txt .= "Tabla             : " . $table . "\n";
        $txt .= "Raw               : " . $asistencia['raw'] . "\n";
        $txt .= str_repeat('=', 80) . "\n\n";

        file_put_contents($log_path, $txt, FILE_APPEND);
    }
}

function rcontrol_get_pending_command_for_reloj($reloj_id)
{
    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return false;
    }

    $sql = "
        SELECT
            id,
            empresa_id,
            sede_id,
            reloj_id,
            trabajador_id,
            pin_reloj,
            tipo_comando,
            comando,
            estado,
            intentos
        FROM reloj_comandos
        WHERE reloj_id = ?
          AND estado = 'pendiente'
        ORDER BY id ASC
        LIMIT 1
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        error_log('RCONTROL DB PREPARE ERROR rcontrol_get_pending_command_for_reloj: ' . $mysqli->error);
        rcontrol_db_close($mysqli);
        return false;
    }

    $stmt->bind_param('i', $reloj_id);
    $stmt->execute();

    $result = $stmt->get_result();

    if (!$result) {
        error_log('RCONTROL DB RESULT ERROR rcontrol_get_pending_command_for_reloj: ' . $stmt->error);
        $stmt->close();
        rcontrol_db_close($mysqli);
        return false;
    }

    $row = $result->fetch_assoc();

    $result->free();
    $stmt->close();
    rcontrol_db_close($mysqli);

    if (!$row) {
        return false;
    }

    return $row;
}

function rcontrol_mark_command_sent($comando_id)
{
    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return false;
    }

    $sql = "
        UPDATE reloj_comandos
        SET
            estado = 'enviado',
            intentos = intentos + 1,
            enviado_at = NOW()
        WHERE id = ?
          AND estado = 'pendiente'
        LIMIT 1
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        error_log('RCONTROL DB PREPARE ERROR rcontrol_mark_command_sent: ' . $mysqli->error);
        rcontrol_db_close($mysqli);
        return false;
    }

    $stmt->bind_param('i', $comando_id);
    $ok = $stmt->execute();

    if (!$ok) {
        error_log('RCONTROL DB EXECUTE ERROR rcontrol_mark_command_sent: ' . $stmt->error);
    }

    $stmt->close();
    rcontrol_db_close($mysqli);

    return $ok;
}

function rcontrol_extract_devicecmd_result($data)
{
    $raw_body = isset($data['body']) ? trim($data['body']) : '';
    $args = isset($data['args']) ? $data['args'] : array();

    $result = array(
        'comando_id' => null,
        'return_code' => null,
        'raw_response' => $raw_body
    );

    if (isset($args['ID'])) {
        $result['comando_id'] = $args['ID'];
    }

    if (isset($args['Return'])) {
        $result['return_code'] = $args['Return'];
    }

    $parsed_body = array();

    if ($raw_body !== '') {
        parse_str($raw_body, $parsed_body);

        if (isset($parsed_body['ID']) && $result['comando_id'] === null) {
            $result['comando_id'] = $parsed_body['ID'];
        }

        if (isset($parsed_body['Return']) && $result['return_code'] === null) {
            $result['return_code'] = $parsed_body['Return'];
        }

        if ($result['comando_id'] === null && preg_match('/(?:^|[^0-9])ID=([0-9]+)/', $raw_body, $matches)) {
            $result['comando_id'] = $matches[1];
        }

        if ($result['return_code'] === null && preg_match('/(?:^|[^A-Za-z])Return=([^&\s]+)/', $raw_body, $matches)) {
            $result['return_code'] = $matches[1];
        }

        if ($result['comando_id'] === null && preg_match('/^([0-9]+)/', $raw_body, $matches)) {
            $result['comando_id'] = $matches[1];
        }
    }

    return $result;
}

function rcontrol_mark_command_device_response($comando_id, $respuesta_reloj, $return_code)
{
    if ($comando_id === null || $comando_id === '') {
        return false;
    }

    $estado = 'confirmado';
    $error = null;

    if ($return_code !== null && $return_code !== '' && (string)$return_code !== '0') {
        $estado = 'error';
        $error = 'Return=' . $return_code;
    }

    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return false;
    }

    $sql = "
        UPDATE reloj_comandos
        SET
            estado = ?,
            respuesta_reloj = ?,
            error = ?,
            confirmado_at = NOW()
        WHERE id = ?
        LIMIT 1
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        error_log('RCONTROL DB PREPARE ERROR rcontrol_mark_command_device_response: ' . $mysqli->error);
        rcontrol_db_close($mysqli);
        return false;
    }

    $comando_id_int = (int)$comando_id;

    $stmt->bind_param(
        'sssi',
        $estado,
        $respuesta_reloj,
        $error,
        $comando_id_int
    );

    $ok = $stmt->execute();

    if (!$ok) {
        error_log('RCONTROL DB EXECUTE ERROR rcontrol_mark_command_device_response: ' . $stmt->error);
    }

    $stmt->close();
    rcontrol_db_close($mysqli);

    return $ok;
}

function rcontrol_parse_key_value_tabs($line, $prefix)
{
    $line = trim($line);

    if ($line === '') {
        return false;
    }

    if (strpos($line, $prefix . ' ') !== 0) {
        return false;
    }

    $line_without_prefix = trim(substr($line, strlen($prefix)));
    $parts = explode("\t", $line_without_prefix);

    $data = array();

    foreach ($parts as $part) {
        $part = trim($part);

        if ($part === '') {
            continue;
        }

        if (strpos($part, '=') === false) {
            continue;
        }

        $kv = explode('=', $part, 2);
        $key = isset($kv[0]) ? trim($kv[0]) : '';
        $value = isset($kv[1]) ? $kv[1] : '';

        if ($key !== '') {
            $data[$key] = $value;
        }
    }

    return $data;
}

function rcontrol_parse_operlog_fp_body($raw_body)
{
    $huellas = array();
    $lines = preg_split('/\r\n|\r|\n/', $raw_body);

    foreach ($lines as $line) {
        $line = trim($line);

        if ($line === '') {
            continue;
        }

        $data = rcontrol_parse_key_value_tabs($line, 'FP');

        if (!$data) {
            continue;
        }

        $pin = isset($data['PIN']) ? trim($data['PIN']) : '';
        $fid = isset($data['FID']) ? trim($data['FID']) : '';
        $size = isset($data['Size']) ? trim($data['Size']) : '';
        $valid = isset($data['Valid']) ? trim($data['Valid']) : '1';
        $template = isset($data['TMP']) ? trim($data['TMP']) : '';

        if ($pin === '' || $fid === '' || $template === '') {
            continue;
        }

        $huellas[] = array(
            'pin_reloj' => $pin,
            'fid' => $fid,
            'size' => ($size !== '' ? (int)$size : null),
            'valid' => ($valid !== '' ? (int)$valid : 1),
            'template' => $template,
            'raw_line' => $line
        );
    }

    return $huellas;
}

function rcontrol_upsert_huella_reloj($reloj, $huella)
{
    $pin_reloj = isset($huella['pin_reloj']) ? $huella['pin_reloj'] : '';
    $fid = isset($huella['fid']) ? $huella['fid'] : '';
    $size = isset($huella['size']) ? $huella['size'] : null;
    $valid = isset($huella['valid']) ? (int)$huella['valid'] : 1;
    $template = isset($huella['template']) ? $huella['template'] : '';
    $raw_line = isset($huella['raw_line']) ? $huella['raw_line'] : '';

    if ($pin_reloj === '' || $fid === '' || $template === '') {
        return array(
            'ok' => false,
            'error' => 'Huella incompleta'
        );
    }

    $trabajador = rcontrol_get_trabajador_by_reloj_pin((int)$reloj['id'], $pin_reloj);
    $trabajador_id = null;

    if ($trabajador && isset($trabajador['trabajador_id'])) {
        $trabajador_id = (int)$trabajador['trabajador_id'];
    }

    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return array(
            'ok' => false,
            'error' => 'Error conexión BD'
        );
    }

    $sql = "
        INSERT INTO reloj_huellas (
            empresa_id,
            sede_id,
            reloj_id,
            trabajador_id,
            pin_reloj,
            fid,
            size,
            valid,
            template,
            raw_line,
            origen,
            activo,
            capturado_at
        ) VALUES (
            ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'reloj', 1, NOW()
        )
        ON DUPLICATE KEY UPDATE
            trabajador_id = VALUES(trabajador_id),
            size = VALUES(size),
            valid = VALUES(valid),
            template = VALUES(template),
            raw_line = VALUES(raw_line),
            origen = 'reloj',
            activo = 1,
            capturado_at = NOW(),
            updated_at = NOW()
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        $error = $mysqli->error;
        rcontrol_db_close($mysqli);

        return array(
            'ok' => false,
            'error' => 'Prepare huella: ' . $error
        );
    }

    $empresa_id = (int)$reloj['empresa_id'];
    $sede_id = (int)$reloj['sede_id'];
    $reloj_id = (int)$reloj['id'];

    $stmt->bind_param(
        'iiiissiiss',
        $empresa_id,
        $sede_id,
        $reloj_id,
        $trabajador_id,
        $pin_reloj,
        $fid,
        $size,
        $valid,
        $template,
        $raw_line
    );

    $ok = $stmt->execute();

    if (!$ok) {
        $error = $stmt->error;
        $stmt->close();
        rcontrol_db_close($mysqli);

        return array(
            'ok' => false,
            'error' => 'Execute huella: ' . $error
        );
    }

    $stmt->close();
    rcontrol_db_close($mysqli);

    return array(
        'ok' => true,
        'trabajador_id' => $trabajador_id,
        'pin_reloj' => $pin_reloj,
        'fid' => $fid
    );
}

function rcontrol_procesar_operlog_huellas($raw_id, $data, $reloj)
{
    $raw_body = isset($data['body']) ? $data['body'] : '';
    $huellas = rcontrol_parse_operlog_fp_body($raw_body);

    if (count($huellas) == 0) {
        rcontrol_marcar_raw_procesado($raw_id, 1, 'OPERLOG sin huellas FP');
        return true;
    }

    $errores = array();

    foreach ($huellas as $huella) {
        $resultado = rcontrol_upsert_huella_reloj($reloj, $huella);

        if (!$resultado['ok']) {
            $errores[] = $resultado['error'];
        }
    }

    if (count($errores) > 0) {
        rcontrol_marcar_raw_procesado($raw_id, 0, implode(' | ', $errores));
        return false;
    }

    rcontrol_marcar_raw_procesado($raw_id, 1, null);
    return true;
}

function rcontrol_limpiar_nombre_reloj($nombre)
{
    $nombre = trim((string)$nombre);

    if ($nombre === '') {
        return 'Usuario';
    }

    $nombre = preg_replace('/[^\p{L}\p{N}\s\.\-_]/u', '', $nombre);
    $nombre = preg_replace('/\s+/', ' ', $nombre);
    $nombre = trim($nombre);

    if ($nombre === '') {
        return 'Usuario';
    }

    if (function_exists('mb_substr')) {
        return mb_substr($nombre, 0, 30, 'UTF-8');
    }

    return substr($nombre, 0, 30);
}

function rcontrol_crear_comando_reloj($empresa_id, $sede_id, $reloj_id, $trabajador_id, $pin_reloj, $tipo_comando, $comando)
{
    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return array(
            'ok' => false,
            'error' => 'Error conexión BD',
            'comando_id' => null
        );
    }

    $sql = "
        INSERT INTO reloj_comandos
        (
            empresa_id,
            sede_id,
            reloj_id,
            trabajador_id,
            pin_reloj,
            tipo_comando,
            comando,
            estado
        )
        VALUES
        (
            ?, ?, ?, ?, ?, ?, ?, 'pendiente'
        )
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        $error = $mysqli->error;
        rcontrol_db_close($mysqli);

        return array(
            'ok' => false,
            'error' => 'Prepare comando: ' . $error,
            'comando_id' => null
        );
    }

    $empresa_id = (int)$empresa_id;
    $sede_id = (int)$sede_id;
    $reloj_id = (int)$reloj_id;
    $trabajador_id_int = null;

    if ($trabajador_id !== null && $trabajador_id !== '') {
        $trabajador_id_int = (int)$trabajador_id;
    }

    $pin_reloj = (string)$pin_reloj;
    $tipo_comando = (string)$tipo_comando;
    $comando = (string)$comando;

    $stmt->bind_param(
        'iiiisss',
        $empresa_id,
        $sede_id,
        $reloj_id,
        $trabajador_id_int,
        $pin_reloj,
        $tipo_comando,
        $comando
    );

    $ok = $stmt->execute();

    if (!$ok) {
        $error = $stmt->error;
        $stmt->close();
        rcontrol_db_close($mysqli);

        return array(
            'ok' => false,
            'error' => 'Execute comando: ' . $error,
            'comando_id' => null
        );
    }

    $comando_id = $stmt->insert_id;

    $stmt->close();
    rcontrol_db_close($mysqli);

    return array(
        'ok' => true,
        'error' => null,
        'comando_id' => $comando_id
    );
}

function rcontrol_build_data_user_command($pin_reloj, $nombre_reloj)
{
    $pin_reloj = trim((string)$pin_reloj);
    $nombre_reloj = rcontrol_limpiar_nombre_reloj($nombre_reloj);

    return
        'DATA USER PIN=' . $pin_reloj .
        "\t" . 'Name=' . $nombre_reloj .
        "\t" . 'Pri=0' .
        "\t" . 'Passwd=' .
        "\t" . 'Card=' .
        "\t" . 'Grp=1' .
        "\t" . 'TZ=0001000100000000' .
        "\t" . 'Verify=0';
}

function rcontrol_crear_comando_usuario_reloj($reloj, $trabajador_id, $pin_reloj, $nombre_reloj)
{
    if (!$reloj || !isset($reloj['id'])) {
        return array(
            'ok' => false,
            'error' => 'Reloj inválido',
            'comando_id' => null
        );
    }

    $pin_reloj = trim((string)$pin_reloj);

    if ($pin_reloj === '') {
        return array(
            'ok' => false,
            'error' => 'PIN reloj vacío',
            'comando_id' => null
        );
    }

    $comando = rcontrol_build_data_user_command($pin_reloj, $nombre_reloj);

    return rcontrol_crear_comando_reloj(
        (int)$reloj['empresa_id'],
        (int)$reloj['sede_id'],
        (int)$reloj['id'],
        $trabajador_id,
        $pin_reloj,
        'crear_usuario',
        $comando
    );
}

function rcontrol_get_huellas_activas_trabajador($trabajador_id)
{
    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return array();
    }

    $sql = "
        SELECT
            id,
            empresa_id,
            sede_id,
            reloj_id,
            trabajador_id,
            pin_reloj,
            fid,
            size,
            valid,
            template,
            raw_line,
            origen,
            activo,
            capturado_at
        FROM reloj_huellas
        WHERE trabajador_id = ?
          AND activo = 1
        ORDER BY id ASC
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        error_log('RCONTROL DB PREPARE ERROR rcontrol_get_huellas_activas_trabajador: ' . $mysqli->error);
        rcontrol_db_close($mysqli);
        return array();
    }

    $trabajador_id = (int)$trabajador_id;

    $stmt->bind_param('i', $trabajador_id);
    $stmt->execute();

    $result = $stmt->get_result();

    if (!$result) {
        error_log('RCONTROL DB RESULT ERROR rcontrol_get_huellas_activas_trabajador: ' . $stmt->error);
        $stmt->close();
        rcontrol_db_close($mysqli);
        return array();
    }

    $rows = array();

    while ($row = $result->fetch_assoc()) {
        $rows[] = $row;
    }

    $result->free();
    $stmt->close();
    rcontrol_db_close($mysqli);

    return $rows;
}

function rcontrol_build_data_fp_command($pin_reloj, $huella)
{
    $pin_reloj = trim((string)$pin_reloj);

    $fid = isset($huella['fid']) ? trim((string)$huella['fid']) : '';
    $size = isset($huella['size']) ? (int)$huella['size'] : 0;
    $valid = isset($huella['valid']) ? (int)$huella['valid'] : 1;
    $template = isset($huella['template']) ? trim((string)$huella['template']) : '';

    if ($pin_reloj === '' || $fid === '' || $template === '') {
        return false;
    }

    return
        'DATA FP PIN=' . $pin_reloj .
        "\t" . 'FID=' . $fid .
        "\t" . 'Size=' . $size .
        "\t" . 'Valid=' . $valid .
        "\t" . 'TMP=' . $template;
}

function rcontrol_crear_comando_huella_reloj($reloj, $trabajador_id, $pin_destino, $huella)
{
    if (!$reloj || !isset($reloj['id'])) {
        return array(
            'ok' => false,
            'error' => 'Reloj inválido',
            'comando_id' => null
        );
    }

    $pin_destino = trim((string)$pin_destino);

    if ($pin_destino === '') {
        return array(
            'ok' => false,
            'error' => 'PIN destino vacío',
            'comando_id' => null
        );
    }

    $comando = rcontrol_build_data_fp_command($pin_destino, $huella);

    if (!$comando) {
        return array(
            'ok' => false,
            'error' => 'No se pudo construir comando DATA FP',
            'comando_id' => null
        );
    }

    return rcontrol_crear_comando_reloj(
        (int)$reloj['empresa_id'],
        (int)$reloj['sede_id'],
        (int)$reloj['id'],
        $trabajador_id,
        $pin_destino,
        'replicar_huella',
        $comando
    );
}

function rcontrol_crear_comandos_huellas_trabajador_reloj($reloj, $trabajador_id, $pin_destino)
{
    $huellas = rcontrol_get_huellas_activas_trabajador($trabajador_id);

    $resultado = array(
        'ok' => true,
        'total_huellas' => count($huellas),
        'comandos_creados' => 0,
        'errores' => array(),
        'comando_ids' => array()
    );

    if (count($huellas) == 0) {
        $resultado['ok'] = false;
        $resultado['errores'][] = 'Trabajador sin huellas activas';
        return $resultado;
    }

    foreach ($huellas as $huella) {
        $res = rcontrol_crear_comando_huella_reloj(
            $reloj,
            $trabajador_id,
            $pin_destino,
            $huella
        );

        if ($res['ok']) {
            $resultado['comandos_creados']++;
            $resultado['comando_ids'][] = $res['comando_id'];
        } else {
            $resultado['ok'] = false;
            $resultado['errores'][] = $res['error'];
        }
    }

    return $resultado;
}

function rcontrol_crear_comandos_usuario_y_huellas_reloj($reloj, $trabajador_id, $pin_reloj, $nombre_reloj)
{
    $resultado = array(
        'ok' => true,
        'usuario' => null,
        'huellas' => null,
        'errores' => array()
    );

    $usuario = rcontrol_crear_comando_usuario_reloj(
        $reloj,
        $trabajador_id,
        $pin_reloj,
        $nombre_reloj
    );

    $resultado['usuario'] = $usuario;

    if (!$usuario['ok']) {
        $resultado['ok'] = false;
        $resultado['errores'][] = $usuario['error'];
        return $resultado;
    }

    $huellas = rcontrol_crear_comandos_huellas_trabajador_reloj(
        $reloj,
        $trabajador_id,
        $pin_reloj
    );

    $resultado['huellas'] = $huellas;

    if (!$huellas['ok']) {
        $resultado['ok'] = false;

        foreach ($huellas['errores'] as $error) {
            $resultado['errores'][] = $error;
        }
    }

    return $resultado;
}

function rcontrol_get_vinculo_by_reloj_pin($reloj_id, $pin_reloj)
{
    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return false;
    }

    $sql = "
        SELECT
            id,
            empresa_id,
            sede_id,
            reloj_id,
            trabajador_id,
            pin_reloj,
            nombre_reloj,
            metodo_enrolamiento,
            fecha_enrolamiento,
            activo
        FROM reloj_trabajadores
        WHERE reloj_id = ?
          AND pin_reloj = ?
        LIMIT 1
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        error_log('RCONTROL DB PREPARE ERROR rcontrol_get_vinculo_by_reloj_pin: ' . $mysqli->error);
        rcontrol_db_close($mysqli);
        return false;
    }

    $reloj_id = (int)$reloj_id;
    $pin_reloj = (string)$pin_reloj;

    $stmt->bind_param('is', $reloj_id, $pin_reloj);
    $stmt->execute();

    $result = $stmt->get_result();

    if (!$result) {
        error_log('RCONTROL DB RESULT ERROR rcontrol_get_vinculo_by_reloj_pin: ' . $stmt->error);
        $stmt->close();
        rcontrol_db_close($mysqli);
        return false;
    }

    $row = $result->fetch_assoc();

    $result->free();
    $stmt->close();
    rcontrol_db_close($mysqli);

    return $row ? $row : false;
}

function rcontrol_get_vinculo_by_reloj_trabajador($reloj_id, $trabajador_id)
{
    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return false;
    }

    $sql = "
        SELECT
            id,
            empresa_id,
            sede_id,
            reloj_id,
            trabajador_id,
            pin_reloj,
            nombre_reloj,
            metodo_enrolamiento,
            fecha_enrolamiento,
            activo
        FROM reloj_trabajadores
        WHERE reloj_id = ?
          AND trabajador_id = ?
        LIMIT 1
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        error_log('RCONTROL DB PREPARE ERROR rcontrol_get_vinculo_by_reloj_trabajador: ' . $mysqli->error);
        rcontrol_db_close($mysqli);
        return false;
    }

    $reloj_id = (int)$reloj_id;
    $trabajador_id = (int)$trabajador_id;

    $stmt->bind_param('ii', $reloj_id, $trabajador_id);
    $stmt->execute();

    $result = $stmt->get_result();

    if (!$result) {
        error_log('RCONTROL DB RESULT ERROR rcontrol_get_vinculo_by_reloj_trabajador: ' . $stmt->error);
        $stmt->close();
        rcontrol_db_close($mysqli);
        return false;
    }

    $row = $result->fetch_assoc();

    $result->free();
    $stmt->close();
    rcontrol_db_close($mysqli);

    return $row ? $row : false;
}

function rcontrol_guardar_vinculo_reloj_trabajador($reloj, $trabajador_id, $pin_reloj, $nombre_reloj, $metodo_enrolamiento)
{
    if (!$reloj || !isset($reloj['id'])) {
        return array(
            'ok' => false,
            'error' => 'Reloj inválido',
            'vinculo_id' => null
        );
    }

    $trabajador_id = (int)$trabajador_id;
    $pin_reloj = trim((string)$pin_reloj);
    $nombre_reloj = rcontrol_limpiar_nombre_reloj($nombre_reloj);

    if ($trabajador_id <= 0) {
        return array(
            'ok' => false,
            'error' => 'Trabajador inválido',
            'vinculo_id' => null
        );
    }

    if ($pin_reloj === '') {
        return array(
            'ok' => false,
            'error' => 'PIN reloj vacío',
            'vinculo_id' => null
        );
    }

    $reloj_id = (int)$reloj['id'];

    $vinculo_pin = rcontrol_get_vinculo_by_reloj_pin($reloj_id, $pin_reloj);

    if ($vinculo_pin && (int)$vinculo_pin['trabajador_id'] !== $trabajador_id) {
        return array(
            'ok' => false,
            'error' => 'El PIN ya está usado por otro trabajador en este reloj',
            'vinculo_id' => (int)$vinculo_pin['id']
        );
    }

    $vinculo_trabajador = rcontrol_get_vinculo_by_reloj_trabajador($reloj_id, $trabajador_id);

    if ($vinculo_trabajador && (string)$vinculo_trabajador['pin_reloj'] !== $pin_reloj) {
        return array(
            'ok' => false,
            'error' => 'El trabajador ya tiene otro PIN asignado en este reloj',
            'vinculo_id' => (int)$vinculo_trabajador['id']
        );
    }

    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return array(
            'ok' => false,
            'error' => 'Error conexión BD',
            'vinculo_id' => null
        );
    }

    if ($vinculo_trabajador) {
        $sql = "
            UPDATE reloj_trabajadores
            SET
                nombre_reloj = ?,
                metodo_enrolamiento = ?,
                fecha_enrolamiento = NOW(),
                activo = 1
            WHERE id = ?
            LIMIT 1
        ";

        $stmt = $mysqli->prepare($sql);

        if (!$stmt) {
            $error = $mysqli->error;
            rcontrol_db_close($mysqli);

            return array(
                'ok' => false,
                'error' => 'Prepare update vínculo: ' . $error,
                'vinculo_id' => null
            );
        }

        $vinculo_id = (int)$vinculo_trabajador['id'];

        $stmt->bind_param(
            'ssi',
            $nombre_reloj,
            $metodo_enrolamiento,
            $vinculo_id
        );

        $ok = $stmt->execute();

        if (!$ok) {
            $error = $stmt->error;
            $stmt->close();
            rcontrol_db_close($mysqli);

            return array(
                'ok' => false,
                'error' => 'Execute update vínculo: ' . $error,
                'vinculo_id' => $vinculo_id
            );
        }

        $stmt->close();
        rcontrol_db_close($mysqli);

        return array(
            'ok' => true,
            'error' => null,
            'vinculo_id' => $vinculo_id
        );
    }

    $sql = "
        INSERT INTO reloj_trabajadores
        (
            empresa_id,
            sede_id,
            reloj_id,
            trabajador_id,
            pin_reloj,
            nombre_reloj,
            metodo_enrolamiento,
            fecha_enrolamiento,
            activo
        )
        VALUES
        (
            ?, ?, ?, ?, ?, ?, ?, NOW(), 1
        )
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        $error = $mysqli->error;
        rcontrol_db_close($mysqli);

        return array(
            'ok' => false,
            'error' => 'Prepare insert vínculo: ' . $error,
            'vinculo_id' => null
        );
    }

    $empresa_id = (int)$reloj['empresa_id'];
    $sede_id = (int)$reloj['sede_id'];

    $stmt->bind_param(
        'iiiisss',
        $empresa_id,
        $sede_id,
        $reloj_id,
        $trabajador_id,
        $pin_reloj,
        $nombre_reloj,
        $metodo_enrolamiento
    );

    $ok = $stmt->execute();

    if (!$ok) {
        $error = $stmt->error;
        $stmt->close();
        rcontrol_db_close($mysqli);

        return array(
            'ok' => false,
            'error' => 'Execute insert vínculo: ' . $error,
            'vinculo_id' => null
        );
    }

    $vinculo_id = $stmt->insert_id;

    $stmt->close();
    rcontrol_db_close($mysqli);

    return array(
        'ok' => true,
        'error' => null,
        'vinculo_id' => $vinculo_id
    );
}

function rcontrol_asociar_huellas_huerfanas_por_vinculo($reloj, $trabajador_id, $pin_reloj)
{
    if (!$reloj || !isset($reloj['id'])) {
        return array(
            'ok' => false,
            'actualizadas' => 0,
            'error' => 'Reloj inválido'
        );
    }

    $pin_reloj = trim((string)$pin_reloj);

    if ($trabajador_id <= 0 || $pin_reloj === '') {
        return array(
            'ok' => false,
            'actualizadas' => 0,
            'error' => 'Datos incompletos para asociar huellas'
        );
    }

    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return array(
            'ok' => false,
            'actualizadas' => 0,
            'error' => 'Error conexión BD'
        );
    }

    $sql = "
        UPDATE reloj_huellas
        SET
            trabajador_id = ?,
            updated_at = NOW()
        WHERE empresa_id = ?
          AND sede_id = ?
          AND reloj_id = ?
          AND pin_reloj = ?
          AND trabajador_id IS NULL
          AND activo = 1
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        $error = $mysqli->error;
        rcontrol_db_close($mysqli);

        return array(
            'ok' => false,
            'actualizadas' => 0,
            'error' => 'Prepare asociar huellas: ' . $error
        );
    }

    $empresa_id = (int)$reloj['empresa_id'];
    $sede_id = (int)$reloj['sede_id'];
    $reloj_id = (int)$reloj['id'];
    $trabajador_id = (int)$trabajador_id;

    $stmt->bind_param(
        'iiiis',
        $trabajador_id,
        $empresa_id,
        $sede_id,
        $reloj_id,
        $pin_reloj
    );

    $ok = $stmt->execute();

    if (!$ok) {
        $error = $stmt->error;
        $stmt->close();
        rcontrol_db_close($mysqli);

        return array(
            'ok' => false,
            'actualizadas' => 0,
            'error' => 'Execute asociar huellas: ' . $error
        );
    }

    $actualizadas = $stmt->affected_rows;

    $stmt->close();
    rcontrol_db_close($mysqli);

    return array(
        'ok' => true,
        'actualizadas' => $actualizadas,
        'error' => null
    );
}

function rcontrol_asociar_asistencias_huerfanas_por_vinculo($reloj, $trabajador_id, $pin_reloj)
{
    if (!$reloj || !isset($reloj['id'])) {
        return array(
            'ok' => false,
            'actualizadas' => 0,
            'error' => 'Reloj inválido'
        );
    }

    $pin_reloj = trim((string)$pin_reloj);

    if ($trabajador_id <= 0 || $pin_reloj === '') {
        return array(
            'ok' => false,
            'actualizadas' => 0,
            'error' => 'Datos incompletos para asociar asistencias'
        );
    }

    $mysqli = rcontrol_db_connect();

    if (!$mysqli) {
        return array(
            'ok' => false,
            'actualizadas' => 0,
            'error' => 'Error conexión BD'
        );
    }

    $sql = "
        UPDATE asistencias
        SET
            trabajador_id = ?,
            asociado = 1,
            observacion = 'Marca asociada desde reloj controlador'
        WHERE empresa_id = ?
          AND sede_id = ?
          AND reloj_id = ?
          AND pin_reloj = ?
          AND asociado = 0
          AND (trabajador_id IS NULL OR trabajador_id = 0)
    ";

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        $error = $mysqli->error;
        rcontrol_db_close($mysqli);

        return array(
            'ok' => false,
            'actualizadas' => 0,
            'error' => 'Prepare asociar asistencias: ' . $error
        );
    }

    $empresa_id = (int)$reloj['empresa_id'];
    $sede_id = (int)$reloj['sede_id'];
    $reloj_id = (int)$reloj['id'];
    $trabajador_id = (int)$trabajador_id;

    $stmt->bind_param(
        'iiiis',
        $trabajador_id,
        $empresa_id,
        $sede_id,
        $reloj_id,
        $pin_reloj
    );

    $ok = $stmt->execute();

    if (!$ok) {
        $error = $stmt->error;
        $stmt->close();
        rcontrol_db_close($mysqli);

        return array(
            'ok' => false,
            'actualizadas' => 0,
            'error' => 'Execute asociar asistencias: ' . $error
        );
    }

    $actualizadas = $stmt->affected_rows;

    $stmt->close();
    rcontrol_db_close($mysqli);

    return array(
        'ok' => true,
        'actualizadas' => $actualizadas,
        'error' => null
    );
}

function rcontrol_preparar_envio_trabajador_reloj($reloj, $trabajador_id, $pin_reloj, $nombre_reloj, $incluir_huellas)
{
    $resultado = array(
        'ok' => true,
        'vinculo' => null,
        'asistencias_asociadas' => null,
        'usuario' => null,
        'huellas' => null,
        'errores' => array()
    );

    $vinculo = rcontrol_guardar_vinculo_reloj_trabajador(
        $reloj,
        $trabajador_id,
        $pin_reloj,
        $nombre_reloj,
        'comando_adms'
    );

    $resultado['vinculo'] = $vinculo;

    if (!$vinculo['ok']) {
        $resultado['ok'] = false;
        $resultado['errores'][] = $vinculo['error'];
        return $resultado;
    }
    
    $huellas_asociadas = rcontrol_asociar_huellas_huerfanas_por_vinculo(
        $reloj,
        $trabajador_id,
        $pin_reloj
    );
    
    $resultado['huellas_asociadas'] = $huellas_asociadas;
    
    if (!$huellas_asociadas['ok']) {
        $resultado['ok'] = false;
        $resultado['errores'][] = $huellas_asociadas['error'];
        return $resultado;
    }
    
    $asistencias_asociadas = rcontrol_asociar_asistencias_huerfanas_por_vinculo(
        $reloj,
        $trabajador_id,
        $pin_reloj
    );
    
    $resultado['asistencias_asociadas'] = $asistencias_asociadas;
    
    if (!$asistencias_asociadas['ok']) {
        $resultado['ok'] = false;
        $resultado['errores'][] = $asistencias_asociadas['error'];
        return $resultado;
    }
    
    $usuario = rcontrol_crear_comando_usuario_reloj(
        $reloj,
        $trabajador_id,
        $pin_reloj,
        $nombre_reloj
    );

    $resultado['usuario'] = $usuario;

    if (!$usuario['ok']) {
        $resultado['ok'] = false;
        $resultado['errores'][] = $usuario['error'];
        return $resultado;
    }

    if ($incluir_huellas) {
        $huellas = rcontrol_crear_comandos_huellas_trabajador_reloj(
            $reloj,
            $trabajador_id,
            $pin_reloj
        );

        $resultado['huellas'] = $huellas;

        if (!$huellas['ok']) {
            $resultado['ok'] = false;

            foreach ($huellas['errores'] as $error) {
                $resultado['errores'][] = $error;
            }
        }
    }

    return $resultado;
}
