#!/usr/bin/env php
<?php
/**
 * ETL Firebird 2.5 → MySQL (utf8mb4)
 * Sistema: DBLAB_XIMENA_FB25 → nuevo sistema WhatsApp-Lab
 *
 * Uso:
 *   php run_etl.php [--dry-run] [--skip-historico] [--only=PASO]
 *
 * Pasos disponibles (--only):
 *   secciones | especialidades | tipos_muestra | protocolos | items |
 *   perfiles  | exam_tipos     | tarifas       | empresas   |
 *   medicos   | pacientes      | recepciones   | relaciones | pagos
 *
 * Requerimientos:
 *   - PHP 8.0+ con extensión PDO_Firebird (php-firebird) instalada
 *   - Archivo config.local.php con credenciales reales
 *   - Las migraciones LIS 01-07 ya ejecutadas en MySQL
 */

declare(strict_types=1);
set_time_limit(0);
ini_set('memory_limit', '512M');

require_once __DIR__ . '/helpers.php';

// ── Configuración ────────────────────────────────────────────────────────────
$cfgFile = file_exists(__DIR__ . '/config.local.php')
    ? __DIR__ . '/config.local.php'
    : __DIR__ . '/config.php';

$cfg = require $cfgFile;

// Argumentos CLI
$args        = array_slice($argv ?? [], 1);
$dryRun      = in_array('--dry-run', $args, true)        || $cfg['options']['dry_run'];
$skipHist    = in_array('--skip-historico', $args, true) || $cfg['options']['skip_historico'];
$onlyPaso    = null;
foreach ($args as $arg) {
    if (str_starts_with($arg, '--only=')) {
        $onlyPaso = strtolower(substr($arg, 7));
    }
}

$logFp = fopen($cfg['options']['log_file'], 'w');

etlLog('=== ETL Firebird → MySQL ===', $logFp);
etlLog("DRY_RUN: " . ($dryRun ? 'SÍ' : 'NO'), $logFp);
if ($onlyPaso) etlLog("Solo paso: $onlyPaso", $logFp);

