feat: enrich sync log with exams, diagnosis, EPS and valor per patient

- scheduler.py: after sync_todos(), inject diagnostico, nit_empresa,
  valor_total and examenes_det (cups/nombre/precio) into each detalle entry
- whatsapp_sync.py: guardar_sync_log stores full enriched fields in detalle_json
- envios_erp.html: expanded row shows per-patient card with Dx, EPS, valor
  and a CUPS/examen/precio table — scrollable, max-h-96

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-17 09:52:08 -05:00
co-authored by Claude Sonnet 4.6
parent e044f21e3e
commit cd63683869
3 changed files with 79 additions and 19 deletions
+15
View File
@@ -167,6 +167,21 @@ async def sync_recientes(ventana_min: int = 2) -> dict:
resultado = await sync_todos(recientes, ingest_url, wa_key, timeout,
modo="upsert", examenes_map=examenes_map)
# Enriquecer detalle con exámenes, diagnóstico, empresa y valor para el log
for entry in resultado.get("detalle", []):
doc = entry.get("doc", "")
exams = examenes_map.get(doc, [])
if exams:
first = exams[0]
entry["diagnostico_cod"] = first.get("diagnostico_cod", "")
entry["diagnostico_nombre"] = first.get("diagnostico_nombre", "")
entry["nit_empresa"] = first.get("nit_empresa", "")
entry["valor_total"] = first.get("valor_total")
entry["examenes_det"] = [
{"cups": e.get("cups", ""), "nombre": e.get("nombre", ""), "precio": e.get("precio")}
for e in exams
]
if resultado["total"] > 0:
guardar_sync_log(resultado, None, origen="scheduler", modo="upsert")
+11 -3
View File
@@ -31,9 +31,17 @@ def guardar_sync_log(
detalle_json = None
if detalle_raw:
detalle_json = json.dumps(
[{"doc": d["doc"], "nombre": d["nombre"], "action": d.get("action", ""),
"examenes": d.get("examenes_guardados", 0)}
for d in detalle_raw],
[{
"doc": d["doc"],
"nombre": d["nombre"],
"action": d.get("action", ""),
"examenes_cnt": d.get("examenes_guardados", 0),
"diagnostico_cod": d.get("diagnostico_cod", ""),
"diagnostico_nombre": d.get("diagnostico_nombre", ""),
"nit_empresa": d.get("nit_empresa", ""),
"valor_total": d.get("valor_total"),
"examenes_det": d.get("examenes_det", []),
} for d in detalle_raw],
ensure_ascii=False,
)
+53 -16
View File
@@ -254,6 +254,55 @@ async function cargarHistorialScheduler() {
const errDet = r.errores_det ? JSON.parse(r.errores_det) : [];
const pacientes = r.detalle_json ? JSON.parse(r.detalle_json) : [];
const rowId = 'hist-det-' + i;
const detHtml = pacientes.map((p, pi) => {
const badge = p.action === 'created' ? '<span class="bg-blue-100 text-blue-700 px-1.5 py-0.5 rounded">Nuevo</span>'
: p.action === 'updated' ? '<span class="bg-green-100 text-green-700 px-1.5 py-0.5 rounded">Actualizado</span>'
: p.action === 'skipped' ? '<span class="bg-gray-100 text-gray-400 px-1.5 py-0.5 rounded">Omitido</span>'
: `<span class="bg-red-100 text-red-600 px-1.5 py-0.5 rounded">${p.action}</span>`;
const diag = p.diagnostico_cod
? `<span class="text-orange-600">${p.diagnostico_cod}</span>${p.diagnostico_nombre ? ' · ' + p.diagnostico_nombre : ''}`
: '<span class="text-gray-300">Sin diagnóstico</span>';
const empresa = p.nit_empresa
? `<span class="text-teal-600">${p.nit_empresa}</span>`
: '<span class="text-gray-300">Sin empresa</span>';
const valor = p.valor_total > 0
? `<span class="text-yellow-700">$${Number(p.valor_total).toLocaleString('es-CO')}</span>`
: '';
const exams = (p.examenes_det || []).map(e =>
`<tr class="border-b border-gray-50">
<td class="py-0.5 pr-3 font-mono text-purple-600">${e.cups || '—'}</td>
<td class="py-0.5 pr-3 text-gray-600">${e.nombre || '—'}</td>
<td class="py-0.5 text-right text-gray-500">${e.precio != null ? '$' + Number(e.precio).toLocaleString('es-CO') : '—'}</td>
</tr>`
).join('');
return `<div class="mb-3 border border-gray-100 rounded-lg overflow-hidden">
<div class="flex items-center gap-3 px-3 py-2 bg-gray-50 border-b border-gray-100">
<span class="text-gray-400 font-mono text-xs">${p.doc}</span>
<span class="font-medium text-gray-700 flex-1">${p.nombre}</span>
${badge}
</div>
<div class="px-3 py-1.5 flex flex-wrap gap-x-4 gap-y-0.5 text-xs border-b border-gray-50">
<span>Dx: ${diag}</span>
<span>EPS: ${empresa}</span>
${valor ? `<span>Total: ${valor}</span>` : ''}
</div>
${exams ? `<table class="w-full text-xs px-3">
<thead><tr class="text-gray-400 border-b border-gray-100">
<th class="py-1 pr-3 text-left font-medium px-3">CUPS</th>
<th class="py-1 pr-3 text-left font-medium">Examen</th>
<th class="py-1 text-right font-medium pr-3">Precio</th>
</tr></thead>
<tbody class="px-3">${exams}</tbody>
</table>` : '<p class="text-xs text-gray-300 px-3 py-1">Sin exámenes registrados</p>'}
</div>`;
}).join('');
return `
<tr class="hover:bg-gray-50">
<td class="py-2 pr-4 text-gray-500 whitespace-nowrap font-mono text-xs">${fecha}</td>
@@ -273,22 +322,10 @@ async function cargarHistorialScheduler() {
</td>
</tr>
${pacientes.length ? `
<tr id="${rowId}" class="hidden bg-gray-50">
<td colspan="7" class="px-4 pb-3 pt-1">
<div class="text-xs text-gray-500 space-y-0.5 max-h-48 overflow-y-auto">
${pacientes.map(p => {
const badge = p.action === 'created' ? '<span class="text-blue-600">Nuevo</span>'
: p.action === 'updated' ? '<span class="text-green-600">Actualizado</span>'
: p.action === 'skipped' ? '<span class="text-gray-400">Omitido</span>'
: `<span class="text-red-500">${p.action}</span>`;
const exBadge = p.examenes > 0
? `<span class="text-purple-500">${p.examenes} exám.</span>` : '';
return `<div class="flex items-center gap-3 py-0.5 border-b border-gray-100">
<span class="text-gray-400 w-28 shrink-0">${p.doc}</span>
<span class="flex-1 truncate">${p.nombre}</span>
${badge} ${exBadge}
</div>`;
}).join('')}
<tr id="${rowId}" class="hidden">
<td colspan="7" class="px-3 pb-4 pt-2">
<div class="max-h-96 overflow-y-auto space-y-2 text-xs">
${detHtml}
</div>
</td>
</tr>` : ''}`;