Skip to content

Commit 6213d56

Browse files
committed
feat(REC): implementa renovación de recetas
1 parent a1b10c4 commit 6213d56

3 files changed

Lines changed: 230 additions & 1 deletion

File tree

modules/recetas/receta-schema.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,16 @@ export const recetaSchema = new mongoose.Schema({
177177
estadoDispensaActual: estadoDispensaSchema,
178178
paciente: { type: PacienteSubSchema, required: true },
179179
renovacion: String,
180+
idRecetaOriginal: {
181+
type: String,
182+
required: false
183+
},
184+
numeroRenovacion: {
185+
type: Number,
186+
required: false,
187+
min: 1,
188+
max: 12
189+
},
180190
appNotificada: [{ app: sistemaSchema, fecha: Date }],
181191
origenExterno: {
182192
id: String, // id receta creada por sistema que no es Andes

modules/recetas/recetas.routes.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { asyncHandler, Request, Response } from '@andes/api-tool';
22
import { MongoQuery, ResourceBase } from '@andes/core';
33
import { Auth } from '../../auth/auth.class';
44
import { Receta } from './receta-schema';
5-
import { buscarRecetas, getMotivosReceta, setEstadoDispensa, suspender, actualizarAppNotificada, cancelarDispensa, create, buscarRecetasPorProfesional } from './recetasController';
5+
import { buscarRecetas, getMotivosReceta, setEstadoDispensa, suspender, actualizarAppNotificada, cancelarDispensa, create, buscarRecetasPorProfesional, renovarRecetas } from './recetasController';
66
import { ParamsIncorrect } from './recetas.error';
77

88
class RecetasResource extends ResourceBase {
@@ -79,6 +79,12 @@ export const post = async (req, res) => {
7979
res.status(status).json(resp);
8080
};
8181

82+
export const postRenovar = async (req, res) => {
83+
const resp = await renovarRecetas(req);
84+
const status = (resp instanceof Error || resp?.status) ? (resp.status || 400) : 200;
85+
res.status(status).json(resp);
86+
};
87+
8288
export const RecetasCtr = new RecetasResource({});
8389
export const RecetasRouter = RecetasCtr.makeRoutes();
8490

@@ -98,3 +104,4 @@ RecetasRouter.get('/recetas/motivos', asyncHandler(getMotivos));
98104
RecetasRouter.get('/recetas/profesional/:id', authorizeByToken,asyncHandler(getByProfesional));
99105
RecetasRouter.patch('/recetas', authorizeByToken, asyncHandler(patch));
100106
RecetasRouter.post('/recetas', authorizeByToken, asyncHandler(post));
107+
RecetasRouter.post('/recetas/renovar', authorizeByToken, asyncHandler(postRenovar));

modules/recetas/recetasController.ts

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,3 +829,215 @@ export async function actualizarEstadosDispensa() {
829829
await informarLog.error('actualizarEstadosDispensa', {}, error);
830830
}
831831
}
832+
833+
async function fetchRecetasExpandidas(recetasOriginales: any[]) {
834+
const recetasExpandidas: any[] = [];
835+
const procesadosIdRegistro = new Set();
836+
837+
for (const recetaOriginal of recetasOriginales) {
838+
const esProlongado = recetaOriginal.medicamento?.tratamientoProlongado && recetaOriginal.idRegistro != null;
839+
if (esProlongado) {
840+
if (!procesadosIdRegistro.has(recetaOriginal.idRegistro)) {
841+
procesadosIdRegistro.add(recetaOriginal.idRegistro);
842+
843+
const prolongadas = await Receta.find({
844+
idRegistro: recetaOriginal.idRegistro,
845+
'medicamento.concepto.conceptId': recetaOriginal.medicamento.concepto.conceptId
846+
}).sort({ 'medicamento.ordenTratamiento': 1 });
847+
848+
for (const r of prolongadas) {
849+
if (!recetasExpandidas.find(e => e._id.toString() === r._id.toString())) {
850+
recetasExpandidas.push(r);
851+
}
852+
}
853+
}
854+
} else {
855+
if (!recetasExpandidas.find(e => e._id.toString() === recetaOriginal._id.toString())) {
856+
recetasExpandidas.push(recetaOriginal);
857+
}
858+
}
859+
}
860+
return recetasExpandidas;
861+
}
862+
863+
async function validarRenovacion(recetaOriginal: any) {
864+
const estadoActual = recetaOriginal.estadoActual?.tipo;
865+
866+
// Solo se puede renovar si está finalizada o vencida
867+
if (!['finalizada', 'vencida'].includes(estadoActual)) {
868+
throw new RecetaNotEdit(
869+
`La receta ${recetaOriginal._id} está en estado "${estadoActual}" y no puede renovarse. Solo se permiten estados: finalizada, vencida`
870+
);
871+
}
872+
873+
// No se puede renovar si fue suspendida en algún momento
874+
const estuveSuspendida = recetaOriginal.estados?.some((e: any) => e.tipo === 'suspendida');
875+
if (estuveSuspendida) {
876+
throw new RecetaNotEdit(`La receta ${recetaOriginal._id} fue suspendida y no puede renovarse`);
877+
}
878+
879+
// No se puede renovar si hay recetas pendientes del mismo tratamiento prolongado
880+
if (recetaOriginal.idRegistro) {
881+
const pendiente = await Receta.findOne({
882+
idRegistro: recetaOriginal.idRegistro,
883+
'estadoActual.tipo': 'pendiente'
884+
});
885+
if (pendiente) {
886+
throw new RecetaNotEdit(
887+
`La receta ${recetaOriginal._id} tiene recetas pendientes del tratamiento prolongado y no puede renovarse`
888+
);
889+
}
890+
}
891+
892+
// Validar antigüedad ≤ 1 año
893+
const fechaLimiteRenovacion = moment().subtract(1, 'year').toDate();
894+
if (recetaOriginal.fechaRegistro < fechaLimiteRenovacion) {
895+
throw new RecetaNotEdit(
896+
`La receta ${recetaOriginal._id} tiene más de 1 año de antigüedad y no puede renovarse`
897+
);
898+
}
899+
}
900+
901+
async function aplicarRenovacion(recetaOriginal: any, profesionalData: any, organizacion: any, ahora: Date, req: any) {
902+
const medicamento = recetaOriginal.medicamento;
903+
const esProlongado = medicamento?.tratamientoProlongado && medicamento?.tiempoTratamiento?.id != null;
904+
const ordenTratamiento = medicamento?.ordenTratamiento || 0;
905+
906+
recetaOriginal.profesional = profesionalData as any;
907+
recetaOriginal.organizacion = {
908+
id: Types.ObjectId(organizacion.id),
909+
nombre: organizacion.nombre
910+
} as any;
911+
912+
// Reajusta las fechas considerando si es mes 0, mes 1, mes 2, etc, en base al orden para prolongados
913+
recetaOriginal.fechaRegistro = moment(ahora).add(ordenTratamiento * 30, 'days').toDate();
914+
915+
// Limpia origenExterno por si la receta original venía de otro lado
916+
recetaOriginal.origenExterno = undefined as any;
917+
918+
// Avanzar el estado a vigente (la inicial o simple) o pendiente (las subsiguientes)
919+
const nuevoEstado = ordenTratamiento < 1 ? 'vigente' : 'pendiente';
920+
recetaOriginal.estados.push({ tipo: nuevoEstado } as any);
921+
recetaOriginal.estadosDispensa.push({ tipo: 'sin-dispensa', fecha: ahora } as any);
922+
923+
// Limpiar historial previo de dispensas y notificaciones para arrancar un ciclo limpio
924+
recetaOriginal.dispensa = [];
925+
recetaOriginal.appNotificada = [];
926+
927+
// Calcular número de renovación
928+
const MAX_RENOVACIONES = 12;
929+
let numRenovacion: number;
930+
931+
if (recetaOriginal.numeroRenovacion != null) {
932+
// Es una renovación de una receta que ya fue renovada previamente
933+
if (esProlongado) {
934+
const cantMeses = parseInt(medicamento.tiempoTratamiento.id, 10);
935+
numRenovacion = recetaOriginal.numeroRenovacion + cantMeses;
936+
} else {
937+
numRenovacion = recetaOriginal.numeroRenovacion + 1;
938+
}
939+
} else {
940+
// Primera renovación
941+
if (esProlongado) {
942+
const cantMeses = parseInt(medicamento.tiempoTratamiento.id, 10);
943+
numRenovacion = cantMeses + 1 + ordenTratamiento;
944+
} else {
945+
numRenovacion = 1;
946+
}
947+
}
948+
949+
if (numRenovacion > MAX_RENOVACIONES) {
950+
throw new RecetaNotEdit(
951+
`La receta ${recetaOriginal._id} ya alcanzó el máximo de ${MAX_RENOVACIONES} renovaciones`
952+
);
953+
}
954+
955+
// Mantenemos idRecetaOriginal si ya lo tenía, si no, se lo asignamos a sí mismo
956+
if (!recetaOriginal.idRecetaOriginal) {
957+
recetaOriginal.idRecetaOriginal = recetaOriginal._id.toString();
958+
}
959+
recetaOriginal.numeroRenovacion = numRenovacion;
960+
961+
Auth.audit(recetaOriginal as any, req);
962+
await recetaOriginal.save();
963+
964+
return recetaOriginal;
965+
}
966+
967+
/**
968+
* Renueva un conjunto de recetas finalizadas o vencidas.
969+
* Se clonan los datos originales sobreescribiendo autor y efector con los valores del body.
970+
* Las nuevas recetas quedan en estado vigente/pendiente con dispensa sin-dispensa.
971+
*/
972+
export async function renovarRecetas(req) {
973+
const { recetasIds, profesional: profBody, organizacion } = req.body;
974+
975+
try {
976+
// Validar parámetros básicos
977+
if (!recetasIds || !Array.isArray(recetasIds) || recetasIds.length === 0) {
978+
throw new ParamsIncorrect('Se requiere al menos un ID de receta en recetasIds');
979+
}
980+
if (!profBody || !profBody.id) {
981+
throw new ParamsIncorrect('Se requiere el objeto profesional con id');
982+
}
983+
if (!organizacion || !organizacion.id) {
984+
throw new ParamsIncorrect('Se requiere el objeto organizacion con id');
985+
}
986+
987+
// Validar que todos los IDs sean ObjectId válidos
988+
const idsInvalidos = recetasIds.filter(id => !Types.ObjectId.isValid(id));
989+
if (idsInvalidos.length > 0) {
990+
throw new ParamsIncorrect(`IDs de receta inválidos: ${idsInvalidos.join(', ')}`);
991+
}
992+
993+
// Buscar recetas originales inicialmente por IDs enviados
994+
const recetasOriginales: any[] = await Receta.find({
995+
_id: { $in: recetasIds.map(id => Types.ObjectId(id)) }
996+
});
997+
998+
if (recetasOriginales.length !== recetasIds.length) {
999+
const encontrados = recetasOriginales.map(r => r._id.toString());
1000+
const faltantes = recetasIds.filter(id => !encontrados.includes(id));
1001+
throw new RecetaNotFound(`Recetas no encontradas: ${faltantes.join(', ')}`);
1002+
}
1003+
1004+
// Expandir y agrupar automáticamente todas las recetas asociadas al mismo tratamiento prolongado
1005+
const recetasExpandidas: any[] = await fetchRecetasExpandidas(recetasOriginales);
1006+
1007+
// Obtener el profesional desde DB para tener matrícula y datos actualizados
1008+
const profAndes: any = await Profesional.findById(profBody.id);
1009+
if (!profAndes) {
1010+
throw new ParamsIncorrect('Profesional no encontrado en la base de datos');
1011+
}
1012+
const { profesionGrado, matriculaGrado, especialidades } = await getProfesionActualizada(profAndes);
1013+
const profesionalData = {
1014+
_id: profAndes._id,
1015+
id: profAndes._id,
1016+
nombre: profAndes.nombre,
1017+
apellido: profAndes.apellido,
1018+
documento: profAndes.documento,
1019+
profesion: profesionGrado,
1020+
especialidad: especialidades,
1021+
matricula: matriculaGrado
1022+
};
1023+
1024+
// Validar primero todas las recetas antes de aplicar cambios
1025+
for (const recetaOriginal of recetasExpandidas) {
1026+
await validarRenovacion(recetaOriginal);
1027+
}
1028+
1029+
// Aplicar renovación y guardar
1030+
const nuevasRecetas = [];
1031+
const ahora = new Date();
1032+
for (const recetaOriginal of recetasExpandidas) {
1033+
const recetaRenovada = await aplicarRenovacion(recetaOriginal, profesionalData, organizacion, ahora, req);
1034+
nuevasRecetas.push(recetaRenovada);
1035+
}
1036+
1037+
return nuevasRecetas;
1038+
1039+
} catch (err) {
1040+
createLog.error('renovarRecetas', { recetasIds, profBody, organizacion }, err, req);
1041+
return err;
1042+
}
1043+
}

0 commit comments

Comments
 (0)