// ── Conexiones ───────────────────────────────────────────────────────────────
try {
    $fb = new PDO(
        $cfg['firebird']['dsn'],
        $cfg['firebird']['user'],
        $cfg['firebird']['password'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
    );
    etlLog('Conexión Firebird OK', $logFp);
} catch (\Throwable $e) {
    etlLog('ERROR conectando Firebird: ' . $e->getMessage(), $logFp);
    exit(1);
}

try {
    $my = new PDO(
        "mysql:host={$cfg['mysql']['host']};port={$cfg['mysql']['port']};dbname={$cfg['mysql']['dbname']};charset=utf8mb4",
        $cfg['mysql']['user'],
        $cfg['mysql']['password'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => true]
    );
    $my->exec("SET NAMES utf8mb4");
    $my->exec("SET foreign_key_checks = 0");
    etlLog('Conexión MySQL OK', $logFp);
} catch (\Throwable $e) {
    etlLog('ERROR conectando MySQL: ' . $e->getMessage(), $logFp);
    exit(1);
}

$charset = $cfg['firebird']['charset'];

// ── Función auxiliar: ¿ejecutar este paso? ──────────────────────────────────
function shouldRun(string $paso, ?string $only): bool {
    return $only === null || $only === $paso;
}

// ════════════════════════════════════════════════════════════════════════════
// PASO 1 — SECCION → lab_secciones
// ════════════════════════════════════════════════════════════════════════════
if (shouldRun('secciones', $onlyPaso)) {
    etlLog('--- SECCION → lab_secciones ---', $logFp);
    $total = fbCount($fb, 'SECCION');
    etlLog("  Origen: $total registros", $logFp);

    $rows = [];
    foreach ($fb->query('SELECT CODSECCION, NOMBSECCION FROM SECCION') as $r) {
        $rows[] = [
            'codigo' => cleanStr($r['CODSECCION'], $charset),
            'nombre' => cleanStr($r['NOMBSECCION'], $charset),
        ];
    }
    if (!$dryRun) {
        $n = batchInsert($my, 'lab_secciones', $rows, true);
        etlLog("  Insertados: $n", $logFp);
    } else {
        etlLog("  [DRY] Se insertarían " . count($rows), $logFp);
    }
}

// ════════════════════════════════════════════════════════════════════════════
// PASO 2 — ESPECIALIDAD → lab_especialidades
// ════════════════════════════════════════════════════════════════════════════
if (shouldRun('especialidades', $onlyPaso)) {
    etlLog('--- ESPECIALIDAD → lab_especialidades ---', $logFp);
    $rows = [];
    foreach ($fb->query('SELECT CODESPECIA, NOMBESPECIA FROM ESPECIALIDAD') as $r) {
        $rows[] = [
            'codigo' => cleanStr($r['CODESPECIA'], $charset),
            'nombre' => cleanStr($r['NOMBESPECIA'], $charset),
        ];
    }
    if (!$dryRun) {
        $n = batchInsert($my, 'lab_especialidades', $rows, true);
        etlLog("  Insertados: $n / " . count($rows), $logFp);
    }
}

// ════════════════════════════════════════════════════════════════════════════
// PASO 3 — EXAMEN.TIPOMUESTRA → lab_tipos_muestra
// ════════════════════════════════════════════════════════════════════════════
if (shouldRun('tipos_muestra', $onlyPaso)) {
    etlLog('--- EXAMEN.TIPOMUESTRA → lab_tipos_muestra ---', $logFp);
    $rows = [];
    $seen = [];
    foreach ($fb->query('SELECT DISTINCT TIPOMUESTRA FROM EXAMEN WHERE TIPOMUESTRA IS NOT NULL') as $r) {
        $cod = cleanStr($r['TIPOMUESTRA'], $charset);
        if (!$cod || isset($seen[$cod])) continue;
        $seen[$cod] = true;
        $rows[] = [
            'codigo' => $cod,
            'nombre' => ucwords(strtolower($cod)),  // nombre provisional, editar luego
        ];
    }
    etlLog("  Tipos únicos encontrados: " . count($rows), $logFp);
    if (!$dryRun) {
        $n = batchInsert($my, 'lab_tipos_muestra', $rows, true);
        etlLog("  Insertados: $n", $logFp);
    }
}

// ════════════════════════════════════════════════════════════════════════════
// PASO 4 — TARIFAID → lab_tarifas_id
// ════════════════════════════════════════════════════════════════════════════
if (shouldRun('tarifas', $onlyPaso)) {
    etlLog('--- TARIFAID → lab_tarifas_id ---', $logFp);
    $rows = [];
    foreach ($fb->query('SELECT IDTARIFA, NOMTARIFA, IDTARIFABASE, PORCENTAJE FROM TARIFAID') as $r) {
        $rows[] = [
            'id'            => (int)$r['IDTARIFA'],
            'nombre'        => cleanStr($r['NOMTARIFA'], $charset),
            'tarifa_origen' => $r['IDTARIFABASE'] ? (int)$r['IDTARIFABASE'] : null,
            'porcentaje'    => (float)($r['PORCENTAJE'] ?? 0),
        ];
    }
    if (!$dryRun) {
        $n = batchInsert($my, 'lab_tarifas_id', $rows, true);
        etlLog("  Insertados: $n / " . count($rows), $logFp);
    }

    // ── TARIFA → lab_tarifas (104k registros, se hace en batches) ────────
    etlLog('--- TARIFA → lab_tarifas ---', $logFp);
    $total = fbCount($fb, 'TARIFA');
    etlLog("  Origen: $total registros", $logFp);

    $stmt  = $fb->query('SELECT CODIGO, IDTARIFA, VALOR, RECARGO_URG, RECARGO_FES, RECARGO_ESP FROM TARIFA');
    $batch = [];
    $count = 0;

    while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $batch[] = [
            'cod_examen_legacy' => cleanStr($r['CODIGO'], $charset),
            'exam_tipo_id'      => null,   // se resuelve en paso 6 (exam_tipos)
            'tarifa_id'         => (int)$r['IDTARIFA'],
            'valor'             => (float)($r['VALOR'] ?? 0),
            'recargo_urg'       => (float)($r['RECARGO_URG'] ?? 0),
            'recargo_fes'       => (float)($r['RECARGO_FES'] ?? 0),
            'recargo_esp'       => (float)($r['RECARGO_ESP'] ?? 0),
        ];
        if (count($batch) >= 500) {
            if (!$dryRun) $count += batchInsert($my, 'lab_tarifas', $batch, true);
            $batch = [];
        }
    }
    if ($batch && !$dryRun) $count += batchInsert($my, 'lab_tarifas', $batch, true);
    etlLog("  Insertados: $count", $logFp);
}

