<?php
// includes/personas_general_helper.php
// Helper central para padrón general tipo RENIEC local.
// Orden correcto:
// participantes.php:
//   personas_general -> API RENIEC -> guardar en personas_general -> concurso_participantes
//
// asignar_salon.php:
//   concurso_participantes -> personas_general -> API RENIEC -> crear/asignar participante
//
// IMPORTANTE:
// Esta versión está ajustada para tu proyecto en:
// /concurso_nobel/dni/buscar_dni.php?dni=

if (!defined('PG_DNI_API_URL')) {
    define('PG_DNI_API_URL', '/concurso_nobel/dni/buscar_dni.php?dni=');
}

if (!defined('PG_API_CONNECT_TIMEOUT_SECONDS')) {
    define('PG_API_CONNECT_TIMEOUT_SECONDS', 1);
}

if (!defined('PG_API_TIMEOUT_SECONDS')) {
    define('PG_API_TIMEOUT_SECONDS', 2);
}

if (!function_exists('pg_mysqli_ok')) {
    function pg_mysqli_ok($conn)
    {
        return ($conn instanceof mysqli);
    }
}

if (!function_exists('pg_limpiar_texto')) {
    function pg_limpiar_texto($texto)
    {
        if ($texto === null) {
            return '';
        }

        $texto = (string)$texto;
        $texto = str_replace(array("\r", "\n", "\t"), ' ', $texto);
        $texto = preg_replace('/\s+/u', ' ', $texto);
        $texto = trim($texto);

        if (function_exists('mb_strtoupper')) {
            return mb_strtoupper($texto, 'UTF-8');
        }

        return strtoupper($texto);
    }
}

if (!function_exists('pg_sin_tildes')) {
    function pg_sin_tildes($texto)
    {
        $texto = (string)$texto;

        $buscar = array(
            'Á', 'É', 'Í', 'Ó', 'Ú', 'Ü', 'Ñ',
            'á', 'é', 'í', 'ó', 'ú', 'ü', 'ñ'
        );

        $reemplazar = array(
            'A', 'E', 'I', 'O', 'U', 'U', 'N',
            'a', 'e', 'i', 'o', 'u', 'u', 'n'
        );

        return str_replace($buscar, $reemplazar, $texto);
    }
}

if (!function_exists('pg_normalizar_dni')) {
    function pg_normalizar_dni($dni)
    {
        return preg_replace('/\D+/', '', (string)$dni);
    }
}

if (!function_exists('pg_dni_valido')) {
    function pg_dni_valido($dni)
    {
        $dni = pg_normalizar_dni($dni);
        return strlen($dni) === 8;
    }
}

if (!function_exists('pg_nombre_completo_desde_partes')) {
    function pg_nombre_completo_desde_partes($apellidoPaterno, $apellidoMaterno, $nombres)
    {
        $apellidoPaterno = pg_limpiar_texto($apellidoPaterno);
        $apellidoMaterno = pg_limpiar_texto($apellidoMaterno);
        $nombres = pg_limpiar_texto($nombres);

        $partes = array();

        if ($apellidoPaterno !== '') {
            $partes[] = $apellidoPaterno;
        }

        if ($apellidoMaterno !== '') {
            $partes[] = $apellidoMaterno;
        }

        if ($nombres !== '') {
            $partes[] = $nombres;
        }

        return trim(implode(' ', $partes));
    }
}

if (!function_exists('pg_separar_nombre_completo_simple')) {
    function pg_separar_nombre_completo_simple($nombreCompleto)
    {
        $nombreCompleto = pg_limpiar_texto($nombreCompleto);

        $salida = array(
            'apellido_paterno' => '',
            'apellido_materno' => '',
            'nombres' => '',
            'nombre_completo' => $nombreCompleto
        );

        if ($nombreCompleto === '') {
            return $salida;
        }

        $partes = preg_split('/\s+/u', $nombreCompleto);

        if (!is_array($partes)) {
            return $salida;
        }

        if (count($partes) >= 3) {
            $salida['apellido_paterno'] = $partes[0];
            $salida['apellido_materno'] = $partes[1];
            $salida['nombres'] = trim(implode(' ', array_slice($partes, 2)));
        } elseif (count($partes) === 2) {
            $salida['apellido_paterno'] = $partes[0];
            $salida['apellido_materno'] = '';
            $salida['nombres'] = $partes[1];
        } elseif (count($partes) === 1) {
            $salida['nombres'] = $partes[0];
        }

        return $salida;
    }
}

if (!function_exists('pg_generar_nombre_busqueda')) {
    function pg_generar_nombre_busqueda($dni, $apellidoPaterno, $apellidoMaterno, $nombres, $nombreCompleto)
    {
        $texto = trim(
            pg_normalizar_dni($dni) . ' ' .
            pg_limpiar_texto($apellidoPaterno) . ' ' .
            pg_limpiar_texto($apellidoMaterno) . ' ' .
            pg_limpiar_texto($nombres) . ' ' .
            pg_limpiar_texto($nombreCompleto)
        );

        $texto = pg_sin_tildes($texto);
        $texto = preg_replace('/\s+/u', ' ', $texto);

        return trim($texto);
    }
}

