<?php
declare(strict_types=1);

require_once __DIR__ . '/../conexion.php';
require_once __DIR__ . '/../_helpers.php';
require_once __DIR__ . '/../auth/guard.php';

$u = require_login();
require_module_write($u, "usuarios");

function ip_to_utf8(string $value): string {
  $value = preg_replace('/^\xEF\xBB\xBF/', '', $value) ?? $value;
  if (function_exists('mb_check_encoding') && mb_check_encoding($value, 'UTF-8')) {
    return $value;
  }
  foreach (['Windows-1252', 'ISO-8859-1', 'ISO-8859-15'] as $enc) {
    $converted = @iconv($enc, 'UTF-8//IGNORE', $value);
    if ($converted !== false && $converted !== '') {
      return $converted;
    }
  }
  $converted = @utf8_encode($value);
  return $converted !== false ? $converted : $value;
}

function ip_normalize_token(string $value): string {
  $value = ip_to_utf8(trim($value));
  if ($value === '') return '';
  $value = str_replace(["\xc2\xa0", "\u{00A0}"], ' ', $value);
  $converted = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value);
  if ($converted !== false && $converted !== '') {
    $value = $converted;
  }
  $value = strtolower($value);
  $value = preg_replace('/[^a-z0-9]+/u', '_', $value) ?? $value;
  $value = trim($value, '_');
  $value = preg_replace('/_+/', '_', $value) ?? $value;
  return $value;
}

function ip_detect_csv_delimiter(string $sample): string {
  $counts = [
    ';' => substr_count($sample, ';'),
    ',' => substr_count($sample, ','),
    "\t" => substr_count($sample, "\t"),
    '|' => substr_count($sample, '|'),
  ];
  arsort($counts);
  $delimiter = (string)array_key_first($counts);
  return $delimiter !== '' ? $delimiter : ',';
}

function ip_read_csv_rows(string $path): array {
  $handle = fopen($path, 'rb');
  if (!$handle) {
    throw new RuntimeException('No se pudo abrir el archivo CSV.');
  }

  $sample = '';
  for ($i = 0; $i < 5 && !feof($handle); $i++) {
    $sample .= (string)fgets($handle);
  }
  rewind($handle);

  $delimiter = ip_detect_csv_delimiter($sample);
  $rows = [];
  $line = 0;

  while (($data = fgetcsv($handle, 0, $delimiter)) !== false) {
    $line++;
    if ($data === [null]) continue;
    $row = [];
    foreach ($data as $idx => $value) {
      $row[$idx] = trim(ip_to_utf8((string)$value));
    }
    $rows[$line] = $row;
  }

  fclose($handle);
  return $rows;
}

function ip_xlsx_col_to_index(string $ref): int {
  $letters = preg_replace('/[^A-Z]/', '', strtoupper($ref)) ?? '';
  $index = 0;
  $len = strlen($letters);
  for ($i = 0; $i < $len; $i++) {
    $index = ($index * 26) + (ord($letters[$i]) - 64);
  }
  return max(0, $index - 1);
}

function ip_xlsx_text_nodes(DOMXPath $xpath, DOMNode $context, string $query): string {
  $parts = [];
  foreach ($xpath->query($query, $context) ?: [] as $node) {
    $parts[] = trim((string)$node->nodeValue);
  }
  return trim(implode('', array_filter($parts, static fn($v) => $v !== '')));
}

function ip_xlsx_cell_value(DOMXPath $xpath, DOMNode $cell, array $sharedStrings): string {
  $type = ($cell instanceof DOMElement) ? (string)$cell->getAttribute('t') : '';

  if ($type === 'inlineStr') {
    return ip_xlsx_text_nodes($xpath, $cell, './main:is//main:t');
  }

  $valueNode = $xpath->query('./main:v', $cell);
  $value = ($valueNode && $valueNode->length > 0) ? trim((string)$valueNode->item(0)->nodeValue) : '';

  if ($type === 's') {
    $idx = (int)$value;
    return isset($sharedStrings[$idx]) ? $sharedStrings[$idx] : '';
  }

  return $value;
}