// ════════════════════════════════════════════════════════════════════════════
// PASO 5 — PROTOCOLO + ITEM → lab_protocolos + lab_items_resultado
// ════════════════════════════════════════════════════════════════════════════
if (shouldRun('protocolos', $onlyPaso)) {
    etlLog('--- PROTOCOLO → lab_protocolos ---', $logFp);
    $rows = [];
    foreach ($fb->query('SELECT CODPROTOCOLO, NOMPROTOCOLO, CODSECCION, IDPLANILLA, ONLYITEMS FROM PROTOCOLO') as $r) {
        $rows[] = [
            'codigo'          => cleanStr($r['CODPROTOCOLO'], $charset),
            'nombre'          => cleanStr($r['NOMPROTOCOLO'], $charset),
            'cod_seccion'     => cleanStr($r['CODSECCION'],   $charset),
            'id_planilla'     => cleanStr($r['IDPLANILLA'],   $charset),
            'only_show_items' => $r['ONLYITEMS'] ? 1 : 0,
        ];
    }
    if (!$dryRun) {
        $n = batchInsert($my, 'lab_protocolos', $rows, true);
        etlLog("  Protocolos insertados: $n / " . count($rows), $logFp);
    }
}

if (shouldRun('items', $onlyPaso)) {
    etlLog('--- ITEM → lab_items_resultado ---', $logFp);
    $total = fbCount($fb, 'ITEM');
    etlLog("  Origen: $total registros", $logFp);

    $stmt  = $fb->query('SELECT CODPROTOCOLO,NOMITEM,TIPOSEXO,TIPO,MEDIDA,ABREVITEM,VMINREF,VMAXREF,ORDEN,FORMULA,CUPS FROM ITEM ORDER BY CODPROTOCOLO, ORDEN');
    $batch = [];
    $count = 0;

    while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $batch[] = [
            'cod_protocolo' => cleanStr($r['CODPROTOCOLO'], $charset),
            'nombre'        => cleanStr($r['NOMITEM'],      $charset),
            'tipo_sexo'     => cleanStr($r['TIPOSEXO'],     $charset),
            'tipo'          => cleanStr($r['TIPO'],         $charset),
            'medida'        => cleanStr($r['MEDIDA'],       $charset),
            'abreviatura'   => cleanStr($r['ABREVITEM'],    $charset),
            'vmin_ref'      => is_numeric($r['VMINREF']) ? (float)$r['VMINREF'] : null,
            'vmax_ref'      => is_numeric($r['VMAXREF']) ? (float)$r['VMAXREF'] : null,
            'orden'         => (int)($r['ORDEN'] ?? 0),
            'formula'       => cleanStr($r['FORMULA'],      $charset),
            'cups_detalle'  => cleanStr($r['CUPS'],         $charset),
        ];
        if (count($batch) >= 500) {
            if (!$dryRun) $count += batchInsert($my, 'lab_items_resultado', $batch, true);
            $batch = [];
        }
    }
    if ($batch && !$dryRun) $count += batchInsert($my, 'lab_items_resultado', $batch, true);
    etlLog("  Insertados: $count", $logFp);
}

