- Migraciones LIS 01-08: schema completo del nuevo LIS (secciones, protocolos, ítems de resultado, perfiles, tarifas, empresas, histórico transaccional) - ETL Firebird→MySQL: script CLI con conversión WIN1252→UTF-8, batches de 500, resolución de FKs y deduplicación de pacientes - turnero_muestras: tracking pendiente/recibida/rechazada por tipo de tubo - lugar.php: widget de recepción de muestras (solo tipo=muestras) - update_muestra_estado.php: API para marcar estado de muestra - create_solicitud.php: auto-crea muestras al guardar solicitud - get_consentimientos.php: incluye muestras[] en el response - 6 vistas SQL: v_muestras_hoy, v_recepcion_completa, v_examen_precio, etc. - numero_orden en encabezado del formulario firmado (D-/F- color diferenciado) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
716 lines
33 KiB
PHP
716 lines
33 KiB
PHP
#!/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";
|