diff --git a/modules/lab_examenes/api/_helpers.php b/modules/lab_examenes/api/_helpers.php new file mode 100644 index 0000000..2bba773 --- /dev/null +++ b/modules/lab_examenes/api/_helpers.php @@ -0,0 +1,55 @@ + false, 'error' => 'Error interno: ' . $err['message']]); + } +}); + +header('Content-Type: application/json; charset=utf-8'); +if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; } + +function jsonOk(array $data = [], string $msg = ''): never { + ob_clean(); + $r = ['ok' => true]; + if ($msg) $r['message'] = $msg; + if ($data) $r = array_merge($r, $data); + echo json_encode($r); + exit; +} + +function jsonError(string $msg, int $code = 400): never { + ob_clean(); + http_response_code($code); + echo json_encode(['ok' => false, 'error' => $msg]); + exit; +} + +function requireAdmin(): void { + if (!isUserLoggedIn()) jsonError('No autorizado', 401); + $role = $_SESSION['admin_user']['role'] ?? ''; + if (!in_array($role, ['superadmin', 'admin', 'supervisor'], true)) jsonError('Sin permiso', 403); +} + +function requireLogin(): void { + if (!isUserLoggedIn()) jsonError('No autorizado', 401); +} + +function inputJson(): array { + return json_decode(file_get_contents('php://input'), true) ?? []; +} + +function db(): PDO { + $pdo = Database::getInstance()->getConnection(); + $pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"); + return $pdo; +} diff --git a/modules/lab_examenes/api/get.php b/modules/lab_examenes/api/get.php new file mode 100644 index 0000000..da063f9 --- /dev/null +++ b/modules/lab_examenes/api/get.php @@ -0,0 +1,20 @@ +prepare('SELECT * FROM exam_tipos WHERE id = ?'); +$s->execute([$id]); +$exam = $s->fetch(PDO::FETCH_ASSOC); +if (!$exam) jsonError('Examen no encontrado', 404); + +$items = $pdo->prepare( + 'SELECT * FROM lab_items_resultado WHERE cod_protocolo = ? ORDER BY orden, id' +); +$items->execute([$exam['cod_protocolo'] ?? $exam['codigo']]); + +jsonOk(['data' => $exam, 'items' => $items->fetchAll(PDO::FETCH_ASSOC)]); diff --git a/modules/lab_examenes/api/get_tarifas.php b/modules/lab_examenes/api/get_tarifas.php new file mode 100644 index 0000000..6d36deb --- /dev/null +++ b/modules/lab_examenes/api/get_tarifas.php @@ -0,0 +1,23 @@ +prepare('SELECT 1 FROM exam_tipos WHERE id = ?'); +$s->execute([$examId]); +if (!$s->fetchColumn()) jsonError('Examen no encontrado', 404); + +$s = $pdo->prepare( + "SELECT ti.id AS tarifa_id, ti.nombre AS tarifa_nombre, + lt.id AS precio_id, lt.valor, lt.recargo_urg, lt.recargo_fes, lt.recargo_esp + FROM lab_tarifas_id ti + LEFT JOIN lab_tarifas lt ON lt.tarifa_id = ti.id AND lt.exam_tipo_id = ? + ORDER BY ti.id" +); +$s->execute([$examId]); + +jsonOk(['data' => $s->fetchAll(PDO::FETCH_ASSOC)]); diff --git a/modules/lab_examenes/api/list.php b/modules/lab_examenes/api/list.php new file mode 100644 index 0000000..3b5acb1 --- /dev/null +++ b/modules/lab_examenes/api/list.php @@ -0,0 +1,37 @@ +prepare("SELECT COUNT(*) FROM exam_tipos $wSql"); +$s->execute($params); +$total = (int)$s->fetchColumn(); + +$s = $pdo->prepare( + "SELECT id, codigo, nombre, categoria, activo, cups, seremite, cod_protocolo, tipo_muestra + FROM exam_tipos $wSql ORDER BY categoria, nombre LIMIT $limit OFFSET $offset" +); +$s->execute($params); + +jsonOk(['data' => $s->fetchAll(PDO::FETCH_ASSOC), 'total' => $total, 'page' => $page, 'limit' => $limit]); diff --git a/modules/lab_examenes/api/save.php b/modules/lab_examenes/api/save.php new file mode 100644 index 0000000..f7658a0 --- /dev/null +++ b/modules/lab_examenes/api/save.php @@ -0,0 +1,73 @@ +prepare('SELECT COUNT(*) FROM turnero_examen_items WHERE exam_tipo_id = ?'); + $s->execute([$id]); + if ($s->fetchColumn() > 0) jsonError('Hay solicitudes con este examen. Desactívelo en su lugar.'); + $pdo->prepare('DELETE FROM exam_tipo_consentimientos WHERE exam_tipo_id = ?')->execute([$id]); + $pdo->prepare('DELETE FROM exam_tipos WHERE id = ?')->execute([$id]); + jsonOk([], 'Examen eliminado'); +} + +$codigo = strtoupper(trim($d['codigo'] ?? '')); +$nombre = trim($d['nombre'] ?? ''); +$categoria = trim($d['categoria'] ?? '') ?: null; +$cups = trim($d['cups'] ?? '') ?: null; +$cod_prot = trim($d['cod_protocolo'] ?? '') ?: null; +$tipo_m = trim($d['tipo_muestra'] ?? '') ?: null; +$nivel = isset($d['nivel']) && $d['nivel'] !== '' ? (int)$d['nivel'] : null; +$abrev = trim($d['abreviatura'] ?? '') ?: null; +$seremite = (int)($d['seremite'] ?? 0); +$serecibe = trim($d['serecibe'] ?? '') ?: null; +$activo = (int)($d['activo'] ?? 1); +$req_ayuno = (int)($d['requiere_ayuno'] ?? 0); +$horas_ayuno = isset($d['horas_ayuno']) && $d['horas_ayuno'] !== '' ? (int)$d['horas_ayuno'] : null; +$instruc = trim($d['instrucciones'] ?? '') ?: null; +$formId = (int)($d['formulario_id'] ?? 0) ?: null; + +if ($codigo === '') jsonError('El código es requerido'); +if ($nombre === '') jsonError('El nombre es requerido'); +if (strlen($codigo) > 20) jsonError('Código máximo 20 caracteres'); + +$pdo = db(); +$pdo->beginTransaction(); +try { + if ($id) { + $pdo->prepare( + 'UPDATE exam_tipos SET codigo=?,nombre=?,categoria=?,cups=?,cod_protocolo=?,tipo_muestra=?, + nivel=?,abreviatura=?,seremite=?,serecibe=?,activo=?,requiere_ayuno=?,horas_ayuno=?,instrucciones=? + WHERE id=?' + )->execute([$codigo,$nombre,$categoria,$cups,$cod_prot,$tipo_m, + $nivel,$abrev,$seremite,$serecibe,$activo,$req_ayuno,$horas_ayuno,$instruc,$id]); + $examId = $id; + } else { + $pdo->prepare( + 'INSERT INTO exam_tipos (codigo,nombre,categoria,cups,cod_protocolo,tipo_muestra, + nivel,abreviatura,seremite,serecibe,activo,codigo_legacy,requiere_ayuno,horas_ayuno,instrucciones) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)' + )->execute([$codigo,$nombre,$categoria,$cups,$cod_prot,$tipo_m, + $nivel,$abrev,$seremite,$serecibe,$activo,$codigo,$req_ayuno,$horas_ayuno,$instruc]); + $examId = (int)$pdo->lastInsertId(); + } + + $pdo->prepare('DELETE FROM exam_tipo_consentimientos WHERE exam_tipo_id = ?')->execute([$examId]); + if ($formId) { + $pdo->prepare('INSERT INTO exam_tipo_consentimientos (exam_tipo_id, formulario_id) VALUES (?,?)') + ->execute([$examId, $formId]); + } + + $pdo->commit(); + jsonOk(['id' => $examId], $id ? 'Examen actualizado' : 'Examen creado'); +} catch (\Throwable $e) { + $pdo->rollBack(); + if ($e->getCode() === '23000') jsonError('Ya existe un examen con ese código'); + jsonError('Error: ' . $e->getMessage(), 500); +} diff --git a/modules/lab_examenes/api/save_item.php b/modules/lab_examenes/api/save_item.php new file mode 100644 index 0000000..499fec8 --- /dev/null +++ b/modules/lab_examenes/api/save_item.php @@ -0,0 +1,44 @@ +prepare('DELETE FROM lab_items_resultado WHERE id = ?')->execute([$id]); + jsonOk([], 'Item eliminado'); +} + +$codProt = trim($d['cod_protocolo'] ?? ''); +$nombre = trim($d['nombre'] ?? ''); +if (!$codProt) jsonError('cod_protocolo requerido'); +if (!$nombre) jsonError('Nombre requerido'); + +$tipoSexo = in_array($d['tipo_sexo'] ?? '', ['M','F']) ? $d['tipo_sexo'] : null; +$tipo = in_array($d['tipo'] ?? '', ['N','T']) ? $d['tipo'] : 'T'; +$medida = trim($d['medida'] ?? '') ?: null; +$abrev = trim($d['abreviatura'] ?? '') ?: null; +$vmin = $d['vmin_ref'] !== '' && $d['vmin_ref'] !== null ? (float)$d['vmin_ref'] : null; +$vmax = $d['vmax_ref'] !== '' && $d['vmax_ref'] !== null ? (float)$d['vmax_ref'] : null; +$orden = (int)($d['orden'] ?? 0); +$formula = trim($d['formula'] ?? '') ?: null; +$cups_d = trim($d['cups_detalle'] ?? '') ?: null; + +$pdo = db(); +if ($id) { + $pdo->prepare( + 'UPDATE lab_items_resultado SET cod_protocolo=?,nombre=?,tipo_sexo=?,tipo=?,medida=?, + abreviatura=?,vmin_ref=?,vmax_ref=?,orden=?,formula=?,cups_detalle=? WHERE id=?' + )->execute([$codProt,$nombre,$tipoSexo,$tipo,$medida,$abrev,$vmin,$vmax,$orden,$formula,$cups_d,$id]); + jsonOk(['id' => $id], 'Item actualizado'); +} else { + $pdo->prepare( + 'INSERT INTO lab_items_resultado + (cod_protocolo,nombre,tipo_sexo,tipo,medida,abreviatura,vmin_ref,vmax_ref,orden,formula,cups_detalle) + VALUES (?,?,?,?,?,?,?,?,?,?,?)' + )->execute([$codProt,$nombre,$tipoSexo,$tipo,$medida,$abrev,$vmin,$vmax,$orden,$formula,$cups_d]); + jsonOk(['id' => (int)$pdo->lastInsertId()], 'Item creado'); +} diff --git a/modules/lab_examenes/api/save_tarifa.php b/modules/lab_examenes/api/save_tarifa.php new file mode 100644 index 0000000..1e81d4f --- /dev/null +++ b/modules/lab_examenes/api/save_tarifa.php @@ -0,0 +1,45 @@ +prepare('DELETE FROM lab_tarifas WHERE exam_tipo_id = ? AND tarifa_id = ?') + ->execute([$examId, $tarifaId]); + jsonOk([], 'Precio eliminado'); +} + +$valor = (float)($d['valor'] ?? 0); +$rec_urg = (float)($d['recargo_urg'] ?? 0); +$rec_fes = (float)($d['recargo_fes'] ?? 0); +$rec_esp = (float)($d['recargo_esp'] ?? 0); + +$s = $pdo->prepare('SELECT COALESCE(codigo_legacy, codigo) FROM exam_tipos WHERE id = ?'); +$s->execute([$examId]); +$leg = $s->fetchColumn(); +if (!$leg) jsonError('Examen no encontrado', 404); + +// Check if price row exists (no unique key in schema, so do it manually) +$s = $pdo->prepare('SELECT id FROM lab_tarifas WHERE exam_tipo_id = ? AND tarifa_id = ?'); +$s->execute([$examId, $tarifaId]); +$precioId = $s->fetchColumn(); + +if ($precioId) { + $pdo->prepare( + 'UPDATE lab_tarifas SET valor=?,recargo_urg=?,recargo_fes=?,recargo_esp=?,cod_examen_legacy=? WHERE id=?' + )->execute([$valor, $rec_urg, $rec_fes, $rec_esp, $leg, $precioId]); +} else { + $pdo->prepare( + 'INSERT INTO lab_tarifas (cod_examen_legacy,exam_tipo_id,tarifa_id,valor,recargo_urg,recargo_fes,recargo_esp) + VALUES (?,?,?,?,?,?,?)' + )->execute([$leg, $examId, $tarifaId, $valor, $rec_urg, $rec_fes, $rec_esp]); +} + +jsonOk([], 'Precio guardado'); diff --git a/modules/lab_examenes/module.php b/modules/lab_examenes/module.php new file mode 100644 index 0000000..69c3008 --- /dev/null +++ b/modules/lab_examenes/module.php @@ -0,0 +1,15 @@ + 'lab_examenes', + 'name' => 'Catálogo de Exámenes', + 'icon' => 'fas fa-flask', + 'category' => 'clinico', + 'route' => '/erp.php?m=lab_examenes&v=index', + 'is_active' => true, + 'sort_order' => 60, + 'oleada' => 2, + 'description' => 'Gestión del catálogo de exámenes, valores de referencia y tarifas.', + 'links' => [ + ['name' => 'Exámenes', 'icon' => 'fas fa-flask', 'route' => '/erp.php?m=lab_examenes&v=index'], + ], +]; diff --git a/modules/lab_examenes/views/examen.php b/modules/lab_examenes/views/examen.php new file mode 100644 index 0000000..199cc30 --- /dev/null +++ b/modules/lab_examenes/views/examen.php @@ -0,0 +1,503 @@ +getConnection(); + +$exam = null; +$items = []; +if ($id) { + $s = $pdo->prepare('SELECT * FROM exam_tipos WHERE id=?'); + $s->execute([$id]); + $exam = $s->fetch(PDO::FETCH_ASSOC); + if (!$exam) { header('Location: ' . BASE_URL . 'erp.php?m=lab_examenes&v=index'); exit; } + + $s = $pdo->prepare('SELECT * FROM lab_items_resultado WHERE cod_protocolo=? ORDER BY orden,id'); + $s->execute([$exam['cod_protocolo'] ?? $exam['codigo']]); + $items = $s->fetchAll(PDO::FETCH_ASSOC); +} + +$protocolos = $pdo->query('SELECT codigo, nombre FROM lab_protocolos ORDER BY nombre')->fetchAll(PDO::FETCH_ASSOC); +$muestras = $pdo->query('SELECT codigo, nombre FROM lab_tipos_muestra ORDER BY nombre')->fetchAll(PDO::FETCH_ASSOC); +$formularios = $pdo->query('SELECT id, nombre FROM lab_formularios ORDER BY nombre')->fetchAll(PDO::FETCH_ASSOC); +$formActual = null; +if ($id) { + $s = $pdo->prepare('SELECT formulario_id FROM exam_tipo_consentimientos WHERE exam_tipo_id=? LIMIT 1'); + $s->execute([$id]); + $formActual = $s->fetchColumn() ?: null; +} + +$titulo = $exam ? 'Editar: ' . htmlspecialchars($exam['nombre']) : 'Nuevo Examen'; +Layout::open($titulo, 'fas fa-flask'); +$API = BASE_URL . 'modules/lab_examenes/api/'; +?> + + + + +
+
+ + +
+
+ +

Datos generales

+ +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ > + +
+
+ > + +
+
+ > + +
+
+
+
+ + +
+
+
+
+ + + +
+
+ +

Items / Valores de referencia

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + +
#Nombre itemSexoTipoMedidaV.MinV.MaxOrd.Fórmula
+
+
+ + +
+
+ +

Precios por tarifa

+ Clic en el valor para editar +
+
+ + + + + + + + + + + + + + + +
#TarifaValorR. UrgenciaR. FestivoR. Especial
Cargando tarifas…
+
+
+ +
+ +'Ambos','M'=>'M','F'=>'F']; + $tipos = ['T'=>'Texto','N'=>'Numérico']; + $sOpts = ''; foreach($sexos as $v=>$l) $sOpts .= ""; + $tOpts = ''; foreach($tipos as $v=>$l) $tOpts .= ""; + return " + + + + + + + + + + + + "; +} +?> + + + + + + + diff --git a/modules/lab_examenes/views/index.php b/modules/lab_examenes/views/index.php new file mode 100644 index 0000000..ce277d1 --- /dev/null +++ b/modules/lab_examenes/views/index.php @@ -0,0 +1,167 @@ +getConnection(); +$categorias = $pdo->query( + "SELECT DISTINCT categoria FROM exam_tipos WHERE categoria IS NOT NULL ORDER BY categoria" +)->fetchAll(PDO::FETCH_COLUMN); + +$API = BASE_URL . 'modules/lab_examenes/api/'; +$DETAIL = BASE_URL . 'erp.php?m=lab_examenes&v=examen&id='; +?> + + + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + +
CódigoNombreCategoríaCUPSProtocoloSe remiteActivo
Cargando…
+
+ +
+
+ + + diff --git a/scripts/etl_examenes.php b/scripts/etl_examenes.php new file mode 100644 index 0000000..923f649 --- /dev/null +++ b/scripts/etl_examenes.php @@ -0,0 +1,303 @@ +getConnection(); +$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +// MariaDB defaultea utf8mb4 a uca1400_ai_ci; forzamos unicode_ci para que +// los literales de string en queries no colisionen con columnas de producción +$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"); + +$doLoad = in_array('--load', $argv); +$doClean = in_array('--clean', $argv); + +// ── helpers ────────────────────────────────────────────────── +function run(PDO $pdo, string $sql, string $label): void { + $t = microtime(true); + $rows = $pdo->exec($sql); + printf(" %-45s %6d filas %.2fs\n", $label, $rows, microtime(true) - $t); +} + +function step(string $msg): void { + echo "\n── $msg\n"; +} + +// ── limpiar staging si se pide ──────────────────────────────── +if ($doClean) { + $staging = ['SECCION','PROTOCOLO','MUESTRA','EXAMEN','ITEM', + 'PERFIL','PERFIL_EXA','TARIFAID','TARIFA','LAB_REFER', + 'stg_empresa','stg_examen_emp']; + $pdo->exec('SET FOREIGN_KEY_CHECKS=0'); + foreach ($staging as $t) { + $pdo->exec("DROP TABLE IF EXISTS `$t`"); + echo " DROP $t\n"; + } + $pdo->exec('SET FOREIGN_KEY_CHECKS=1'); + echo "Staging eliminado.\n"; + exit(0); +} + +// ── cargar staging desde examenes_completo.sql ──────────────── +if ($doLoad) { + $sqlFile = realpath(__DIR__ . '/../database/examenes_completo.sql'); + if (!$sqlFile) { echo "ERROR: No se encuentra examenes_completo.sql\n"; exit(1); } + + $tmpFile = sys_get_temp_dir() . '/examenes_staging_' . getmypid() . '.sql'; + echo "Preparando staging SQL...\n"; + + $sql = file_get_contents($sqlFile); + $sql = preg_replace('/\blab_examenes_empresa\b/', 'stg_examen_emp', $sql); + $sql = preg_replace('/\blab_empresas\b/', 'stg_empresa', $sql); + $sql = preg_replace('/\bINSERT INTO\b/', 'INSERT IGNORE INTO', $sql); + file_put_contents($tmpFile, $sql); + unset($sql); + + $host = DB_HOST; $port = DB_PORT; $user = DB_USER; $pass = DB_PASS; $db = DB_NAME; + echo "Cargando staging vía mysql CLI (puede tardar 2-3 min)...\n"; + $cmd = "mysql -h " . escapeshellarg($host) + . " -P " . escapeshellarg($port) + . " -u " . escapeshellarg($user) + . " -p" . escapeshellarg($pass) + . " --default-character-set=utf8mb4" + . " " . escapeshellarg($db) + . " < " . escapeshellarg($tmpFile) + . " 2>&1"; + passthru($cmd, $ret); + unlink($tmpFile); + if ($ret !== 0) { echo "ERROR cargando staging.\n"; exit(1); } + echo "Staging cargado.\n"; +} + +// ── verificar staging ───────────────────────────────────────── +try { + $pdo->query("SELECT 1 FROM EXAMEN LIMIT 1"); +} catch (\Throwable $e) { + echo "ERROR: Tablas staging no encontradas. Ejecuta: php etl_examenes.php --load\n"; + exit(1); +} + +echo "ETL Firebird → MySQL\n"; +echo str_repeat('─', 60) . "\n"; + +// convertir staging a misma collation que producción (MariaDB usa uca1400 por defecto) +$pdo->exec('SET FOREIGN_KEY_CHECKS = 0'); +foreach (['SECCION','PROTOCOLO','MUESTRA','EXAMEN','ITEM', + 'PERFIL','PERFIL_EXA','TARIFAID','TARIFA','LAB_REFER', + 'stg_empresa','stg_examen_emp'] as $_t) { + try { + $pdo->exec("ALTER TABLE `$_t` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); + } catch (\Throwable $_e) { /* tabla no existe aún, ok */ } +} +$pdo->exec('SET UNIQUE_CHECKS = 0'); + +// ── 1. SECCION → lab_secciones ─────────────────────────────── +step('1. SECCION → lab_secciones'); +run($pdo, " + INSERT IGNORE INTO lab_secciones (codigo, nombre) + SELECT CODIGO, NOMBRE FROM SECCION +", 'lab_secciones'); + +// ── 2. PROTOCOLO → lab_protocolos ──────────────────────────── +step('2. PROTOCOLO → lab_protocolos'); +run($pdo, " + INSERT IGNORE INTO lab_protocolos (codigo, nombre, cod_seccion) + SELECT CODIGO, NOMBRE, COD_SECCION FROM PROTOCOLO +", 'lab_protocolos'); + +// ── 3. MUESTRA → lab_tipos_muestra ─────────────────────────── +step('3. MUESTRA → lab_tipos_muestra'); +run($pdo, " + INSERT IGNORE INTO lab_tipos_muestra (codigo, nombre) + SELECT CODIGO, NOMBRE FROM MUESTRA +", 'lab_tipos_muestra'); + +// ── 4. EXAMEN → exam_tipos ─────────────────────────────────── +step('4. EXAMEN → exam_tipos'); +// codigo = EXAMEN.CODIGO (unique en Firebird, usamos como código interno también) +// categoria = nombre de la sección del protocolo +run($pdo, " + INSERT IGNORE INTO exam_tipos + (codigo, nombre, categoria, activo, + codigo_legacy, cups, cod_protocolo, tipo_muestra, + nivel, abreviatura, seremite, serecibe) + SELECT + e.CODIGO, + e.NOMBRE, + s.NOMBRE, + 1, + e.CODIGO, + NULLIF(e.CUPS, ''), + NULLIF(e.COD_PROTOCOLO, ''), + NULLIF(e.TIPOMUESTRA, ''), + e.NIVEL, + NULLIF(e.ABREVIATURA, ''), + IF(e.SEREMITE = 'T', 1, 0), + NULLIF(e.SERECIBE, '') + FROM EXAMEN e + LEFT JOIN PROTOCOLO p ON p.CODIGO = e.COD_PROTOCOLO + LEFT JOIN SECCION s ON s.CODIGO = p.COD_SECCION +", 'exam_tipos'); + +// ── 5. ITEM → lab_items_resultado ──────────────────────────── +step('5. ITEM → lab_items_resultado'); +// ponytail: truncate antes de insertar — ITEM no tiene UK en MySQL +$pdo->exec('DELETE FROM lab_items_resultado'); +run($pdo, " + INSERT INTO lab_items_resultado + (cod_protocolo, nombre, tipo_sexo, tipo, + medida, abreviatura, vmin_ref, vmax_ref, + orden, formula, cups_detalle) + SELECT + i.COD_PROTOCOLO, + i.NOM_ITEM, + CASE i.TIPO_SEXO WHEN 'M' THEN 'M' WHEN 'F' THEN 'F' ELSE NULL END, + CASE WHEN i.TIPO = '2' THEN 'N' ELSE 'T' END, + NULLIF(i.MEDIDA, ''), + NULLIF(i.ABREVIATURA, ''), + NULLIF(i.VMINREF, 0), + NULLIF(i.VMAXREF, 0), + COALESCE(i.ORDEN, 0), + NULLIF(i.FORMULA, ''), + NULLIF(i.CUPS_DETALLE, '') + FROM ITEM i + WHERE EXISTS (SELECT 1 FROM lab_protocolos p WHERE p.codigo = i.COD_PROTOCOLO) +", 'lab_items_resultado'); + +// ── 6. PERFIL → lab_perfiles ───────────────────────────────── +step('6. PERFIL → lab_perfiles'); +run($pdo, " + INSERT IGNORE INTO lab_perfiles (id, nombre, activo) + SELECT COD_PERFIL, NOM_PERFIL, 1 FROM PERFIL +", 'lab_perfiles'); + +// ── 7. PERFIL_EXA → lab_perfil_examenes ────────────────────── +step('7. PERFIL_EXA → lab_perfil_examenes'); +run($pdo, " + INSERT IGNORE INTO lab_perfil_examenes (perfil_id, exam_tipo_id) + SELECT pe.COD_PERFIL, et.id + FROM PERFIL_EXA pe + JOIN exam_tipos et ON et.codigo_legacy = pe.COD_EXA +", 'lab_perfil_examenes'); + +// ── 8. TARIFAID → lab_tarifas_id ───────────────────────────── +step('8. TARIFAID → lab_tarifas_id'); +run($pdo, " + INSERT IGNORE INTO lab_tarifas_id (id, nombre, tarifa_origen, porcentaje) + SELECT + COD_TARIFA, + NOMBRE, + NULLIF(TARIFAORIGEN, 0), + PORCENTAJE + FROM TARIFAID +", 'lab_tarifas_id'); + +// ── 9. TARIFA → lab_tarifas (104 788 filas) ────────────────── +step('9. TARIFA → lab_tarifas [puede tardar ~30s]'); +run($pdo, " + INSERT IGNORE INTO lab_tarifas + (cod_examen_legacy, exam_tipo_id, tarifa_id, + valor, recargo_urg, recargo_fes, recargo_esp) + SELECT + t.COD_EXAMEN, + et.id, + t.TARIFA, + COALESCE(t.VALOR, 0), + COALESCE(t.RECARGO_URG, 0), + COALESCE(t.RECARGO_FES, 0), + COALESCE(t.RECARGO_ESP, 0) + FROM TARIFA t + LEFT JOIN exam_tipos et ON et.codigo_legacy = t.COD_EXAMEN +", 'lab_tarifas'); + +// ── 10. LAB_REFER → lab_laboratorios_externos ──────────────── +step('10. LAB_REFER → lab_laboratorios_externos'); +$pdo->exec(" + CREATE TABLE IF NOT EXISTS lab_laboratorios_externos ( + codigo VARCHAR(20) NOT NULL, + nombre VARCHAR(100), + nit_lab VARCHAR(30), + direccion VARCHAR(200), + telefonos VARCHAR(50), + email VARCHAR(100), + activo TINYINT(1) NOT NULL DEFAULT 1, + PRIMARY KEY (codigo) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + COMMENT='Laboratorios externos de referencia — replica LAB_REFER Firebird' +"); +run($pdo, " + INSERT IGNORE INTO lab_laboratorios_externos + (codigo, nombre, nit_lab, direccion, telefonos, email, activo) + SELECT + CODIGO, NOMBRE, NIT_LAB, DIRECCION, TELEFONOS, EMAIL, + IF(ACTIVADO = 'T', 1, 0) + FROM LAB_REFER +", 'lab_laboratorios_externos'); + +// ── 11. EMPRESA (staging) → lab_empresas ───────────────────── +step('11. EMPRESA → lab_empresas'); +// ponytail: staging usa columnas Firebird, producción tiene columnas en snake_case +run($pdo, " + INSERT IGNORE INTO lab_empresas + (nit, nombre, razon_social, tarifa_id, descuento_pct, + codigo_eps, tipo_usuario, tipo_usuario_sispro, + cod_contrato, cod_tercero, centro_costo, + req_autoriza, activa) + SELECT + NIT, + NOMBRE, + NULLIF(RAZONSOCIAL, ''), + NULLIF(TARIFA, 0), + COALESCE(DSCTO, 0), + NULLIF(CODIGOEPS, ''), + NULLIF(TIPOUSUARIO, ''), + NULLIF(TIPOUSUARIOSISPRO, ''), + NULLIF(CODCONTRATO, ''), + NULLIF(CODTERCERO, ''), + NULLIF(CENTROCOSTO, ''), + IF(REQAUTORIZA = 'T', 1, 0), + IF(ACTIVADA = 'T', 1, 0) + FROM stg_empresa +", 'lab_empresas'); + +// ── 12. EXAMEN_EMP (staging) → lab_examenes_empresa ────────── +step('12. EXAMEN_EMP → lab_examenes_empresa'); +run($pdo, " + INSERT IGNORE INTO lab_examenes_empresa + (nit_empresa, cod_examen_legacy, exam_tipo_id, codigo_empresa) + SELECT + ee.NIT_EMP, + ee.COD_EXA, + et.id, + NULLIF(ee.EXA_EMP, '') + FROM stg_examen_emp ee + LEFT JOIN exam_tipos et ON et.codigo_legacy = ee.COD_EXA +", 'lab_examenes_empresa'); + +// ── fin ─────────────────────────────────────────────────────── +$pdo->exec('SET FOREIGN_KEY_CHECKS = 1'); +$pdo->exec('SET UNIQUE_CHECKS = 1'); + +echo "\n" . str_repeat('─', 60) . "\n"; +echo "ETL completado.\n\n"; + +// resumen rápido +$tablas = [ + 'lab_secciones', 'lab_protocolos', 'lab_tipos_muestra', 'exam_tipos', + 'lab_items_resultado', 'lab_perfiles', 'lab_perfil_examenes', + 'lab_tarifas_id', 'lab_tarifas', 'lab_laboratorios_externos', + 'lab_empresas', 'lab_examenes_empresa', +]; +echo "Totales en producción:\n"; +foreach ($tablas as $t) { + $n = $pdo->query("SELECT COUNT(*) FROM `$t`")->fetchColumn(); + printf(" %-35s %7d\n", $t, $n); +} +echo "\nListo.\n";