// ════════════════════════════════════════════════════════════════════════════
// PASO 6 — PERFIL + PERFIL_EXA → lab_perfiles + lab_perfil_examenes
// ════════════════════════════════════════════════════════════════════════════
if (shouldRun('perfiles', $onlyPaso)) {
    etlLog('--- PERFIL → lab_perfiles ---', $logFp);
    $rows = [];
    foreach ($fb->query('SELECT CODPERFIL, NOMPERFIL FROM PERFIL') as $r) {
        $rows[] = [
            'nombre' => cleanStr($r['NOMPERFIL'], $charset),
        ];
    }
    // Firebird usa VARCHAR código; MySQL usa INT AUTO_INCREMENT
    // Guardamos el código viejo → id nuevo en memoria para PERFIL_EXA
    if (!$dryRun) {
        // Insertar uno a uno para mapear código → id nuevo
        $mapaPerfiles = [];
        foreach ($fb->query('SELECT CODPERFIL, NOMPERFIL FROM PERFIL') as $r) {
            $stmt2 = $my->prepare('INSERT IGNORE INTO lab_perfiles (nombre) VALUES (?)');
            $stmt2->execute([cleanStr($r['NOMPERFIL'], $charset)]);
            $newId = (int)$my->lastInsertId();
            if ($newId) $mapaPerfiles[cleanStr($r['CODPERFIL'], $charset)] = $newId;
        }
        etlLog("  Perfiles insertados: " . count($mapaPerfiles), $logFp);

        // PERFIL_EXA — mapear cod_examen → exam_tipo_id
        etlLog('--- PERFIL_EXA → lab_perfil_examenes ---', $logFp);
        $examMap = [];
        foreach ($my->query('SELECT id, codigo_legacy FROM exam_tipos WHERE codigo_legacy IS NOT NULL') as $r) {
            $examMap[$r['codigo_legacy']] = (int)$r['id'];
        }

        $peBatch = [];
        foreach ($fb->query('SELECT CODPERFIL, CODEXAMEN FROM PERFIL_EXA') as $r) {
            $pId = $mapaPerfiles[cleanStr($r['CODPERFIL'], $charset)] ?? null;
            $eId = $examMap[cleanStr($r['CODEXAMEN'], $charset)] ?? null;
            if ($pId && $eId) {
                $peBatch[] = ['perfil_id' => $pId, 'exam_tipo_id' => $eId];
            }
        }
        $n = batchInsert($my, 'lab_perfil_examenes', $peBatch, true);
        etlLog("  Relaciones perfil-examen: $n", $logFp);
    }
}

// ════════════════════════════════════════════════════════════════════════════
// PASO 7 — EXAMEN → exam_tipos (ampliar con campos legacy)
// ════════════════════════════════════════════════════════════════════════════
if (shouldRun('exam_tipos', $onlyPaso)) {
    etlLog('--- EXAMEN → exam_tipos ---', $logFp);
    $total = fbCount($fb, 'EXAMEN');
    etlLog("  Origen: $total registros", $logFp);

    $insertados = 0;
    $actualizados = 0;

    $stmt = $fb->query(
        'SELECT CODIGO, NOMEXAMEN, CODPROT, TIPOMUESTRA, NIVEL, CUPS,
                ABREVEXAMEN, SEREMITE, SERECIBE, CODSECCION
           FROM EXAMEN'
    );

    $checkStmt = $my->prepare('SELECT id FROM exam_tipos WHERE codigo_legacy = ?');
    $updStmt   = $my->prepare(
        'UPDATE exam_tipos SET
            cod_protocolo = ?, tipo_muestra = ?, nivel = ?, cups = ?,
            abreviatura = ?, seremite = ?, serecibe = ?
         WHERE codigo_legacy = ?'
    );
    $insStmt   = $my->prepare(
        'INSERT IGNORE INTO exam_tipos
            (codigo, nombre, categoria, codigo_legacy, cod_protocolo,
             tipo_muestra, nivel, cups, abreviatura, seremite, serecibe)
         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
    );

    while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $codLeg  = cleanStr($r['CODIGO'],       $charset);
        $nombre  = cleanStr($r['NOMEXAMEN'],     $charset);
        $proto   = cleanStr($r['CODPROT'],       $charset);
        $tMuest  = cleanStr($r['TIPOMUESTRA'],   $charset);
        $nivel   = $r['NIVEL']  ? (int)$r['NIVEL']  : null;
        $cups    = cleanStr($r['CUPS'],          $charset);
        $abrev   = cleanStr($r['ABREVEXAMEN'],   $charset);
        $serem   = $r['SEREMITE'] ? 1 : 0;
        $serec   = cleanStr($r['SERECIBE'],      $charset);
        $seccion = cleanStr($r['CODSECCION'],    $charset);

        if (!$codLeg || !$nombre) continue;

        $checkStmt->execute([$codLeg]);
        $existing = $checkStmt->fetchColumn();

        if ($dryRun) continue;

        if ($existing) {
            $updStmt->execute([$proto, $tMuest, $nivel, $cups, $abrev, $serem, $serec, $codLeg]);
            $actualizados++;
        } else {
            // Usar el código Firebird como código del nuevo sistema (si no hay conflicto)
            $insStmt->execute([$codLeg, $nombre, $seccion, $codLeg, $proto, $tMuest, $nivel, $cups, $abrev, $serem, $serec]);
            $insertados++;
        }
    }

    etlLog("  Nuevos: $insertados | Actualizados: $actualizados", $logFp);

    // Resolver exam_tipo_id en lab_tarifas (ahora que los exámenes ya están)
    if (!$dryRun) {
        etlLog('  Resolviendo exam_tipo_id en lab_tarifas...', $logFp);
        $updated = $my->exec(
            'UPDATE lab_tarifas t
               JOIN exam_tipos e ON e.codigo_legacy = t.cod_examen_legacy
                SET t.exam_tipo_id = e.id
              WHERE t.exam_tipo_id IS NULL'
        );
        etlLog("  lab_tarifas actualizadas: $updated filas", $logFp);
    }
}

