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 58f8d8d324..ea19eb6e82 100644 --- a/modules/descargas/informe-rup/elementos-rup/receta-medica.component.ts +++ b/modules/descargas/informe-rup/elementos-rup/receta-medica.component.ts @@ -142,7 +142,7 @@ del Ministerio de Salud de la Nación - RL-2025-24026558-APN-SSVEIYES#MS this.data = { registro: this.registro, - esReceta: this.depth ? 1 : 0, // Si es 0 no muestra el código de barras + esReceta: 0, idReceta: finalIdReceta, estadoReceta, barcodeSVG: generateBarcodeSVG(finalIdReceta, 'code128') diff --git a/modules/descargas/recetas/recetas-body.ts b/modules/descargas/recetas/recetas-body.ts new file mode 100644 index 0000000000..241266f9c5 --- /dev/null +++ b/modules/descargas/recetas/recetas-body.ts @@ -0,0 +1,284 @@ +import { HTMLComponent } from '../model/html-component.class'; +import * as moment from 'moment'; +import { generateBarcodeSVG } from '../model/barcode'; +import { makeFsFirma } from '../../../core/tm/schemas/firmaProf'; +import { streamToBase64 } from '../../../core/tm/controller/file-storage'; +import { searchMatriculas } from '../../../core/tm/controller/profesional'; +import { Profesional } from '../../../core/tm/schemas/profesional'; + +export class RecetasBody extends HTMLComponent { + template = ` +
+ {{#each recetas}} +
+
+
+
+
Vigente Desde
+
{{fechaEmision}}
+
+
+
Fecha de Vencimiento
+
{{fechaVencimiento}}
+
+
+
+
+
+ + + + + + + +
+

RECETA MÉDICA

+
+
ESTADO RECETA.
+
{{estadoReceta}}
+
+
+
+ {{{barcodeSVG}}} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
{{nombre}}
+ {{#if esMagistral}}
FÓRMULA MAGISTRAL
{{/if}} + {{#if tratamientoProlongado}}
TRATAMIENTO PROLONGADO — Mes {{ordenTratamiento}} de {{totalTratamiento}}
{{/if}} +
Cantidad{{cantidad}}
Dosis diaria{{dosisDiaria}}
Diagnóstico{{diagnostico}}
Obra Social{{obraSocial}}
+ + {{#if notaMedica}} +
+
NOTA MÉDICA PARA EL PACIENTE
+

{{notaMedica}}

+
+ {{/if}} + {{#if firma}} +
+

Este documento ha sido firmado electronicamente por:

+ {{#if firma}}{{/if}} +
+

{{detalle}}

+ {{#if matriculas}}

{{{matriculas}}}

{{/if}} +
+ {{/if}} +
+
+ {{/each}} +
+ `; + + constructor( + public prestacion, + public paciente, + public organizacion, + public recetasData: any[], + public registroId: string | null + ) { + super(); + } + + public async process() { + + const recetas = []; + for (const receta of this.recetasData) { + const esInsumo = !!receta.insumo; + const fechaEmision = moment(receta.fechaRegistro).format('DD/MM/YYYY'); + const fechaVencimiento = moment(receta.fechaRegistro).add(30, 'days').format('DD/MM/YYYY'); + + let nombre = ''; + if (esInsumo) { + nombre = receta.insumo?.nombre || receta.insumo?.especificacion || ''; + } else { + if (receta.medicamento?.esMagistral) { + nombre = receta.medicamento?.magistral?.nombre || receta.medicamento?.nombre || ''; + } else { + nombre = receta.medicamento?.nombre || receta.medicamento?.concepto?.term || ''; + } + } + + let presentacion = ''; + if (esInsumo) { + presentacion = receta.insumo?.tipo || receta.insumo?.unidades || ''; + if (receta.insumo?.especificacion) { + presentacion = receta.insumo.especificacion; + } + } else { + const pres = receta.medicamento?.presentacion || ''; + const unidades = receta.medicamento?.unidades ? `${receta.medicamento.unidades} ` : ''; + presentacion = `${unidades}${pres}`.trim() || (pres || ''); + if (!presentacion && receta.medicamento?.presentacion) { + presentacion = receta.medicamento.presentacion; + } + } + if (!presentacion) { + presentacion = esInsumo ? (receta.insumo?.tipo || '-') : (receta.medicamento?.presentacion || '-'); + } + + let cantidad = ''; + if (esInsumo) { + const cant = receta.insumo?.cantidad; + const tipo = receta.insumo?.tipo || ''; + cantidad = cant ? `${cant} ${tipo}`.trim() : `${receta.insumo?.cantidad || ''}`.trim(); + if (!cantidad) { cantidad = '-'; } + if (receta.insumo?.cantidad) { + cantidad = `${receta.insumo.cantidad} envase(s)`; + if (receta.insumo?.unidades) { cantidad += ` de ${receta.insumo.unidades}`; } + } + } else { + const cantEnvases = receta.medicamento?.cantEnvases; + const cantidadUni = receta.medicamento?.cantidad; + const pres = receta.medicamento?.presentacion || 'comp'; + if (cantEnvases && cantidadUni) { + cantidad = `${cantEnvases} envase(s) de ${cantidadUni} ${pres}(s)`; + } else if (cantEnvases) { + cantidad = `${cantEnvases} envase(s)`; + } else { + cantidad = `${cantidadUni || ''} ${pres}`.trim() || '-'; + } + } + + // Dosis diaria + let dosisDiaria = '-'; + if (esInsumo) { + dosisDiaria = receta.insumo?.especificacion || receta.diagnostico?.term || '-'; + + if (receta.insumo?.cantidad) { + dosisDiaria = receta.insumo?.especificacion || '-'; + } + } else { + const dd = receta.medicamento?.dosisDiaria; + if (dd && (dd.dosis || dd.intervalo || dd.dias)) { + const parts = []; + if (dd.dosis) { parts.push(`${dd.dosis}`); } + if (dd.intervalo?.nombre) { parts.push(`cada ${dd.intervalo.nombre}`); } + if (dd.dias) { parts.push(`durante ${dd.dias} día(s)`); } + dosisDiaria = parts.join(' ') || '-'; + + if (dd.dosis && dd.intervalo?.nombre && dd.dias) { + dosisDiaria = `${dd.dosis} cada ${dd.intervalo.nombre} durante ${dd.dias} día(s)`; + } else if (dd.dosis && dd.intervalo?.nombre) { + dosisDiaria = `${dd.dosis} cada ${dd.intervalo.nombre}`; + } + } else { + dosisDiaria = '-'; + } + } + + const diagnostico = receta.diagnostico?.term || receta.diagnostico?.descripcion || '-'; + const obraSocial = receta.paciente?.obraSocial?.financiador || receta.paciente?.obraSocial?.nombre || this.prestacion?.paciente?.obraSocial?.financiador || 'sin obra social'; + const notaMedica = !esInsumo ? (receta.medicamento?.dosisDiaria?.notaMedica || null) : (receta.insumo?.especificacion || null); + const notaFinal = (!esInsumo && notaMedica) ? notaMedica : (esInsumo ? null : notaMedica); + const estadoRaw = receta.estadoActual?.tipo || 'vigente'; + const estadoReceta = estadoRaw.charAt(0).toUpperCase() + estadoRaw.slice(1).replace('-', ' '); + + // Tratamiento prolongado + const tratamientoProlongado = !esInsumo ? !!receta.medicamento?.tratamientoProlongado : !!receta.insumo?.tratamientoProlongado; + const ordenTratamiento = !esInsumo ? ((receta.medicamento?.ordenTratamiento ?? 0) + 1) : ((receta.insumo?.ordenTratamiento ?? 0) + 1); + const totalTratamiento = !esInsumo ? (receta.medicamento?.tiempoTratamiento?.id || receta.medicamento?.tiempoTratamiento?.nombre || '') : (receta.insumo?.tiempoTratamiento?.id || ''); + + const esMagistral = !esInsumo ? !!receta.medicamento?.esMagistral : receta.insumo?.tipo === 'magistral'; + + const idReceta = receta.idReceta || receta._id?.toString() || ''; + const barcodeSVG = idReceta ? generateBarcodeSVG(idReceta, 'code128') : ''; + + // Firma y formación del profesional (por receta, igual que informe-rup firma) + let firma = null; + let detalle = null; + let matriculas = null; + try { + const profData = receta.profesional; + const prof = profData?._id || profData?.id ? await Profesional.findOne({ _id: profData.id || profData._id }) as any : await Profesional.findOne({ documento: profData?.documento }) as any; + if (prof) { + firma = await this.getFirma(prof); + const mat = await this.getMatriculas(prof); + matriculas = mat; + detalle = prof.apellido + ', ' + prof.nombre; + } else if (profData) { + detalle = (profData.apellido || '') + ', ' + (profData.nombre || ''); + } + } catch (e) { + // silencioso, sin firma + } + + recetas.push({ + esInsumo, + idReceta, + barcodeSVG, + fechaEmision, + fechaVencimiento, + estadoReceta, + nombre, + presentacion, + cantidad, + dosisDiaria, + diagnostico, + obraSocial, + notaMedica: notaFinal, + esMagistral, + tratamientoProlongado, + ordenTratamiento, + totalTratamiento, + firma, + detalle, + matriculas + }); + } + + this.data = { + recetas + }; + } + + private async getFirma(profesional) { + try { + const FirmaSchema = makeFsFirma(); + const idProfesional = String(profesional.id || profesional._id); + const file = await FirmaSchema.findOne({ 'metadata.idProfesional': idProfesional }); + if (file && file._id) { + const stream = await FirmaSchema.readFile({ _id: file._id }); + const base64 = await streamToBase64(stream); + return base64; + } + } catch (e) {} + return null; + } + + private async getMatriculas(profesional) { + try { + const info = await searchMatriculas(profesional.id || profesional._id); + const grado = (info?.formacionGrado || []).map(e => `${e.nombre} MP ${e.numero}`); + const posgrado = (info?.formacionPosgrado || []).map(e => `${e.nombre} ME ${e.numero}`); + return [...grado, ...posgrado].join('
'); + } catch (e) { + return null; + } + } +} diff --git a/modules/descargas/recetas/recetas.ts b/modules/descargas/recetas/recetas.ts new file mode 100644 index 0000000000..ce2326aeb6 --- /dev/null +++ b/modules/descargas/recetas/recetas.ts @@ -0,0 +1,122 @@ +import { Types } from 'mongoose'; +import { InformePDF, getAssetsURL } from '../model/informe.class'; +import { Prestacion } from '../../rup/schemas/prestacion'; +import { Organizacion } from '../../../core/tm/schemas/organizacion'; +import { InformeRupHeader } from '../informe-rup/informe-header'; +import { InformeRupFooter } from '../informe-rup/informe-footer'; +import { RecetasBody } from './recetas-body'; +import { elementosRUPAsSet, fulfillPrestacion } from '../../rup/controllers/elementos-rup.controller'; +import { findByPaciente } from '../../rup/internacion/camas.controller'; +import { findById } from '../../../core-v2/mpi/paciente/paciente.controller'; +import { Receta } from '../../recetas/receta-schema'; +import { RecetaInsumo } from '../../recetas/recetasInsumos/receta-insumo.schema'; + +export class InformeRecetas extends InformePDF { + + constructor(private prestacionId: string | Types.ObjectId, private registroId: string | Types.ObjectId = null, private usuario: any, private snapshots: any = null, private recetasIds: string[] = null) { + super(); + } + + stylesUrl = [ + getAssetsURL('templates/rup/informes/sass/main.scss') + ]; + + public async process() { + const prestacion: any = await Prestacion.findById(this.prestacionId); + + if (!prestacion) { + throw new Error('Prestación no encontrada'); + } + + if (this.snapshots) { + prestacion.ejecucion.registros.forEach(r => { + if (this.snapshots[r.id]) { + r.valor = r.valor || {}; + r.valor.snapshot = this.snapshots[r.id]; + } + }); + } + + const paciente = await findById(prestacion.paciente.id); + const organizacion = await Organizacion.findById(prestacion.ejecucion.organizacion.id); + const elementosRUPSet = await elementosRUPAsSet(); + + await fulfillPrestacion(prestacion, elementosRUPSet); + + const cama = await this.getCamaInternacion(prestacion); + + // Obtener recetas asociadas + const recetas = await this.getRecetas(prestacion); + + this.header = new InformeRupHeader(prestacion, paciente, organizacion, cama); + this.body = new RecetasBody(prestacion, paciente, organizacion, recetas, this.registroId ? String(this.registroId) : null); + this.footer = new InformeRupFooter(prestacion, paciente, organizacion, this.usuario); + + await super.process(); + } + + private async getRecetas(prestacion: any) { + if (this.recetasIds && this.recetasIds.length) { + const ids = this.recetasIds.map(id => Types.ObjectId(id)); + const [medicamentos, insumos] = await Promise.all([ + Receta.find({ _id: { $in: ids } }).sort({ fechaRegistro: 1 }), + RecetaInsumo.find({ _id: { $in: ids } }).sort({ fechaRegistro: 1 }) + ]); + const all: any[] = [...medicamentos, ...insumos].sort((a: any, b: any) => new Date(a.fechaRegistro).getTime() - new Date(b.fechaRegistro).getTime()); + return all; + } + + const queryBase: any = { + idPrestacion: prestacion._id.toString() + }; + if (this.registroId) { + queryBase.idRegistro = String(this.registroId); + } + + const [recetasMed, recetasInsumo] = await Promise.all([ + Receta.find(queryBase).sort({ fechaRegistro: 1 }), + RecetaInsumo.find(queryBase).sort({ fechaRegistro: 1 }) + ]); + + let todas: any[] = [...recetasMed, ...recetasInsumo]; + + if (!this.registroId && todas.length > 1) { + const vigentesPendientes = todas.filter(receta => + ['vigente', 'pendiente'].includes(receta.estadoActual?.tipo) + ); + // Si hay al menos una vigente/pendiente, mostramos solo esas; sino mostramos todas (ej vencidas) + if (vigentesPendientes.length > 0) { + todas = vigentesPendientes; + } + } + + // Ordenar por fechaRegistro y por ordenTratamiento si existe + todas.sort((a: any, b: any) => { + const fechaA = new Date(a.fechaRegistro).getTime(); + const fechaB = new Date(b.fechaRegistro).getTime(); + if (fechaA !== fechaB) { return fechaA - fechaB; } + const ordenA = a.medicamento?.ordenTratamiento ?? a.insumo?.ordenTratamiento ?? 0; + const ordenB = b.medicamento?.ordenTratamiento ?? b.insumo?.ordenTratamiento ?? 0; + return ordenA - ordenB; + }); + + return todas; + } + + async getCamaInternacion(prestacion) { + if (prestacion.solicitud.ambitoOrigen === 'internacion') { + const org = prestacion.solicitud.organizacion; + const cama: any = await findByPaciente( + { organizacion: org, capa: 'medica', ambito: 'internacion' }, + prestacion.paciente.id, + prestacion.ejecucion.fecha + ); + if (cama) { + const sectores = cama.sectores || []; + cama.sectorName = [...sectores].reverse().map(s => s.nombre).join(', '); + return cama; + } + } + return null; + } +} diff --git a/modules/descargas/routes/descargas.ts b/modules/descargas/routes/descargas.ts index 7b0927c8e0..c5d670d6fc 100644 --- a/modules/descargas/routes/descargas.ts +++ b/modules/descargas/routes/descargas.ts @@ -22,6 +22,7 @@ import { laboratorioLog } from './../../rup/laboratorio.log'; import { Paciente } from './../../../core-v2/mpi'; import { FichaEpidemiologica } from '../ficha-epidemiologica/ficha'; import { Forms } from '../../forms/forms.schema'; +import { InformeRecetas } from '../recetas/recetas'; const router = express.Router(); @@ -265,6 +266,41 @@ router.post('/listadoTurnos', Auth.authenticate(), async (req: any, res, next) = }); +/** + * Descarga de recetas por prestación/registro o por ids de recetas. + * Genera una hoja por cada medicamento/insumo con barcode propio, + * obra social, nota médica, fórmula magistral y fechas emisión/vencimiento. + */ +router.get('/recetas/:idPrestacion/(:idRegistro)?', Auth.authenticate(), async (req: any, res, next) => { + try { + const idPrestacion = req.params.idPrestacion; + const idRegistro = req.params.idRegistro; + const recetasIds = req.query.recetasIds ? String(req.query.recetasIds).split(',').filter(Boolean) : null; + const informe = new InformeRecetas(idPrestacion, idRegistro, req.user, null, recetasIds); + const fileName = await informe.informe(); + return res.download(fileName); + } catch (err) { + return next(err); + } +}); + +router.post('/recetas', Auth.authenticate(), async (req: any, res, next) => { + try { + const idPrestacion = req.body.idPrestacion; + const idRegistro = req.body.idRegistro; + const recetasIds = req.body.recetasIds || req.body.recetas || null; + const snapshots = req.body.snapshots || null; + if (!idPrestacion && (!recetasIds || !recetasIds.length)) { + return next(new Error('Se requiere idPrestacion o recetasIds')); + } + const informe = new InformeRecetas(idPrestacion, idRegistro, req.user, snapshots, recetasIds); + const fileName = await informe.informe(); + return res.download(fileName); + } catch (err) { + return next(err); + } +}); + /** * Se usa POST para generar la descarga porque se envían datos * que van a ser parte del archivo diff --git a/modules/recetas/receta-schema.ts b/modules/recetas/receta-schema.ts index e9d1a9a35c..4ac78fc10f 100644 --- a/modules/recetas/receta-schema.ts +++ b/modules/recetas/receta-schema.ts @@ -222,6 +222,18 @@ recetaSchema.virtual('nombre').get(function (this: any) { return this.medicamento?.nombre || (this.medicamento?.esMagistral ? (this.medicamento?.magistral?.nombre || '') : (this.medicamento?.concepto?.term || '')); }); +recetaSchema.virtual('fechaVencimiento').get(function (this: any) { + if (!this.fechaRegistro) { return null; } + const fecha = new Date(this.fechaRegistro); + fecha.setDate(fecha.getDate() + 30); + return fecha; +}); + +recetaSchema.virtual('fechaEmision').get(function (this: any) { + return this.fechaRegistro; +}); + + recetaSchema.pre('save', function (next) { const receta: any = this; diff --git a/modules/recetas/recetasInsumos/receta-insumo.schema.ts b/modules/recetas/recetasInsumos/receta-insumo.schema.ts index db7d5ea3ef..225be5b797 100644 --- a/modules/recetas/recetasInsumos/receta-insumo.schema.ts +++ b/modules/recetas/recetasInsumos/receta-insumo.schema.ts @@ -172,6 +172,17 @@ recetaInsumoSchema.post('save', async (prescription: any) => { } }); +recetaInsumoSchema.virtual('fechaVencimiento').get(function (this: any) { + if (!this.fechaRegistro) { return null; } + const fecha = new Date(this.fechaRegistro); + fecha.setDate(fecha.getDate() + 30); + return fecha; +}); + +recetaInsumoSchema.virtual('fechaEmision').get(function (this: any) { + return this.fechaRegistro; +}); + recetaInsumoSchema.plugin(AuditPlugin); recetaInsumoSchema.index({