diff --git a/jobs/jobRecetaControl.ts b/jobs/jobRecetaControl.ts new file mode 100644 index 0000000000..7a6bc27e2e --- /dev/null +++ b/jobs/jobRecetaControl.ts @@ -0,0 +1,485 @@ +import * as moment from 'moment'; +import { Prestacion } from '../modules/rup/schemas/prestacion'; +import { Profesional } from '../core/tm/schemas/profesional'; +import { Receta } from '../modules/recetas/receta-schema'; +import { RecetaInsumo } from '../modules/recetas/recetasInsumos/receta-insumo.schema'; +import { RecetaControl } from '../modules/recetas/receta-control-schema'; +import { crearReceta, getProfesionActualizada } from '../modules/recetas/recetasController'; +import { crearRecetaInsumo } from '../modules/recetas/recetasInsumos/recetaInsumosController'; +import { generarCUIL } from '../core-v2/mpi/validacion/validacion.controller'; +import { jobsLog as logger } from '../modules/recetas/recetaLogs'; +import { userScheduler } from '../config.private'; +import { Auth } from '../auth/auth.class'; + +/** + * Job: generarRecetasFaltantes (Autosaneamiento de Recetas) + * + * Recorre la colección `recetaControl` buscando registros no completos + * (estados 'pendiente' o 'incompleto') que tengan menos de 3 intentos, + * reconstruye el contexto desde la Prestación RUP y genera las recetas faltantes + * (tanto para tratamientos prolongados como simples, medicamentos e insumos). + * + * En producción se ejecuta aproximadamente cada 10 minutos. + * + * Parametrización opcional por CLI: + * --desde YYYY-MM-DD[THH:mm] (opcional) + * --hasta YYYY-MM-DD[THH:mm] (opcional, default: ahora - 5 min) + * --horas N (opcional, default: sin límite, recorre todo) + * N (opcional posicional o --minutos, default: 5 min) + */ + +interface ParamsJob { + desde?: string; + hasta?: string; + horas?: number; + minutosHasta?: number; +} + +function resolverRangoFechas(params: ParamsJob) { + // Por defecto se resta 5 minutos al hasta para evitar procesar registros que se estén validando en el instante actual + // Si se pasa minutosHasta (ej: node jobs/manual.js jobs/jobRecetaControl.js 3), se restan esos minutos + const minutosHasta = (params.minutosHasta !== undefined && !isNaN(params.minutosHasta) && params.minutosHasta >= 0) + ? params.minutosHasta + : 5; + + const hasta = params.hasta ? moment(params.hasta).toDate() : moment().subtract(minutosHasta, 'minutes').toDate(); + + let desde: Date | null = null; + if (params.desde) { + desde = moment(params.desde).toDate(); + } else if (params.horas && Number(params.horas) > 0) { + desde = moment(hasta).subtract(Number(params.horas), 'hours').toDate(); + } + // Si no se especifica desde ni horas, queda null (recorre todo hasta la fecha fin) + + return { desde, hasta }; +} + +/** + * Reconstruye el contexto común: prestación, registro RUP y organización. + */ +async function reconstruirContexto(control: any) { + const prestacion: any = await Prestacion.findById(control.idPrestacion); + if (!prestacion) { + throw new Error(`No se encontró la prestación ${control.idPrestacion}`); + } + + const registros = prestacion.getRegistros ? prestacion.getRegistros() : (prestacion.ejecucion?.registros || []); + const registro = (registros || []).find((r: any) => String(r._id) === String(control.idRegistro)) + || (prestacion.evolucion || []).find((r: any) => String(r._id) === String(control.idRegistro)) + || (prestacion.solicitud?.registros || []).find((r: any) => String(r._id) === String(control.idRegistro)); + + if (!registro) { + throw new Error(`No se encontró el registro ${control.idRegistro} en la prestación ${control.idPrestacion}`); + } + + const organizacion = { + id: prestacion.ejecucion?.organizacion?.id, + nombre: prestacion.ejecucion?.organizacion?.nombre + }; + + return { prestacion, registro, organizacion }; +} + +/** Resuelve profesional para medicamento/magistral */ +async function resolverProfesionalReceta(prestacion: any) { + const documentoProfesional = prestacion.estadoActual?.createdBy?.documento + ? prestacion.estadoActual.createdBy.documento + : prestacion.solicitud?.profesional?.documento; + + if (!documentoProfesional) { + throw new Error('No se pudo identificar el documento del profesional en la prestación'); + } + + const profPrestacion = await Profesional.findOne({ documento: documentoProfesional }); + if (!profPrestacion) { + throw new Error(`No se encontró el profesional con documento ${documentoProfesional}`); + } + + const { profesionGrado, matriculaGrado, especialidades } = await getProfesionActualizada(profPrestacion); + + return { + id: profPrestacion.id, + nombre: profPrestacion.nombre, + apellido: profPrestacion.apellido, + documento: profPrestacion.documento, + profesion: profesionGrado, + especialidad: especialidades, + matricula: matriculaGrado + }; +} + +/** Resuelve profesional para insumo */ +async function resolverProfesionalInsumo(prestacion: any) { + const profPrestacion = prestacion.solicitud?.profesional; + if (profPrestacion) { + const { profesionGrado, matriculaGrado, especialidades } = await getProfesionActualizada(profPrestacion); + return { + id: profPrestacion.id, + nombre: profPrestacion.nombre, + apellido: profPrestacion.apellido, + documento: profPrestacion.documento, + profesion: profesionGrado, + especialidad: especialidades, + matricula: matriculaGrado + }; + } + return resolverProfesionalReceta(prestacion); +} + +/** Procesa control de medicamentos / magistrales */ +async function procesarMedicamento(control: any) { + const { prestacion, registro, organizacion } = await reconstruirContexto(control); + const profesional = await resolverProfesionalReceta(prestacion); + + const pacienteCUIL = prestacion.paciente.cuil || generarCUIL(prestacion.paciente.documento, prestacion.paciente.sexo); + const pacienteObj = (typeof prestacion.paciente.toObject === 'function') ? prestacion.paciente.toObject() : prestacion.paciente; + const paciente = { ...pacienteObj, id: pacienteObj.id || pacienteObj._id || prestacion.paciente.id || prestacion.paciente._id, cuil: pacienteCUIL }; + + const dataRecetaBase = { + idPrestacion: prestacion.id, + idRegistro: control.idRegistro, + fechaRegistro: prestacion.ejecucion?.fecha || moment().toDate(), + fechaPrestacion: prestacion.ejecucion?.fecha, + paciente, + profesional, + organizacion, + medicamento: null, + diagnostico: null, + }; + + const medicamentos = registro.valor?.medicamentos || []; + const esMagistral = control.tipo === 'magistral'; + const conceptIdControl = control.identificador?.conceptId; + const codigosControl = (control.identificador?.codigo || []).map((c: any) => c.valor); + + // Buscamos el medicamento correspondiente a este control + const medicamento = medicamentos.find((m: any) => { + if (esMagistral) { + const codigosM = (m.magistral?.codigo || m.concepto?.codigo || []).map((c: any) => c.valor); + const matchCodigo = codigosM.some((c: string) => codigosControl.includes(c)); + return m.esMagistral && ( + matchCodigo || + (conceptIdControl && (m.concepto?.conceptId === conceptIdControl || String(m.concepto?._id) === conceptIdControl)) || + m.magistral?.nombre === control.nombre || + m.concepto?.nombre === control.nombre + ); + } + const cId = m.concepto?.conceptId || m.generico?.conceptId; + return (conceptIdControl && cId === conceptIdControl) + || m.concepto?.term === control.nombre + || m.generico?.term === control.nombre; + }) || medicamentos[0]; // fallback al primero si no hubo match estricto + + if (!medicamento) { + throw new Error(`No se encontró el medicamento en el registro ${control.idRegistro}`); + } + + const dataReceta = { + ...dataRecetaBase, + medicamento, + diagnostico: medicamento?.diagnostico || null, + }; + + // Crear las recetas faltantes de forma idempotente + const respCrear: any = await crearReceta(dataReceta, userScheduler); + if (respCrear instanceof Error || (respCrear && !Array.isArray(respCrear) && (respCrear.message || respCrear.errors || respCrear.status))) { + throw (respCrear instanceof Error ? respCrear : new Error(respCrear.message || JSON.stringify(respCrear))); + } + + // Recuperamos todas las recetas en DB para sincronizar control.recetas + const queryRecetasDb: any = { + idPrestacion: prestacion.id, + idRegistro: control.idRegistro + }; + if (esMagistral) { + queryRecetasDb['medicamento.magistral.nombre'] = medicamento.magistral?.nombre || control.nombre; + } else if (medicamento.concepto?.conceptId || medicamento.generico?.conceptId) { + queryRecetasDb['medicamento.concepto.conceptId'] = medicamento.concepto?.conceptId || medicamento.generico?.conceptId; + } + + const recetasEnDb = await Receta.find(queryRecetasDb); + + if (!Array.isArray(control.recetas)) { + control.recetas = []; + } + + for (const r of recetasEnDb) { + const orden = r.medicamento?.ordenTratamiento ?? 0; + const indexExistente = control.recetas.findIndex((item: any) => { + const id = item.receta_id || item.idReceta; + return (id && (String(id) === String(r._id) || String(id) === String(r.idReceta))) || item.ordenTratamiento === orden; + }); + const esRecetar = Boolean(r.origenExterno?.id || r.origenExterno?.app === 'recetar'); + const origenReceta = esRecetar ? 'recetar' : 'andes'; + const itemReceta = { + receta_id: r._id || r.id, + ordenTratamiento: orden, + origen: (indexExistente >= 0 && control.recetas[indexExistente].origen === 'recetar') ? 'recetar' : origenReceta, + fecha: r.createdAt || new Date() + }; + if (indexExistente >= 0) { + control.recetas[indexExistente] = { ...control.recetas[indexExistente], ...itemReceta }; + } else { + control.recetas.push(itemReceta); + } + } +} + +/** Procesa control de insumos (dispositivo, nutrición, etc.) */ +async function procesarInsumo(control: any) { + const { prestacion, registro, organizacion } = await reconstruirContexto(control); + const profesional = await resolverProfesionalInsumo(prestacion); + + const pacienteObj = (typeof prestacion.paciente.toObject === 'function') ? prestacion.paciente.toObject() : prestacion.paciente; + const paciente = { ...pacienteObj, id: pacienteObj.id || pacienteObj._id || prestacion.paciente.id || prestacion.paciente._id }; + + const dataRecetaBase = { + idPrestacion: prestacion.id, + idRegistro: control.idRegistro, + fechaRegistro: prestacion.ejecucion?.fecha || moment().toDate(), + fechaPrestacion: prestacion.ejecucion?.fecha, + paciente, + profesional, + organizacion, + insumo: null, + diagnostico: null, + }; + + const insumos = registro.valor?.insumos || []; + const codigosControl = (control.identificador?.codigo || []).map((c: any) => c.valor); + const conceptIdControl = control.identificador?.conceptId; + + const insumo = insumos.find((ins: any) => { + const codigosIns = (ins.generico?.codigo || ins.codigo || []).map((c: any) => c.valor); + const matchCodigo = codigosIns.some((c: string) => codigosControl.includes(c)); + const insId = ins.generico?.id || ins.id; + return matchCodigo + || (conceptIdControl && (insId === conceptIdControl || ins.generico?.conceptId === conceptIdControl)) + || ins.generico?.nombre === control.nombre + || ins.nombre === control.nombre; + }) || insumos[0]; + + if (!insumo) { + throw new Error(`No se encontró el insumo en el registro ${control.idRegistro}`); + } + + const dataRecetaInsumo = { + ...dataRecetaBase, + insumo + }; + + const respInsumo: any = await crearRecetaInsumo(dataRecetaInsumo, { user: userScheduler }); + if (respInsumo instanceof Error || (respInsumo && !Array.isArray(respInsumo) && (respInsumo.message || respInsumo.errors || respInsumo.status))) { + throw (respInsumo instanceof Error ? respInsumo : new Error(respInsumo.message || JSON.stringify(respInsumo))); + } + + const insumoId = insumo.generico?.id || insumo.id; + const queryInsumosDb: any = { + idPrestacion: prestacion.id, + idRegistro: control.idRegistro + }; + if (insumoId) { + queryInsumosDb['insumo.id'] = insumoId; + } + + const recetasEnDb = await RecetaInsumo.find(queryInsumosDb); + + if (!Array.isArray(control.recetas)) { + control.recetas = []; + } + + for (const r of recetasEnDb) { + const orden = r.insumo?.ordenTratamiento ?? 0; + const indexExistente = control.recetas.findIndex((item: any) => { + const id = item.receta_id || item.idReceta; + return (id && (String(id) === String(r._id) || String(id) === String(r.idReceta))) || item.ordenTratamiento === orden; + }); + const esRecetar = Boolean(r.origenExterno?.id || r.origenExterno?.app === 'recetar'); + const origenReceta = esRecetar ? 'recetar' : 'andes'; + const itemReceta = { + receta_id: r._id || r.id, + ordenTratamiento: orden, + origen: (indexExistente >= 0 && control.recetas[indexExistente].origen === 'recetar') ? 'recetar' : origenReceta, + fecha: r.createdAt || new Date() + }; + if (indexExistente >= 0) { + control.recetas[indexExistente] = { ...control.recetas[indexExistente], ...itemReceta }; + } else { + control.recetas.push(itemReceta); + } + } +} + +async function procesarControl(control: any) { + switch (control.tipo) { + case 'medicamento': + return procesarMedicamento(control); + case 'magistral': + // Puede provenir de medicamentos o de insumos + return procesarMedicamento(control); + case 'dispositivo': + case 'nutricion': + case 'insumos': + return procesarInsumo(control); + default: + throw new Error(`Tipo de control '${control.tipo}' no reconocido`); + } +} + +async function generarRecetasFaltantes(params: ParamsJob | any, done?: any) { + let callback = done; + let actualParams = params; + + if (typeof params === 'function') { + callback = params; + actualParams = {}; + } + + if (!actualParams || Object.keys(actualParams).length === 0) { + actualParams = {}; + process.argv.forEach((val, idx) => { + if (val.startsWith('--desde=')) { + actualParams.desde = val.split('=')[1]; + } else if (val === '--desde') { + const nextVal = process.argv[idx + 1]; + if (nextVal && !nextVal.startsWith('--')) { + actualParams.desde = nextVal; + } + } + if (val.startsWith('--hasta=')) { + actualParams.hasta = val.split('=')[1]; + } else if (val === '--hasta') { + const nextVal = process.argv[idx + 1]; + if (nextVal && !nextVal.startsWith('--')) { + actualParams.hasta = nextVal; + } + } + if (val.startsWith('--horas=')) { + actualParams.horas = parseInt(val.split('=')[1], 10); + } else if (val === '--horas') { + const nextVal = process.argv[idx + 1]; + if (nextVal && !nextVal.startsWith('--')) { + actualParams.horas = parseInt(nextVal, 10); + } + } + if (val.startsWith('--minutos=')) { + actualParams.minutosHasta = parseInt(val.split('=')[1], 10); + } else if (val === '--minutos') { + const nextVal = process.argv[idx + 1]; + if (nextVal && !nextVal.startsWith('--')) { + actualParams.minutosHasta = parseInt(nextVal, 10); + } + } else if (idx >= 3 && /^\d+$/.test(val)) { + const prevVal = process.argv[idx - 1]; + if (prevVal !== '--horas' && prevVal !== '--minutos' && prevVal !== '--desde' && prevVal !== '--hasta') { + // Argumento posicional numérico (ej: node jobs/manual.js jobs/jobRecetaControl.js 3) + actualParams.minutosHasta = parseInt(val, 10); + } + } + }); + } + + const { desde, hasta } = resolverRangoFechas(actualParams); + + const resultados = { + total: 0, + completas: 0, + incompletas: 0, + errores: 0, + porTipo: { medicamento: 0, magistral: 0, dispositivo: 0, nutricion: 0, insumos: 0 } + }; + + try { + // Se procesan estados 'pendiente' e 'incompleto'. No se filtra por intentos en la query. + const query: any = { + estado: { $in: ['pendiente', 'incompleto'] }, + createdAt: { $lte: hasta } + }; + if (desde) { + query.createdAt.$gte = desde; + } + + const cursor = RecetaControl.find(query).cursor({ batchSize: 50 }); + + await cursor.eachAsync(async (control: any) => { + resultados.total++; + control.intento = (control.intento || 0) + 1; + control.fechaUltimoIntento = new Date(); + + try { + await procesarControl(control); + + if (control.recetas.length >= control.cantTotalRecetas) { + control.estado = 'completo'; + control.ultimoError = null; + control.markModified('ultimoError'); + resultados.completas++; + } else { + if (control.intento >= 3) { + control.estado = 'error'; + } else { + control.estado = 'incompleto'; + } + if (!control.ultimoError) { + control.ultimoError = { + message: `No se generaron todas las recetas (${control.recetas.length}/${control.cantTotalRecetas})`, + fecha: new Date() + }; + } + control.markModified('ultimoError'); + resultados.incompletas++; + } + + if (control.tipo && resultados.porTipo[control.tipo] !== undefined) { + resultados.porTipo[control.tipo]++; + } + } catch (error: any) { + resultados.errores++; + if (control.intento >= 3) { + control.estado = 'error'; + } else { + control.estado = 'incompleto'; + } + const errorObj: any = { + message: error?.message || (typeof error === 'string' ? error : JSON.stringify(error)), + fecha: new Date() + }; + if (error?.status) { + errorObj.status = error.status; + } + if (error?.name) { + errorObj.name = error.name; + } + control.ultimoError = errorObj; + control.markModified('ultimoError'); + await logger.error('generarRecetasFaltantes:item', { controlId: control._id, tipo: control.tipo, intento: control.intento }, error); + } + + try { + if (typeof (control as any).audit === 'function') { + (control as any).audit(userScheduler); + } else { + Auth.audit(control, { user: userScheduler } as any); + } + await control.save(); + } catch (errSave) { + await logger.error('generarRecetasFaltantes:save', { controlId: control._id }, errSave); + } + }, { parallel: 1 }); + + await logger.info('generarRecetasFaltantes:fin', { desde, hasta, resultados }); + } catch (err) { + await logger.error('generarRecetasFaltantes:general', { desde, hasta, resultados }, err); + if (callback) { + return callback(err); + } + return; + } + + if (callback) { + callback(); + } +} + +export = generarRecetasFaltantes; diff --git a/modules/recetas/receta-control-schema.ts b/modules/recetas/receta-control-schema.ts new file mode 100644 index 0000000000..b08913f9bb --- /dev/null +++ b/modules/recetas/receta-control-schema.ts @@ -0,0 +1,115 @@ +import { AuditPlugin } from '@andes/mongoose-plugin-audit'; +import { Schema, SchemaTypes, model } from 'mongoose'; + + +export const identificadorSubschema = new Schema({ + _id: false, + nombre: { + type: String, + required: false + }, + conceptId: { + type: String, + required: false + }, + codigo: [{ + _id: false, + fuente: { + type: String, + required: false + }, + valor: { + type: String, + required: false + } + }] +}, { _id: false }); + +export const recetaControlSchema = new Schema({ + idPrestacion: { + type: SchemaTypes.ObjectId, + required: true + }, + idRegistro: { + type: SchemaTypes.ObjectId, + required: true + }, + idPaciente: { + type: SchemaTypes.ObjectId, + required: true + }, + tipo: { + type: String, + enum: ['medicamento', 'magistral', 'dispositivo', 'nutricion'], + required: true + }, + nombre: { + type: String, + required: false + }, + identificador: { + type: identificadorSubschema, + required: false + }, + tratamientoProlongado: { + type: Boolean, + default: false, + required: true + }, + cantTotalRecetas: { + type: Number, + default: 1, + required: true + }, + intento: { + type: Number, + default: 0, + required: true + }, + estado: { + type: String, + enum: ['pendiente', 'incompleto', 'completo', 'suspendido', 'error'], + default: 'pendiente', + required: true + }, + recetas: [{ + _id: false, + receta_id: { + type: SchemaTypes.ObjectId, + required: false + }, + ordenTratamiento: { + type: Number, + required: true + }, + origen: { + type: String, + enum: ['andes', 'recetar'], + required: false + }, + fecha: { + type: Date, + default: Date.now + } + }], + ultimoError: { + type: SchemaTypes.Mixed, + required: false + }, + fechaUltimoIntento: { + type: Date, + required: false + } +}); + +recetaControlSchema.plugin(AuditPlugin); + +recetaControlSchema.index({ idPrestacion: 1, idRegistro: 1 }); +recetaControlSchema.index({ idPaciente: 1 }); +recetaControlSchema.index({ estado: 1, createdAt: 1 }); +recetaControlSchema.index({ 'identificador.conceptId': 1 }); +recetaControlSchema.index({ 'identificador.codigo.valor': 1 }); + +export const RecetaControl = model('recetaControl', recetaControlSchema, 'recetaControl'); + + diff --git a/modules/recetas/recetaLogs.ts b/modules/recetas/recetaLogs.ts index 5f4d4b9539..e9236911ae 100644 --- a/modules/recetas/recetaLogs.ts +++ b/modules/recetas/recetaLogs.ts @@ -8,7 +8,7 @@ export const updateLog = new Logger({ application: 'andes', bucketBy: 'h', bucketSize: 100, - expiredAt: '6 M' + expiredAt: '3 M' }); export const informarLog = new Logger({ @@ -38,5 +38,15 @@ export const jobsLog = new Logger({ application: 'andes', bucketBy: 'h', bucketSize: 100, + expiredAt: '3 M' +}); + +export const preCargaLog = new Logger({ + connection: Connections.logs, + module: 'recetas', + type: 'precarga', + application: 'andes', + bucketBy: 'h', + bucketSize: 100, expiredAt: '6 M' }); diff --git a/modules/recetas/recetas.helpers.ts b/modules/recetas/recetas.helpers.ts new file mode 100644 index 0000000000..9493009a19 --- /dev/null +++ b/modules/recetas/recetas.helpers.ts @@ -0,0 +1,71 @@ +export interface CodigoPrescripcion { + fuente: string; + valor: string; +} + +export interface IdentificadorPrescripcion { + nombre: string; + conceptId: string; + codigo: CodigoPrescripcion[]; +} + +/** + * Resuelve y normaliza la identificación de una prescripción (medicamento, magistral, insumo, dispositivo, nutrición). + * Extrae nombre descriptivo, conceptId de SNOMED/ANDES y códigos externos/magistrales. + */ +export function resolverIdentificador(prescripcion: any, tipo?: string): IdentificadorPrescripcion { + if (!prescripcion) { + return null; + } + const esMagistral = tipo === 'magistral' || !!prescripcion.esMagistral; + const esMedicamento = tipo === 'medicamento'; + + // 1. Nombre + const nombre = esMagistral + ? (prescripcion.magistral?.nombre || prescripcion.concepto?.nombre || prescripcion.concepto?.term || prescripcion.nombre) + : (esMedicamento + ? (prescripcion.concepto?.term || prescripcion.generico?.term || prescripcion.nombre) + : (prescripcion.generico?.nombre || prescripcion.nombre)); + + // 2. conceptId de ANDES (si es de SNOMED o concepto ANDES) + const conceptId = prescripcion.concepto?.conceptId + || prescripcion.generico?.conceptId + || (prescripcion.concepto?._id ? String(prescripcion.concepto._id) : null); + + // 3. Códigos (magistrales, insumos con [{ fuente, valor }], o código alfanumérico externo) + let codigosRaw = prescripcion.magistral?.codigo + || prescripcion.concepto?.codigo + || prescripcion.generico?.codigo + || prescripcion.codigo + || prescripcion.insumo?.codigo + || []; + + if (!Array.isArray(codigosRaw)) { + if (codigosRaw && typeof codigosRaw === 'object' && (codigosRaw.fuente || codigosRaw.valor)) { + codigosRaw = [codigosRaw]; + } else if (typeof codigosRaw === 'string' && codigosRaw.trim()) { + codigosRaw = [{ fuente: prescripcion.fuente || 'externo', valor: codigosRaw.trim() }]; + } else { + codigosRaw = []; + } + } + + const codigo: CodigoPrescripcion[] = codigosRaw.map((c: any) => ({ + fuente: c.fuente || 'externo', + valor: c.valor || c.codigo || (typeof c === 'string' ? c : '') + })).filter((c: any) => c.valor); + + // Si viene código externo en prescripcion.origenExterno y no está en codigo + if (prescripcion.origenExterno?.id && !codigo.some((c: any) => c.valor === prescripcion.origenExterno.id)) { + codigo.push({ + fuente: prescripcion.origenExterno.app || 'recetar', + valor: String(prescripcion.origenExterno.id) + }); + } + + return { + nombre, + conceptId, + codigo + }; +} diff --git a/modules/recetas/recetasController.ts b/modules/recetas/recetasController.ts index 13d093bcfb..261abe06b1 100644 --- a/modules/recetas/recetasController.ts +++ b/modules/recetas/recetasController.ts @@ -4,12 +4,14 @@ import { Auth } from '../../auth/auth.class'; import { userScheduler } from '../../config.private'; import { RecetasParametros } from './parametros.schema'; import { MotivosReceta, Receta } from './receta-schema'; -import { createLog, informarLog, updateLog, jobsLog } from './recetaLogs'; +import { createLog, informarLog, updateLog, jobsLog, preCargaLog } from './recetaLogs'; import { ParamsIncorrect, RecetaNotEdit, RecetaNotFound } from './recetas.error'; import { getReceta } from './services/receta'; import { Paciente } from '../../core-v2/mpi/paciente/paciente.schema'; import { Profesional } from '../../core/tm/schemas/profesional'; import { Counter } from './counter.model'; +import { RecetaControl } from './receta-control-schema'; +import { resolverIdentificador } from './recetas.helpers'; const pad = (num: number, size: number) => num.toString().padStart(size, '0'); @@ -417,6 +419,43 @@ export async function suspender(recetaId, req) { const rec = recSusp.concat(await Promise.all(await recetasUpdate(recetasEliminar, 'eliminada', motivo, observacion, profesional, req))); + try { + const esMagistral = !!recetaR?.medicamento?.esMagistral; + const tipo = esMagistral ? 'magistral' : 'medicamento'; + const identificador = resolverIdentificador(recetaR.medicamento, tipo); + const nombre = identificador?.nombre; + + const queryControl: any = { + idPrestacion: recetaR.idPrestacion, + idRegistro: recetaR.idRegistro, + tipo + }; + const orConditions: any[] = []; + if (recetaR._id) { + orConditions.push({ 'recetas.receta_id': recetaR._id }); + } + if (identificador?.conceptId) { + orConditions.push({ 'identificador.conceptId': identificador.conceptId }); + } + if (identificador?.codigo?.length) { + orConditions.push({ 'identificador.codigo.valor': { $in: identificador.codigo.map((c: any) => c.valor) } }); + } + if (nombre) { + orConditions.push({ nombre }); + orConditions.push({ 'identificador.nombre': nombre }); + } + if (orConditions.length > 0) { + queryControl.$or = orConditions; + } + + await RecetaControl.updateMany( + queryControl, + { $set: { estado: 'suspendido' } } + ); + } catch (errCtrl) { + updateLog.error('suspender:control', { recetaId, idRegistro: recetaR.idRegistro }, errCtrl); + } + return rec; } return recetasASuspender; @@ -767,7 +806,96 @@ export async function create(req) { matricula: matriculaGrado }; } - return await crearReceta(dataReceta, req); + const recetasCreadas: any = await crearReceta(dataReceta, req); + if (Array.isArray(recetasCreadas) && recetasCreadas.length > 0) { + try { + const medicamento = dataReceta.medicamento; + const esMagistral = !!medicamento.esMagistral; + const tipo = esMagistral ? 'magistral' : 'medicamento'; + const identificador = resolverIdentificador(medicamento, tipo); + const nombre = identificador?.nombre; + + const queryControl: any = { + idPrestacion: dataReceta.idPrestacion, + idRegistro: dataReceta.idRegistro, + tipo + }; + const orConditions: any[] = []; + if (identificador?.conceptId) { + orConditions.push({ 'identificador.conceptId': identificador.conceptId }); + } + if (identificador?.codigo?.length) { + orConditions.push({ 'identificador.codigo.valor': { $in: identificador.codigo.map((c: any) => c.valor) } }); + } + if (nombre) { + orConditions.push({ nombre }); + orConditions.push({ 'identificador.nombre': nombre }); + } + if (orConditions.length > 0) { + queryControl.$or = orConditions; + } + + let control: any = await RecetaControl.findOne(queryControl); + if (!control) { + const tratamientoProlongado = !!(medicamento.tratamientoProlongado && medicamento.tiempoTratamiento && medicamento.tiempoTratamiento.id !== null); + const cantRecetas = tratamientoProlongado ? parseInt(medicamento.tiempoTratamiento.id, 10) : 1; + control = new RecetaControl({ + idPrestacion: dataReceta.idPrestacion, + idRegistro: dataReceta.idRegistro, + idPaciente: dataReceta.paciente?.id || dataReceta.paciente?._id, + tipo, + nombre, + identificador, + tratamientoProlongado, + cantTotalRecetas: cantRecetas, + intento: 0, + estado: 'pendiente', + recetas: [] + }); + } + + if (!Array.isArray(control.recetas)) { + control.recetas = []; + } + + for (const r of recetasCreadas) { + const orden = r.medicamento?.ordenTratamiento ?? 0; + const indexExistente = control.recetas.findIndex((item: any) => { + const id = item.receta_id || item.idReceta; + return (id && (String(id) === String(r._id) || String(id) === String(r.idReceta))) || item.ordenTratamiento === orden; + }); + const itemReceta = { + receta_id: r._id || r.id, + ordenTratamiento: orden, + origen: 'recetar', + fecha: r.createdAt || new Date() + }; + if (indexExistente >= 0) { + control.recetas[indexExistente] = { ...control.recetas[indexExistente], ...itemReceta }; + } else { + control.recetas.push(itemReceta); + } + } + + if (control.estado !== 'suspendido') { + if (control.recetas.length >= control.cantTotalRecetas) { + control.estado = 'completo'; + } else { + control.estado = 'incompleto'; + } + } + + if (req.user) { + Auth.audit(control, req as any); + } else if (typeof (control as any).audit === 'function') { + (control as any).audit(req); + } + await control.save(); + } catch (errControl) { + createLog.error('create:recetaControl', { dataReceta }, errControl, req); + } + } + return recetasCreadas; } catch (err) { createLog.error('create', { dataReceta, pacienteRecetar, profRecetar }, err, req); return err; @@ -776,6 +904,7 @@ export async function create(req) { export async function crearReceta(dataReceta, req) { + const medicamento = dataReceta.medicamento; const tratamientoProlongado: Boolean = medicamento.tratamientoProlongado && medicamento.tiempoTratamiento && medicamento.tiempoTratamiento.id !== null; const cantRecetas = (tratamientoProlongado) ? parseInt(medicamento.tiempoTratamiento.id, 10) : 1; @@ -783,12 +912,31 @@ export async function crearReceta(dataReceta, req) { let receta; for (let i = 0; i < cantRecetas; i++) { try { + const esMagistral = !!medicamento.esMagistral; + const conceptId = medicamento.concepto?.conceptId || medicamento.generico?.conceptId; + + const queryExistente: any = { + idPrestacion: dataReceta.idPrestacion, + idRegistro: dataReceta.idRegistro, + 'medicamento.ordenTratamiento': i + }; + if (esMagistral && medicamento.magistral?.nombre) { + queryExistente['medicamento.magistral.nombre'] = medicamento.magistral.nombre; + } else if (conceptId) { + queryExistente['medicamento.concepto.conceptId'] = conceptId; + } + + const yaExiste = await Receta.findOne(queryExistente); + if (yaExiste) { + recetas.push(yaExiste); + continue; + } + receta = new Receta(); receta.idPrestacion = dataReceta.idPrestacion; receta.idRegistro = dataReceta.idRegistro; const diag = medicamento.diagnostico; receta.diagnostico = (typeof diag === 'string') ? { descripcion: diag } : diag; - const esMagistral = !!medicamento.esMagistral; receta.medicamento = { concepto: esMagistral ? null : (medicamento.concepto || medicamento.generico), presentacion: medicamento.presentacion?.term || medicamento.presentacion, @@ -1069,3 +1217,83 @@ export async function verificarRecetaExistente(documento: string, sexo: string, receta: receta || null }; } + +/// PRECARGA DE RECETAS: COLECCIÓN AUXILIAR + +export async function generarPrecargaReceta(prestacion, req) { + const registros = (prestacion.getRegistros ? prestacion.getRegistros() : (prestacion?.ejecucion?.registros || [])); + const tipoPrescripciones = [ + { conceptId: '33633005', tipo: 'medicamento' }, + { conceptId: '313047003', tipo: 'dispositivo' }, + { conceptId: '1217195001', tipo: 'nutricion' }, + { conceptId: '1217196000', tipo: 'nutricion' } + ]; + const getTipo = (conceptId) => tipoPrescripciones.find(t => t.conceptId === conceptId); + + const registrosReceta = registros.filter(r => r.concepto?.conceptId && getTipo(r.concepto.conceptId)); + await Promise.all(registrosReceta.map(async (registro) => { + let recetaAuxiliar: any; + try { + const prescripciones = registro.valor?.medicamentos || registro.valor?.insumos || []; + if (!prescripciones.length) { + return; + } + + for (const prescripcion of prescripciones) { + const tipoConfig = getTipo(registro.concepto.conceptId)?.tipo; + const tipo = prescripcion.esMagistral ? 'magistral' : (prescripcion.tipo || tipoConfig || 'medicamento'); + const identificador = resolverIdentificador(prescripcion, tipo); + const nombre = identificador?.nombre; + + // Verificamos si ya existe control para esta prescripción específica + const queryExiste: any = { + idPrestacion: prestacion._id, + idRegistro: registro._id, + tipo + }; + const orConditions: any[] = []; + if (identificador?.conceptId) { + orConditions.push({ 'identificador.conceptId': identificador.conceptId }); + } + if (identificador?.codigo?.length) { + orConditions.push({ 'identificador.codigo.valor': { $in: identificador.codigo.map((c: any) => c.valor) } }); + } + if (nombre) { + orConditions.push({ nombre }); + orConditions.push({ 'identificador.nombre': nombre }); + } + if (orConditions.length > 0) { + queryExiste.$or = orConditions; + } + + const existe = await RecetaControl.findOne(queryExiste); + if (!existe) { + const tratamientoProlongado = !!(prescripcion.tratamientoProlongado && prescripcion.tiempoTratamiento?.id); + const cantTotalRecetas = tratamientoProlongado ? Number(prescripcion.tiempoTratamiento.id) : 1; + + recetaAuxiliar = new RecetaControl({ + idPrestacion: prestacion._id, + idRegistro: registro._id, + idPaciente: prestacion.paciente?.id || prestacion.paciente?._id, + tipo, + nombre, + identificador, + tratamientoProlongado, + cantTotalRecetas, + intento: 0, + estado: 'pendiente', + recetas: [] + }); + if (req.user) { + Auth.audit(recetaAuxiliar, req); + } else { + (recetaAuxiliar as any).audit(req); + } + await recetaAuxiliar.save(); + } + } + } catch (error) { + preCargaLog.error('generarPrecarga', { idPrestacion: prestacion._id, registro, recetaAuxiliar }, error, req); + } + })); +} diff --git a/modules/recetas/recetasInsumos/receta-insumo.events.ts b/modules/recetas/recetasInsumos/receta-insumo.events.ts index f0aac21e7b..45c6e3a8e0 100644 --- a/modules/recetas/recetasInsumos/receta-insumo.events.ts +++ b/modules/recetas/recetasInsumos/receta-insumo.events.ts @@ -3,8 +3,11 @@ import { crearRecetaInsumo } from '../../recetas/recetasInsumos/recetaInsumosCon import { getProfesionActualizada } from '../../recetas/recetasController'; import * as moment from 'moment'; import { RecetaInsumo } from './receta-insumo.schema'; -import { Profesional } from '../../../core/tm/schemas/profesional'; +import { RecetaControl } from '../../recetas/receta-control-schema'; +import { resolverIdentificador } from '../../recetas/recetas.helpers'; import { createLog } from './../recetaLogs'; +import { Profesional } from '../../../core/tm/schemas/profesional'; +import { Auth } from '../../../auth/auth.class'; EventCore.on('prestacion:recetaInsumo:create', async ({ prestacion, registro }) => { let dataReceta: any = {}; @@ -44,18 +47,112 @@ EventCore.on('prestacion:recetaInsumo:create', async ({ prestacion, registro }) }; for (const insumo of registro.valor.insumos) { - const receta: any = await RecetaInsumo.findOne({ - 'insumo.nombre': insumo.generico.nombre, + const insumoId = insumo.generico?.id || insumo.id; + if (!insumoId) { + continue; + } + + dataReceta.insumo = insumo; + await crearRecetaInsumo(dataReceta, prestacion.createdBy); + + const queryInsumosDb: any = { + idPrestacion: prestacion.id, idRegistro - }); - if (!receta) { - dataReceta.insumo = insumo; - await crearRecetaInsumo(dataReceta, prestacion.createdBy); + }; + if (insumoId) { + queryInsumosDb['insumo.id'] = insumoId; + } + const recetasEnDb = await RecetaInsumo.find(queryInsumosDb); + + const tipoInsumo = insumo.esMagistral ? 'magistral' : (insumo.generico?.tipo || insumo.tipo || 'dispositivo'); + const tratamientoProlongado = !!(insumo.tratamientoProlongado && insumo.tiempoTratamiento && insumo.tiempoTratamiento.id !== null); + const cantRecetas = tratamientoProlongado ? parseInt(insumo.tiempoTratamiento.id, 10) : 1; + const identificador = resolverIdentificador(insumo, tipoInsumo); + const nombreInsumo = identificador?.nombre; + + const queryControl: any = { + idPrestacion: prestacion.id || prestacion._id, + idRegistro: idRegistro || registro._id, + tipo: tipoInsumo + }; + const orConditions: any[] = []; + if (identificador?.conceptId) { + orConditions.push({ 'identificador.conceptId': identificador.conceptId }); + } + if (identificador?.codigo?.length) { + orConditions.push({ 'identificador.codigo.valor': { $in: identificador.codigo.map((c: any) => c.valor) } }); + } + if (nombreInsumo) { + orConditions.push({ nombre: nombreInsumo }); + orConditions.push({ 'identificador.nombre': nombreInsumo }); + } + if (orConditions.length > 0) { + queryControl.$or = orConditions; } + let control: any = await RecetaControl.findOne(queryControl); + + if (!control) { + control = new RecetaControl({ + idPrestacion: prestacion.id || prestacion._id, + idRegistro: idRegistro || registro._id, + idPaciente: prestacion.paciente.id || prestacion.paciente._id, + tipo: tipoInsumo, + nombre: nombreInsumo, + identificador, + tratamientoProlongado, + cantTotalRecetas: cantRecetas, + intento: 0, + estado: 'pendiente', + recetas: [] + }); + } + + if (!Array.isArray(control.recetas)) { + control.recetas = []; + } + + for (const r of recetasEnDb) { + const orden = r.insumo?.ordenTratamiento ?? 0; + const indexExistente = control.recetas.findIndex((item: any) => { + const id = item.receta_id || item.idReceta; + return (id && (String(id) === String(r._id) || String(id) === String(r.idReceta))) || item.ordenTratamiento === orden; + }); + const esRecetar = Boolean(r.origenExterno?.id || r.origenExterno?.app === 'recetar'); + const origenReceta = esRecetar ? 'recetar' : 'andes'; + const itemReceta = { + receta_id: r._id || r.id, + ordenTratamiento: orden, + origen: (indexExistente >= 0 && control.recetas[indexExistente].origen === 'recetar') ? 'recetar' : origenReceta, + fecha: r.createdAt || new Date() + }; + if (indexExistente >= 0) { + control.recetas[indexExistente] = { ...control.recetas[indexExistente], ...itemReceta }; + } else { + control.recetas.push(itemReceta); + } + } + + if (control.estado !== 'suspendido') { + if (control.recetas.length >= control.cantTotalRecetas) { + control.estado = 'completo'; + } else { + control.estado = 'incompleto'; + } + } + + if (prestacion.createdBy) { + if (typeof (control as any).audit === 'function') { + (control as any).audit(prestacion.createdBy); + } else { + Auth.audit(control, { user: prestacion.createdBy } as any); + } + } + await control.save(); } } catch (err) { createLog.error('create', { dataReceta, prestacion, profesional }, err, { prestacion, registro }); return err; } }); + diff --git a/modules/recetas/recetasInsumos/recetaInsumosController.ts b/modules/recetas/recetasInsumos/recetaInsumosController.ts index 729fbe26f8..a8a8e50357 100644 --- a/modules/recetas/recetasInsumos/recetaInsumosController.ts +++ b/modules/recetas/recetasInsumos/recetaInsumosController.ts @@ -2,6 +2,8 @@ import * as moment from 'moment'; import { Types } from 'mongoose'; import { Auth } from '../../../auth/auth.class'; import { RecetaInsumo } from './receta-insumo.schema'; +import { RecetaControl } from '../receta-control-schema'; +import { resolverIdentificador } from '../recetas.helpers'; import { createLog, informarLog, updateLog } from '../recetaLogs'; import { ParamsIncorrect, RecetaNotFound, RecetaNotEdit } from '../recetas.error'; import { Paciente } from '../../../core-v2/mpi/paciente/paciente.schema'; @@ -181,7 +183,96 @@ export async function create(req) { matricula: matriculaGrado }; } - return await crearRecetaInsumo(dataRecetaInsumo, req); + const recetasCreadas: any = await crearRecetaInsumo(dataRecetaInsumo, req); + if (Array.isArray(recetasCreadas) && recetasCreadas.length > 0) { + try { + const insumo = dataRecetaInsumo.insumo; + const tipoInsumo = insumo.generico?.tipo || insumo.tipo; + const tipo = (tipoInsumo === 'magistral') ? 'magistral' : (tipoInsumo || 'dispositivo'); + const identificador = resolverIdentificador(insumo, tipo); + const nombre = identificador?.nombre; + + const queryControl: any = { + idPrestacion: dataRecetaInsumo.idPrestacion, + idRegistro: dataRecetaInsumo.idRegistro, + tipo + }; + const orConditions: any[] = []; + if (identificador?.conceptId) { + orConditions.push({ 'identificador.conceptId': identificador.conceptId }); + } + if (identificador?.codigo?.length) { + orConditions.push({ 'identificador.codigo.valor': { $in: identificador.codigo.map((c: any) => c.valor) } }); + } + if (nombre) { + orConditions.push({ nombre }); + orConditions.push({ 'identificador.nombre': nombre }); + } + if (orConditions.length > 0) { + queryControl.$or = orConditions; + } + + let control: any = await RecetaControl.findOne(queryControl); + if (!control) { + const tratamientoProlongado = !!(insumo.tratamientoProlongado && insumo.tiempoTratamiento && insumo.tiempoTratamiento.id !== null); + const cantRecetas = tratamientoProlongado ? parseInt(insumo.tiempoTratamiento.id, 10) : 1; + control = new RecetaControl({ + idPrestacion: dataRecetaInsumo.idPrestacion, + idRegistro: dataRecetaInsumo.idRegistro, + idPaciente: dataRecetaInsumo.paciente?.id || dataRecetaInsumo.paciente?._id, + tipo, + nombre, + identificador, + tratamientoProlongado, + cantTotalRecetas: cantRecetas, + intento: 0, + estado: 'pendiente', + recetas: [] + }); + } + + if (!Array.isArray(control.recetas)) { + control.recetas = []; + } + + for (const r of recetasCreadas) { + const orden = r.insumo?.ordenTratamiento ?? 0; + const indexExistente = control.recetas.findIndex((item: any) => { + const id = item.receta_id || item.idReceta; + return (id && (String(id) === String(r._id) || String(id) === String(r.idReceta))) || item.ordenTratamiento === orden; + }); + const itemReceta = { + receta_id: r._id || r.id, + ordenTratamiento: orden, + origen: 'recetar', + fecha: r.createdAt || new Date() + }; + if (indexExistente >= 0) { + control.recetas[indexExistente] = { ...control.recetas[indexExistente], ...itemReceta }; + } else { + control.recetas.push(itemReceta); + } + } + + if (control.estado !== 'suspendido') { + if (control.recetas.length >= control.cantTotalRecetas) { + control.estado = 'completo'; + } else { + control.estado = 'incompleto'; + } + } + + if (req.user) { + Auth.audit(control, req as any); + } else if (typeof (control as any).audit === 'function') { + (control as any).audit(req); + } + await control.save(); + } catch (errControl) { + createLog.error('create:recetaControl', { dataRecetaInsumo }, errControl, req); + } + } + return recetasCreadas; } catch (err) { createLog.error('create', { dataRecetaInsumo, pacienteRecetar, profRecetar }, err, req); return err; @@ -196,6 +287,22 @@ export async function crearRecetaInsumo(dataRecetaInsumo, req) { let recetaInsumo; for (let i = 0; i < cantRecetas; i++) { try { + const insumoId = insumo.generico?.id || insumo.id; + const queryExistente: any = { + idPrestacion: dataRecetaInsumo.idPrestacion, + idRegistro: dataRecetaInsumo.idRegistro, + 'insumo.ordenTratamiento': i + }; + if (insumoId) { + queryExistente['insumo.id'] = insumoId; + } + + const yaExiste = await RecetaInsumo.findOne(queryExistente); + if (yaExiste) { + recetas.push(yaExiste); + continue; + } + recetaInsumo = new RecetaInsumo(); recetaInsumo.idPrestacion = dataRecetaInsumo.idPrestacion; recetaInsumo.idRegistro = dataRecetaInsumo.idRegistro; @@ -339,6 +446,42 @@ export async function suspender(recetaInsumoId, req) { const rec = recSusp.concat(await Promise.all(await recetasInsumoUpdate(recetasEliminar, 'eliminada', motivo, observacion, profesional, req))); + try { + const tipoInsumo = recetaInsumo.insumo?.esMagistral ? 'magistral' : (recetaInsumo.insumo?.tipo || 'dispositivo'); + const identificador = resolverIdentificador(recetaInsumo.insumo, tipoInsumo); + const nombre = identificador?.nombre; + + const queryControl: any = { + idPrestacion: recetaInsumo.idPrestacion, + idRegistro: recetaInsumo.idRegistro, + tipo: tipoInsumo + }; + const orConditions: any[] = []; + if (recetaInsumo._id) { + orConditions.push({ 'recetas.receta_id': recetaInsumo._id }); + } + if (identificador?.conceptId) { + orConditions.push({ 'identificador.conceptId': identificador.conceptId }); + } + if (identificador?.codigo?.length) { + orConditions.push({ 'identificador.codigo.valor': { $in: identificador.codigo.map((c: any) => c.valor) } }); + } + if (nombre) { + orConditions.push({ nombre }); + orConditions.push({ 'identificador.nombre': nombre }); + } + if (orConditions.length > 0) { + queryControl.$or = orConditions; + } + + await RecetaControl.updateMany( + queryControl, + { $set: { estado: 'suspendido' } } + ); + } catch (errCtrl) { + updateLog.error('suspender:insumo:control', { recetaInsumoId, idRegistro: recetaInsumo.idRegistro }, errCtrl); + } + return rec; } return recetasASuspender; diff --git a/modules/rup/controllers/rup.events.ts b/modules/rup/controllers/rup.events.ts index 42ff8bc3be..3936f83462 100644 --- a/modules/rup/controllers/rup.events.ts +++ b/modules/rup/controllers/rup.events.ts @@ -2,10 +2,13 @@ import { EventCore } from '@andes/event-bus'; import { getProfesionActualizada, crearReceta } from '../../recetas/recetasController'; import * as moment from 'moment'; import { Receta } from '../../recetas/receta-schema'; +import { RecetaControl } from '../../recetas/receta-control-schema'; +import { resolverIdentificador } from '../../recetas/recetas.helpers'; import { rupEventsLog as logger } from './rup.events.log'; import { Profesional } from '../../../core/tm/schemas/profesional'; import { generarCUIL } from '../../../core-v2/mpi/validacion/validacion.controller'; import { Organizacion } from '../../../core/tm/schemas/organizacion'; +import { Auth } from '../../../auth/auth.class'; EventCore.on('prestacion:receta:create', async ({ prestacion, registro }) => { try { @@ -71,26 +74,179 @@ EventCore.on('prestacion:receta:create', async ({ prestacion, registro }) => { continue; } - const queryReceta: any = { idRegistro }; + const dataReceta = { + ...dataRecetaBase, + medicamento, + diagnostico: medicamento?.diagnostico || null, + }; + + const respCrear: any = await crearReceta(dataReceta, prestacion.createdBy); + if (respCrear instanceof Error || (respCrear && !Array.isArray(respCrear) && (respCrear.message || respCrear.errors || respCrear.status))) { + throw (respCrear instanceof Error ? respCrear : new Error(respCrear.message || JSON.stringify(respCrear))); + } + + // Buscamos todas las recetas creadas / existentes para este registro + const queryRecetasDb: any = { + idPrestacion: prestacion.id || prestacion._id, + idRegistro: idRegistro || registro._id + }; if (esMagistral) { - queryReceta['medicamento.magistral.nombre'] = medicamento.magistral.nombre; + queryRecetasDb['medicamento.magistral.nombre'] = medicamento.magistral?.nombre; } else { - queryReceta['medicamento.concepto.conceptId'] = conceptId; + queryRecetasDb['medicamento.concepto.conceptId'] = conceptId; + } + const recetasEnDb = await Receta.find(queryRecetasDb); + + const tratamientoProlongado = !!(medicamento.tratamientoProlongado && medicamento.tiempoTratamiento && medicamento.tiempoTratamiento.id !== null); + const cantRecetas = tratamientoProlongado ? parseInt(medicamento.tiempoTratamiento.id, 10) : 1; + const tipo = esMagistral ? 'magistral' : 'medicamento'; + const identificador = resolverIdentificador(medicamento, tipo); + const nombre = identificador?.nombre; + + const queryControl: any = { + idPrestacion: prestacion.id || prestacion._id, + idRegistro: idRegistro || registro._id, + tipo + }; + const orConditions: any[] = []; + if (identificador?.conceptId) { + orConditions.push({ 'identificador.conceptId': identificador.conceptId }); + } + if (identificador?.codigo?.length) { + orConditions.push({ 'identificador.codigo.valor': { $in: identificador.codigo.map((c: any) => c.valor) } }); + } + if (nombre) { + orConditions.push({ nombre }); + orConditions.push({ 'identificador.nombre': nombre }); + } + if (orConditions.length > 0) { + queryControl.$or = orConditions; } - const receta: any = await Receta.findOne(queryReceta); + let control: any = await RecetaControl.findOne(queryControl); - if (!receta) { - const dataReceta = { - ...dataRecetaBase, - medicamento, - diagnostico: medicamento?.diagnostico || null, + if (!control) { + control = new RecetaControl({ + idPrestacion: prestacion.id || prestacion._id, + idRegistro: idRegistro || registro._id, + idPaciente: prestacion.paciente.id || prestacion.paciente._id, + tipo, + nombre, + identificador, + tratamientoProlongado, + cantTotalRecetas: cantRecetas, + intento: 0, + estado: 'pendiente', + recetas: [] + }); + } + + if (!Array.isArray(control.recetas)) { + control.recetas = []; + } + + for (const r of recetasEnDb) { + const orden = r.medicamento?.ordenTratamiento ?? 0; + const indexExistente = control.recetas.findIndex((item: any) => { + const id = item.receta_id || item.idReceta; + return (id && (String(id) === String(r._id) || String(id) === String(r.idReceta))) || item.ordenTratamiento === orden; + }); + const esRecetar = Boolean(r.origenExterno?.id || r.origenExterno?.app === 'recetar'); + const origenReceta = esRecetar ? 'recetar' : 'andes'; + const itemReceta = { + receta_id: r._id || r.id, + ordenTratamiento: orden, + origen: (indexExistente >= 0 && control.recetas[indexExistente].origen === 'recetar') ? 'recetar' : origenReceta, + fecha: r.createdAt || new Date() }; + if (indexExistente >= 0) { + control.recetas[indexExistente] = { ...control.recetas[indexExistente], ...itemReceta }; + } else { + control.recetas.push(itemReceta); + } + } - await crearReceta(dataReceta, prestacion.createdBy); + if (control.estado !== 'suspendido') { + if (control.recetas.length >= control.cantTotalRecetas) { + control.estado = 'completo'; + control.ultimoError = null; + } else { + control.estado = 'incompleto'; + if (!control.ultimoError) { + control.ultimoError = { + message: `No se generaron todas las recetas (${control.recetas.length}/${control.cantTotalRecetas})`, + fecha: new Date() + }; + } + } + control.markModified('ultimoError'); } - } catch (errorMedicamento) { + if (prestacion.createdBy) { + if (typeof (control as any).audit === 'function') { + (control as any).audit(prestacion.createdBy); + } else { + Auth.audit(control, { user: prestacion.createdBy } as any); + } + } + await control.save(); + } catch (errorMedicamento: any) { logger.error('prestacion:receta:create', { idRegistro, medicamento }, errorMedicamento); + try { + const tipo = medicamento?.esMagistral ? 'magistral' : 'medicamento'; + const identificador = resolverIdentificador(medicamento, tipo); + const queryControl: any = { + idPrestacion: prestacion.id || prestacion._id, + idRegistro: idRegistro || registro._id, + tipo + }; + const orConditions: any[] = []; + if (identificador?.conceptId) { + orConditions.push({ 'identificador.conceptId': identificador.conceptId }); + } + if (identificador?.codigo?.length) { + orConditions.push({ 'identificador.codigo.valor': { $in: identificador.codigo.map((c: any) => c.valor) } }); + } + if (identificador?.nombre) { + orConditions.push({ nombre: identificador.nombre }); + orConditions.push({ 'identificador.nombre': identificador.nombre }); + } + if (orConditions.length > 0) { + queryControl.$or = orConditions; + } + let controlErr: any = await RecetaControl.findOne(queryControl); + if (!controlErr) { + controlErr = new RecetaControl({ + idPrestacion: prestacion.id || prestacion._id, + idRegistro: idRegistro || registro._id, + idPaciente: prestacion.paciente.id || prestacion.paciente._id, + tipo, + nombre: identificador?.nombre, + identificador, + tratamientoProlongado: !!(medicamento.tratamientoProlongado && medicamento.tiempoTratamiento?.id), + cantTotalRecetas: (medicamento.tratamientoProlongado && medicamento.tiempoTratamiento?.id) ? parseInt(medicamento.tiempoTratamiento.id, 10) : 1, + intento: 0, + estado: 'incompleto', + recetas: [] + }); + } + controlErr.ultimoError = { + message: errorMedicamento?.message || String(errorMedicamento), + status: errorMedicamento?.status, + name: errorMedicamento?.name, + fecha: new Date() + }; + controlErr.markModified('ultimoError'); + if (prestacion.createdBy) { + if (typeof (controlErr as any).audit === 'function') { + (controlErr as any).audit(prestacion.createdBy); + } else { + Auth.audit(controlErr, { user: prestacion.createdBy } as any); + } + } + await controlErr.save(); + } catch (saveErr) { + logger.error('prestacion:receta:create:saveError', { idRegistro }, saveErr); + } } } diff --git a/modules/rup/routes/prestacion.ts b/modules/rup/routes/prestacion.ts index bfa5a2a5da..19c81a9a75 100755 --- a/modules/rup/routes/prestacion.ts +++ b/modules/rup/routes/prestacion.ts @@ -23,6 +23,7 @@ import { Prestacion } from '../schemas/prestacion'; import { Auth } from './../../../auth/auth.class'; import { parseDate } from './../../../shared/parse'; import { Agenda } from '../../turnos/schemas/agenda'; +import { generarPrecargaReceta } from '../../recetas/recetasController'; const router = express.Router(); @@ -145,7 +146,8 @@ router.get('/prestaciones/resumenPaciente/:idPaciente', async (req: any, res, ne fecha: prestacion.createdAt, profesional: prestacion.createdBy, conceptos: registros - }); + } + ); } } }); @@ -804,6 +806,9 @@ router.patch('/prestaciones/:id', (req: Request, res, next) => { AppCache.clear(`huds-${data.paciente.id}`); } if (req.body.estado?.tipo === 'validada') { + + // obtener todas las recetas que se generaron en la prestacion y crear las recetas auxiliares correspondientes + generarPrecargaReceta(prestacion, req as any); EventCore.emitAsync('rup:prestacion:validate', data); // buscarYCrearSolicitudes y saveTurnoProfesional se hace acá para obtener datos del REQ a futuro se debería asociar al EventCore @@ -946,3 +951,5 @@ EventCore.on('rup:prestacion:romperValidacion', async (prestacion: IPrestacionDo } } }); + +