// ════════════════════════════════════════════════════════════════════════════
// PASO 8 — EMPRESA + EMPRESA_SUB + EXAMEN_EMP
// ════════════════════════════════════════════════════════════════════════════
if (shouldRun('empresas', $onlyPaso)) {
    etlLog('--- EMPRESA → lab_empresas ---', $logFp);
    $rows = [];
    foreach ($fb->query(
        'SELECT NIT,NOMBRE,RAZSOCIAL,IDTARIFA,DESCUENTO,CODEEPS,
                TIPOUSUARIO,TIPOUSUARIOSISPRO,CODCONTRATO,CODTERCERO,
                CENTROCOSTO,REQAUTORIZA,ACTIVA
           FROM EMPRESA'
    ) as $r) {
        $rows[] = [
            'nit'                  => cleanStr($r['NIT'],              $charset),
            'nombre'               => cleanStr($r['NOMBRE'],           $charset),
            'razon_social'         => cleanStr($r['RAZSOCIAL'],        $charset),
            'tarifa_id'            => $r['IDTARIFA'] ? (int)$r['IDTARIFA'] : null,
            'descuento_pct'        => (float)($r['DESCUENTO'] ?? 0),
            'codigo_eps'           => cleanStr($r['CODEEPS'],          $charset),
            'tipo_usuario'         => cleanStr($r['TIPOUSUARIO'],      $charset),
            'tipo_usuario_sispro'  => cleanStr($r['TIPOUSUARIOSISPRO'],$charset),
            'cod_contrato'         => cleanStr($r['CODCONTRATO'],      $charset),
            'cod_tercero'          => cleanStr($r['CODTERCERO'],       $charset),
            'centro_costo'         => cleanStr($r['CENTROCOSTO'],      $charset),
            'req_autoriza'         => $r['REQAUTORIZA'] ? 1 : 0,
            'activa'               => $r['ACTIVA']      ? 1 : 0,
        ];
    }
    if (!$dryRun) {
        $n = batchInsert($my, 'lab_empresas', $rows, true);
        etlLog("  Empresas: $n / " . count($rows), $logFp);
    }

    etlLog('--- EMPRESA_SUB → lab_empresa_subgrupos ---', $logFp);
    $rows = [];
    foreach ($fb->query('SELECT NIT,SUBGRUPO,IDTARIFA,REF_SUBGRUPO,CODCONTRATO FROM EMPRESA_SUB') as $r) {
        $rows[] = [
            'nit_empresa'  => cleanStr($r['NIT'],         $charset),
            'subgrupo'     => cleanStr($r['SUBGRUPO'],    $charset),
            'tarifa_id'    => $r['IDTARIFA'] ? (int)$r['IDTARIFA'] : null,
            'ref_subgrupo' => cleanStr($r['REF_SUBGRUPO'],$charset),
            'cod_contrato' => cleanStr($r['CODCONTRATO'], $charset),
        ];
    }
    if (!$dryRun) {
        $n = batchInsert($my, 'lab_empresa_subgrupos', $rows, true);
        etlLog("  Subgrupos: $n", $logFp);
    }

    etlLog('--- EXAMEN_EMP → lab_examenes_empresa ---', $logFp);
    $rows = [];
    foreach ($fb->query('SELECT NIT,CODIGO,CODIGOEMP FROM EXAMEN_EMP') as $r) {
        $rows[] = [
            'nit_empresa'       => cleanStr($r['NIT'],      $charset),
            'cod_examen_legacy' => cleanStr($r['CODIGO'],   $charset),
            'exam_tipo_id'      => null,
            'codigo_empresa'    => cleanStr($r['CODIGOEMP'],$charset),
        ];
    }
    if (!$dryRun) {
        $n = batchInsert($my, 'lab_examenes_empresa', $rows, true);
        // Resolver exam_tipo_id
        $my->exec(
            'UPDATE lab_examenes_empresa ee
               JOIN exam_tipos e ON e.codigo_legacy = ee.cod_examen_legacy
                SET ee.exam_tipo_id = e.id
              WHERE ee.exam_tipo_id IS NULL'
        );
        etlLog("  Examenes-empresa: $n", $logFp);
    }
}

