Files
whatsapp/modules/lab_empresas/views/tarifas.php
T
Lizandro GuarnizoandClaude Sonnet 4.6 b9bb4fc628 feat(lab_empresas): módulo CRUD de empresas, convenios y tarifas
- 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>
2026-07-04 22:05:14 -05:00

213 lines
9.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
</script>
<?php Layout::close(); ?>