Mismo patrón que turnero/configuracion: form_ids[] en lugar de formulario_id único, checkboxes en la vista, INSERT múltiple en save.php Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
508 lines
26 KiB
PHP
508 lines
26 KiB
PHP
<?php
|
||
require_once APP_ROOT . '/config/config.php';
|
||
if (!isUserLoggedIn()) { header('Location: ' . BASE_URL . 'login.php'); exit; }
|
||
|
||
$id = (int)($_GET['id'] ?? 0);
|
||
$pdo = Database::getInstance()->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);
|
||
$formsActuales = [];
|
||
if ($id) {
|
||
$s = $pdo->prepare('SELECT formulario_id FROM exam_tipo_consentimientos WHERE exam_tipo_id=?');
|
||
$s->execute([$id]);
|
||
$formsActuales = $s->fetchAll(PDO::FETCH_COLUMN);
|
||
}
|
||
|
||
$titulo = $exam ? 'Editar: ' . htmlspecialchars($exam['nombre']) : 'Nuevo Examen';
|
||
Layout::open($titulo, 'fas fa-flask');
|
||
$API = BASE_URL . 'modules/lab_examenes/api/';
|
||
?>
|
||
<style>
|
||
body { background:#f1f5f9 }
|
||
.page-header { background:#fff; border-bottom:1px solid #e2e8f0; padding:12px 24px;
|
||
display:flex; align-items:center; gap:10px; flex-wrap:wrap }
|
||
.breadcrumb-lab { font-size:.82rem; color:#64748b }
|
||
.breadcrumb-lab a { color:#2563eb; text-decoration:none }
|
||
.content-wrap { max-width:1100px; margin:20px auto; padding:0 16px 60px }
|
||
.panel { background:#fff; border:1px solid #e2e8f0; border-radius:12px; margin-bottom:20px }
|
||
.panel-hdr { padding:14px 20px; border-bottom:1px solid #e2e8f0; display:flex; align-items:center; gap:8px }
|
||
.panel-hdr h2 { font-size:1rem; font-weight:700; color:#1e293b; margin:0; flex:1 }
|
||
.panel-body { padding:20px }
|
||
.form-label { font-size:.8rem; font-weight:600; color:#374151 }
|
||
/* items table */
|
||
.items-table { width:100%; border-collapse:collapse; font-size:.83rem }
|
||
.items-table th { background:#f8fafc; padding:7px 10px; font-size:.7rem; text-transform:uppercase;
|
||
letter-spacing:.06em; color:#64748b; border-bottom:1px solid #e2e8f0; white-space:nowrap }
|
||
.items-table td { padding:6px 8px; border-bottom:1px solid #f1f5f9; vertical-align:middle }
|
||
.items-table tr:last-child td { border-bottom:none }
|
||
.items-table input, .items-table select { font-size:.8rem; padding:3px 6px; border:1px solid #d1d5db;
|
||
border-radius:5px; width:100% }
|
||
.items-table input:focus, .items-table select:focus { outline:none; border-color:#6366f1 }
|
||
/* tarifas table */
|
||
.tar-table { width:100%; border-collapse:collapse; font-size:.83rem }
|
||
.tar-table th { background:#f8fafc; padding:7px 14px; font-size:.7rem; text-transform:uppercase;
|
||
letter-spacing:.06em; color:#64748b; border-bottom:1px solid #e2e8f0 }
|
||
.tar-table td { padding:7px 14px; border-bottom:1px solid #f1f5f9; vertical-align:middle }
|
||
.tar-table tr:last-child td { border-bottom:none }
|
||
.tar-table tr:hover td { background:#f8fafc }
|
||
.tar-val { font-family:monospace; font-size:.88rem }
|
||
.edit-inline { display:none; gap:4px; align-items:center }
|
||
.tr-editing .edit-inline { display:flex }
|
||
.tr-editing .val-display { display:none }
|
||
.inp-val { width:90px; padding:3px 6px; font-size:.83rem; border:1px solid #d1d5db; border-radius:5px }
|
||
.inp-val:focus { border-color:#6366f1; outline:none }
|
||
</style>
|
||
|
||
<div class="page-header">
|
||
<div class="breadcrumb-lab">
|
||
<a href="<?= BASE_URL ?>erp.php?m=lab_examenes&v=index"><i class="fas fa-flask me-1"></i>Exámenes</a>
|
||
<i class="fas fa-chevron-right mx-1" style="font-size:.7rem"></i>
|
||
<?= $exam ? htmlspecialchars($exam['nombre']) : 'Nuevo examen' ?>
|
||
</div>
|
||
<?php if ($exam): ?>
|
||
<button class="btn btn-outline-danger btn-sm ms-auto" onclick="eliminar(<?=$exam['id']?>, '<?= addslashes(htmlspecialchars($exam['nombre'])) ?>')">
|
||
<i class="fas fa-trash me-1"></i>Eliminar
|
||
</button>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div class="content-wrap">
|
||
<div id="alerta-global" class="alert d-none mb-3 py-2"></div>
|
||
|
||
<!-- ── Datos generales ── -->
|
||
<div class="panel">
|
||
<div class="panel-hdr">
|
||
<i class="fas fa-info-circle text-primary"></i>
|
||
<h2>Datos generales</h2>
|
||
<button class="btn btn-primary btn-sm" onclick="guardarExamen()">
|
||
<i class="fas fa-save me-1"></i>Guardar
|
||
</button>
|
||
</div>
|
||
<div class="panel-body">
|
||
<input type="hidden" id="exam-id" value="<?= $exam['id'] ?? '' ?>">
|
||
<div class="row g-3">
|
||
<div class="col-sm-3">
|
||
<label class="form-label">Código <span class="text-danger">*</span></label>
|
||
<input type="text" id="f-codigo" class="form-control form-control-sm"
|
||
value="<?= htmlspecialchars($exam['codigo'] ?? '') ?>" maxlength="20" style="text-transform:uppercase">
|
||
</div>
|
||
<div class="col-sm-3">
|
||
<label class="form-label">Código CUPS</label>
|
||
<input type="text" id="f-cups" class="form-control form-control-sm"
|
||
value="<?= htmlspecialchars($exam['cups'] ?? '') ?>" maxlength="20">
|
||
</div>
|
||
<div class="col-sm-3">
|
||
<label class="form-label">Abreviatura</label>
|
||
<input type="text" id="f-abreviatura" class="form-control form-control-sm"
|
||
value="<?= htmlspecialchars($exam['abreviatura'] ?? '') ?>" maxlength="30">
|
||
</div>
|
||
<div class="col-sm-3">
|
||
<label class="form-label">Nivel</label>
|
||
<input type="number" id="f-nivel" class="form-control form-control-sm"
|
||
value="<?= $exam['nivel'] ?? '' ?>" min="0" max="9">
|
||
</div>
|
||
<div class="col-12">
|
||
<label class="form-label">Nombre <span class="text-danger">*</span></label>
|
||
<input type="text" id="f-nombre" class="form-control form-control-sm"
|
||
value="<?= htmlspecialchars($exam['nombre'] ?? '') ?>" maxlength="150">
|
||
</div>
|
||
<div class="col-sm-4">
|
||
<label class="form-label">Protocolo</label>
|
||
<select id="f-protocolo" class="form-select form-select-sm">
|
||
<option value="">— ninguno —</option>
|
||
<?php foreach ($protocolos as $p): ?>
|
||
<option value="<?= htmlspecialchars($p['codigo']) ?>"
|
||
<?= ($exam['cod_protocolo'] ?? '') === $p['codigo'] ? 'selected' : '' ?>>
|
||
<?= htmlspecialchars($p['codigo'] . ' – ' . $p['nombre']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div class="col-sm-4">
|
||
<label class="form-label">Tipo de muestra</label>
|
||
<select id="f-muestra" class="form-select form-select-sm">
|
||
<option value="">— ninguna —</option>
|
||
<?php foreach ($muestras as $m): ?>
|
||
<option value="<?= htmlspecialchars($m['codigo']) ?>"
|
||
<?= ($exam['tipo_muestra'] ?? '') === $m['codigo'] ? 'selected' : '' ?>>
|
||
<?= htmlspecialchars($m['nombre']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div class="col-sm-4">
|
||
<label class="form-label">Formularios consentimiento</label>
|
||
<div id="f-formularios-wrap" class="border rounded p-2" style="max-height:120px;overflow-y:auto;background:#fffbeb">
|
||
<?php foreach ($formularios as $f): ?>
|
||
<div class="form-check mb-1">
|
||
<input class="form-check-input f-formulario-chk" type="checkbox"
|
||
value="<?= $f['id'] ?>" id="f-fc-<?= $f['id'] ?>"
|
||
<?= in_array((int)$f['id'], array_map('intval', $formsActuales)) ? 'checked' : '' ?>>
|
||
<label class="form-check-label small" for="f-fc-<?= $f['id'] ?>">
|
||
<?= htmlspecialchars($f['nombre']) ?>
|
||
</label>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
<div class="col-sm-6">
|
||
<label class="form-label">Se recibe de (lab externo)</label>
|
||
<input type="text" id="f-serecibe" class="form-control form-control-sm"
|
||
value="<?= htmlspecialchars($exam['serecibe'] ?? '') ?>" maxlength="50">
|
||
</div>
|
||
<div class="col-sm-6">
|
||
<label class="form-label">Instrucciones para el paciente</label>
|
||
<input type="text" id="f-instrucciones" class="form-control form-control-sm"
|
||
value="<?= htmlspecialchars($exam['instrucciones'] ?? '') ?>">
|
||
</div>
|
||
<div class="col-12">
|
||
<div class="d-flex gap-4 flex-wrap">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="f-seremite"
|
||
<?= ($exam['seremite'] ?? 0) ? 'checked' : '' ?>>
|
||
<label class="form-check-label small" for="f-seremite">Se remite a laboratorio externo</label>
|
||
</div>
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="f-ayuno"
|
||
<?= ($exam['requiere_ayuno'] ?? 0) ? 'checked' : '' ?>>
|
||
<label class="form-check-label small" for="f-ayuno">Requiere ayuno</label>
|
||
</div>
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" id="f-activo"
|
||
<?= ($exam['activo'] ?? 1) ? 'checked' : '' ?>>
|
||
<label class="form-check-label small" for="f-activo">Activo</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-sm-3" id="blq-ayuno" style="display:<?= ($exam['requiere_ayuno'] ?? 0) ? 'block' : 'none' ?>">
|
||
<label class="form-label">Horas de ayuno</label>
|
||
<input type="number" id="f-horas-ayuno" class="form-control form-control-sm"
|
||
value="<?= $exam['horas_ayuno'] ?? '' ?>" min="1" max="72">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if ($exam): ?>
|
||
<!-- ── Items de resultado ── -->
|
||
<div class="panel">
|
||
<div class="panel-hdr">
|
||
<i class="fas fa-list text-success"></i>
|
||
<h2>Items / Valores de referencia</h2>
|
||
<button class="btn btn-outline-success btn-sm" onclick="addItem()">
|
||
<i class="fas fa-plus me-1"></i>Agregar item
|
||
</button>
|
||
</div>
|
||
<div style="overflow-x:auto">
|
||
<table class="items-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:30px">#</th>
|
||
<th>Nombre item</th>
|
||
<th style="width:70px">Sexo</th>
|
||
<th style="width:70px">Tipo</th>
|
||
<th style="width:90px">Medida</th>
|
||
<th style="width:80px">V.Min</th>
|
||
<th style="width:80px">V.Max</th>
|
||
<th style="width:50px">Ord.</th>
|
||
<th>Fórmula</th>
|
||
<th style="width:60px"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="items-body">
|
||
<?php foreach ($items as $it): ?>
|
||
<tr data-id="<?=$it['id']?>" data-cod="<?=htmlspecialchars($exam['cod_protocolo']??$exam['codigo'])?>">
|
||
<?= _itemRow($it) ?>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Tarifas ── -->
|
||
<div class="panel">
|
||
<div class="panel-hdr">
|
||
<i class="fas fa-tags text-warning"></i>
|
||
<h2>Precios por tarifa</h2>
|
||
<small class="text-muted">Clic en el valor para editar</small>
|
||
</div>
|
||
<div style="overflow-x:auto">
|
||
<table class="tar-table" id="tar-table">
|
||
<thead>
|
||
<tr>
|
||
<th style="width:30px">#</th>
|
||
<th>Tarifa</th>
|
||
<th style="width:120px">Valor</th>
|
||
<th style="width:100px">R. Urgencia</th>
|
||
<th style="width:100px">R. Festivo</th>
|
||
<th style="width:100px">R. Especial</th>
|
||
<th style="width:80px"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tar-body">
|
||
<tr><td colspan="7" class="text-center text-muted py-3">Cargando tarifas…</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php
|
||
function _itemRow(array $it): string {
|
||
$sexos = [''=>'Ambos','M'=>'M','F'=>'F'];
|
||
$tipos = ['T'=>'Texto','N'=>'Numérico'];
|
||
$sOpts = ''; foreach($sexos as $v=>$l) $sOpts .= "<option value='$v'".($it['tipo_sexo']===$v?' selected':'').">$l</option>";
|
||
$tOpts = ''; foreach($tipos as $v=>$l) $tOpts .= "<option value='$v'".($it['tipo']===$v?' selected':'').">$l</option>";
|
||
return "
|
||
<td><input type='number' name='orden' value='{$it['orden']}' style='width:45px'></td>
|
||
<td><input type='text' name='nombre' value='".htmlspecialchars($it['nombre'])."' style='min-width:160px'></td>
|
||
<td><select name='tipo_sexo'>$sOpts</select></td>
|
||
<td><select name='tipo'>$tOpts</select></td>
|
||
<td><input type='text' name='medida' value='".htmlspecialchars($it['medida']??'')."'></td>
|
||
<td><input type='number' name='vmin_ref' value='".($it['vmin_ref']??'')."' step='any'></td>
|
||
<td><input type='number' name='vmax_ref' value='".($it['vmax_ref']??'')."' step='any'></td>
|
||
<td><input type='number' name='orden2' value='{$it['orden']}' style='width:45px' disabled></td>
|
||
<td><input type='text' name='formula' value='".htmlspecialchars($it['formula']??'')."'></td>
|
||
<td><button type='button' class='btn btn-sm btn-outline-success py-0 px-1 me-1' onclick='saveItem(this)' title='Guardar'><i class='fas fa-check'></i></button>
|
||
<button type='button' class='btn btn-sm btn-outline-danger py-0 px-1' onclick='delItem(this)' title='Eliminar'><i class='fas fa-trash'></i></button></td>
|
||
";
|
||
}
|
||
?>
|
||
|
||
<script>
|
||
const API = '<?= $API ?>';
|
||
const EXAM_ID = <?= $id ?: 'null' ?>;
|
||
const COD_PROT = '<?= addslashes($exam['cod_protocolo'] ?? ($exam['codigo'] ?? '')) ?>';
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
document.getElementById('f-ayuno')?.addEventListener('change', e => {
|
||
document.getElementById('blq-ayuno').style.display = e.target.checked ? 'block' : 'none';
|
||
});
|
||
if (EXAM_ID) cargarTarifas();
|
||
});
|
||
|
||
// ── Guardar examen ─────────────────────────────────
|
||
async function guardarExamen() {
|
||
const payload = {
|
||
id: document.getElementById('exam-id').value || null,
|
||
codigo: document.getElementById('f-codigo').value.trim().toUpperCase(),
|
||
nombre: document.getElementById('f-nombre').value.trim(),
|
||
cups: document.getElementById('f-cups').value.trim(),
|
||
abreviatura: document.getElementById('f-abreviatura').value.trim(),
|
||
nivel: document.getElementById('f-nivel').value,
|
||
cod_protocolo: document.getElementById('f-protocolo').value,
|
||
tipo_muestra: document.getElementById('f-muestra').value,
|
||
seremite: document.getElementById('f-seremite').checked ? 1 : 0,
|
||
requiere_ayuno: document.getElementById('f-ayuno').checked ? 1 : 0,
|
||
horas_ayuno: document.getElementById('f-horas-ayuno')?.value || null,
|
||
instrucciones: document.getElementById('f-instrucciones').value.trim(),
|
||
serecibe: document.getElementById('f-serecibe').value.trim(),
|
||
activo: document.getElementById('f-activo').checked ? 1 : 0,
|
||
form_ids: [...document.querySelectorAll('.f-formulario-chk:checked')].map(c => parseInt(c.value)),
|
||
};
|
||
const r = await fetch(API+'save.php', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}).then(r=>r.json());
|
||
if (r.ok) {
|
||
mostrarAlerta('success', r.message || 'Guardado');
|
||
if (!payload.id && r.id) setTimeout(() => location.href = location.href.split('?')[0] + '?m=lab_examenes&v=examen&id='+r.id, 800);
|
||
} else {
|
||
mostrarAlerta('danger', r.error || 'Error');
|
||
}
|
||
}
|
||
|
||
async function eliminar(id, nombre) {
|
||
if (!confirm(`¿Eliminar "${nombre}"? No se puede deshacer.`)) return;
|
||
const r = await fetch(API+'save.php', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id,_delete:true})}).then(r=>r.json());
|
||
if (r.ok) location.href = '<?= BASE_URL ?>erp.php?m=lab_examenes&v=index';
|
||
else mostrarAlerta('danger', r.error);
|
||
}
|
||
|
||
// ── Items ─────────────────────────────────────────
|
||
function addItem() {
|
||
const tr = document.createElement('tr');
|
||
tr.dataset.id = '0';
|
||
tr.dataset.cod = COD_PROT;
|
||
tr.innerHTML = `
|
||
<td><input type='number' name='orden' value='0' style='width:45px'></td>
|
||
<td><input type='text' name='nombre' placeholder='Nombre del item' style='min-width:160px'></td>
|
||
<td><select name='tipo_sexo'><option value=''>Ambos</option><option value='M'>M</option><option value='F'>F</option></select></td>
|
||
<td><select name='tipo'><option value='T'>Texto</option><option value='N'>Numérico</option></select></td>
|
||
<td><input type='text' name='medida'></td>
|
||
<td><input type='number' name='vmin_ref' step='any'></td>
|
||
<td><input type='number' name='vmax_ref' step='any'></td>
|
||
<td><input type='number' name='orden2' style='width:45px' disabled></td>
|
||
<td><input type='text' name='formula'></td>
|
||
<td><button type='button' class='btn btn-sm btn-outline-success py-0 px-1 me-1' onclick='saveItem(this)'><i class='fas fa-check'></i></button>
|
||
<button type='button' class='btn btn-sm btn-outline-danger py-0 px-1' onclick='this.closest("tr").remove()'><i class='fas fa-times'></i></button></td>`;
|
||
document.getElementById('items-body').appendChild(tr);
|
||
tr.querySelector('[name=nombre]').focus();
|
||
}
|
||
|
||
async function saveItem(btn) {
|
||
const tr = btn.closest('tr');
|
||
const g = n => tr.querySelector(`[name=${n}]`)?.value ?? '';
|
||
const payload = {
|
||
id: +tr.dataset.id || null,
|
||
cod_protocolo: tr.dataset.cod || COD_PROT,
|
||
nombre: g('nombre').trim(),
|
||
tipo_sexo: g('tipo_sexo') || null,
|
||
tipo: g('tipo') || 'T',
|
||
medida: g('medida'),
|
||
vmin_ref: g('vmin_ref'),
|
||
vmax_ref: g('vmax_ref'),
|
||
orden: +g('orden') || 0,
|
||
formula: g('formula'),
|
||
};
|
||
if (!payload.nombre) { alert('Nombre requerido'); return; }
|
||
const r = await fetch(API+'save_item.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}).then(r=>r.json());
|
||
if (r.ok) { tr.dataset.id = r.id || tr.dataset.id; mostrarAlerta('success','Item guardado'); }
|
||
else mostrarAlerta('danger', r.error);
|
||
}
|
||
|
||
async function delItem(btn) {
|
||
const tr = btn.closest('tr');
|
||
const id = +tr.dataset.id;
|
||
if (id && !confirm('¿Eliminar este item?')) return;
|
||
if (id) {
|
||
const r = await fetch(API+'save_item.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id,_delete:true})}).then(r=>r.json());
|
||
if (!r.ok) { mostrarAlerta('danger', r.error); return; }
|
||
}
|
||
tr.remove();
|
||
}
|
||
|
||
// ── Tarifas ──────────────────────────────────────
|
||
let _tarifas = [];
|
||
async function cargarTarifas() {
|
||
const r = await fetch(API+'get_tarifas.php?exam_id='+EXAM_ID).then(r=>r.json());
|
||
if (!r.ok) return;
|
||
_tarifas = r.data;
|
||
renderTarifas();
|
||
}
|
||
|
||
function renderTarifas() {
|
||
const body = document.getElementById('tar-body');
|
||
if (!_tarifas.length) { body.innerHTML='<tr><td colspan="7" class="text-center text-muted py-3">Sin tarifas.</td></tr>'; return; }
|
||
body.innerHTML = _tarifas.map((t,i) => {
|
||
const v = t.valor !== null ? parseFloat(t.valor).toLocaleString('es-CO') : '—';
|
||
return `<tr data-tidx="${i}">
|
||
<td class="text-muted" style="font-size:.75rem">${t.tarifa_id}</td>
|
||
<td>${esc(t.tarifa_nombre)}</td>
|
||
<td class="tar-val val-display" onclick="editTarifa(this)">${v}</td>
|
||
<td class="text-muted tar-val" style="font-size:.8rem">${t.recargo_urg>0?parseFloat(t.recargo_urg).toLocaleString('es-CO'):'—'}</td>
|
||
<td class="text-muted tar-val" style="font-size:.8rem">${t.recargo_fes>0?parseFloat(t.recargo_fes).toLocaleString('es-CO'):'—'}</td>
|
||
<td class="text-muted tar-val" style="font-size:.8rem">${t.recargo_esp>0?parseFloat(t.recargo_esp).toLocaleString('es-CO'):'—'}</td>
|
||
<td>
|
||
<button class="btn btn-outline-primary btn-sm py-0 px-2" onclick="abrirEditorTarifa(${i})" title="Editar precios">
|
||
<i class="fas fa-pencil-alt"></i>
|
||
</button>
|
||
</td>
|
||
</tr>`;
|
||
}).join('');
|
||
}
|
||
|
||
function abrirEditorTarifa(idx) {
|
||
const t = _tarifas[idx];
|
||
const modal = new bootstrap.Modal(document.getElementById('modalTarifa'));
|
||
document.getElementById('mt-nombre').textContent = t.tarifa_nombre;
|
||
document.getElementById('mt-idx').value = idx;
|
||
document.getElementById('mt-valor').value = t.valor ?? '';
|
||
document.getElementById('mt-urg').value = t.recargo_urg ?? 0;
|
||
document.getElementById('mt-fes').value = t.recargo_fes ?? 0;
|
||
document.getElementById('mt-esp').value = t.recargo_esp ?? 0;
|
||
modal.show();
|
||
document.getElementById('mt-valor').focus();
|
||
}
|
||
|
||
async function guardarTarifa() {
|
||
const idx = +document.getElementById('mt-idx').value;
|
||
const t = _tarifas[idx];
|
||
const payload = {
|
||
exam_tipo_id: EXAM_ID,
|
||
tarifa_id: t.tarifa_id,
|
||
valor: parseFloat(document.getElementById('mt-valor').value) || 0,
|
||
recargo_urg: parseFloat(document.getElementById('mt-urg').value) || 0,
|
||
recargo_fes: parseFloat(document.getElementById('mt-fes').value) || 0,
|
||
recargo_esp: parseFloat(document.getElementById('mt-esp').value) || 0,
|
||
};
|
||
const btn = document.getElementById('btn-guardar-tarifa');
|
||
btn.disabled = true;
|
||
const r = await fetch(API+'save_tarifa.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}).then(r=>r.json());
|
||
btn.disabled = false;
|
||
if (r.ok) {
|
||
bootstrap.Modal.getInstance(document.getElementById('modalTarifa')).hide();
|
||
cargarTarifas();
|
||
mostrarAlerta('success','Precio guardado');
|
||
} else {
|
||
mostrarAlerta('danger', r.error);
|
||
}
|
||
}
|
||
|
||
function mostrarAlerta(tipo, msg) {
|
||
const el = document.getElementById('alerta-global');
|
||
el.className = `alert alert-${tipo} py-2`;
|
||
el.textContent = msg;
|
||
el.classList.remove('d-none');
|
||
setTimeout(()=>el.classList.add('d-none'), 3500);
|
||
el.scrollIntoView({behavior:'smooth',block:'nearest'});
|
||
}
|
||
|
||
function esc(s){if(!s)return'';return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"')}
|
||
</script>
|
||
|
||
<!-- Modal editar tarifa -->
|
||
<div class="modal fade" id="modalTarifa" tabindex="-1">
|
||
<div class="modal-dialog modal-sm">
|
||
<div class="modal-content">
|
||
<div class="modal-header py-2">
|
||
<h6 class="modal-title"><i class="fas fa-tag me-1"></i><span id="mt-nombre"></span></h6>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<input type="hidden" id="mt-idx">
|
||
<div class="row g-2">
|
||
<div class="col-6">
|
||
<label class="form-label" style="font-size:.78rem;font-weight:600">Valor base</label>
|
||
<input type="number" id="mt-valor" class="form-control form-control-sm" step="1" min="0">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="form-label" style="font-size:.78rem;font-weight:600">R. Urgencia</label>
|
||
<input type="number" id="mt-urg" class="form-control form-control-sm" step="1" min="0">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="form-label" style="font-size:.78rem;font-weight:600">R. Festivo</label>
|
||
<input type="number" id="mt-fes" class="form-control form-control-sm" step="1" min="0">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="form-label" style="font-size:.78rem;font-weight:600">R. Especial</label>
|
||
<input type="number" id="mt-esp" class="form-control form-control-sm" step="1" min="0">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer py-2">
|
||
<button class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancelar</button>
|
||
<button class="btn btn-primary btn-sm" id="btn-guardar-tarifa" onclick="guardarTarifa()">
|
||
<i class="fas fa-save me-1"></i>Guardar
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<?php Layout::close(); ?>
|