From 32d4efafa5f46d7edee16ffd69f345bf531a232d Mon Sep 17 00:00:00 2001 From: AgustinRodriguez-Andes <63318331+agustin1996ra@users.noreply.github.com> Date: Thu, 15 Oct 2026 11:09:23 -0300 Subject: [PATCH 1/2] feat(recetas): integra RecetAr (ingesta, proxy de lecturas y suspension) --- modules/recetas/recetar-client.ts | 48 +++++++++++++++++++++++++++ modules/recetas/recetas.routes.ts | 44 ++++++++++++++++++++++-- modules/rup/controllers/rup.events.ts | 7 +++- 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 modules/recetas/recetar-client.ts diff --git a/modules/recetas/recetar-client.ts b/modules/recetas/recetar-client.ts new file mode 100644 index 0000000000..3a8d8cbc6f --- /dev/null +++ b/modules/recetas/recetar-client.ts @@ -0,0 +1,48 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const request = require('request'); + +export const isRecetarConfigured = (): boolean => !!process.env.RECETAR_API_URL; + +const baseUrl = (): string => (process.env.RECETAR_API_URL || '').replace(/\/$/, ''); +const token = (): string => process.env.RECETAR_APP_TOKEN || ''; + +function call(method: string, path: string, payload?: any, qs?: any): Promise { + return new Promise((resolve, reject) => { + request({ + method, + url: `${baseUrl()}${path}`, + qs, + body: payload, + json: true, + headers: { Authorization: `Bearer ${token()}` }, + }, (error: any, response: any, body: any) => { + if (error) { + return reject(error); + } + if (response && response.statusCode >= 400) { + return reject(new Error(`RecetAr respondio ${response.statusCode}`)); + } + resolve(body); + }); + }); +} + +const unwrap = (body: any): any[] => (body && body.data) ? body.data : body; + +export const recetarClient = { + ingest(dataReceta: Record): Promise { + return call('POST', '/modules/recetas', dataReceta); + }, + + async getRecetas(params: Record): Promise { + return unwrap(await call('GET', '/modules/recetas/filtros', undefined, params)); + }, + + async getRecetasPorPrestacion(idPrestacion: string): Promise { + return unwrap(await call('GET', `/modules/recetas/por-prestacion/${idPrestacion}`)); + }, + + patchReceta(body: Record): Promise { + return call('PATCH', '/modules/recetas', body); + }, +}; diff --git a/modules/recetas/recetas.routes.ts b/modules/recetas/recetas.routes.ts index ed62fbbcbc..f85595e9a7 100644 --- a/modules/recetas/recetas.routes.ts +++ b/modules/recetas/recetas.routes.ts @@ -4,6 +4,7 @@ import { Auth } from '../../auth/auth.class'; import { Receta } from './receta-schema'; import { buscarRecetas, getMotivosReceta, setEstadoDispensa, suspender, actualizarAppNotificada, cancelarDispensa, create, buscarRecetasPorProfesional, buscarRecetasConFiltros, verificarRecetaExistente } from './recetasController'; import { ParamsIncorrect } from './recetas.error'; +import { recetarClient, isRecetarConfigured } from './recetar-client'; class RecetasResource extends ResourceBase { Model = Receta; @@ -28,7 +29,8 @@ class RecetasResource extends ResourceBase { export const get = async (req, res) => { const result = await buscarRecetas(req); - res.json(result); + const extra = await fetchRecetar(req); + res.json(extra ? mergeResult(result, extra) : result); }; export const getMotivos = async (req, res) => { @@ -43,9 +45,37 @@ export const getByProfesional = async (req, res) => { export const getConFiltros = async (req, res) => { const result = await buscarRecetasConFiltros(req); - res.json(result); + const extra = await fetchRecetar(req); + res.json(extra ? mergeResult(result, extra) : result); }; +// Lee las recetas nuevas desde RecetAr (fuente de verdad) para mergear con las historicas de ANDES. +async function fetchRecetar(req: any): Promise { + if (!isRecetarConfigured()) { + return null; + } + const documento = req.query?.documento; + if (!documento) { + return null; + } + try { + const estado = req.query.estado || req.query.status; + return await recetarClient.getRecetas({ documento, estado, ambito: req.query.ambito }); + } catch (err) { + return null; + } +} + +function mergeResult(result: any, extra: any[]): any { + if (Array.isArray(result)) { + return [...extra, ...result]; + } + if (result && Array.isArray(result.data)) { + return { ...result, data: [...extra, ...result.data] }; + } + return result; +} + export const getVerificarReceta = async (req, res) => { try { const { documento, sexo, esMagistral, codigoFuente, codigoValor, conceptId } = req.query; @@ -79,6 +109,16 @@ export const patch = async (req, res) => { } else { switch (operacion) { case 'suspender': + if (isRecetarConfigured()) { + try { + const proxied = await recetarClient.patchReceta({ op: 'suspender', recetaId, motivo: req.body.motivo, observacion: req.body.observacion, sistema: app }); + if (proxied) { + return res.status(200).json(proxied); + } + } catch (err) { + // fallback a la coleccion local de ANDES (historicas) + } + } result = await suspender(recetaId, req); break; case 'dispensar': diff --git a/modules/rup/controllers/rup.events.ts b/modules/rup/controllers/rup.events.ts index 42ff8bc3be..b6a3b31894 100644 --- a/modules/rup/controllers/rup.events.ts +++ b/modules/rup/controllers/rup.events.ts @@ -1,5 +1,6 @@ import { EventCore } from '@andes/event-bus'; import { getProfesionActualizada, crearReceta } from '../../recetas/recetasController'; +import { recetarClient, isRecetarConfigured } from '../../recetas/recetar-client'; import * as moment from 'moment'; import { Receta } from '../../recetas/receta-schema'; import { rupEventsLog as logger } from './rup.events.log'; @@ -87,7 +88,11 @@ EventCore.on('prestacion:receta:create', async ({ prestacion, registro }) => { diagnostico: medicamento?.diagnostico || null, }; - await crearReceta(dataReceta, prestacion.createdBy); + if (isRecetarConfigured()) { + await recetarClient.ingest(dataReceta); + } else { + await crearReceta(dataReceta, prestacion.createdBy); + } } } catch (errorMedicamento) { logger.error('prestacion:receta:create', { idRegistro, medicamento }, errorMedicamento); From e6cd04973c984ad67666edc5b99dfb9c6a68e268 Mon Sep 17 00:00:00 2001 From: AgustinRodriguez-Andes <63318331+agustin1996ra@users.noreply.github.com> Date: Thu, 15 Oct 2026 11:18:45 -0300 Subject: [PATCH 2/2] feat(recetas): informe RUP mergea recetas nuevas de RecetAr por prestacion --- .../elementos-rup/receta-medica.component.ts | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/modules/descargas/informe-rup/elementos-rup/receta-medica.component.ts b/modules/descargas/informe-rup/elementos-rup/receta-medica.component.ts index eb385e1806..398a17a798 100644 --- a/modules/descargas/informe-rup/elementos-rup/receta-medica.component.ts +++ b/modules/descargas/informe-rup/elementos-rup/receta-medica.component.ts @@ -1,6 +1,7 @@ import { HTMLComponent } from '../../model/html-component.class'; import { generateBarcodeSVG } from '../../model/barcode'; import { Receta } from '../../../recetas/receta-schema'; +import { recetarClient, isRecetarConfigured } from '../../../recetas/recetar-client'; export class RecetaMedicaComponent extends HTMLComponent { @@ -119,10 +120,7 @@ del Ministerio de Salud de la Nación - RL-2025-24026558-APN-SSVEIYES#MS if (tieneReceta) { try { - const recetas: any[] = await Receta.find({ - idPrestacion: this.prestacion._id.toString(), - idRegistro: this.registro.id, - }).sort({ fechaRegistro: -1 }); + const recetas = await this.getRecetasCombinadas(); const recetaPorPrestacion = recetas.find(r => !['pendiente', 'eliminada'].includes(r.estadoActual?.tipo) @@ -134,6 +132,9 @@ del Ministerio de Salud de la Nación - RL-2025-24026558-APN-SSVEIYES#MS .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); } + if (!idReceta && recetaPorPrestacion?.idReceta) { + idReceta = recetaPorPrestacion.idReceta; + } } catch (error) { estadoReceta = null; } @@ -160,4 +161,23 @@ del Ministerio de Salud de la Nación - RL-2025-24026558-APN-SSVEIYES#MS barcodeSVG: generateBarcodeSVG(finalIdReceta, 'code128') }; } + + private async getRecetasCombinadas(): Promise { + const local: any[] = await Receta.find({ + idPrestacion: this.prestacion._id.toString(), + idRegistro: this.registro.id, + }).sort({ fechaRegistro: -1 }); + + if (!isRecetarConfigured()) { + return local; + } + + try { + const remotas: any[] = await recetarClient.getRecetasPorPrestacion(this.prestacion._id.toString()); + const filtradas = remotas.filter(r => !r.idRegistro || r.idRegistro === this.registro.id); + return [...local, ...filtradas]; + } catch (error) { + return local; + } + } }