if (!function_exists('pg_stmt_bind_params_dynamic')) {
    function pg_stmt_bind_params_dynamic($stmt, $types, $values)
    {
        if (!$stmt || $types === '' || empty($values)) {
            return true;
        }

        $refs = array();
        $refs[] = $types;

        foreach ($values as $key => $value) {
            $refs[] = &$values[$key];
        }

        return call_user_func_array(array($stmt, 'bind_param'), $refs);
    }
}

if (!function_exists('pg_stmt_fetch_assoc')) {
    function pg_stmt_fetch_assoc($stmt)
    {
        if (!$stmt) {
            return null;
        }

        if (method_exists($stmt, 'get_result')) {
            $res = $stmt->get_result();

            if ($res) {
                $row = $res->fetch_assoc();
                return $row ? $row : null;
            }
        }

        $meta = $stmt->result_metadata();

        if (!$meta) {
            return null;
        }

        $row = array();
        $bind = array();

        while ($field = $meta->fetch_field()) {
            $row[$field->name] = null;
            $bind[] = &$row[$field->name];
        }

        call_user_func_array(array($stmt, 'bind_result'), $bind);

        if ($stmt->fetch()) {
            return $row;
        }

        return null;
    }
}

if (!function_exists('pg_table_exists')) {
    function pg_table_exists($conn, $table)
    {
        if (!pg_mysqli_ok($conn)) {
            return false;
        }

        if (!preg_match('/^[a-zA-Z0-9_]+$/', $table)) {
            return false;
        }

        $tableEsc = $conn->real_escape_string($table);
        $res = $conn->query("SHOW TABLES LIKE '{$tableEsc}'");

        return ($res && $res->num_rows > 0);
    }
}

if (!function_exists('pg_get_columns')) {
    function pg_get_columns($conn, $table)
    {
        static $cache = array();

        if (!pg_mysqli_ok($conn)) {
            return array();
        }

        if (!preg_match('/^[a-zA-Z0-9_]+$/', $table)) {
            return array();
        }

        if (isset($cache[$table])) {
            return $cache[$table];
        }

        $cols = array();
        $res = $conn->query("SHOW COLUMNS FROM `{$table}`");

        if ($res) {
            while ($row = $res->fetch_assoc()) {
                if (isset($row['Field'])) {
                    $cols[$row['Field']] = true;
                }
            }
        }

        $cache[$table] = $cols;

        return $cols;
    }
}

if (!function_exists('pg_has_column')) {
    function pg_has_column($conn, $table, $column)
    {
        $cols = pg_get_columns($conn, $table);
        return isset($cols[$column]);
    }
}

if (!function_exists('pg_key_norm')) {
    function pg_key_norm($key)
    {
        $key = pg_sin_tildes((string)$key);
        $key = strtolower($key);
        $key = str_replace(array('_', '-', ' '), '', $key);

        return $key;
    }
}

if (!function_exists('pg_array_buscar_clave')) {
    function pg_array_buscar_clave($array, $alias)
    {
        if (!is_array($array)) {
            return null;
        }

        $targets = array();

        foreach ($alias as $a) {
            $targets[] = pg_key_norm($a);
        }

        foreach ($array as $k => $v) {
            if (in_array(pg_key_norm($k), $targets, true) && !is_array($v) && !is_object($v)) {
                return $v;
            }
        }

        foreach ($array as $v) {
            if (is_array($v)) {
                $r = pg_array_buscar_clave($v, $alias);

                if ($r !== null && $r !== '') {
                    return $r;
                }
            }
        }

        return null;
    }
}