// ════════════════════════════════════════════════════════════════════════════
// PASO 9 — MEDICO → medicos
// ════════════════════════════════════════════════════════════════════════════
if (shouldRun('medicos', $onlyPaso)) {
    etlLog('--- MEDICO → medicos ---', $logFp);
    $rows = [];
    foreach ($fb->query(
        'SELECT CODMEDICO,NOMBRES,APELLIDOS,CODESPECIA,TELEFONO1,EMAIL,DOCIDMEDICO,ACTIVO
           FROM MEDICO'
    ) as $r) {
        $rows[] = [
            'codigo'          => cleanStr($r['CODMEDICO'],   $charset),
            'nombres'         => cleanStr($r['NOMBRES'],     $charset),
            'apellidos'       => cleanStr($r['APELLIDOS'],   $charset),
            'cod_especialidad'=> cleanStr($r['CODESPECIA'],  $charset),
            'telefonos'       => cleanStr($r['TELEFONO1'],   $charset),
            'email'           => cleanStr($r['EMAIL'],       $charset),
            'docidmedico'     => cleanStr($r['DOCIDMEDICO'], $charset),
            'activo'          => $r['ACTIVO'] ? 1 : 0,
        ];
    }
    if (!$dryRun) {
        $n = batchInsert($my, 'medicos', $rows, true);
        etlLog("  Médicos: $n / " . count($rows), $logFp);
    }
}

// ════════════════════════════════════════════════════════════════════════════
// PASO 10 — PACIENTE → lab_pacientes
// ════════════════════════════════════════════════════════════════════════════
if (shouldRun('pacientes', $onlyPaso)) {
    etlLog('--- PACIENTE → lab_pacientes ---', $logFp);
    $total = fbCount($fb, 'PACIENTE');
    etlLog("  Origen: $total registros", $logFp);

    $stmt = $fb->query(
        'SELECT CODPAC,DOCIDENT,TIPOIDENT,NOMBRES,APELLIDOS,
                TELEFONO1,EMAIL,FECHANAC,SEXO,CIUDAD,
                OCUPACION,CODETNIA,TIPORES
           FROM PACIENTE
          ORDER BY CODPAC'
    );

    $checkStmt = $my->prepare('SELECT id FROM lab_pacientes WHERE numero_documento = ?');
    $insStmt   = $my->prepare(
        'INSERT IGNORE INTO lab_pacientes
            (numero_documento, tipo_documento, nombre_completo, telefono,
             email, fecha_nacimiento, genero, ciudad,
             ocupacion, codetnia, tipores, codigo_legacy, es_historico)
         VALUES (?,?,?,?,?,?,?,?,?,?,?,?,1)'
    );
    $updLegacy = $my->prepare(
        'UPDATE lab_pacientes SET codigo_legacy = ? WHERE numero_documento = ? AND codigo_legacy IS NULL'
    );

    $nuevos = $coincidentes = 0;

    while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $doc = cleanStr($r['DOCIDENT'], $charset);
        if (!$doc) continue;

        $nombres   = cleanStr($r['NOMBRES'],   $charset);
        $apellidos = cleanStr($r['APELLIDOS'], $charset);
        $nombre    = trim("$nombres $apellidos");

        if ($dryRun) continue;

        $checkStmt->execute([$doc]);
        $existeId = $checkStmt->fetchColumn();

        if ($existeId) {
            // Ya existe → solo actualizar codigo_legacy si falta
            $updLegacy->execute([cleanStr($r['CODPAC'], $charset), $doc]);
            $coincidentes++;
        } else {
            $tipoDoc = match(strtoupper(cleanStr($r['TIPOIDENT'], $charset) ?? '')) {
                'CC'  => 'CC',
                'CE'  => 'CE',
                'TI'  => 'TI',
                'PA'  => 'PA',
                'NIT' => 'NIT',
                'RC'  => 'RC',
                'MS'  => 'MS',
                default => 'CC',
            };
            $genero = match(strtoupper(cleanStr($r['SEXO'], $charset) ?? '')) {
                'M' => 'M', 'F' => 'F', default => null
            };
            $insStmt->execute([
                $doc,
                $tipoDoc,
                $nombre ?: 'Sin nombre',
                cleanStr($r['TELEFONO1'],  $charset),
                cleanStr($r['EMAIL'],      $charset),
                fbDate($r['FECHANAC']),
                $genero,
                cleanStr($r['CIUDAD'],     $charset),
                cleanStr($r['OCUPACION'],  $charset),
                cleanStr($r['CODETNIA'],   $charset),
                cleanStr($r['TIPORES'],    $charset),
                cleanStr($r['CODPAC'],     $charset),
            ]);
            $nuevos++;
        }
    }

    etlLog("  Nuevos: $nuevos | Coincidentes (codigo_legacy actualizado): $coincidentes", $logFp);
}