function ip_read_xlsx_rows(string $path, int $sheetNumber = 1): array {
  if (!class_exists('ZipArchive')) {
    throw new RuntimeException('Tu PHP no tiene ZipArchive habilitado. Para XLSX habilita zip en php.ini o usa CSV.');
  }

  $zip = new ZipArchive();
  if ($zip->open($path) !== true) {
    throw new RuntimeException('No se pudo abrir el archivo Excel.');
  }

  $ns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
  $sharedStrings = [];
  $sharedXml = $zip->getFromName('xl/sharedStrings.xml');
  if ($sharedXml !== false) {
    $domShared = new DOMDocument();
    if (@$domShared->loadXML($sharedXml)) {
      $xpathShared = new DOMXPath($domShared);
      $xpathShared->registerNamespace('main', $ns);
      foreach ($xpathShared->query('//main:si') ?: [] as $si) {
        $sharedStrings[] = ip_to_utf8(ip_xlsx_text_nodes($xpathShared, $si, './/main:t'));
      }
    }
  }

  $sheetXml = $zip->getFromName('xl/worksheets/sheet' . $sheetNumber . '.xml');
  if ($sheetXml === false) {
    $zip->close();
    throw new RuntimeException('No encontré la hoja principal del Excel.');
  }

  $dom = new DOMDocument();
  if (!@$dom->loadXML($sheetXml)) {
    $zip->close();
    throw new RuntimeException('No se pudo leer el contenido interno del Excel.');
  }

  $xpath = new DOMXPath($dom);
  $xpath->registerNamespace('main', $ns);

  $rows = [];
  foreach ($xpath->query('//main:sheetData/main:row') ?: [] as $rowNode) {
    if (!$rowNode instanceof DOMElement) continue;

    $excelRow = (int)$rowNode->getAttribute('r');
    if ($excelRow <= 0) $excelRow = count($rows) + 1;

    $row = [];
    foreach ($xpath->query('./main:c', $rowNode) ?: [] as $cell) {
      if (!$cell instanceof DOMElement) continue;
      $ref = (string)$cell->getAttribute('r');
      $col = ip_xlsx_col_to_index($ref);
      $row[$col] = trim(ip_to_utf8(ip_xlsx_cell_value($xpath, $cell, $sharedStrings)));
    }
    if ($row) ksort($row);
    $rows[$excelRow] = $row;
  }

  $zip->close();
  ksort($rows);
  return $rows;
}

function ip_find_header_map(array $row): array {
  $aliases = [
    'rut' => ['numero_tipo_identificacion'],
    'fecha_nacimiento' => ['fecha_de_nacimiento'],
    'nombres' => ['nombres'],
    'apellido_paterno' => ['apellido_paterno'],
    'apellido_materno' => ['apellido_materno'],
    'sexo' => ['sexo'],
    'estado_civil' => ['estado_civil'],
    'escolaridad' => ['escolaridad'],
    'institucion_previsional' => ['institucion_previsional'],
    'situacion' => ['situacion'],
    'nacionalidad' => ['nacionalidad'],
    'pueblo_indig' => ['pueblo_indig'],
    'calle_residencial' => ['calle_residencial'],
    'numero_residencial' => ['numero_residencial'],
    'sector' => ['sector'],
    'movil_contacto' => ['movil_contacto'],
    'tel_laboral' => ['tel_laboral'],
    'tel2_contacto' => ['tel2_contacto'],
    'fecha_ultima_actualizacion' => ['fecha_ultima_actualizacion'],
    'hora_ultima_actualizacion' => ['hora_ultima_actualizacion'],
  ];

  $map = [];
  foreach ($row as $colIndex => $rawHeader) {
    $normalized = ip_normalize_token((string)$rawHeader);
    if ($normalized === '') continue;
    foreach ($aliases as $field => $names) {
      if (in_array($normalized, $names, true) && !isset($map[$field])) {
        $map[$field] = $colIndex;
      }
    }
  }
  return $map;
}

function ip_detect_header_row(array $rows): array {
  if (!$rows) {
    throw new RuntimeException('El archivo no contiene filas para procesar.');
  }

  $bestRowNumber = null;
  $bestMap = [];
  $bestScore = -1;
  $debug = [];

  foreach ($rows as $rowNumber => $row) {
    if ((int)$rowNumber > 40) break;
    if (!is_array($row) || !$row) continue;

    $map = ip_find_header_map($row);
    $score = count($map);
    foreach (['rut', 'nombres', 'apellido_paterno', 'apellido_materno', 'sexo', 'fecha_nacimiento', 'situacion'] as $required) {
      if (isset($map[$required])) $score += 5;
    }

    $preview = [];
    foreach (array_slice($row, 0, 12, true) as $cell) {
      $n = ip_normalize_token((string)$cell);
      if ($n !== '') $preview[] = $n;
    }
    if ($preview) {
      $debug[] = 'Fila ' . $rowNumber . ': ' . implode(' | ', $preview);
    }

    if ($score > $bestScore) {
      $bestScore = $score;
      $bestRowNumber = (int)$rowNumber;
      $bestMap = $map;
    }
  }

  $requiredOk = isset($bestMap['rut'], $bestMap['nombres'], $bestMap['apellido_paterno'], $bestMap['apellido_materno'], $bestMap['sexo'], $bestMap['fecha_nacimiento'], $bestMap['situacion']);
  if (!$requiredOk || $bestRowNumber === null) {
    throw new RuntimeException(
      'No pude ubicar la fila de encabezados del padrón. Vista previa detectada: ' . implode(' || ', array_slice($debug, 0, 6))
    );
  }

  return [$bestRowNumber, $bestMap];
}