if (!function_exists('pg_normalizar_datos_persona')) {
    function pg_normalizar_datos_persona($datos)
    {
        if (!is_array($datos)) {
            $datos = array();
        }

        $dni = '';

        if (isset($datos['numero_documento'])) {
            $dni = $datos['numero_documento'];
        } elseif (isset($datos['documento'])) {
            $dni = $datos['documento'];
        } elseif (isset($datos['dni'])) {
            $dni = $datos['dni'];
        }

        $dni = pg_normalizar_dni($dni);

        $apellidoPaterno = isset($datos['apellido_paterno']) ? $datos['apellido_paterno'] : '';
        $apellidoMaterno = isset($datos['apellido_materno']) ? $datos['apellido_materno'] : '';
        $nombres = isset($datos['nombres']) ? $datos['nombres'] : '';

        $nombreCompleto = '';

        if (isset($datos['nombre_completo'])) {
            $nombreCompleto = $datos['nombre_completo'];
        } elseif (isset($datos['apellidos_nombres'])) {
            $nombreCompleto = $datos['apellidos_nombres'];
        } elseif (isset($datos['nombre'])) {
            $nombreCompleto = $datos['nombre'];
        }

        $apellidoPaterno = pg_limpiar_texto($apellidoPaterno);
        $apellidoMaterno = pg_limpiar_texto($apellidoMaterno);
        $nombres = pg_limpiar_texto($nombres);
        $nombreCompleto = pg_limpiar_texto($nombreCompleto);

        if (($apellidoPaterno === '' || $apellidoMaterno === '' || $nombres === '') && $nombreCompleto !== '') {
            $separado = pg_separar_nombre_completo_simple($nombreCompleto);

            if ($apellidoPaterno === '') {
                $apellidoPaterno = $separado['apellido_paterno'];
            }

            if ($apellidoMaterno === '') {
                $apellidoMaterno = $separado['apellido_materno'];
            }

            if ($nombres === '') {
                $nombres = $separado['nombres'];
            }
        }

        if ($nombreCompleto === '') {
            $nombreCompleto = pg_nombre_completo_desde_partes($apellidoPaterno, $apellidoMaterno, $nombres);
        }

        $sexo = isset($datos['sexo']) ? pg_limpiar_texto($datos['sexo']) : '';
        $fechaNacimiento = isset($datos['fecha_nacimiento']) ? trim((string)$datos['fecha_nacimiento']) : '';

        $fuenteRegistro = isset($datos['fuente_registro'])
            ? pg_limpiar_texto($datos['fuente_registro'])
            : 'MANUAL';

        $estadoVerificacion = isset($datos['estado_verificacion'])
            ? pg_limpiar_texto($datos['estado_verificacion'])
            : 'SIN_VERIFICAR';

        $rawJson = null;

        if (isset($datos['raw_json'])) {
            if (is_array($datos['raw_json']) || is_object($datos['raw_json'])) {
                $rawJson = json_encode($datos['raw_json'], JSON_UNESCAPED_UNICODE);
            } else {
                $rawJson = (string)$datos['raw_json'];
            }
        }

        return array(
            'tipo_documento' => 'DNI',
            'numero_documento' => $dni,
            'apellido_paterno' => $apellidoPaterno,
            'apellido_materno' => $apellidoMaterno,
            'nombres' => $nombres,
            'nombre_completo' => $nombreCompleto,
            'sexo' => $sexo,
            'fecha_nacimiento' => $fechaNacimiento !== '' ? $fechaNacimiento : null,
            'nombre_busqueda' => pg_generar_nombre_busqueda($dni, $apellidoPaterno, $apellidoMaterno, $nombres, $nombreCompleto),
            'fuente_registro' => $fuenteRegistro,
            'estado_verificacion' => $estadoVerificacion,
            'fecha_verificacion' => date('Y-m-d H:i:s'),
            'raw_json' => $rawJson,
            'estado' => 1
        );
    }
}

if (!function_exists('pg_buscar_persona_general_por_dni')) {
    function pg_buscar_persona_general_por_dni($conn, $dni)
    {
        if (!pg_mysqli_ok($conn)) {
            return null;
        }

        if (!pg_table_exists($conn, 'personas_general')) {
            return null;
        }

        $dni = pg_normalizar_dni($dni);

        if (!pg_dni_valido($dni)) {
            return null;
        }

        $whereEstado = '';

        if (pg_has_column($conn, 'personas_general', 'estado')) {
            $whereEstado = " AND estado = 1 ";
        }

        $sql = "
            SELECT *
            FROM personas_general
            WHERE tipo_documento = 'DNI'
              AND numero_documento = ?
              {$whereEstado}
            LIMIT 1
        ";

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

        if (!$stmt) {
            return null;
        }

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

        $row = pg_stmt_fetch_assoc($stmt);
        $stmt->close();

        return $row ? $row : null;
    }
}