// ════════════════════════════════════════════════════════════════════════════
// PASO 11-13 — Histórico transaccional (RECEPCION / RELACION / PAGOS)
// ════════════════════════════════════════════════════════════════════════════
if (!$skipHist) {

    // ── 11. RECEPCION → lab_recepciones ──────────────────────────────────
    if (shouldRun('recepciones', $onlyPaso)) {
        etlLog('--- RECEPCION → lab_recepciones ---', $logFp);
        $total = fbCount($fb, 'RECEPCION');
        etlLog("  Origen: $total registros", $logFp);

        // Mapa paciente legacy → id nuevo
        $pacMap = [];
        foreach ($my->query('SELECT id, codigo_legacy FROM lab_pacientes WHERE codigo_legacy IS NOT NULL') as $r) {
            $pacMap[$r['codigo_legacy']] = (int)$r['id'];
        }
        // Mapa médico legacy → id nuevo
        $medMap = [];
        foreach ($my->query('SELECT id, codigo FROM medicos WHERE codigo IS NOT NULL') as $r) {
            $medMap[$r['codigo']] = (int)$r['id'];
        }

        $stmt  = $fb->query(
            'SELECT IDRECEPCION,CODPAC,CODMEDICO,NIT,SUBGRUPO,
                    FECHA,HORAINICIO,PREFIJO,NUMFACTURA,
                    VALORTOTAL,VALORDESC,DIAGPPAL,TIPOUSUARIO,AUTORIZACION,USUARIO
               FROM RECEPCION
              ORDER BY IDRECEPCION'
        );
        $batch = [];
        $count = 0;

        while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $codPac = cleanStr($r['CODPAC'],   $charset);
            $codMed = cleanStr($r['CODMEDICO'],$charset);

            $batch[] = [
                'id'                  => (int)$r['IDRECEPCION'],
                'cod_paciente_legacy' => $codPac,
                'paciente_id'         => $pacMap[$codPac] ?? null,
                'cod_medico_legacy'   => $codMed,
                'medico_id'           => $medMap[$codMed] ?? null,
                'nit_empresa'         => cleanStr($r['NIT'],          $charset),
                'subgrupo'            => cleanStr($r['SUBGRUPO'],     $charset),
                'fecha_recepcion'     => fbDate($r['FECHA']),
                'hora_inicio'         => cleanStr($r['HORAINICIO'],   $charset),
                'prefijo'             => cleanStr($r['PREFIJO'],      $charset),
                'num_factura'         => $r['NUMFACTURA'] ? (int)$r['NUMFACTURA'] : null,
                'valor_total'         => (float)($r['VALORTOTAL']    ?? 0),
                'valor_desc'          => (float)($r['VALORDESC']     ?? 0),
                'diag_ppal'           => cleanStr($r['DIAGPPAL'],     $charset),
                'tipo_usuario'        => cleanStr($r['TIPOUSUARIO'],  $charset),
                'autorizacion'        => cleanStr($r['AUTORIZACION'], $charset),
                'usuario'             => cleanStr($r['USUARIO'],      $charset),
            ];

            if (count($batch) >= 500) {
                if (!$dryRun) $count += batchInsert($my, 'lab_recepciones', $batch, true);
                $batch = [];
                if ($count % 5000 === 0) etlLog("  ... $count procesadas", $logFp);
            }
        }
        if ($batch && !$dryRun) $count += batchInsert($my, 'lab_recepciones', $batch, true);
        etlLog("  Insertadas: $count", $logFp);
    }

    // ── 12. RELACION → lab_relaciones ─────────────────────────────────────
    if (shouldRun('relaciones', $onlyPaso)) {
        etlLog('--- RELACION → lab_relaciones ---', $logFp);
        $total = fbCount($fb, 'RELACION');
        etlLog("  Origen: $total registros", $logFp);

        // Mapa cod_examen_legacy → exam_tipo_id
        $examMap = [];
        foreach ($my->query('SELECT id, codigo_legacy FROM exam_tipos WHERE codigo_legacy IS NOT NULL') as $r) {
            $examMap[$r['codigo_legacy']] = (int)$r['id'];
        }

        $stmt  = $fb->query(
            'SELECT IDRECEPCION,CODIGO,PRECIO,FECHAREPORT,REPORTADO,REPORPOR,VALIDADO,USRVALIDA,FECHAVALIDA
               FROM RELACION'
        );
        $batch = [];
        $count = 0;

        while ($r = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $codExam = cleanStr($r['CODIGO'], $charset);
            $batch[] = [
                'recepcion_id'      => (int)$r['IDRECEPCION'],
                'cod_examen_legacy' => $codExam,
                'exam_tipo_id'      => $examMap[$codExam] ?? null,
                'precio'            => (float)($r['PRECIO']     ?? 0),
                'fecha_reportado'   => fbDate($r['FECHAREPORT']),
                'reportado'         => $r['REPORTADO']    ? 1 : 0,
                'reportado_por'     => cleanStr($r['REPORPOR'],  $charset),
                'validado'          => $r['VALIDADO']     ? 1 : 0,
                'usuario_valida'    => cleanStr($r['USRVALIDA'], $charset),
                'fecha_valida'      => fbDate($r['FECHAVALIDA']),
            ];

            if (count($batch) >= 500) {
                if (!$dryRun) $count += batchInsert($my, 'lab_relaciones', $batch, true);
                $batch = [];
                if ($count % 10000 === 0) etlLog("  ... $count", $logFp);
            }
        }
        if ($batch && !$dryRun) $count += batchInsert($my, 'lab_relaciones', $batch, true);
        etlLog("  Insertadas: $count", $logFp);
    }

    // ── 13. PAGOS + PAGOS_DET ─────────────────────────────────────────────
    if (shouldRun('pagos', $onlyPaso)) {
        etlLog('--- PAGOS → lab_pagos ---', $logFp);
        $rows = [];
        foreach ($fb->query('SELECT NUMCAJA,IDRECEPCION,VALOR,FECHAPAGO,USUARIO FROM PAGOS') as $r) {
            $rows[] = [
                'numcaja_legacy' => (int)$r['NUMCAJA'],
                'recepcion_id'   => (int)$r['IDRECEPCION'],
                'valor'          => (float)($r['VALOR'] ?? 0),
                'fecha'          => fbDate($r['FECHAPAGO']),
                'usuario'        => cleanStr($r['USUARIO'], $charset),
            ];
        }
        if (!$dryRun) {
            $n = batchInsert($my, 'lab_pagos', $rows, true);
            etlLog("  Pagos: $n / " . count($rows), $logFp);
        }

        etlLog('--- PAGOS_DET → lab_pagos_det ---', $logFp);
        // Mapa numcaja → id nuevo
        $pagoMap = [];
        if (!$dryRun) {
            foreach ($my->query('SELECT id, numcaja_legacy FROM lab_pagos') as $r) {
                $pagoMap[$r['numcaja_legacy']] = (int)$r['id'];
            }
        }
        $rows = [];
        foreach ($fb->query('SELECT NUMCAJA,TIPOPAGO,VALOR,NUMDOC FROM PAGOS_DET') as $r) {
            $pagoId = $pagoMap[(int)$r['NUMCAJA']] ?? null;
            if (!$pagoId) continue;
            $rows[] = [
                'pago_id'   => $pagoId,
                'tipo_pago' => cleanStr($r['TIPOPAGO'], $charset),
                'valor'     => (float)($r['VALOR'] ?? 0),
                'num_doc'   => cleanStr($r['NUMDOC'],   $charset),
            ];
        }
        if (!$dryRun) {
            $n = batchInsert($my, 'lab_pagos_det', $rows, true);
            etlLog("  Detalles de pago: $n", $logFp);
        }
    }

} else {
    etlLog('--- Histórico omitido (--skip-historico) ---', $logFp);
}

// ── Restaurar FK checks ──────────────────────────────────────────────────────
if (!$dryRun) {
    $my->exec("SET foreign_key_checks = 1");
}

etlLog('=== ETL COMPLETADO ===', $logFp);
fclose($logFp);
echo "Log guardado en: {$cfg['options']['log_file']}\n";
