#!/usr/bin/env php 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";