if (!function_exists('pg_guardar_persona_general')) {
    function pg_guardar_persona_general($conn, $datos)
    {
        if (!pg_mysqli_ok($conn)) {
            return array(
                'ok' => false,
                'mensaje' => 'Conexión MySQL inválida.',
                'id_persona' => null,
                'persona' => null
            );
        }

        if (!pg_table_exists($conn, 'personas_general')) {
            return array(
                'ok' => false,
                'mensaje' => 'No existe la tabla personas_general.',
                'id_persona' => null,
                'persona' => null
            );
        }

        $data = pg_normalizar_datos_persona($datos);

        if (!pg_dni_valido($data['numero_documento'])) {
            return array(
                'ok' => false,
                'mensaje' => 'DNI inválido. Debe tener 8 dígitos.',
                'id_persona' => null,
                'persona' => null
            );
        }

        $colsExistentes = pg_get_columns($conn, 'personas_general');

        $camposPermitidos = array(
            'tipo_documento',
            'numero_documento',
            'apellido_paterno',
            'apellido_materno',
            'nombres',
            'nombre_completo',
            'sexo',
            'fecha_nacimiento',
            'nombre_busqueda',
            'fuente_registro',
            'estado_verificacion',
            'fecha_verificacion',
            'raw_json',
            'estado'
        );

        $insertCols = array();
        $placeholders = array();
        $values = array();
        $types = '';

        foreach ($camposPermitidos as $campo) {
            if (!isset($colsExistentes[$campo])) {
                continue;
            }

            $insertCols[] = $campo;
            $placeholders[] = '?';

            if ($campo === 'estado') {
                $values[] = (int)$data[$campo];
                $types .= 'i';
            } else {
                $values[] = isset($data[$campo]) ? $data[$campo] : null;
                $types .= 's';
            }
        }

        if (empty($insertCols)) {
            return array(
                'ok' => false,
                'mensaje' => 'No hay columnas compatibles en personas_general.',
                'id_persona' => null,
                'persona' => null
            );
        }

        $updates = array();

        if (isset($colsExistentes['id_persona'])) {
            $updates[] = "`id_persona` = LAST_INSERT_ID(`id_persona`)";
        }

        foreach ($insertCols as $campo) {
            if ($campo === 'tipo_documento' || $campo === 'numero_documento') {
                continue;
            }

            if ($campo === 'estado') {
                $updates[] = "`estado` = 1";
            } else {
                $updates[] = "`{$campo}` = COALESCE(NULLIF(VALUES(`{$campo}`), ''), `{$campo}`)";
            }
        }

        if (empty($updates)) {
            $updates[] = "`numero_documento` = VALUES(`numero_documento`)";
        }

        $sql = "
            INSERT INTO personas_general
            (`" . implode('`, `', $insertCols) . "`)
            VALUES (" . implode(', ', $placeholders) . ")
            ON DUPLICATE KEY UPDATE
            " . implode(",\n            ", $updates) . "
        ";

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

        if (!$stmt) {
            return array(
                'ok' => false,
                'mensaje' => 'Error preparando INSERT personas_general: ' . $conn->error,
                'id_persona' => null,
                'persona' => null
            );
        }

        pg_stmt_bind_params_dynamic($stmt, $types, $values);

        if (!$stmt->execute()) {
            $error = $stmt->error;
            $stmt->close();

            return array(
                'ok' => false,
                'mensaje' => 'Error guardando persona_general: ' . $error,
                'id_persona' => null,
                'persona' => null
            );
        }

        $stmt->close();

        $idPersona = (int)$conn->insert_id;
        $persona = pg_buscar_persona_general_por_dni($conn, $data['numero_documento']);

        if ($persona && isset($persona['id_persona'])) {
            $idPersona = (int)$persona['id_persona'];
        }

        return array(
            'ok' => true,
            'mensaje' => 'Persona guardada correctamente.',
            'id_persona' => $idPersona,
            'persona' => $persona
        );
    }
}

if (!function_exists('pg_extraer_base_json_api')) {
    function pg_extraer_base_json_api($json)
    {
        if (!is_array($json)) {
            return array();
        }

        if (isset($json['datos']) && is_array($json['datos'])) {
            return $json['datos'];
        }

        if (isset($json['data']) && is_array($json['data'])) {
            return $json['data'];
        }

        if (isset($json['persona']) && is_array($json['persona'])) {
            return $json['persona'];
        }

        if (isset($json['result']) && is_array($json['result'])) {
            return $json['result'];
        }

        return $json;
    }
}

if (!function_exists('pg_normalizar_respuesta_api_reniec')) {
    function pg_normalizar_respuesta_api_reniec($dni, $json, $rawBody)
    {
        if (!is_array($json)) {
            return array(
                'ok' => false,
                'encontrado' => false,
                'mensaje' => 'La API no devolvió JSON válido.',
                'persona' => null,
                'raw_json' => $rawBody
            );
        }

        $base = pg_extraer_base_json_api($json);

        $dniApi = pg_array_buscar_clave($base, array(
            'dni',
            'numero_documento',
            'documento',
            'numDocumento'
        ));

        if ($dniApi !== null && pg_dni_valido($dniApi)) {
            $dni = pg_normalizar_dni($dniApi);
        }

        $apellidoPaterno = pg_array_buscar_clave($base, array(
            'apellido_paterno',
            'apellidoPaterno',
            'ape_paterno',
            'apePaterno',
            'ap_paterno',
            'apPaterno',
            'paterno',
            'primer_apellido',
            'primerApellido'
        ));

        $apellidoMaterno = pg_array_buscar_clave($base, array(
            'apellido_materno',
            'apellidoMaterno',
            'ape_materno',
            'apeMaterno',
            'ap_materno',
            'apMaterno',
            'materno',
            'segundo_apellido',
            'segundoApellido'
        ));

        $nombres = pg_array_buscar_clave($base, array(
            'nombres',
            'nombre',
            'prenombres',
            'pre_nombres',
            'preNombres',
            'name'
        ));

        $nombreCompleto = pg_array_buscar_clave($base, array(
            'nombre_completo',
            'nombreCompleto',
            'apellidos_nombres',
            'apellidosNombres',
            'razon_social',
            'razonSocial',
            'fullname',
            'full_name'
        ));

        $sexo = pg_array_buscar_clave($base, array(
            'sexo',
            'genero',
            'gender'
        ));

        $fechaNacimiento = pg_array_buscar_clave($base, array(
            'fecha_nacimiento',
            'fechaNacimiento',
            'fec_nacimiento',
            'fecNacimiento',
            'nacimiento',
            'birthdate'
        ));

        $persona = pg_normalizar_datos_persona(array(
            'numero_documento' => $dni,
            'apellido_paterno' => $apellidoPaterno,
            'apellido_materno' => $apellidoMaterno,
            'nombres' => $nombres,
            'nombre_completo' => $nombreCompleto,
            'sexo' => $sexo,
            'fecha_nacimiento' => $fechaNacimiento,
            'fuente_registro' => 'API_RENIEC',
            'estado_verificacion' => 'VERIFICADO',
            'raw_json' => $json
        ));

        $tieneDatos = (
            $persona['apellido_paterno'] !== '' ||
            $persona['apellido_materno'] !== '' ||
            $persona['nombres'] !== '' ||
            $persona['nombre_completo'] !== ''
        );

        if (!$tieneDatos) {
            return array(
                'ok' => false,
                'encontrado' => false,
                'mensaje' => 'La API respondió, pero no trajo nombres/apellidos.',
                'persona' => null,
                'raw_json' => json_encode($json, JSON_UNESCAPED_UNICODE)
            );
        }

        return array(
            'ok' => true,
            'encontrado' => true,
            'mensaje' => 'Persona encontrada en API RENIEC.',
            'persona' => $persona,
            'raw_json' => json_encode($json, JSON_UNESCAPED_UNICODE)
        );
    }
}