function ip_parse_date_or_null($value): ?string {
  if ($value === null) return null;
  $raw = trim((string)$value);
  if ($raw === '') return null;

  if (is_numeric($raw) && preg_match('/^\d+(?:\.\d+)?$/', $raw)) {
    $n = (float)$raw;
    if ($n > 0 && $n < 100000) {
      $days = (int)floor($n);
      $dt = DateTime::createFromFormat('Y-m-d', '1899-12-30');
      if ($dt instanceof DateTime) {
        $dt->modify('+' . $days . ' days');
        return $dt->format('Y-m-d');
      }
    }
  }

  if (preg_match('/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/', $raw, $m) === 1) {
    $dd = (int)$m[1];
    $mm = (int)$m[2];
    $yyyy = (int)$m[3];
    if (checkdate($mm, $dd, $yyyy)) {
      return sprintf('%04d-%02d-%02d', $yyyy, $mm, $dd);
    }
  }

  if (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $raw) === 1) {
    return $raw;
  }

  return null;
}

function ip_parse_time_or_null($value): ?string {
  if ($value === null) return null;

  if ($value instanceof DateTimeInterface) {
    return $value->format('H:i:s');
  }

  $raw = trim((string)$value);
  if ($raw === '') return null;

  if (is_numeric($raw) && preg_match('/^\d+(?:\.\d+)?$/', $raw)) {
    $n = (float)$raw;
    if ($n >= 0 && $n < 1) {
      $seconds = (int)round($n * 86400);
      $seconds = $seconds % 86400;
      $h = (int)floor($seconds / 3600);
      $m = (int)floor(($seconds % 3600) / 60);
      $s = $seconds % 60;
      return sprintf('%02d:%02d:%02d', $h, $m, $s);
    }
  }

  if (preg_match('/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/', $raw, $m) === 1) {
    $hh = (int)$m[1];
    $mm = (int)$m[2];
    $ss = isset($m[3]) ? (int)$m[3] : 0;
    if ($hh >= 0 && $hh <= 23 && $mm >= 0 && $mm <= 59 && $ss >= 0 && $ss <= 59) {
      return sprintf('%02d:%02d:%02d', $hh, $mm, $ss);
    }
  }

  return null;
}

function ip_combine_excel_update($fecha, $hora): ?string {
  $date = ip_parse_date_or_null($fecha);
  if ($date === null) return null;

  $time = ip_parse_time_or_null($hora) ?? '00:00:00';
  return $date . ' ' . $time;
}

function ip_clean_string($value): ?string {
  if ($value === null) return null;
  $value = trim(ip_to_utf8((string)$value));
  if ($value === '') return null;
  $value = preg_replace('/\s+/u', ' ', $value) ?? $value;
  return $value;
}

function ip_concat(array $parts, string $separator = ' '): ?string {
  $clean = [];
  foreach ($parts as $part) {
    $value = ip_clean_string((string)$part);
    if ($value !== null && $value !== '') {
      $clean[] = $value;
    }
  }
  $clean = array_values(array_unique($clean));
  if (!$clean) return null;
  return implode($separator, $clean);
}

function ip_map_genero(?string $sexo): ?string {
  $sexo = ip_clean_string((string)$sexo);
  if ($sexo === null) return null;
  $norm = ip_normalize_token($sexo);
  if (in_array($norm, ['hombre', 'masculino'], true)) return 'Masculino';
  if (in_array($norm, ['mujer', 'femenino'], true)) return 'Femenino';
  return $sexo;
}

function ip_map_inscrito($situacion): ?int {
  $value = ip_normalize_token((string)$situacion);
  if ($value === '') return null;
  if (str_contains($value, 'inscrito')) return 1;
  if (str_contains($value, 'adscrito')) return 0;
  return null;
}

