diff --git a/migrations/20260704_lis_11_recepciones_new.sql b/migrations/20260704_lis_11_recepciones_new.sql new file mode 100644 index 0000000..0fab35d --- /dev/null +++ b/migrations/20260704_lis_11_recepciones_new.sql @@ -0,0 +1,25 @@ +-- ============================================================= +-- LIS 11 — Habilita lab_recepciones para registros nuevos (no históricos) +-- +-- El esquema original fue diseñado SOLO para histórico Firebird: +-- · id INT NOT NULL (no AUTO_INCREMENT) — el ID venía de Firebird +-- · lab_pagos.numcaja_legacy NOT NULL UNIQUE — también era de Firebird +-- +-- Aquí los ajustamos para aceptar registros del nuevo sistema +-- sin tocar los datos históricos migrados. +-- ============================================================= + +-- 1. Hacer id AUTO_INCREMENT (MySQL usará MAX(id)+1 como siguiente valor, +-- así los registros nuevos nunca colisionan con los históricos de Firebird). +ALTER TABLE lab_recepciones + MODIFY COLUMN id INT NOT NULL AUTO_INCREMENT; + +-- 2. Enlace de vuelta a la solicitud del turnero +ALTER TABLE lab_recepciones + ADD COLUMN IF NOT EXISTS solicitud_id INT UNSIGNED DEFAULT NULL + COMMENT 'FK turnero_solicitudes.id — NULL para registros históricos Firebird'; + +-- 3. numcaja_legacy era NOT NULL UNIQUE (campo obligatorio en Firebird). +-- Los registros nuevos no tienen NUMCAJA → lo hacemos nullable. +ALTER TABLE lab_pagos + MODIFY COLUMN numcaja_legacy INT DEFAULT NULL; diff --git a/modules/turnero/api/create_solicitud.php b/modules/turnero/api/create_solicitud.php index ede802f..0817db9 100644 --- a/modules/turnero/api/create_solicitud.php +++ b/modules/turnero/api/create_solicitud.php @@ -199,6 +199,119 @@ try { // Tabla aún no existe (migración pendiente) — continuar sin muestras } + // ── Crear lab_recepciones + lab_relaciones + lab_pagos ─────── + // Envuelto en try/catch para no romper el flujo si la migración 11 + // aún no se ha ejecutado en este entorno. + $recepcionId = null; + try { + $operador = $_SESSION['admin_user']['username'] ?? ('u' . adminId()); + + // Subgrupo nombre (para campo subgrupo en recepcion) + $subgrupoNombre = null; + if ($subgrupoId) { + $stS = $pdo->prepare("SELECT subgrupo FROM lab_empresa_subgrupos WHERE id = ?"); + $stS->execute([$subgrupoId]); + $subgrupoNombre = $stS->fetchColumn() ?: null; + } + + // Tipo usuario de la empresa + $tipoUsuario = null; + if ($nitEmpresa) { + $stE = $pdo->prepare("SELECT tipo_usuario FROM lab_empresas WHERE nit = ?"); + $stE->execute([$nitEmpresa]); + $tipoUsuario = $stE->fetchColumn() ?: null; + } + + // Siguiente num_factura para registros nuevos (es_historico=0) + // FOR UPDATE garantiza secuencia correcta bajo concurrencia + $nextFac = (int)$pdo->query( + "SELECT COALESCE(MAX(num_factura), 0) + 1 FROM lab_recepciones WHERE es_historico = 0 FOR UPDATE" + )->fetchColumn(); + + // Calcular valor_desc desde el snapshot de precios + $valorTotal = $total ?? 0; + $valorDesc = 0; + if ($itemsPrecio) { + $snap = json_decode($itemsPrecio, true) ?? []; + $subtotalS = array_sum(array_column($snap, 'valor_final')); + $valorDesc = max(0, round($subtotalS - $valorTotal, 2)); + } + + $stR = $pdo->prepare( + "INSERT INTO lab_recepciones + (solicitud_id, paciente_id, medico_id, nit_empresa, subgrupo, + fecha_recepcion, hora_inicio, num_factura, + valor_total, valor_desc, diag_ppal, tipo_usuario, autorizacion, + usuario, es_historico) + VALUES (?, ?, ?, ?, ?, CURDATE(), CURTIME(), ?, + ?, ?, ?, ?, ?, ?, 0)" + ); + $stR->execute([ + $solicitudId, $pacienteId, $medicoId, $nitEmpresa, $subgrupoNombre, + $nextFac, + $valorTotal, $valorDesc, $diagPpal, $tipoUsuario, $autorizacion, + $operador, + ]); + $recepcionId = (int)$pdo->lastInsertId(); + + // lab_relaciones: un registro por examen + if (!empty($examIds)) { + $ph = implode(',', array_fill(0, count($examIds), '?')); + $stEx = $pdo->prepare("SELECT id, codigo FROM exam_tipos WHERE id IN ($ph)"); + $stEx->execute($examIds); + $codigos = $stEx->fetchAll(PDO::FETCH_ASSOC); + + // Mapa exam_tipo_id → precio desde el snapshot + $precioMap = []; + if ($itemsPrecio) { + foreach (json_decode($itemsPrecio, true) ?? [] as $item) { + $precioMap[(int)$item['exam_tipo_id']] = (float)($item['valor_final'] ?? 0); + } + } + + $stRel = $pdo->prepare( + "INSERT INTO lab_relaciones (recepcion_id, cod_examen_legacy, exam_tipo_id, precio) + VALUES (?, ?, ?, ?)" + ); + foreach ($codigos as $ex) { + $stRel->execute([ + $recepcionId, + $ex['codigo'], + (int)$ex['id'], + $precioMap[(int)$ex['id']] ?? 0, + ]); + } + } + + // lab_pagos + lab_pagos_det si hay cobro + if ($metodoPago && ($valorTotal > 0 || $metodoPago === 'cortesia')) { + $stP = $pdo->prepare( + "INSERT INTO lab_pagos (recepcion_id, valor, fecha, usuario) + VALUES (?, ?, CURDATE(), ?)" + ); + $stP->execute([$recepcionId, $valorTotal, $operador]); + $pagoId = (int)$pdo->lastInsertId(); + + $stD = $pdo->prepare( + "INSERT INTO lab_pagos_det (pago_id, tipo_pago, valor) VALUES (?, ?, ?)" + ); + if ($metodoPago === 'combinado' && $pagosDetalle) { + foreach (json_decode($pagosDetalle, true) ?? [] as $tipo => $val) { + if ((float)$val > 0) { + $stD->execute([$pagoId, $tipo, (float)$val]); + } + } + } else { + $stD->execute([$pagoId, $metodoPago, $valorTotal]); + } + } + } catch (\Throwable $eRec) { + // Si falla (migración no aplicada, constraint, etc.) solo logueamos. + // La solicitud principal ya está lista; no la revertimos por esto. + error_log('[create_solicitud] lab_recepciones error: ' . $eRec->getMessage()); + $recepcionId = null; + } + $pdo->commit(); notificarSSE(obtenerOCrearSesionHoy()); @@ -218,7 +331,8 @@ try { $solicitud = $stmt->fetch(PDO::FETCH_ASSOC); jsonOk([ - 'solicitud' => $solicitud, + 'solicitud' => $solicitud, + 'recepcion_id' => $recepcionId, 'consentimientos_requeridos' => $consentimientosRequeridos, ], 'Solicitud guardada correctamente'); diff --git a/modules/turnero/views/recepcion.php b/modules/turnero/views/recepcion.php index aea1cd3..ee712f4 100644 --- a/modules/turnero/views/recepcion.php +++ b/modules/turnero/views/recepcion.php @@ -1577,6 +1577,11 @@ async function guardarSolicitud() { btn.innerHTML = 'Guardado'; + // Confirmación de recepción generada + if (json.recepcion_id) { + mostrarToast(`Recepción #${json.recepcion_id} generada`, 'ok', 3000); + } + // Detener polling de consentimientos (ya se guardó) if (pollingConsentimientosId) { clearInterval(pollingConsentimientosId);