The second definition (for RIPS 2.0 transacción grouping) was never called
but silently overrode the CMXC ventas grouper, causing grupos_vta to always
be empty (values were dicts, not lists, so isinstance(v, list) was False).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
codigoPrefijo in ventas JSON must be the actual Firebird PREFIJO
(e.g. "CMXC"), not the prefijo_tns_default config value which is
intended for RDA/transaccion. Falls back to default_prefijo if row
has no PREFIJO.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
HORAINICIORECEPCION is a TIME field that can be NULL or outside the
2-minute window even when FECHA_RECEPCION matches, causing patients
to sync without their exams. Using the same FECHA_RECEPCION filter
for both queries ensures consistency.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A single CMXC invoice (e.g. CMXC-307) can have multiple receptions.
Previously each reception was sent as a separate ventas, causing TNS
to accept the first and reject the rest with "ya existe" — leaving
only 1/N of the services registered.
Now agrupar_por_factura groups all receptions sharing the same
NUM_FACTURA into one ventas JSON with all their services in
detallePedido. One factura = one send.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Las facturas de venta de contratos externos (ej. CMXC) deben ir a nombre
de la empresa referente (NIT sin dígito de verificación), no del paciente.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El filtro Python era frágil: si HORAINICIORECEPCION era NULL o tenía formato inesperado, los pacientes no se capturaban. Ahora el filtro de ventana de tiempo va directo en Firebird usando DATEADD sobre FECHA_RECEPCION, que siempre está poblado.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- whatsapp_sync: send patients concurrently (asyncio.gather + semaphore=10) instead of sequentially — eliminates long waits with many patients
- envios_erp: auto-refresh historial every 60s when scheduler tab is active
- main: wrap scheduler job to catch exceptions and log them to sync_wa_log so failures are visible in historial
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PRECIO now falls back to catalog price (TARIFA) when 0/null
- JOIN EMPRESA + EMPRESA_SUB + TARIFA in exam query
- NOM_EMPRESA propagated to log and historial display
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- sync_paciente: catch JSONDecodeError separately and include HTTP status
+ first 300 chars of raw response so PHP errors are visible in the log
- envios_erp.html: Errores column shows count only (not all inline messages)
— error detail is already visible in the expandable Ver N row
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- Add detalle_json column to sync_wa_log via migration
- guardar_sync_log() now stores doc, nombre, action, examenes count per patient
- Historial table shows "Ver N" button that expands a row with patient list,
action badge (Nuevo/Actualizado/Omitido) and exam count per patient
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sync_wa_log.user_id has a FK to users; passing 0 fails since no user
with id=0 exists. Scheduler runs without a user context, so use NULL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PRECIO and VALORTOTAL are Decimal from fdb driver; convert via _safe_num()
- HORAINICIORECEPCION is a datetime object; use _fmt_hora_str() for HH:MM:SS
- _parse_hora() now handles datetime objects directly (no str() roundtrip)
- Same fixes applied to pacientes.py exam builder
- sync-now endpoint catches exceptions and returns traceback as JSON
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FECHA_RECEPCION is a TIMESTAMP field, so comparing directly with
CURRENT_DATE (DATE) always returns 0 rows in Firebird. Cast to DATE first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- POST /envios/erp/sync-now?ventana_min=N calls sync_recientes with configurable window
- sync_recientes() now returns a result dict (ok, total, created, updated, skipped, errores, detalle)
- ERP tab shows a "Prueba manual" card with ventana selector (2m / 30m / 2h / hoy) and result breakdown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Hub /envios/tns con 5 sub-tabs (Terceros, Transacción RIPS, Facturas Venta, Prueba RDA, Automatización) via iframe lazy
- Hub /envios/erp con sub-tabs Pacientes y Sync Automático (historial scheduler)
- Modo embed (?embed=1) en base.html para cargar páginas sin sidebar/header dentro de iframes
- Sidebar simplificado: 6 ítems individuales reemplazados por módulos TNS y ERP Lab
- Scheduler y endpoint /pacientes/examenes ahora incluyen médico ordenante (DOCIDMEDICO), empresa/EPS (NIT_EMPRESA) y valor total (VALORTOTAL) desde Firebird
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- generar_factura_venta en json_generator.py con mapeo completo del JSON
- Queries default tipo 'ventas' (por fecha y por número) filtradas a contrato 040
- Ruta /ventas con preview, send-one y send masivo
- Template ventas.html con tabla, resumen y modal JSON
- Link en navegación lateral
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Revertir el uso de COD_ESPECIALIDAD desde la BD; la especialidad
siempre debe venir del valor configurado (especialidad_default).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
La especialidad ahora toma el valor del campo COD_ESPECIALIDAD de la tabla
MEDICO (ya presente en todos los SELECTs), con fallback al default de config.
El profesional sigue viniendo de configuración en todos los flujos.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TNS API (C#) expects System.Char for tipoDocumento; "PE" and "PT" caused
400 validation errors. Mapped both to "E" (Cédula Extranjería).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Agrega columna sin_contrato en tabla contratos (migration automatica)
- Contrato 040 marcado excluir=0, sin_contrato=1 en seed
- load_sin_contrato_set() retorna contratos que deben ir con null
- generar_rda_paciente acepta sin_contrato_set y fuerza numeroContrato=null
- Automation paso 2 y 3 pasan sin_contrato_set al generador
- Pagina contratos: columna y toggle "Sin contrato", checkbox en modal editar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Agrega paso 3 en automatización: envía RDA desde PRESSERV_DIAN (prefijos RCXC/SC) para pacientes EPS con PS_NUM IS NOT NULL
- Filtra paso 2 con PS_NUM IS NULL para separar particulares/empresas de EPS
- Elimina parámetro copago y función _calcular_descuento_pct (código muerto)
- Limpia referencia a d.copago en automation.html (UI paso 3)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El check `"012".lstrip("0") == "12"` causaba false positive marcando
contratos EPS como particulares. Ahora la heurística solo aplica si el
contrato no está en la tabla contratos; si está, el tipo_usuario del mapa
tiene precedencia y también controla es_particular.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Agrega tabla `contratos` en SQLite sembrada con los 17 contratos del
laboratorio. `generar_rda_paciente` consulta el mapa contrato→tipoUsuario
como primera prioridad antes de derivar de TIPOUSU/TIPOUSUSISPRO.
Incluye CRUD en /contratos con UI en sidebar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Nueva tabla sync_wa_log (total, created, skipped, updated, errores, origen, modo)
- Log guardado en /pacientes/sync-all, /terceros/send y /automation/run
- Ruta GET /pacientes/historial devuelve últimos 100 registros
- UI: tabla de historial con origen, modo, conteos y detalle de errores
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sync_todos() ahora acepta modo='insertar'|'upsert'. La ruta /pacientes/sync-all
usa insertar: skippea cédulas que ya están en lab_pacientes. Los hooks
automáticos (terceros/automation) siguen en upsert.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Nuevo servicio whatsapp_sync.py: mapeo de campos RIPS→lab_pacientes
y cliente HTTP para /api/lab/ingest_paciente.php
- Nueva ruta /pacientes con sync masiva por rango de fechas o todos
- Hook en /terceros/send y /automation/run: sync silencioso a WhatsApp
después de cada envío a TNS
- Config: campos whatsapp_url y whatsapp_api_key + botón probar conexión
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Joins TARIFA using EMPRESA.TARIFA to get the base price per exam,
then calculates descuento = round((sum(TARIFA) - sum(PRECIO)) / sum(TARIFA) * 100)
and sends it as integer percentage in the RDA header encabezado.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pulls r.VALORDESC in all SQL queries (automation, test_rda, queries
defaults) and maps it to the top-level descuento field in the
RdaPaciente JSON encabezado.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LEFT JOIN ITEM on COD_EXAMEN to get CUPS_DETALLE. Falls back to
COD_EXAMEN if CUPS is empty. Applied to automation, test_rda, and
saved queries in SQLite.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- json_generator: remove valor entirely from detalle items (not even 0)
- transaccion: after fetching rows, filter by prefix if user typed full
factura like "LHXC03726" and query returns PREFIJO column
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>