- Nuevo módulo lab_empresas: lista paginada, modal create/edit empresa, gestión de subgrupos inline y CRUD de catálogo lab_tarifas_id - APIs: empresas.php (list/get/save/toggle/delete), empresa_subgrupos.php, tarifas_id.php - Migración 09 registra el módulo en system_modules y asigna permiso admin Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
213 lines
9.3 KiB
PHP
213 lines
9.3 KiB
PHP
<?php
|
||
/**
|
||
* modules/lab_empresas/views/tarifas.php
|
||
* CRUD del catálogo de tarifas (lab_tarifas_id).
|
||
*/
|
||
require_once APP_ROOT . '/config/config.php';
|
||
Layout::open('Catálogo de Tarifas', 'fas fa-tags');
|
||
?>
|
||
<div class="container-fluid py-3">
|
||
|
||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
|
||
<div>
|
||
<h4 class="mb-0"><i class="fas fa-tags me-2 text-primary"></i>Catálogo de Tarifas</h4>
|
||
<small class="text-muted">IDs de tarifa usados en el motor de precios</small>
|
||
</div>
|
||
<div class="d-flex gap-2">
|
||
<a href="<?= BASE_URL ?>/erp.php?m=lab_empresas&v=index"
|
||
class="btn btn-outline-secondary btn-sm">
|
||
<i class="fas fa-building me-1"></i>Empresas
|
||
</a>
|
||
<button class="btn btn-primary btn-sm" onclick="abrirForm()">
|
||
<i class="fas fa-plus me-1"></i>Nueva tarifa
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row g-3">
|
||
<!-- Tabla tarifas -->
|
||
<div class="col-md-7">
|
||
<div class="card shadow-sm">
|
||
<div class="card-body p-0">
|
||
<table class="table table-hover table-sm mb-0" id="tablaTarifas">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th style="width:60px">ID</th>
|
||
<th>Nombre</th>
|
||
<th class="text-end">%</th>
|
||
<th>Origen</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tbodyTarifas">
|
||
<tr><td colspan="5" class="text-center py-4 text-muted">Cargando…</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<p class="text-muted small mt-2">
|
||
<i class="fas fa-info-circle me-1"></i>
|
||
Tarifas con <strong>% > 0</strong> y <strong>Origen</strong> se calculan automáticamente
|
||
como: precio_origen × (1 + %/100).
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Formulario -->
|
||
<div class="col-md-5">
|
||
<div class="card shadow-sm">
|
||
<div class="card-header py-2">
|
||
<h6 class="mb-0 small fw-semibold" id="formTitulo">Nueva tarifa</h6>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="mb-3">
|
||
<label class="form-label fw-semibold small">ID numérico</label>
|
||
<input type="number" id="tId" class="form-control form-control-sm"
|
||
placeholder="Dejar vacío para auto-asignar" min="1">
|
||
<div class="form-text">El ID debe coincidir con el ID Firebird si se va a migrar.</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label fw-semibold small">Nombre <span class="text-danger">*</span></label>
|
||
<input type="text" id="tNombre" class="form-control form-control-sm"
|
||
placeholder="Ej: PARTICULAR, EPS SURA" maxlength="150">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label fw-semibold small">Porcentaje sobre tarifa origen</label>
|
||
<div class="input-group input-group-sm">
|
||
<input type="number" id="tPorcentaje" class="form-control"
|
||
value="0" min="0" step="0.01" max="999">
|
||
<span class="input-group-text">%</span>
|
||
</div>
|
||
<div class="form-text">0 = precios fijos. >0 = derivada de otra tarifa.</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label fw-semibold small">Tarifa origen</label>
|
||
<select id="tOrigen" class="form-select form-select-sm">
|
||
<option value="">— Precios directos —</option>
|
||
</select>
|
||
<div class="form-text">Solo si esta tarifa deriva de otra por porcentaje.</div>
|
||
</div>
|
||
<div id="tError" class="alert alert-danger d-none py-2 small mb-2"></div>
|
||
<div class="d-flex gap-2">
|
||
<button class="btn btn-primary btn-sm flex-fill" onclick="guardarTarifa()">
|
||
<i class="fas fa-save me-1"></i>Guardar
|
||
</button>
|
||
<button class="btn btn-outline-secondary btn-sm" onclick="resetForm()">Cancelar</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const BASE = '<?= BASE_URL ?>';
|
||
let _tarifas = [];
|
||
let _editId = null;
|
||
|
||
document.addEventListener('DOMContentLoaded', () => cargarTarifas());
|
||
|
||
async function cargarTarifas() {
|
||
const res = await fetch(`${BASE}/api/lab/tarifas_id.php`);
|
||
const j = await res.json();
|
||
_tarifas = j.tarifas || [];
|
||
|
||
// Poblar select origen (excluyendo la propia tarifa en edición)
|
||
const optsOrigen = _tarifas
|
||
.filter(t => _editId === null || t.id != _editId)
|
||
.map(t => `<option value="${t.id}">${escHtml(t.nombre)} (ID ${t.id})</option>`)
|
||
.join('');
|
||
document.getElementById('tOrigen').innerHTML = '<option value="">— Precios directos —</option>' + optsOrigen;
|
||
|
||
const tbody = document.getElementById('tbodyTarifas');
|
||
if (!_tarifas.length) {
|
||
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-4 text-muted">Sin tarifas registradas</td></tr>';
|
||
return;
|
||
}
|
||
tbody.innerHTML = _tarifas.map(t => `
|
||
<tr>
|
||
<td class="text-monospace fw-semibold text-primary">${t.id}</td>
|
||
<td>${escHtml(t.nombre)}</td>
|
||
<td class="text-end">${parseFloat(t.porcentaje) > 0 ? `<span class="badge bg-info text-dark">+${t.porcentaje}%</span>` : '—'}</td>
|
||
<td>${t.tarifa_origen_nombre ? `<span class="small text-muted">${escHtml(t.tarifa_origen_nombre)}</span>` : '—'}</td>
|
||
<td class="text-end pe-2">
|
||
<button class="btn btn-outline-primary btn-sm py-0 px-2 me-1"
|
||
onclick="editarTarifa(${t.id})" title="Editar">
|
||
<i class="fas fa-pen"></i>
|
||
</button>
|
||
<button class="btn btn-outline-danger btn-sm py-0 px-2"
|
||
onclick="eliminarTarifa(${t.id})" title="Eliminar">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function editarTarifa(id) {
|
||
const t = _tarifas.find(x => x.id == id);
|
||
if (!t) return;
|
||
_editId = id;
|
||
document.getElementById('formTitulo').textContent = 'Editar tarifa';
|
||
document.getElementById('tId').value = t.id;
|
||
document.getElementById('tId').readOnly = true;
|
||
document.getElementById('tNombre').value = t.nombre;
|
||
document.getElementById('tPorcentaje').value = t.porcentaje;
|
||
document.getElementById('tOrigen').value = t.tarifa_origen || '';
|
||
document.getElementById('tError').classList.add('d-none');
|
||
}
|
||
|
||
function resetForm() {
|
||
_editId = null;
|
||
document.getElementById('formTitulo').textContent = 'Nueva tarifa';
|
||
document.getElementById('tId').value = '';
|
||
document.getElementById('tId').readOnly = false;
|
||
document.getElementById('tNombre').value = '';
|
||
document.getElementById('tPorcentaje').value = '0';
|
||
document.getElementById('tOrigen').value = '';
|
||
document.getElementById('tError').classList.add('d-none');
|
||
}
|
||
|
||
function abrirForm() { resetForm(); document.getElementById('tNombre').focus(); }
|
||
|
||
async function guardarTarifa() {
|
||
const errEl = document.getElementById('tError');
|
||
errEl.classList.add('d-none');
|
||
const nombre = document.getElementById('tNombre').value.trim();
|
||
if (!nombre) { errEl.textContent='El nombre es obligatorio'; errEl.classList.remove('d-none'); return; }
|
||
|
||
const payload = {
|
||
action: 'save',
|
||
nombre,
|
||
porcentaje: document.getElementById('tPorcentaje').value,
|
||
tarifa_origen:document.getElementById('tOrigen').value,
|
||
};
|
||
const idVal = document.getElementById('tId').value.trim();
|
||
if (idVal) payload.id = parseInt(idVal);
|
||
|
||
const res = await fetch(`${BASE}/api/lab/tarifas_id.php`, {
|
||
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload)
|
||
});
|
||
const j = await res.json();
|
||
if (!j.ok) { errEl.textContent = j.error || 'Error'; errEl.classList.remove('d-none'); return; }
|
||
resetForm();
|
||
cargarTarifas();
|
||
}
|
||
|
||
async function eliminarTarifa(id) {
|
||
if (!confirm('¿Eliminar esta tarifa? Solo es posible si no tiene precios de exámenes asociados.')) return;
|
||
const res = await fetch(`${BASE}/api/lab/tarifas_id.php`, {
|
||
method:'POST', headers:{'Content-Type':'application/json'},
|
||
body: JSON.stringify({action:'delete', id})
|
||
});
|
||
const j = await res.json();
|
||
if (!j.ok) { alert(j.error || 'Error al eliminar'); return; }
|
||
cargarTarifas();
|
||
}
|
||
|
||
function escHtml(s) {
|
||
if (s == null) return '';
|
||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||
}
|
||
</script>
|
||
<?php Layout::close(); ?>
|