function ip_map_migrante(?string $nacionalidad): ?int {
  $nacionalidad = ip_clean_string((string)$nacionalidad);
  if ($nacionalidad === null) return null;
  $value = ip_normalize_token($nacionalidad);
  return str_contains($value, 'chilena') || str_contains($value, 'chileno') ? 0 : 1;
}

function ip_map_sectorizacion($sector): int {
  $value = ip_normalize_token((string)$sector);
  if (str_contains($value, 'verde')) return 1;
  if (str_contains($value, 'amarillo')) return 2;
  return 3;
}

function ip_map_rows(array $rows): array {
  [$headerRowNumber, $headerMap] = ip_detect_header_row($rows);
  $out = [];

  foreach ($rows as $rowNumber => $rawRow) {
    if ((int)$rowNumber <= $headerRowNumber) continue;
    if (!is_array($rawRow) || !$rawRow) continue;

    $rut = isset($headerMap['rut'], $rawRow[$headerMap['rut']])
      ? normalize_rut((string)$rawRow[$headerMap['rut']])
      : '';

    $nombres = isset($headerMap['nombres'], $rawRow[$headerMap['nombres']]) ? $rawRow[$headerMap['nombres']] : null;
    $apellidoP = isset($headerMap['apellido_paterno'], $rawRow[$headerMap['apellido_paterno']]) ? $rawRow[$headerMap['apellido_paterno']] : null;
    $apellidoM = isset($headerMap['apellido_materno'], $rawRow[$headerMap['apellido_materno']]) ? $rawRow[$headerMap['apellido_materno']] : null;
    $sexo = isset($headerMap['sexo'], $rawRow[$headerMap['sexo']]) ? $rawRow[$headerMap['sexo']] : null;
    $fechaNacimiento = isset($headerMap['fecha_nacimiento'], $rawRow[$headerMap['fecha_nacimiento']]) ? $rawRow[$headerMap['fecha_nacimiento']] : null;
    $estadoCivil = isset($headerMap['estado_civil'], $rawRow[$headerMap['estado_civil']]) ? $rawRow[$headerMap['estado_civil']] : null;
    $escolaridad = isset($headerMap['escolaridad'], $rawRow[$headerMap['escolaridad']]) ? $rawRow[$headerMap['escolaridad']] : null;
    $prevision = isset($headerMap['institucion_previsional'], $rawRow[$headerMap['institucion_previsional']]) ? $rawRow[$headerMap['institucion_previsional']] : null;
    $situacion = isset($headerMap['situacion'], $rawRow[$headerMap['situacion']]) ? $rawRow[$headerMap['situacion']] : null;
    $nacionalidad = isset($headerMap['nacionalidad'], $rawRow[$headerMap['nacionalidad']]) ? $rawRow[$headerMap['nacionalidad']] : null;
    $puebloIndig = isset($headerMap['pueblo_indig'], $rawRow[$headerMap['pueblo_indig']]) ? $rawRow[$headerMap['pueblo_indig']] : null;
    $calleResidencial = isset($headerMap['calle_residencial'], $rawRow[$headerMap['calle_residencial']]) ? $rawRow[$headerMap['calle_residencial']] : null;
    $numeroResidencial = isset($headerMap['numero_residencial'], $rawRow[$headerMap['numero_residencial']]) ? $rawRow[$headerMap['numero_residencial']] : null;
    $sector = isset($headerMap['sector'], $rawRow[$headerMap['sector']]) ? $rawRow[$headerMap['sector']] : null;
    $movilContacto = isset($headerMap['movil_contacto'], $rawRow[$headerMap['movil_contacto']]) ? $rawRow[$headerMap['movil_contacto']] : null;
    $telLaboral = isset($headerMap['tel_laboral'], $rawRow[$headerMap['tel_laboral']]) ? $rawRow[$headerMap['tel_laboral']] : null;
    $tel2Contacto = isset($headerMap['tel2_contacto'], $rawRow[$headerMap['tel2_contacto']]) ? $rawRow[$headerMap['tel2_contacto']] : null;
    $fechaUltimaActualizacion = isset($headerMap['fecha_ultima_actualizacion'], $rawRow[$headerMap['fecha_ultima_actualizacion']]) ? $rawRow[$headerMap['fecha_ultima_actualizacion']] : null;
    $horaUltimaActualizacion = isset($headerMap['hora_ultima_actualizacion'], $rawRow[$headerMap['hora_ultima_actualizacion']]) ? $rawRow[$headerMap['hora_ultima_actualizacion']] : null;

    if ($rut === '' && !array_filter([$nombres, $apellidoP, $apellidoM, $movilContacto], static fn($v) => trim((string)$v) !== '')) {
      continue;
    }

    $out[] = [
      'rut' => $rut,
      'fecha_nacimiento' => ip_parse_date_or_null($fechaNacimiento),
      'nombres' => ip_clean_string((string)$nombres),
      'apellidos' => ip_concat([$apellidoP, $apellidoM], ' '),
      'genero' => ip_map_genero((string)$sexo),
      'estado_civil' => ip_clean_string((string)$estadoCivil),
      'escolaridad' => ip_clean_string((string)$escolaridad),
      'prevision' => ip_clean_string((string)$prevision),
      'inscrito_cesfam' => ip_map_inscrito($situacion),
      'migrante' => ip_map_migrante((string)$nacionalidad),
      'nacionalidad' => ip_clean_string((string)$nacionalidad),
      'pueblo_originario' => ip_clean_string((string)$puebloIndig),
      'direccion' => ip_concat([$calleResidencial, $numeroResidencial], ' '),
      'sectorizacion' => ip_map_sectorizacion($sector),
      'celular' => ip_clean_string((string)$movilContacto),
      'telefonos' => ip_concat([$telLaboral, $tel2Contacto], ' | '),
      'excel_ultima_actualizacion' => ip_combine_excel_update($fechaUltimaActualizacion, $horaUltimaActualizacion),
    ];
  }

  return $out;
}

