feat(automation): resultados con filtro Errores/Enviados/Todos y filas descriptivas
- Lista unificada reemplaza los dos paneles separados (failures/successes) - Tabs de filtro: Errores (default si hay), Enviados, Todos — con contadores - Filas de error: borde rojo, badge del paso, factura+paciente, mensaje inline, botón Reenviar - Reenvío actualiza la fila en tiempo real: rojo → verde sin recarga - btn-run solo se habilita si los pasos activos tienen datos reales Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
fe31af2f9a
commit
6dee8fba82
+176
-129
@@ -116,38 +116,35 @@
|
||||
<!-- ─── Resultados ────────────────────────────────────────────────────────── -->
|
||||
<div id="results-section" class="hidden space-y-3">
|
||||
|
||||
<!-- Barra de resumen -->
|
||||
<!-- Barra de resumen + filtros -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 px-5 py-4">
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
<span class="text-xs font-semibold text-gray-500 shrink-0">RESULTADO</span>
|
||||
<div id="res-summary" class="flex flex-wrap gap-2"></div>
|
||||
<div class="flex items-center justify-between gap-4 flex-wrap">
|
||||
<!-- Chips por paso -->
|
||||
<div class="flex items-center gap-2 flex-wrap min-w-0">
|
||||
<span class="text-xs font-semibold text-gray-500 shrink-0">RESULTADO</span>
|
||||
<div id="res-summary" class="flex flex-wrap gap-2"></div>
|
||||
</div>
|
||||
<!-- Filtros -->
|
||||
<div class="flex items-center gap-1 bg-gray-100 rounded-lg p-1 shrink-0">
|
||||
<button id="filter-btn-errores" onclick="setResultFilter('errores')"
|
||||
class="px-3 py-1 text-xs font-medium rounded-md transition-all">
|
||||
<i class="fas fa-times-circle mr-1"></i>Errores <span id="filter-cnt-errores" class="font-bold"></span>
|
||||
</button>
|
||||
<button id="filter-btn-enviados" onclick="setResultFilter('enviados')"
|
||||
class="px-3 py-1 text-xs font-medium rounded-md transition-all">
|
||||
<i class="fas fa-check-circle mr-1"></i>Enviados <span id="filter-cnt-enviados" class="font-bold"></span>
|
||||
</button>
|
||||
<button id="filter-btn-todos" onclick="setResultFilter('todos')"
|
||||
class="px-3 py-1 text-xs font-medium rounded-md transition-all">
|
||||
<i class="fas fa-list mr-1"></i>Todos <span id="filter-cnt-todos" class="font-bold"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Panel de fallos -->
|
||||
<div id="failures-panel" class="hidden bg-white rounded-xl shadow-sm border border-red-200 overflow-hidden">
|
||||
<button onclick="togglePanel('failures-body','failures-chev')"
|
||||
class="w-full px-5 py-3.5 flex items-center gap-3 text-left bg-red-50 hover:bg-red-100 transition-colors rounded-t-xl border-b border-red-100">
|
||||
<div class="w-6 h-6 bg-red-500 rounded-full flex items-center justify-center text-white text-xs shrink-0">
|
||||
<i class="fas fa-exclamation"></i>
|
||||
</div>
|
||||
<span class="font-semibold text-red-700 flex-1 text-sm" id="failures-title">Fallos</span>
|
||||
<i id="failures-chev" class="fas fa-chevron-down text-red-400 text-xs transition-transform"></i>
|
||||
</button>
|
||||
<div id="failures-body" class="divide-y divide-red-50"></div>
|
||||
</div>
|
||||
|
||||
<!-- Panel de exitosos (colapsado) -->
|
||||
<div id="successes-panel" class="hidden bg-white rounded-xl shadow-sm border border-green-200 overflow-hidden">
|
||||
<button onclick="togglePanel('successes-body','successes-chev')"
|
||||
class="w-full px-5 py-3.5 flex items-center gap-3 text-left hover:bg-green-50 transition-colors rounded-xl border-b border-transparent">
|
||||
<div class="w-6 h-6 bg-green-500 rounded-full flex items-center justify-center text-white text-xs shrink-0">
|
||||
<i class="fas fa-check"></i>
|
||||
</div>
|
||||
<span class="font-semibold text-green-700 flex-1 text-sm" id="successes-title">Enviados correctamente</span>
|
||||
<i id="successes-chev" class="fas fa-chevron-right text-green-400 text-xs transition-transform"></i>
|
||||
</button>
|
||||
<div id="successes-body" class="hidden divide-y divide-green-50"></div>
|
||||
<!-- Lista unificada -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div id="results-list" class="divide-y divide-gray-100"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -172,22 +169,27 @@
|
||||
<div id="mc-terceros" class="bg-blue-50 rounded-lg p-3 text-center transition-opacity">
|
||||
<div id="modal-cnt-pac" class="text-2xl font-bold text-blue-600">0</div>
|
||||
<div class="text-xs text-blue-700">Terceros</div>
|
||||
<div class="text-xs text-blue-400">Tercero/Crear</div>
|
||||
</div>
|
||||
<div id="mc-rda" class="bg-purple-50 rounded-lg p-3 text-center transition-opacity">
|
||||
<div id="modal-cnt-rec" class="text-2xl font-bold text-purple-600">0</div>
|
||||
<div class="text-xs text-purple-700">RDA</div>
|
||||
<div class="text-xs text-purple-400">RdaPaciente/Insertar</div>
|
||||
</div>
|
||||
<div id="mc-examenes" class="bg-green-50 rounded-lg p-3 text-center transition-opacity">
|
||||
<div id="modal-cnt-exa" class="text-2xl font-bold text-green-600">0</div>
|
||||
<div class="text-xs text-green-700">Exámenes</div>
|
||||
<div class="text-xs text-green-400">detallePedido</div>
|
||||
</div>
|
||||
<div id="mc-preserv" class="bg-orange-50 rounded-lg p-3 text-center transition-opacity">
|
||||
<div id="modal-cnt-ps" class="text-2xl font-bold text-orange-600">0</div>
|
||||
<div class="text-xs text-orange-700">Pre-servicios</div>
|
||||
<div class="text-xs text-orange-400">RCXC / SC</div>
|
||||
</div>
|
||||
<div id="mc-ventas" class="bg-emerald-50 rounded-lg p-3 text-center transition-opacity">
|
||||
<div id="modal-cnt-vta" class="text-2xl font-bold text-emerald-600">0</div>
|
||||
<div class="text-xs text-emerald-700">Ventas</div>
|
||||
<div class="text-xs text-emerald-400">Ventas/Crear</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-amber-50 border border-amber-200 rounded-lg px-4 py-3 text-xs text-amber-700 flex items-start gap-2">
|
||||
@@ -210,8 +212,10 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let _previewData = null;
|
||||
let _previewData = null;
|
||||
let _rdaJsonStore = {};
|
||||
let _resultAll = [];
|
||||
let _activeFilter = 'errores';
|
||||
|
||||
// ── Estado de pasos ──────────────────────────────────────────────────────────
|
||||
const _pasos = new Set(['terceros','rda','preserv','ventas']);
|
||||
@@ -278,13 +282,21 @@ async function previewAutomation() {
|
||||
document.getElementById('cnt-ventas').textContent = data.ventas || 0;
|
||||
document.getElementById('preview-fecha').textContent = data.fecha;
|
||||
|
||||
// Opacidad según pasos activos
|
||||
document.getElementById('card-terceros').style.opacity = _pasos.has('terceros') ? '1' : '0.3';
|
||||
document.getElementById('card-rda').style.opacity = _pasos.has('rda') ? '1' : '0.3';
|
||||
document.getElementById('card-examenes').style.opacity = (_pasos.has('rda') || _pasos.has('preserv')) ? '1' : '0.3';
|
||||
document.getElementById('card-examenes').style.opacity = (_pasos.has('rda')||_pasos.has('preserv')) ? '1' : '0.3';
|
||||
document.getElementById('card-preserv').style.opacity = _pasos.has('preserv') ? '1' : '0.3';
|
||||
document.getElementById('card-ventas').style.opacity = _pasos.has('ventas') ? '1' : '0.3';
|
||||
|
||||
// Habilitar run solo si hay datos para al menos un paso activo
|
||||
const hasDatos = (
|
||||
(_pasos.has('terceros') && data.pacientes > 0) ||
|
||||
(_pasos.has('rda') && data.recepciones > 0) ||
|
||||
(_pasos.has('preserv') && data.preservicios > 0) ||
|
||||
(_pasos.has('ventas') && data.ventas > 0)
|
||||
);
|
||||
document.getElementById('btn-run').disabled = !hasDatos;
|
||||
|
||||
// Sección Terceros/RDA
|
||||
const secTR = document.getElementById('sec-terceros-rda');
|
||||
if (_pasos.has('terceros') || _pasos.has('rda')) {
|
||||
@@ -380,9 +392,13 @@ async function previewAutomation() {
|
||||
vtaSection.classList.add('hidden');
|
||||
}
|
||||
|
||||
document.getElementById('btn-run').disabled = false;
|
||||
document.getElementById('preview-section').classList.remove('hidden');
|
||||
showToast(`${data.pacientes} pac · ${data.recepciones} RDA · ${data.preservicios||0} PS · ${data.ventas||0} VTA`, 'success');
|
||||
|
||||
if (!hasDatos) {
|
||||
showToast('Sin datos para los pasos seleccionados en esa fecha', 'warning');
|
||||
} else {
|
||||
showToast(`${data.pacientes} pac · ${data.recepciones} RDA · ${data.preservicios||0} PS · ${data.ventas||0} VTA`, 'success');
|
||||
}
|
||||
} catch(e) {
|
||||
hideLoading(btn, '<i class="fas fa-eye"></i> Vista previa');
|
||||
showToast('Error de conexión', 'error');
|
||||
@@ -421,7 +437,7 @@ function cerrarModalConfirmar() {
|
||||
// ── Ejecutar envío ───────────────────────────────────────────────────────────
|
||||
async function confirmarEnvio() {
|
||||
cerrarModalConfirmar();
|
||||
const fecha = document.getElementById('fecha-input').value;
|
||||
const fecha = document.getElementById('fecha-input').value;
|
||||
const contrato = document.getElementById('contrato-input').value.trim();
|
||||
const btn = document.getElementById('btn-run');
|
||||
showLoading(btn);
|
||||
@@ -451,116 +467,153 @@ async function confirmarEnvio() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Renderizar resultados ────────────────────────────────────────────────────
|
||||
// ── Definiciones de pasos ────────────────────────────────────────────────────
|
||||
const _STEP_DEFS = [
|
||||
{ pasoKey:'terceros', resultKey:'paso1_terceros', label:'Terceros', color:'blue', tipo:'tercero', idField:'codigo' },
|
||||
{ pasoKey:'rda', resultKey:'paso2_rda', label:'RDA', color:'purple', tipo:'rda', idField:'idrecepcion' },
|
||||
{ pasoKey:'preserv', resultKey:'paso3_preserv', label:'Pre-servicios', color:'orange', tipo:'preserv', idField:'idrecepcion' },
|
||||
{ pasoKey:'ventas', resultKey:'paso4_ventas', label:'Ventas', color:'emerald',tipo:'venta', idField:'idrecepcion' },
|
||||
{ pasoKey:'terceros', resultKey:'paso1_terceros', label:'Terceros', num:1, color:'blue', tipo:'tercero', icon:'fa-user' },
|
||||
{ pasoKey:'rda', resultKey:'paso2_rda', label:'RDA', num:2, color:'purple', tipo:'rda', icon:'fa-exchange-alt' },
|
||||
{ pasoKey:'preserv', resultKey:'paso3_preserv', label:'Pre-servicios', num:3, color:'orange', tipo:'preserv', icon:'fa-file-alt' },
|
||||
{ pasoKey:'ventas', resultKey:'paso4_ventas', label:'Ventas', num:4, color:'emerald',tipo:'venta', icon:'fa-file-invoice-dollar'},
|
||||
];
|
||||
|
||||
const _COLOR_BADGE = {
|
||||
const _BADGE = {
|
||||
blue: 'bg-blue-100 text-blue-700',
|
||||
purple: 'bg-purple-100 text-purple-700',
|
||||
orange: 'bg-orange-100 text-orange-700',
|
||||
emerald:'bg-emerald-100 text-emerald-700',
|
||||
};
|
||||
const _COLOR_SUM = {
|
||||
const _CHIP = {
|
||||
blue: 'bg-blue-100 text-blue-800',
|
||||
purple: 'bg-purple-100 text-purple-800',
|
||||
orange: 'bg-orange-100 text-orange-800',
|
||||
emerald:'bg-emerald-100 text-emerald-800',
|
||||
};
|
||||
|
||||
// ── Renderizar resultados ────────────────────────────────────────────────────
|
||||
function renderResults(r) {
|
||||
const activePasos = r.pasos || _STEP_DEFS.map(s => s.pasoKey);
|
||||
const activeSteps = _STEP_DEFS.filter(s => activePasos.includes(s.pasoKey));
|
||||
|
||||
// Chips de resumen
|
||||
// Summary chips
|
||||
document.getElementById('res-summary').innerHTML = activeSteps.map(s => {
|
||||
const d = r[s.resultKey];
|
||||
return `<span class="${_COLOR_SUM[s.color]} rounded-full px-3 py-1 text-xs font-semibold inline-flex items-center gap-1.5">
|
||||
${s.label}
|
||||
<span class="text-green-700 font-bold">✓${d.enviados}</span>
|
||||
${d.errores > 0 ? `<span class="text-red-600 font-bold">✗${d.errores}</span>` : ''}
|
||||
const hasErr = d.errores > 0;
|
||||
return `<span class="${_CHIP[s.color]} rounded-full px-3 py-1 text-xs font-semibold inline-flex items-center gap-1.5">
|
||||
<i class="fas ${s.icon} text-xs opacity-70"></i> ${s.label}
|
||||
<span class="text-green-700">✓${d.enviados}</span>
|
||||
${hasErr ? `<span class="text-red-600 font-bold">✗${d.errores}</span>` : ''}
|
||||
</span>`;
|
||||
}).join('');
|
||||
|
||||
// Clasificar items
|
||||
const failures = [], successes = [];
|
||||
// Clasificar todos los items
|
||||
_resultAll = [];
|
||||
activeSteps.forEach(s => {
|
||||
(r[s.resultKey].detalle || []).forEach((item, idx) => {
|
||||
const entry = { item, step: s, idx: `${s.pasoKey}-${idx}` };
|
||||
(item.ok ? successes : failures).push(entry);
|
||||
(r[s.resultKey].detalle || []).forEach((item, i) => {
|
||||
_resultAll.push({ item, step: s, idx: `${s.pasoKey}-${i}`, ok: item.ok });
|
||||
});
|
||||
});
|
||||
|
||||
// Panel fallos
|
||||
const failPanel = document.getElementById('failures-panel');
|
||||
const failBody = document.getElementById('failures-body');
|
||||
if (failures.length > 0) {
|
||||
document.getElementById('failures-title').textContent = `Fallos (${failures.length})`;
|
||||
failBody.innerHTML = failures.map(({ item, step: s, idx }) => {
|
||||
let label1 = '', label2 = '';
|
||||
let dtype = s.tipo, did = '';
|
||||
if (s.tipo === 'tercero') {
|
||||
label1 = item.doc || '';
|
||||
label2 = item.nombre || '';
|
||||
did = escHtml(String(item.codigo || ''));
|
||||
} else {
|
||||
label1 = item.factura || '';
|
||||
label2 = `Pac.${item.paciente||''} · ${item.examenes||0} exam.`;
|
||||
did = escHtml(String(item.idrecepcion || 0));
|
||||
}
|
||||
const errCount = _resultAll.filter(e => !e.ok).length;
|
||||
const okCount = _resultAll.filter(e => e.ok).length;
|
||||
|
||||
// Filter counts
|
||||
document.getElementById('filter-cnt-errores').textContent = `(${errCount})`;
|
||||
document.getElementById('filter-cnt-enviados').textContent = `(${okCount})`;
|
||||
document.getElementById('filter-cnt-todos').textContent = `(${_resultAll.length})`;
|
||||
|
||||
// Default filter: errores si hay, si no todos
|
||||
const defaultFilter = errCount > 0 ? 'errores' : 'todos';
|
||||
setResultFilter(defaultFilter);
|
||||
|
||||
document.getElementById('results-section').classList.remove('hidden');
|
||||
|
||||
showToast(
|
||||
errCount === 0 ? `Envío completado · ${okCount} registros OK` : `${errCount} fallo(s) · ${okCount} OK`,
|
||||
errCount === 0 ? 'success' : 'warning'
|
||||
);
|
||||
}
|
||||
|
||||
// ── Filtro de resultados ─────────────────────────────────────────────────────
|
||||
function setResultFilter(filter) {
|
||||
_activeFilter = filter;
|
||||
|
||||
// Actualizar estilos de botones
|
||||
const configs = {
|
||||
errores: { active: 'bg-white shadow text-red-600', inactive: 'text-gray-500 hover:text-gray-700' },
|
||||
enviados: { active: 'bg-white shadow text-green-600', inactive: 'text-gray-500 hover:text-gray-700' },
|
||||
todos: { active: 'bg-white shadow text-gray-700', inactive: 'text-gray-500 hover:text-gray-700' },
|
||||
};
|
||||
['errores','enviados','todos'].forEach(f => {
|
||||
const btn = document.getElementById(`filter-btn-${f}`);
|
||||
const base = 'px-3 py-1 text-xs font-medium rounded-md transition-all';
|
||||
btn.className = `${base} ${f === filter ? configs[f].active : configs[f].inactive}`;
|
||||
});
|
||||
|
||||
// Filtrar items
|
||||
let items;
|
||||
if (filter === 'errores') items = _resultAll.filter(e => !e.ok);
|
||||
else if (filter === 'enviados') items = _resultAll.filter(e => e.ok);
|
||||
else items = _resultAll;
|
||||
|
||||
const listEl = document.getElementById('results-list');
|
||||
|
||||
if (items.length === 0) {
|
||||
const msg = filter === 'errores' ? 'Sin errores — todo enviado correctamente' :
|
||||
filter === 'enviados' ? 'No hay registros enviados' : 'Sin resultados';
|
||||
listEl.innerHTML = `<div class="p-8 text-center text-gray-400 text-sm"><i class="fas ${filter==='errores'?'fa-check-circle text-green-400':'fa-inbox'} text-2xl mb-2 block"></i>${msg}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
listEl.innerHTML = items.map(({ item, step: s, idx, ok }) => {
|
||||
let label1 = '', label2 = '', dtype = s.tipo, did = '';
|
||||
|
||||
if (s.tipo === 'tercero') {
|
||||
label1 = item.doc || '—';
|
||||
label2 = item.nombre || '—';
|
||||
did = escHtml(String(item.codigo || ''));
|
||||
} else {
|
||||
label1 = item.factura || '—';
|
||||
label2 = `Pac. ${item.paciente||'—'} · ${item.examenes||0} examen(es)`;
|
||||
did = escHtml(String(item.idrecepcion || 0));
|
||||
}
|
||||
|
||||
const msg = (item.msg || '').trim();
|
||||
|
||||
if (ok) {
|
||||
// Fila exitosa
|
||||
return `
|
||||
<div data-resend-row class="px-5 py-3 flex items-start gap-3 bg-red-50 border-b border-red-100 last:border-0">
|
||||
<span class="text-red-400 mt-0.5 shrink-0 text-base fail-status-icon"><i class="fas fa-times-circle"></i></span>
|
||||
<span class="${_COLOR_BADGE[s.color]} text-xs font-medium px-2 py-0.5 rounded shrink-0 mt-0.5">${s.label}</span>
|
||||
<span class="font-mono text-gray-700 text-xs w-28 shrink-0 mt-0.5 truncate">${escHtml(label1)}</span>
|
||||
<span class="text-gray-600 text-xs flex-1 mt-0.5">${escHtml(label2)}</span>
|
||||
<span class="text-red-500 text-xs max-w-[200px] truncate mt-0.5 fail-msg" title="${escHtml(item.msg||'')}">${escHtml((item.msg||'Sin detalle').slice(0,70))}</span>
|
||||
<div class="px-5 py-3 flex items-center gap-3 hover:bg-gray-50 transition-colors">
|
||||
<span class="text-green-500 shrink-0 text-base"><i class="fas fa-check-circle"></i></span>
|
||||
<span class="${_BADGE[s.color]} text-xs font-semibold px-2 py-0.5 rounded shrink-0">
|
||||
<i class="fas ${s.icon} mr-1 opacity-70 text-xs"></i>${s.label}
|
||||
</span>
|
||||
<span class="font-mono text-gray-800 text-xs w-28 shrink-0 truncate" title="${escHtml(label1)}">${escHtml(label1)}</span>
|
||||
<span class="text-gray-600 text-xs flex-1 truncate">${escHtml(label2)}</span>
|
||||
<span class="text-green-600 text-xs max-w-[220px] truncate shrink-0" title="${escHtml(msg)}">${escHtml(msg.slice(0,60))}</span>
|
||||
</div>`;
|
||||
} else {
|
||||
// Fila con error + botón reenviar
|
||||
return `
|
||||
<div data-resend-row class="px-5 py-3 flex items-start gap-3 bg-red-50 hover:bg-red-100 transition-colors border-l-4 border-red-400">
|
||||
<span class="text-red-400 shrink-0 text-base mt-0.5 fail-status-icon"><i class="fas fa-times-circle"></i></span>
|
||||
<span class="${_BADGE[s.color]} text-xs font-semibold px-2 py-0.5 rounded shrink-0 mt-0.5">
|
||||
<i class="fas ${s.icon} mr-1 opacity-70 text-xs"></i>${s.label}
|
||||
</span>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<span class="font-mono text-gray-800 text-xs font-semibold">${escHtml(label1)}</span>
|
||||
<span class="text-gray-600 text-xs truncate">${escHtml(label2)}</span>
|
||||
</div>
|
||||
${msg ? `<div class="mt-1 text-xs text-red-600 break-words fail-msg" title="${escHtml(msg)}">${escHtml(msg.slice(0,120))}</div>` : ''}
|
||||
</div>
|
||||
<button id="rbtn-${idx}"
|
||||
data-tipo="${dtype}" data-id="${did}"
|
||||
onclick="reenviarItem(this)"
|
||||
class="shrink-0 flex items-center gap-1.5 px-2.5 py-1.5 bg-white border border-gray-200 hover:bg-blue-50 hover:border-blue-300 text-xs text-gray-600 hover:text-blue-700 rounded-lg transition-colors font-medium">
|
||||
class="shrink-0 flex items-center gap-1.5 px-2.5 py-1.5 bg-white border border-gray-200 hover:bg-blue-50 hover:border-blue-300 text-xs text-gray-600 hover:text-blue-700 rounded-lg transition-colors font-medium mt-0.5">
|
||||
<i class="fas fa-redo-alt text-xs"></i> Reenviar
|
||||
</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
failPanel.classList.remove('hidden');
|
||||
} else {
|
||||
failPanel.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Panel exitosos
|
||||
const okPanel = document.getElementById('successes-panel');
|
||||
const okBody = document.getElementById('successes-body');
|
||||
if (successes.length > 0) {
|
||||
document.getElementById('successes-title').textContent = `Enviados correctamente (${successes.length})`;
|
||||
okBody.innerHTML = successes.map(({ item, step: s }) => {
|
||||
let label1 = '', label2 = '';
|
||||
if (s.tipo === 'tercero') { label1 = item.doc||''; label2 = item.nombre||''; }
|
||||
else { label1 = item.factura||''; label2 = `Pac.${item.paciente||''} · ${item.examenes||0} exam.`; }
|
||||
return `
|
||||
<div class="px-5 py-2.5 flex items-center gap-3 border-b border-green-50 last:border-0">
|
||||
<span class="text-green-500 shrink-0"><i class="fas fa-check-circle"></i></span>
|
||||
<span class="${_COLOR_BADGE[s.color]} text-xs font-medium px-2 py-0.5 rounded shrink-0">${s.label}</span>
|
||||
<span class="font-mono text-gray-700 text-xs w-28 shrink-0 truncate">${escHtml(label1)}</span>
|
||||
<span class="text-gray-500 text-xs flex-1 truncate">${escHtml(label2)}</span>
|
||||
<span class="text-green-600 text-xs truncate max-w-xs">${escHtml((item.msg||'').slice(0,60))}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
okPanel.classList.remove('hidden');
|
||||
} else {
|
||||
okPanel.classList.add('hidden');
|
||||
}
|
||||
|
||||
document.getElementById('results-section').classList.remove('hidden');
|
||||
const totalErr = failures.length;
|
||||
showToast(
|
||||
totalErr === 0 ? `Envío completado · ${successes.length} registros OK` : `${totalErr} fallo(s) · ${successes.length} OK`,
|
||||
totalErr === 0 ? 'success' : 'warning'
|
||||
);
|
||||
}
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// ── Reenviar item individual ─────────────────────────────────────────────────
|
||||
@@ -596,20 +649,28 @@ async function reenviarItem(btn) {
|
||||
const data = await resp.json();
|
||||
|
||||
if (data.success) {
|
||||
row.classList.remove('bg-red-50');
|
||||
row.classList.add('bg-green-50');
|
||||
// Marcar la fila como resuelta visualmente
|
||||
row.classList.remove('bg-red-50','bg-red-100','border-l-4','border-red-400');
|
||||
row.classList.add('bg-green-50','border-l-4','border-green-400');
|
||||
row.removeAttribute('data-resend-row');
|
||||
const icon = row.querySelector('.fail-status-icon');
|
||||
if (icon) icon.innerHTML = '<i class="fas fa-check-circle text-green-500"></i>';
|
||||
const msgEl = row.querySelector('.fail-msg');
|
||||
if (msgEl) { msgEl.textContent = (data.message||'OK').slice(0,60); msgEl.className = 'text-green-600 text-xs max-w-[200px] truncate mt-0.5'; }
|
||||
if (msgEl) { msgEl.textContent = (data.message||'OK enviado').slice(0,120); msgEl.className = 'mt-1 text-xs text-green-700'; }
|
||||
btn.innerHTML = '<i class="fas fa-check text-green-500 text-xs"></i> OK';
|
||||
btn.className = 'shrink-0 flex items-center gap-1.5 px-2.5 py-1.5 bg-green-50 border border-green-200 text-xs text-green-600 rounded-lg font-medium cursor-default';
|
||||
btn.className = 'shrink-0 flex items-center gap-1.5 px-2.5 py-1.5 bg-green-50 border border-green-200 text-xs text-green-700 rounded-lg font-medium cursor-default mt-0.5';
|
||||
showToast('Reenviado correctamente', 'success');
|
||||
} else {
|
||||
btn.innerHTML = origHtml;
|
||||
btn.disabled = false;
|
||||
const msgEl = row.querySelector('.fail-msg');
|
||||
if (msgEl) msgEl.textContent = (data.message||'Error').slice(0,70);
|
||||
if (msgEl) msgEl.textContent = (data.message||'Error').slice(0,120);
|
||||
else {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'mt-1 text-xs text-red-600 break-words fail-msg';
|
||||
div.textContent = (data.message||'Error').slice(0,120);
|
||||
row.querySelector('.flex-1').appendChild(div);
|
||||
}
|
||||
showToast(data.message || 'Error al reenviar', 'error');
|
||||
}
|
||||
} catch(e) {
|
||||
@@ -620,20 +681,6 @@ async function reenviarItem(btn) {
|
||||
}
|
||||
|
||||
// ── Helpers UI ───────────────────────────────────────────────────────────────
|
||||
function togglePanel(bodyId, chevId) {
|
||||
const body = document.getElementById(bodyId);
|
||||
const chev = document.getElementById(chevId);
|
||||
const hidden = body.classList.contains('hidden');
|
||||
body.classList.toggle('hidden', !hidden);
|
||||
if (chev) {
|
||||
if (hidden) {
|
||||
chev.classList.replace('fa-chevron-right','fa-chevron-down');
|
||||
} else {
|
||||
chev.classList.replace('fa-chevron-down','fa-chevron-right');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSubRow(id, chevId) {
|
||||
const div = document.getElementById(id);
|
||||
const chev = document.getElementById(chevId);
|
||||
|
||||
Reference in New Issue
Block a user