fix: refactor ventas page to group by factura instead of idrecepcion
- _agrupar now keys by PREFIJO-NUM_FACTURA so all services of a factura are grouped together - _is_sent checks by factura key instead of idrecepcion - send-one and send endpoints accept/return factura_key instead of idrecepcion - ventas.html JS updated: factura_key used throughout (filaHtml, marcarFila, enviarUno, enviarTodosPendientes, verDetalle) - DOM element IDs sanitized with _safeId() to handle dashes in keys like CMXC-309 - onclick handlers now pass string keys with proper quoting Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
c1925ef6da
commit
8ed8f0999f
+32
-23
@@ -193,37 +193,41 @@ function renderTabla() {
|
||||
}
|
||||
|
||||
function filaHtml(item) {
|
||||
const id = item.idrecepcion;
|
||||
const id = item.factura_key;
|
||||
const sid = escAttr(id);
|
||||
const env = item.enviado;
|
||||
const examsStr = Array.isArray(item.examenes) ? item.examenes.join(', ') : (item.examenes || '');
|
||||
const rowId = 'row-' + id.replace(/[^a-zA-Z0-9]/g, '_');
|
||||
const estadoId = 'estado-' + id.replace(/[^a-zA-Z0-9]/g, '_');
|
||||
const btnId = 'btnenv-' + id.replace(/[^a-zA-Z0-9]/g, '_');
|
||||
|
||||
let badgeHtml, rowCls, accionHtml;
|
||||
if (!env) {
|
||||
badgeHtml = `<span class="px-2 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-700">Pendiente</span>`;
|
||||
rowCls = '';
|
||||
accionHtml = `<button onclick="enviarUno(${id})" title="Enviar" class="px-2 py-1 bg-emerald-600 hover:bg-emerald-700 text-white rounded text-xs"><i class="fas fa-paper-plane"></i></button>`;
|
||||
accionHtml = `<button onclick="enviarUno('${sid}')" title="Enviar" class="px-2 py-1 bg-emerald-600 hover:bg-emerald-700 text-white rounded text-xs"><i class="fas fa-paper-plane"></i></button>`;
|
||||
} else if (env.status === 'success') {
|
||||
const at = (env.at || '').slice(0, 16).replace('T', ' ');
|
||||
badgeHtml = `<span class="px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-700" title="Enviado ${at}">✓ Enviado</span>`;
|
||||
rowCls = 'bg-green-50/50 opacity-80';
|
||||
accionHtml = `<button onclick="enviarUno(${id})" title="Volver a enviar" class="px-2 py-1 bg-gray-400 hover:bg-gray-500 text-white rounded text-xs"><i class="fas fa-redo"></i></button>`;
|
||||
accionHtml = `<button onclick="enviarUno('${sid}')" title="Volver a enviar" class="px-2 py-1 bg-gray-400 hover:bg-gray-500 text-white rounded text-xs"><i class="fas fa-redo"></i></button>`;
|
||||
} else {
|
||||
const tip = escAttr(env.mensaje || 'Error');
|
||||
badgeHtml = `<span class="px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-700" title="${tip}">✗ Error</span>`;
|
||||
rowCls = 'bg-red-50/50';
|
||||
accionHtml = `<button onclick="enviarUno(${id})" title="Reintentar" class="px-2 py-1 bg-orange-500 hover:bg-orange-600 text-white rounded text-xs"><i class="fas fa-redo"></i></button>`;
|
||||
accionHtml = `<button onclick="enviarUno('${sid}')" title="Reintentar" class="px-2 py-1 bg-orange-500 hover:bg-orange-600 text-white rounded text-xs"><i class="fas fa-redo"></i></button>`;
|
||||
}
|
||||
|
||||
return `<tr id="row-${id}" class="border-b border-gray-50 hover:bg-gray-50/80 transition-colors ${rowCls}">
|
||||
<td class="py-2 pr-3 font-mono text-gray-600">${id}</td>
|
||||
return `<tr id="${rowId}" class="border-b border-gray-50 hover:bg-gray-50/80 transition-colors ${rowCls}">
|
||||
<td class="py-2 pr-3 font-mono text-gray-600">${escHtml(id)}</td>
|
||||
<td class="py-2 pr-3 text-gray-600">${escHtml(String(item.factura || '-'))}</td>
|
||||
<td class="py-2 pr-3 text-gray-600">${escHtml(String(item.paciente || '-'))}</td>
|
||||
<td class="py-2 pr-3"><span class="px-1.5 py-0.5 bg-emerald-100 text-emerald-700 rounded text-xs font-mono">${escHtml(String(item.contrato || '-'))}</span></td>
|
||||
<td class="py-2 pr-3 text-gray-500 max-w-[180px] truncate" title="${escAttr(examsStr)}">${escHtml(examsStr)}</td>
|
||||
<td id="estado-${id}" class="py-2 pr-3">${badgeHtml}</td>
|
||||
<td id="${estadoId}" class="py-2 pr-3">${badgeHtml}</td>
|
||||
<td class="py-2 flex gap-1 items-center">
|
||||
<button onclick="verDetalle(${id})" title="Ver JSON" class="px-2 py-1 bg-gray-100 hover:bg-gray-200 text-gray-600 rounded text-xs"><i class="fas fa-code"></i></button>
|
||||
<span id="btnenv-${id}">${accionHtml}</span>
|
||||
<button onclick="verDetalle('${sid}')" title="Ver JSON" class="px-2 py-1 bg-gray-100 hover:bg-gray-200 text-gray-600 rounded text-xs"><i class="fas fa-code"></i></button>
|
||||
<span id="${btnId}">${accionHtml}</span>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
@@ -240,34 +244,39 @@ function actualizarResumen() {
|
||||
document.getElementById('res-err-wrap').classList.toggle('hidden', err === 0);
|
||||
}
|
||||
|
||||
function _safeId(id) { return id.replace(/[^a-zA-Z0-9]/g, '_'); }
|
||||
|
||||
function marcarFila(id, ok, mensaje, rawTns) {
|
||||
const idx = allItems.findIndex(i => i.idrecepcion === id);
|
||||
const idx = allItems.findIndex(i => i.factura_key === id);
|
||||
if (idx !== -1) {
|
||||
allItems[idx].enviado = { status: ok ? 'success' : 'error', mensaje, at: new Date().toISOString() };
|
||||
if (rawTns !== undefined) allItems[idx]._rawTns = rawTns;
|
||||
}
|
||||
const row = document.getElementById(`row-${id}`);
|
||||
const estadoCell = document.getElementById(`estado-${id}`);
|
||||
const btnWrap = document.getElementById(`btnenv-${id}`);
|
||||
const sid = escAttr(id);
|
||||
const safe = _safeId(id);
|
||||
const row = document.getElementById(`row-${safe}`);
|
||||
const estadoCell = document.getElementById(`estado-${safe}`);
|
||||
const btnWrap = document.getElementById(`btnenv-${safe}`);
|
||||
if (!row || !estadoCell) return;
|
||||
|
||||
if (ok) {
|
||||
row.className = row.className.replace(/bg-red-50\/50|bg-green-50\/50/g, '') + ' bg-green-50/50 opacity-80';
|
||||
estadoCell.innerHTML = `<span class="px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-700">✓ Enviado</span>`;
|
||||
if (btnWrap) btnWrap.innerHTML = `<button onclick="enviarUno(${id})" title="Volver a enviar" class="px-2 py-1 bg-gray-400 hover:bg-gray-500 text-white rounded text-xs"><i class="fas fa-redo"></i></button>`;
|
||||
if (btnWrap) btnWrap.innerHTML = `<button onclick="enviarUno('${sid}')" title="Volver a enviar" class="px-2 py-1 bg-gray-400 hover:bg-gray-500 text-white rounded text-xs"><i class="fas fa-redo"></i></button>`;
|
||||
} else {
|
||||
row.className = row.className.replace(/bg-red-50\/50|bg-green-50\/50/g, '') + ' bg-red-50/50';
|
||||
estadoCell.innerHTML = `<span class="px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-700" title="${escAttr(mensaje||'')}">✗ Error</span>`;
|
||||
if (btnWrap) btnWrap.innerHTML = `<button onclick="enviarUno(${id})" title="Reintentar" class="px-2 py-1 bg-orange-500 hover:bg-orange-600 text-white rounded text-xs"><i class="fas fa-redo"></i></button>`;
|
||||
if (btnWrap) btnWrap.innerHTML = `<button onclick="enviarUno('${sid}')" title="Reintentar" class="px-2 py-1 bg-orange-500 hover:bg-orange-600 text-white rounded text-xs"><i class="fas fa-redo"></i></button>`;
|
||||
}
|
||||
actualizarResumen();
|
||||
}
|
||||
|
||||
async function enviarUno(id) {
|
||||
const btnWrap = document.getElementById(`btnenv-${id}`);
|
||||
const safe = _safeId(id);
|
||||
const btnWrap = document.getElementById(`btnenv-${safe}`);
|
||||
if (btnWrap) btnWrap.innerHTML = '<span class="px-2 py-1 text-gray-400 text-xs"><i class="fas fa-spinner fa-spin"></i></span>';
|
||||
|
||||
const fd = new URLSearchParams({ ...formParams, idrecepcion: id });
|
||||
const fd = new URLSearchParams({ ...formParams, factura_key: id });
|
||||
let result;
|
||||
try {
|
||||
const resp = await fetch('/ventas/send-one', { method: 'POST', credentials: 'include', body: fd });
|
||||
@@ -278,9 +287,9 @@ async function enviarUno(id) {
|
||||
|
||||
marcarFila(id, result.success, result.message, result.raw_tns);
|
||||
if (result.success) {
|
||||
showToast(`IDRECEPCION ${id}: enviado`, 'success');
|
||||
showToast(`Factura ${escHtml(id)}: enviada`, 'success');
|
||||
} else {
|
||||
showToast(`IDRECEPCION ${id}: ${result.message}`, 'error');
|
||||
showToast(`Factura ${escHtml(id)}: ${escHtml(result.message || 'Error')}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,8 +304,8 @@ async function enviarTodosPendientes() {
|
||||
|
||||
let ok = 0, err = 0;
|
||||
for (const item of pendientes) {
|
||||
await enviarUno(item.idrecepcion);
|
||||
if (allItems.find(i => i.idrecepcion === item.idrecepcion)?.enviado?.status === 'success') ok++; else err++;
|
||||
await enviarUno(item.factura_key);
|
||||
if (allItems.find(i => i.factura_key === item.factura_key)?.enviado?.status === 'success') ok++; else err++;
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
@@ -305,9 +314,9 @@ async function enviarTodosPendientes() {
|
||||
}
|
||||
|
||||
function verDetalle(id) {
|
||||
const item = allItems.find(i => i.idrecepcion === id);
|
||||
const item = allItems.find(i => i.factura_key === id);
|
||||
if (!item) return;
|
||||
document.getElementById('modal-titulo').innerHTML = `<i class="fas fa-code mr-2 text-emerald-500"></i>IDRECEPCION ${id} — ${item.paciente || ''} — Factura ${item.factura || '-'}`;
|
||||
document.getElementById('modal-titulo').innerHTML = `<i class="fas fa-code mr-2 text-emerald-500"></i>${escHtml(id)} — ${escHtml(item.paciente || '')} — Factura ${escHtml(item.factura || '-')}`;
|
||||
document.getElementById('tab-json').innerHTML = syntaxHighlight(item.json || {});
|
||||
const tnsPre = document.getElementById('tab-tns');
|
||||
if (item.enviado) {
|
||||
|
||||
Reference in New Issue
Block a user