feat: paso 4 ventas en automation + fixes varios

- Automation: agrega Paso 4 (Ventas/Crear) con fetch _SQL_VENTAS,
  filtro por excluded_ventas_set, loop de envío y log de actividad
- Automation: _guardar_envio ahora almacena idrecepcion y contrato
  para que /ventas reconozca registros enviados por automation
- Contratos: corrige NameError en toggle_excluir (faltaba request: Request)
- Ventas: agrega check if not q en send_one y send para evitar TypeError
- Ventas HTML: agrega columna Contrato en la tabla de facturas
- json_generator: normaliza etnia >2 chars a '99' (ej: 999 → 99)
- json_generator: agrega campo ciudadExp usando el mismo cod_ciudad

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-11 14:29:39 -05:00
co-authored by Claude Sonnet 4.6
parent 21eb99d18d
commit 73062af8bc
7 changed files with 249 additions and 68 deletions
+73 -3
View File
@@ -48,7 +48,7 @@
<span id="preview-fecha" class="text-sm text-gray-500"></span>
</div>
<div class="p-6 space-y-4">
<div class="grid grid-cols-4 gap-4">
<div class="grid grid-cols-5 gap-4">
<div class="bg-blue-50 rounded-lg p-4 text-center">
<div id="cnt-pacientes" class="text-3xl font-bold text-blue-600">0</div>
<div class="text-sm text-blue-700 mt-1">Pacientes</div>
@@ -66,6 +66,11 @@
<div class="text-sm text-orange-700 mt-1">Pre-servicios</div>
<div class="text-xs text-orange-400">RCXC / SC</div>
</div>
<div class="bg-emerald-50 rounded-lg p-4 text-center">
<div id="cnt-ventas" class="text-3xl font-bold text-emerald-600">0</div>
<div class="text-sm text-emerald-700 mt-1">Ventas</div>
<div class="text-xs text-emerald-400">Ventas/Crear</div>
</div>
</div>
<div>
<p class="text-xs font-medium text-gray-600 mb-2">Pacientes — clic para ver sus recepciones:</p>
@@ -75,6 +80,10 @@
<p class="text-xs font-medium text-orange-600 mb-2 mt-2">Pre-servicios RCXC / SC — clic para ver JSON:</p>
<div id="preview-ps-table" class="border border-orange-100 rounded-lg overflow-hidden text-sm"></div>
</div>
<div id="ventas-section" class="hidden">
<p class="text-xs font-medium text-emerald-600 mb-2 mt-2">Facturas Venta — clic para ver JSON:</p>
<div id="preview-vta-table" class="border border-emerald-100 rounded-lg overflow-hidden text-sm"></div>
</div>
</div>
</div>
@@ -110,6 +119,16 @@
</div>
<div id="p3-detalle" class="divide-y divide-gray-100 hidden"></div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
<div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-8 h-8 bg-emerald-500 rounded-full flex items-center justify-center text-white text-sm font-bold">4</div>
<span class="font-semibold text-gray-800">Facturas Venta</span>
</div>
<span id="p4-badge" class="text-sm text-gray-400">Esperando...</span>
</div>
<div id="p4-detalle" class="divide-y divide-gray-100 hidden"></div>
</div>
</div>
</div>
@@ -133,7 +152,7 @@
<strong id="modal-fecha" class="text-blue-600"></strong>:
</p>
<div class="grid grid-cols-4 gap-3">
<div class="grid grid-cols-5 gap-3">
<div class="bg-blue-50 rounded-lg p-3 text-center">
<div id="modal-cnt-pac" class="text-2xl font-bold text-blue-600">0</div>
<div class="text-xs text-blue-700">Terceros</div>
@@ -154,6 +173,11 @@
<div class="text-xs text-orange-700">Pre-servicios</div>
<div class="text-xs text-orange-400">RCXC / SC</div>
</div>
<div class="bg-emerald-50 rounded-lg p-3 text-center">
<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">
@@ -214,6 +238,7 @@ async function previewAutomation() {
document.getElementById('cnt-recepciones').textContent = data.recepciones;
document.getElementById('cnt-examenes').textContent = data.examenes;
document.getElementById('cnt-preservicios').textContent = data.preservicios || 0;
document.getElementById('cnt-ventas').textContent = data.ventas || 0;
document.getElementById('preview-fecha').textContent = data.fecha;
const psTable = document.getElementById('preview-ps-table');
@@ -240,6 +265,31 @@ async function previewAutomation() {
psSection.classList.add('hidden');
}
const vtaTable = document.getElementById('preview-vta-table');
const vtaSection = document.getElementById('ventas-section');
if (data.ventas_preview && data.ventas_preview.length > 0) {
vtaTable.innerHTML = data.ventas_preview.map((vta, i) => {
const key = `vta-json-${i}`;
_rdaJsonStore[key] = vta.json;
return `
<div>
<div class="flex items-center gap-3 px-4 py-2.5 hover:bg-emerald-50 cursor-pointer select-none border-b border-emerald-100"
onclick="toggleVta('${key}', this)">
<i class="fas fa-chevron-right text-emerald-300 text-xs transition-transform duration-150" id="vtachev-${i}"></i>
<span class="font-mono text-emerald-700 font-medium w-32">${vta.factura}</span>
<span class="text-gray-500 w-24 text-xs">${vta.fecha}</span>
<span class="font-mono bg-emerald-100 text-emerald-800 px-1.5 py-0.5 rounded text-xs w-14 text-center">${vta.contrato || '—'}</span>
<span class="text-gray-600 flex-1 text-xs">Pac. ${vta.paciente}</span>
<span class="text-xs text-emerald-500">${vta.examenes} examen(es)</span>
</div>
<div id="${key}" class="hidden bg-gray-900 text-green-300 text-xs font-mono p-4 overflow-x-auto whitespace-pre border-b border-emerald-100"></div>
</div>`;
}).join('');
vtaSection.classList.remove('hidden');
} else {
vtaSection.classList.add('hidden');
}
const table = document.getElementById('preview-table');
if (data.pacientes === 0) {
table.innerHTML = '<div class="p-4 text-gray-400 text-center">No hay pacientes para esta fecha</div>';
@@ -297,6 +347,7 @@ function abrirModalConfirmar() {
document.getElementById('modal-cnt-rec').textContent = d.recepciones;
document.getElementById('modal-cnt-exa').textContent = d.examenes;
document.getElementById('modal-cnt-ps').textContent = d.preservicios || 0;
document.getElementById('modal-cnt-vta').textContent = d.ventas || 0;
document.getElementById('modal-lista-pac').innerHTML = d.pacientes_preview.map(p => `
<div class="flex items-center gap-3 px-3 py-1.5 border-b border-gray-50 text-xs last:border-0">
@@ -327,6 +378,8 @@ async function confirmarEnvio() {
document.getElementById('p2-badge').className = 'text-sm text-gray-400';
document.getElementById('p3-badge').textContent = 'Esperando...';
document.getElementById('p3-badge').className = 'text-sm text-gray-400';
document.getElementById('p4-badge').textContent = 'Esperando...';
document.getElementById('p4-badge').className = 'text-sm text-gray-400';
const contrato = document.getElementById('contrato-input').value.trim();
const form = new FormData();
@@ -364,8 +417,14 @@ async function confirmarEnvio() {
col2: `Pac. ${d.paciente}${d.examenes} examen(es)`,
col3: `${d.ok ? '✅' : '❌'} ${d.msg || ''}`, ok: d.ok,
})));
renderBadge('p4-badge', r.paso4_ventas.enviados, r.paso4_ventas.errores);
renderDetalle('p4-detalle', r.paso4_ventas.detalle.map(d => ({
col1: `${d.factura}`,
col2: `Pac. ${d.paciente}${d.examenes} examen(es)`,
col3: `${d.ok ? '✅' : '❌'} ${d.msg || ''}`, ok: d.ok,
})));
const totalErr = r.paso1_terceros.errores + r.paso2_rda.errores + r.paso3_preserv.errores;
const totalErr = r.paso1_terceros.errores + r.paso2_rda.errores + r.paso3_preserv.errores + r.paso4_ventas.errores;
showToast(totalErr === 0 ? 'Envío completado sin errores' : `Completado con ${totalErr} error(es)`,
totalErr === 0 ? 'success' : 'warning');
} catch (e) {
@@ -405,6 +464,17 @@ function togglePs(id, row) {
if (chev) chev.style.transform = hidden ? 'rotate(90deg)' : '';
}
function toggleVta(id, row) {
const div = document.getElementById(id);
const chev = row.querySelector('[id^="vtachev-"]');
const hidden = div.classList.contains('hidden');
if (hidden && _rdaJsonStore[id]) {
div.textContent = JSON.stringify(_rdaJsonStore[id], null, 2);
}
div.classList.toggle('hidden', !hidden);
if (chev) chev.style.transform = hidden ? 'rotate(90deg)' : '';
}
function renderBadge(id, enviados, errores) {
const el = document.getElementById(id);
if (errores === 0) {