feat(recepcion): motor de precios + selector empresa/convenio

- Nueva sección empresa/convenio en recepcion.php: búsqueda live por
  nombre/NIT, selector de subgrupo, campo autorización (cuando req_autoriza),
  diagnóstico CIE-10
- Tabla de precios calculados en tiempo real: resolución por tarifa de la
  empresa o subgrupo, descuento empresa aplicado, botón "Aplicar al cobro"
- Al agregar/quitar exámenes o cambiar empresa/subgrupo se recalcula automáticamente
- Nuevo API get_precios_examenes.php: resuelve precios desde lab_tarifas
  con soporte de tarifas derivadas por porcentaje
- create_solicitud.php guarda nit_empresa, subgrupo_id, autorizacion,
  diag_ppal, items_precio (snapshot JSON al momento de la recepción)
- Migración 10: ALTER turnero_solicitudes añade los 5 campos nuevos

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-04 22:09:26 -05:00
co-authored by Claude Sonnet 4.6
parent b9bb4fc628
commit a7aac706ee
4 changed files with 423 additions and 4 deletions
+13 -4
View File
@@ -32,7 +32,13 @@ $obs = isset($datos['observaciones']) ? trim((string) $datos['observac
$numOrden = isset($datos['numero_orden']) ? trim((string) $datos['numero_orden']) : null;
$embarazada = !empty($datos['embarazada']) ? 1 : 0;
$soloMuestras = !empty($datos['solo_muestras']) ? 1 : 0;
$medicoId = !empty($datos['medico_id']) ? (int)$datos['medico_id'] : null;
$medicoId = !empty($datos['medico_id']) ? (int)$datos['medico_id'] : null;
$nitEmpresa = !empty($datos['nit_empresa']) ? trim((string)$datos['nit_empresa']) : null;
$subgrupoId = !empty($datos['subgrupo_id']) ? (int)$datos['subgrupo_id'] : null;
$autorizacion = !empty($datos['autorizacion']) ? trim((string)$datos['autorizacion']) : null;
$diagPpal = !empty($datos['diag_ppal']) ? trim((string)$datos['diag_ppal']) : null;
$itemsPrecio = isset($datos['items_precio']) && is_array($datos['items_precio'])
? json_encode($datos['items_precio']) : null;
$pagosDetalle = null;
if ($metodoPago === 'combinado' && isset($datos['pagos_detalle']) && is_array($datos['pagos_detalle'])) {
$pd = $datos['pagos_detalle'];
@@ -117,12 +123,15 @@ try {
$stmt = $pdo->prepare(
"INSERT INTO turnero_solicitudes
(turno_id, paciente_id, lugar_id, numero_orden, total_cobrado, metodo_pago, pagos_detalle, observaciones, embarazada, solo_muestras, medico_id, creado_por)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
(turno_id, paciente_id, lugar_id, numero_orden, total_cobrado, metodo_pago, pagos_detalle,
observaciones, embarazada, solo_muestras, medico_id,
nit_empresa, subgrupo_id, autorizacion, diag_ppal, items_precio, creado_por)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
$stmt->execute([
$turnoId, $pacienteId, $lugarId, $numOrden,
$total, $metodoPago ?: null, $pagosDetalle, $obs ?: null, $embarazada, $soloMuestras, $medicoId, adminId(),
$total, $metodoPago ?: null, $pagosDetalle, $obs ?: null, $embarazada, $soloMuestras, $medicoId,
$nitEmpresa, $subgrupoId, $autorizacion, $diagPpal, $itemsPrecio, adminId(),
]);
$solicitudId = (int) $pdo->lastInsertId();
@@ -0,0 +1,149 @@
<?php
/**
* GET /modules/turnero/api/get_precios_examenes.php
*
* Calcula precios de exámenes según empresa/tarifa.
*
* Query params:
* exam_ids[] int[] IDs de exam_tipos
* nit_empresa string opcional
* subgrupo_id int opcional
*
* Response:
* items[] { exam_tipo_id, codigo, nombre, valor_base, valor_final, tiene_precio }
* subtotal precio antes de descuento empresa
* descuento_pct porcentaje de descuento empresa
* descuento_val valor del descuento
* total total a cobrar
* tarifa_id tarifa aplicada
* tarifa_nombre nombre de la tarifa
*/
require_once __DIR__ . '/_helpers.php';
requireMethod('GET');
$examIds = array_filter(array_map('intval', (array)($_GET['exam_ids'] ?? [])));
$nitEmpresa = trim($_GET['nit_empresa'] ?? '');
$subgrupoId = (int)($_GET['subgrupo_id'] ?? 0);
if (empty($examIds)) jsonOk([
'items' => [],
'subtotal' => 0,
'descuento_pct' => 0,
'descuento_val' => 0,
'total' => 0,
'tarifa_id' => null,
'tarifa_nombre' => null,
]);
$pdo = db();
// ── Resolver tarifa efectiva ───────────────────────────────────
$tarifaId = null;
$tarifaNombre = null;
$descuentoPct = 0;
if ($nitEmpresa !== '') {
$stmtE = $pdo->prepare(
"SELECT e.tarifa_id, e.descuento_pct, ti.nombre AS tarifa_nombre
FROM lab_empresas e
LEFT JOIN lab_tarifas_id ti ON ti.id = e.tarifa_id
WHERE e.nit = ?"
);
$stmtE->execute([$nitEmpresa]);
$empresa = $stmtE->fetch(PDO::FETCH_ASSOC);
if ($empresa) {
$tarifaId = $empresa['tarifa_id'];
$tarifaNombre = $empresa['tarifa_nombre'];
$descuentoPct = (float)$empresa['descuento_pct'];
// Si el subgrupo tiene tarifa propia, override
if ($subgrupoId > 0) {
$stmtS = $pdo->prepare(
"SELECT es.tarifa_id, ti.nombre AS tarifa_nombre
FROM lab_empresa_subgrupos es
LEFT JOIN lab_tarifas_id ti ON ti.id = es.tarifa_id
WHERE es.id = ? AND es.nit_empresa = ? AND es.tarifa_id IS NOT NULL"
);
$stmtS->execute([$subgrupoId, $nitEmpresa]);
$sub = $stmtS->fetch(PDO::FETCH_ASSOC);
if ($sub) {
$tarifaId = $sub['tarifa_id'];
$tarifaNombre = $sub['tarifa_nombre'];
}
}
}
}
// ── Obtener nombre/codigo de los exámenes ─────────────────────
$ph = implode(',', array_fill(0, count($examIds), '?'));
$stmtX = $pdo->prepare(
"SELECT id AS exam_tipo_id, codigo, nombre FROM exam_tipos WHERE id IN ($ph)"
);
$stmtX->execute($examIds);
$examMap = [];
foreach ($stmtX->fetchAll(PDO::FETCH_ASSOC) as $r) {
$examMap[(int)$r['exam_tipo_id']] = $r;
}
// ── Obtener precios desde lab_tarifas (con derivación por %) ──
$items = [];
$subtotal = 0;
foreach ($examIds as $examId) {
$examen = $examMap[$examId] ?? ['codigo' => '?', 'nombre' => 'Desconocido'];
$valorFinal = null;
$valorBase = null;
$tienePrecio = false;
if ($tarifaId !== null) {
// Intentar precio en la tarifa exacta
$stmtP = $pdo->prepare(
"SELECT lt.valor, ti.porcentaje, ti.tarifa_origen,
lt_base.valor AS valor_origen
FROM lab_tarifas lt
JOIN lab_tarifas_id ti ON ti.id = lt.tarifa_id
LEFT JOIN lab_tarifas lt_base ON lt_base.exam_tipo_id = lt.exam_tipo_id
AND lt_base.tarifa_id = ti.tarifa_origen
WHERE lt.exam_tipo_id = ? AND lt.tarifa_id = ?"
);
$stmtP->execute([$examId, $tarifaId]);
$precio = $stmtP->fetch(PDO::FETCH_ASSOC);
if ($precio) {
$tienePrecio = true;
$valorBase = (float)$precio['valor'];
if ((float)$precio['porcentaje'] > 0 && $precio['tarifa_origen'] && $precio['valor_origen'] !== null) {
$valorFinal = round((float)$precio['valor_origen'] * (1 + (float)$precio['porcentaje'] / 100), 0);
} else {
$valorFinal = $valorBase;
}
}
}
// Si no hay precio, dejar null (sin tarifa o sin mapeo)
$subtotal += $valorFinal ?? 0;
$items[] = [
'exam_tipo_id' => $examId,
'codigo' => $examen['codigo'],
'nombre' => $examen['nombre'],
'valor_base' => $valorBase,
'valor_final' => $valorFinal,
'tiene_precio' => $tienePrecio,
];
}
// ── Aplicar descuento empresa ─────────────────────────────────
$descuentoVal = round($subtotal * $descuentoPct / 100, 0);
$total = $subtotal - $descuentoVal;
jsonOk([
'items' => $items,
'subtotal' => $subtotal,
'descuento_pct' => $descuentoPct,
'descuento_val' => $descuentoVal,
'total' => $total,
'tarifa_id' => $tarifaId,
'tarifa_nombre' => $tarifaNombre,
]);