if (!function_exists('pg_construir_url_api_dni')) {
    function pg_construir_url_api_dni($dni)
    {
        $base = PG_DNI_API_URL;
        $dni = urlencode(pg_normalizar_dni($dni));

        if (strpos($base, '{dni}') !== false) {
            $url = str_replace('{dni}', $dni, $base);
        } elseif (substr($base, -1) === '=') {
            $url = $base . $dni;
        } elseif (strpos($base, '?') !== false) {
            $url = $base . '&dni=' . $dni;
        } else {
            $url = rtrim($base, '/') . '?dni=' . $dni;
        }

        if (preg_match('#^https?://#i', $url)) {
            return $url;
        }

        $scheme = 'http';

        if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
            $scheme = 'https';
        }

        $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';

        return $scheme . '://' . $host . '/' . ltrim($url, '/');
    }
}

if (!function_exists('pg_consultar_api_reniec')) {
    function pg_consultar_api_reniec($dni)
    {
        $dni = pg_normalizar_dni($dni);

        if (!pg_dni_valido($dni)) {
            return array(
                'ok' => false,
                'encontrado' => false,
                'mensaje' => 'DNI inválido para consultar API.',
                'persona' => null,
                'raw_json' => null,
                'http_code' => 0
            );
        }

        $url = pg_construir_url_api_dni($dni);
        $body = false;
        $httpCode = 0;
        $error = '';

        if (function_exists('curl_init')) {
            $ch = curl_init($url);

            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, PG_API_CONNECT_TIMEOUT_SECONDS);
            curl_setopt($ch, CURLOPT_TIMEOUT, PG_API_TIMEOUT_SECONDS);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            curl_setopt($ch, CURLOPT_USERAGENT, 'NOBEX-PADRON-GENERAL/1.0');
            curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                'Accept: application/json'
            ));

            $body = curl_exec($ch);
            $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);

            if ($body === false) {
                $error = curl_error($ch);
            }

            curl_close($ch);
        } else {
            $context = stream_context_create(array(
                'http' => array(
                    'timeout' => PG_API_TIMEOUT_SECONDS,
                    'ignore_errors' => true,
                    'header' => "Accept: application/json\r\n"
                ),
                'ssl' => array(
                    'verify_peer' => false,
                    'verify_peer_name' => false
                )
            ));

            $body = @file_get_contents($url, false, $context);
        }

        if ($body === false || trim((string)$body) === '') {
            return array(
                'ok' => false,
                'encontrado' => false,
                'mensaje' => 'No se pudo consultar API RENIEC. ' . $error,
                'persona' => null,
                'raw_json' => null,
                'http_code' => $httpCode
            );
        }

        $json = json_decode($body, true);

        if (!is_array($json)) {
            return array(
                'ok' => false,
                'encontrado' => false,
                'mensaje' => 'Respuesta API no es JSON válido.',
                'persona' => null,
                'raw_json' => $body,
                'http_code' => $httpCode
            );
        }

        $normalizado = pg_normalizar_respuesta_api_reniec($dni, $json, $body);
        $normalizado['http_code'] = $httpCode;

        return $normalizado;
    }
}