function ip_extract_uploaded_rows(array $file): array {
  $tmp = (string)($file['tmp_name'] ?? '');
  if ($tmp === '') {
    throw new RuntimeException('No se recibió correctamente el archivo.');
  }

  $ext = strtolower(pathinfo((string)($file['name'] ?? ''), PATHINFO_EXTENSION));
  if ($ext === 'csv' || $ext === 'txt') {
    return ip_map_rows(ip_read_csv_rows($tmp));
  }
  if ($ext === 'xlsx') {
    return ip_map_rows(ip_read_xlsx_rows($tmp, 1));
  }
  throw new RuntimeException('Formato no soportado. Sube un .csv o .xlsx');
}

if (!isset($_FILES['excel'])) {
  json_response(['ok' => false, 'message' => 'No llegó el archivo al servidor.'], 400);
}

$file = $_FILES['excel'];
if ((int)($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
  json_response(['ok' => false, 'message' => 'Error al subir el archivo. Código: ' . (int)$file['error']], 400);
}

try {
  $rows = ip_extract_uploaded_rows($file);
} catch (Throwable $e) {
  json_response(['ok' => false, 'message' => $e->getMessage()], 400);
}

if (!$rows) {
  json_response(['ok' => false, 'message' => 'No encontré filas válidas para importar el padrón.'], 400);
}

$registradoPor = normalize_rut((string)($u['rut'] ?? ''));
if ($registradoPor === '') $registradoPor = null;

try {
  $existing = [];
  $stAll = $pdo->query('SELECT rut, ultima_actualizacion FROM usuarios');
  foreach ($stAll->fetchAll(PDO::FETCH_ASSOC) as $item) {
    $existing[normalize_rut((string)($item['rut'] ?? ''))] = [
      'ultima_actualizacion' => !empty($item['ultima_actualizacion']) ? (string)$item['ultima_actualizacion'] : null,
    ];
  }

  $pdo->beginTransaction();

  $sqlInsert = "INSERT INTO usuarios (
            rut,
            fecha_nacimiento,
            nombres,
            apellidos,
            genero,
            estado_civil,
            escolaridad,
            prevision,
            inscrito_cesfam,
            migrante,
            nacionalidad,
            pueblo_originario,
            direccion,
            sectorizacion,
            celular,
            telefonos,
            registrado_por,
            ultima_actualizacion
          ) VALUES (
            :rut,
            :fecha_nacimiento,
            :nombres,
            :apellidos,
            :genero,
            :estado_civil,
            :escolaridad,
            :prevision,
            :inscrito_cesfam,
            :migrante,
            :nacionalidad,
            :pueblo_originario,
            :direccion,
            :sectorizacion,
            :celular,
            :telefonos,
            :registrado_por,
            COALESCE(:ultima_actualizacion, CURRENT_TIMESTAMP)
          )";

  $sqlUpdate = "UPDATE usuarios SET
            fecha_nacimiento = :fecha_nacimiento,
            nombres = :nombres,
            apellidos = :apellidos,
            genero = :genero,
            estado_civil = :estado_civil,
            escolaridad = :escolaridad,
            prevision = :prevision,
            inscrito_cesfam = :inscrito_cesfam,
            migrante = :migrante,
            nacionalidad = :nacionalidad,
            pueblo_originario = :pueblo_originario,
            direccion = :direccion,
            sectorizacion = :sectorizacion,
            celular = :celular,
            telefonos = :telefonos,
            ultima_actualizacion = COALESCE(:ultima_actualizacion, ultima_actualizacion)
          WHERE rut = :rut";

  $stInsert = $pdo->prepare($sqlInsert);
  $stUpdate = $pdo->prepare($sqlUpdate);

  $summary = [
    'recibidos' => count($rows),
    'procesados' => 0,
    'insertados' => 0,
    'actualizados' => 0,
    'sin_rut' => 0,
    'sin_fecha_excel' => 0,
    'omitidos_por_fecha' => 0,
    'omitidos' => 0,
  ];

  foreach ($rows as $row) {
    if (!is_array($row)) {
      $summary['omitidos']++;
      continue;
    }

    $rut = normalize_rut((string)($row['rut'] ?? ''));
    if ($rut === '') {
      $summary['sin_rut']++;
      continue;
    }

    $summary['procesados']++;
    $existingInfo = $existing[$rut] ?? null;
    $excelUltima = !empty($row['excel_ultima_actualizacion']) ? (string)$row['excel_ultima_actualizacion'] : null;

    if ($existingInfo !== null) {
      $dbUltima = !empty($existingInfo['ultima_actualizacion']) ? (string)$existingInfo['ultima_actualizacion'] : null;

      if ($excelUltima === null) {
        $summary['sin_fecha_excel']++;
        $summary['omitidos']++;
        continue;
      }

      $excelTs = strtotime($excelUltima);
      $dbTs = $dbUltima ? strtotime($dbUltima) : false;

      if ($excelTs === false) {
        $summary['sin_fecha_excel']++;
        $summary['omitidos']++;
        continue;
      }

      if ($dbTs !== false && $excelTs <= $dbTs) {
        $summary['omitidos_por_fecha']++;
        $summary['omitidos']++;
        continue;
      }
    }

    $basePayload = [
      ':rut' => $rut,
      ':fecha_nacimiento' => $row['fecha_nacimiento'] ?? null,
      ':nombres' => $row['nombres'] ?? null,
      ':apellidos' => $row['apellidos'] ?? null,
      ':genero' => $row['genero'] ?? null,
      ':estado_civil' => $row['estado_civil'] ?? null,
      ':escolaridad' => $row['escolaridad'] ?? null,
      ':prevision' => $row['prevision'] ?? null,
      ':inscrito_cesfam' => $row['inscrito_cesfam'] ?? null,
      ':migrante' => $row['migrante'] ?? null,
      ':nacionalidad' => $row['nacionalidad'] ?? null,
      ':pueblo_originario' => $row['pueblo_originario'] ?? null,
      ':direccion' => $row['direccion'] ?? null,
      ':sectorizacion' => $row['sectorizacion'] ?? null,
      ':celular' => $row['celular'] ?? null,
      ':telefonos' => $row['telefonos'] ?? null,
      ':ultima_actualizacion' => $excelUltima,
    ];

    if ($existingInfo !== null) {
      $stUpdate->execute($basePayload);
      $summary['actualizados']++;
      $existing[$rut]['ultima_actualizacion'] = $excelUltima ?: ($existing[$rut]['ultima_actualizacion'] ?? null);
    } else {
      $insertPayload = $basePayload + [
        ':registrado_por' => $registradoPor,
      ];
      $stInsert->execute($insertPayload);
      $summary['insertados']++;
      $existing[$rut] = ['ultima_actualizacion' => $excelUltima];
    }
  }

  $pdo->commit();
  fp_log_activity($pdo, $u, 'Personas', 'Importar inscritos/adscritos', 'Se importó padrón: ' . (int)$summary['insertados'] . ' insertados y ' . (int)$summary['actualizados'] . ' actualizados.', 'importacion', 'padron');
  json_response(['ok' => true, 'summary' => $summary]);
} catch (Throwable $e) {
  if ($pdo->inTransaction()) {
    $pdo->rollBack();
  }
  json_response(['ok' => false, 'message' => 'Error importando padrón: ' . $e->getMessage()], 500);
}