if (!function_exists('pg_registrar_log_consulta')) {
    function pg_registrar_log_consulta($conn, $dni, $origen, $resultado, $mensaje, $idPersona = null, $rawJson = null)
    {
        if (!pg_mysqli_ok($conn)) {
            return false;
        }

        if (!pg_table_exists($conn, 'personas_consultas_log')) {
            return false;
        }

        $cols = pg_get_columns($conn, 'personas_consultas_log');

        $map = array(
            'id_persona' => $idPersona,
            'tipo_documento' => 'DNI',
            'numero_documento' => pg_normalizar_dni($dni),
            'origen_consulta' => pg_limpiar_texto($origen),
            'origen' => pg_limpiar_texto($origen),
            'resultado' => pg_limpiar_texto($resultado),
            'mensaje' => (string)$mensaje,
            'ip' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '',
            'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
            'raw_json' => is_array($rawJson) ? json_encode($rawJson, JSON_UNESCAPED_UNICODE) : $rawJson,
            'creado_en' => date('Y-m-d H:i:s'),
            'created_at' => date('Y-m-d H:i:s'),
            'fecha_registro' => date('Y-m-d H:i:s')
        );

        $insertCols = array();
        $values = array();
        $types = '';
        $placeholders = array();

        foreach ($map as $campo => $valor) {
            if (!isset($cols[$campo])) {
                continue;
            }

            $insertCols[] = $campo;
            $values[] = $valor;

            if ($campo === 'id_persona') {
                $types .= 'i';
            } else {
                $types .= 's';
            }

            $placeholders[] = '?';
        }

        if (empty($insertCols)) {
            return false;
        }

        $sql = "
            INSERT INTO personas_consultas_log
            (`" . implode('`, `', $insertCols) . "`)
            VALUES (" . implode(', ', $placeholders) . ")
        ";

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

        if (!$stmt) {
            return false;
        }

        pg_stmt_bind_params_dynamic($stmt, $types, $values);
        $ok = $stmt->execute();
        $stmt->close();

        return $ok;
    }
}

if (!function_exists('pg_resolver_persona_por_dni')) {
    function pg_resolver_persona_por_dni($conn, $dni, $usarApi = true)
    {
        $dni = pg_normalizar_dni($dni);

        if (!pg_dni_valido($dni)) {
            return array(
                'ok' => false,
                'encontrado' => false,
                'origen' => 'INVALIDO',
                'mensaje' => 'DNI inválido. Debe tener 8 dígitos.',
                'id_persona' => null,
                'persona' => null
            );
        }

        $personaLocal = pg_buscar_persona_general_por_dni($conn, $dni);

        if ($personaLocal) {
            pg_registrar_log_consulta(
                $conn,
                $dni,
                'PERSONAS_GENERAL',
                'ENCONTRADO',
                'Persona encontrada en padrón local.',
                isset($personaLocal['id_persona']) ? (int)$personaLocal['id_persona'] : null,
                null
            );

            return array(
                'ok' => true,
                'encontrado' => true,
                'origen' => 'PERSONAS_GENERAL',
                'mensaje' => 'Persona encontrada en padrón local.',
                'id_persona' => isset($personaLocal['id_persona']) ? (int)$personaLocal['id_persona'] : null,
                'persona' => $personaLocal
            );
        }

        if (!$usarApi) {
            pg_registrar_log_consulta(
                $conn,
                $dni,
                'PERSONAS_GENERAL',
                'NO_ENCONTRADO',
                'No existe en padrón local y no se consultó API.',
                null,
                null
            );

            return array(
                'ok' => false,
                'encontrado' => false,
                'origen' => 'PERSONAS_GENERAL',
                'mensaje' => 'No existe en padrón local.',
                'id_persona' => null,
                'persona' => null
            );
        }

        $api = pg_consultar_api_reniec($dni);

        if (empty($api['ok']) || empty($api['persona'])) {
            pg_registrar_log_consulta(
                $conn,
                $dni,
                'API_RENIEC',
                'NO_ENCONTRADO',
                isset($api['mensaje']) ? $api['mensaje'] : 'No encontrado en API.',
                null,
                isset($api['raw_json']) ? $api['raw_json'] : null
            );

            return array(
                'ok' => false,
                'encontrado' => false,
                'origen' => 'API_RENIEC',
                'mensaje' => isset($api['mensaje']) ? $api['mensaje'] : 'No encontrado en API.',
                'id_persona' => null,
                'persona' => null
            );
        }

        $guardar = pg_guardar_persona_general($conn, $api['persona']);

        if (empty($guardar['ok'])) {
            pg_registrar_log_consulta(
                $conn,
                $dni,
                'API_RENIEC',
                'ERROR_GUARDANDO',
                $guardar['mensaje'],
                null,
                isset($api['raw_json']) ? $api['raw_json'] : null
            );

            return array(
                'ok' => false,
                'encontrado' => false,
                'origen' => 'API_RENIEC',
                'mensaje' => $guardar['mensaje'],
                'id_persona' => null,
                'persona' => null
            );
        }

        pg_registrar_log_consulta(
            $conn,
            $dni,
            'API_RENIEC',
            'ENCONTRADO_Y_GUARDADO',
            'Persona encontrada en API y guardada en padrón local.',
            $guardar['id_persona'],
            isset($api['raw_json']) ? $api['raw_json'] : null
        );

        return array(
            'ok' => true,
            'encontrado' => true,
            'origen' => 'API_RENIEC',
            'mensaje' => 'Persona encontrada en API y guardada en padrón local.',
            'id_persona' => $guardar['id_persona'],
            'persona' => $guardar['persona']
        );
    }
}

if (!function_exists('pg_buscar_participante_concurso_por_dni')) {
    function pg_buscar_participante_concurso_por_dni($conn, $idConcurso, $dni)
    {
        if (!pg_mysqli_ok($conn)) {
            return null;
        }

        if (!pg_table_exists($conn, 'concurso_participantes')) {
            return null;
        }

        $dni = pg_normalizar_dni($dni);
        $idConcurso = (int)$idConcurso;

        if ($idConcurso <= 0 || !pg_dni_valido($dni)) {
            return null;
        }

        $whereEstado = '';

        if (pg_has_column($conn, 'concurso_participantes', 'estado')) {
            $whereEstado = " AND p.estado = 'ACTIVO' ";
        }

        $sql = "
            SELECT p.*
            FROM concurso_participantes p
            WHERE p.id_concurso = ?
              AND p.documento = ?
              {$whereEstado}
            ORDER BY p.id_participante DESC
            LIMIT 1
        ";

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

        if (!$stmt) {
            return null;
        }

        $stmt->bind_param('is', $idConcurso, $dni);
        $stmt->execute();

        $row = pg_stmt_fetch_assoc($stmt);
        $stmt->close();

        return $row ? $row : null;
    }
}

if (!function_exists('pg_vincular_participante_con_persona')) {
    function pg_vincular_participante_con_persona($conn, $idParticipante, $idPersona)
    {
        if (!pg_mysqli_ok($conn)) {
            return false;
        }

        $idParticipante = (int)$idParticipante;
        $idPersona = (int)$idPersona;

        if ($idParticipante <= 0 || $idPersona <= 0) {
            return false;
        }

        if (!pg_has_column($conn, 'concurso_participantes', 'id_persona')) {
            return false;
        }

        if (!pg_has_column($conn, 'concurso_participantes', 'id_participante')) {
            return false;
        }

        $sql = "
            UPDATE concurso_participantes
            SET id_persona = ?
            WHERE id_participante = ?
            LIMIT 1
        ";

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

        if (!$stmt) {
            return false;
        }

        $stmt->bind_param('ii', $idPersona, $idParticipante);
        $ok = $stmt->execute();
        $stmt->close();

        return $ok;
    }
}

if (!function_exists('pg_persona_desde_participante')) {
    function pg_persona_desde_participante($participante)
    {
        if (!is_array($participante)) {
            return null;
        }

        $dni = '';

        if (isset($participante['documento'])) {
            $dni = $participante['documento'];
        } elseif (isset($participante['numero_documento'])) {
            $dni = $participante['numero_documento'];
        }

        $nombreCompleto = '';

        if (isset($participante['apellidos_nombres'])) {
            $nombreCompleto = $participante['apellidos_nombres'];
        } elseif (isset($participante['nombre_completo'])) {
            $nombreCompleto = $participante['nombre_completo'];
        }

        return pg_normalizar_datos_persona(array(
            'numero_documento' => $dni,
            'apellido_paterno' => isset($participante['apellido_paterno']) ? $participante['apellido_paterno'] : '',
            'apellido_materno' => isset($participante['apellido_materno']) ? $participante['apellido_materno'] : '',
            'nombres' => isset($participante['nombres']) ? $participante['nombres'] : '',
            'nombre_completo' => $nombreCompleto,
            'fuente_registro' => 'CONCURSO_PARTICIPANTE',
            'estado_verificacion' => 'IMPORTADO'
        ));
    }
}

if (!function_exists('pg_resolver_para_asignar_salon')) {
    function pg_resolver_para_asignar_salon($conn, $idConcurso, $dni, $usarApi = true)
    {
        $dni = pg_normalizar_dni($dni);
        $idConcurso = (int)$idConcurso;

        if (!pg_dni_valido($dni)) {
            return array(
                'ok' => false,
                'encontrado' => false,
                'origen' => 'INVALIDO',
                'mensaje' => 'DNI inválido. Debe tener 8 dígitos.',
                'participante' => null,
                'persona' => null,
                'id_persona' => null
            );
        }

        // 1) Primero buscar en concurso_participantes.
        $participante = pg_buscar_participante_concurso_por_dni($conn, $idConcurso, $dni);

        if ($participante) {
            $persona = null;
            $idPersona = null;

            if (isset($participante['id_persona']) && (int)$participante['id_persona'] > 0) {
                $idPersona = (int)$participante['id_persona'];
                $persona = pg_buscar_persona_general_por_dni($conn, $dni);
            } else {
                $personaLocal = pg_buscar_persona_general_por_dni($conn, $dni);

                if ($personaLocal && isset($personaLocal['id_persona'])) {
                    $persona = $personaLocal;
                    $idPersona = (int)$personaLocal['id_persona'];
                } else {
                    $datosPersona = pg_persona_desde_participante($participante);
                    $guardar = pg_guardar_persona_general($conn, $datosPersona);

                    if (!empty($guardar['ok'])) {
                        $persona = $guardar['persona'];
                        $idPersona = (int)$guardar['id_persona'];
                    }
                }

                if ($idPersona > 0 && isset($participante['id_participante'])) {
                    pg_vincular_participante_con_persona(
                        $conn,
                        (int)$participante['id_participante'],
                        $idPersona
                    );

                    $participante['id_persona'] = $idPersona;
                }
            }

            return array(
                'ok' => true,
                'encontrado' => true,
                'origen' => 'CONCURSO_PARTICIPANTES',
                'mensaje' => 'Participante encontrado en el concurso actual.',
                'participante' => $participante,
                'persona' => $persona,
                'id_persona' => $idPersona
            );
        }

        // 2) Si no existe en participantes, buscar en personas_general.
        // 3) Si tampoco existe, consultar API RENIEC.
        $resolverPersona = pg_resolver_persona_por_dni($conn, $dni, $usarApi);

        if (!empty($resolverPersona['ok']) && !empty($resolverPersona['encontrado'])) {
            return array(
                'ok' => true,
                'encontrado' => true,
                'origen' => $resolverPersona['origen'],
                'mensaje' => $resolverPersona['mensaje'],
                'participante' => null,
                'persona' => $resolverPersona['persona'],
                'id_persona' => $resolverPersona['id_persona']
            );
        }

        return array(
            'ok' => false,
            'encontrado' => false,
            'origen' => isset($resolverPersona['origen']) ? $resolverPersona['origen'] : 'NO_ENCONTRADO',
            'mensaje' => isset($resolverPersona['mensaje']) ? $resolverPersona['mensaje'] : 'No encontrado.',
            'participante' => null,
            'persona' => null,
            'id_persona' => null
        );
    }
}

if (!function_exists('pg_guardar_persona_institucion')) {
    function pg_guardar_persona_institucion(
        $conn,
        $idPersona,
        $idInstitucionCatalogo,
        $institucionEducativa,
        $tipoInstitucion,
        $codigoModular,
        $nivelModalidad,
        $fuenteRegistro = 'CONCURSO'
    ) {
        if (!pg_mysqli_ok($conn)) {
            return false;
        }

        if (!pg_table_exists($conn, 'personas_instituciones')) {
            return false;
        }

        $idPersona = (int)$idPersona;
        $idInstitucionCatalogo = (int)$idInstitucionCatalogo;

        if ($idPersona <= 0) {
            return false;
        }

        $institucionEducativa = pg_limpiar_texto($institucionEducativa);
        $tipoInstitucion = pg_limpiar_texto($tipoInstitucion);
        $codigoModular = trim((string)$codigoModular);
        $nivelModalidad = pg_limpiar_texto($nivelModalidad);
        $fuenteRegistro = pg_limpiar_texto($fuenteRegistro);
        $anio = (int)date('Y');

        $cols = pg_get_columns($conn, 'personas_instituciones');

        if (isset($cols['codigo_modular_ie']) && isset($cols['anio'])) {
            $sqlCheck = "
                SELECT id_persona
                FROM personas_instituciones
                WHERE id_persona = ?
                  AND codigo_modular_ie = ?
                  AND anio = ?
            ";

            if (isset($cols['estado'])) {
                $sqlCheck .= " AND estado = 1 ";
            }

            $sqlCheck .= " LIMIT 1 ";

            $stmtCheck = $conn->prepare($sqlCheck);

            if ($stmtCheck) {
                $stmtCheck->bind_param('isi', $idPersona, $codigoModular, $anio);
                $stmtCheck->execute();
                $rowCheck = pg_stmt_fetch_assoc($stmtCheck);
                $stmtCheck->close();

                if ($rowCheck) {
                    return true;
                }
            }
        }

        $map = array(
            'id_persona' => array('i', $idPersona),
            'id_institucion' => array('i', $idInstitucionCatalogo),
            'institucion_educativa' => array('s', $institucionEducativa),
            'tipo_institucion' => array('s', $tipoInstitucion),
            'codigo_modular_ie' => array('s', $codigoModular),
            'nivel_modalidad_ie' => array('s', $nivelModalidad),
            'anio' => array('i', $anio),
            'fuente_registro' => array('s', $fuenteRegistro),
            'estado' => array('i', 1),
            'created_at' => array('RAW', 'NOW()'),
            'creado_en' => array('RAW', 'NOW()')
        );

        $columnas = array();
        $placeholders = array();
        $types = '';
        $params = array();

        foreach ($map as $campo => $def) {
            if (!isset($cols[$campo])) {
                continue;
            }

            $columnas[] = $campo;

            if ($def[0] === 'RAW') {
                $placeholders[] = $def[1];
            } else {
                $placeholders[] = '?';
                $types .= $def[0];
                $params[] = $def[1];
            }
        }

        if (empty($columnas)) {
            return false;
        }

        $sql = "
            INSERT INTO personas_instituciones
            (" . implode(', ', $columnas) . ")
            VALUES
            (" . implode(', ', $placeholders) . ")
        ";

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

        if (!$stmt) {
            return false;
        }

        if ($types !== '') {
            pg_stmt_bind_params_dynamic($stmt, $types, $params);
        }

        $ok = $stmt->execute();
        $stmt->close();

        return $ok;
    }
}