Skip to content

Commit b7b1b3f

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

3 files changed

Lines changed: 175 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: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,3 +829,160 @@ export async function actualizarEstadosDispensa() {
829829
await informarLog.error('actualizarEstadosDispensa', {}, error);
830830
}
831831
}
832+
833+
/**
834+
* Renueva un conjunto de recetas finalizadas o vencidas.
835+
* Se clonan los datos originales sobreescribiendo autor y efector con los valores del body.
836+
* Las nuevas recetas quedan en estado vigente/pendiente con dispensa sin-dispensa.
837+
*/
838+
export async function renovarRecetas(req) {
839+
const { recetasIds, profesional: profBody, organizacion } = req.body;
840+
841+
try {
842+
// Validar parámetros básicos
843+
if (!recetasIds || !Array.isArray(recetasIds) || recetasIds.length === 0) {
844+
throw new ParamsIncorrect('Se requiere al menos un ID de receta en recetasIds');
845+
}
846+
if (!profBody || !profBody.id) {
847+
throw new ParamsIncorrect('Se requiere el objeto profesional con id');
848+
}
849+
if (!organizacion || !organizacion.id) {
850+
throw new ParamsIncorrect('Se requiere el objeto organizacion con id');
851+
}
852+
853+
// Validar que todos los IDs sean ObjectId válidos
854+
const idsInvalidos = recetasIds.filter(id => !Types.ObjectId.isValid(id));
855+
if (idsInvalidos.length > 0) {
856+
throw new ParamsIncorrect(`IDs de receta inválidos: ${idsInvalidos.join(', ')}`);
857+
}
858+
859+
// Buscar recetas originales
860+
const recetasOriginales: any[] = await Receta.find({
861+
_id: { $in: recetasIds.map(id => Types.ObjectId(id)) }
862+
});
863+
864+
if (recetasOriginales.length !== recetasIds.length) {
865+
const encontrados = recetasOriginales.map(r => r._id.toString());
866+
const faltantes = recetasIds.filter(id => !encontrados.includes(id));
867+
throw new RecetaNotFound(`Recetas no encontradas: ${faltantes.join(', ')}`);
868+
}
869+
870+
// Obtener el profesional desde DB para tener matrícula y datos actualizados
871+
const profAndes: any = await Profesional.findById(profBody.id);
872+
if (!profAndes) {
873+
throw new ParamsIncorrect('Profesional no encontrado en la base de datos');
874+
}
875+
const { profesionGrado, matriculaGrado, especialidades } = await getProfesionActualizada(profAndes);
876+
const profesionalData = {
877+
_id: profAndes._id,
878+
id: profAndes._id,
879+
nombre: profAndes.nombre,
880+
apellido: profAndes.apellido,
881+
documento: profAndes.documento,
882+
profesion: profesionGrado,
883+
especialidad: especialidades,
884+
matricula: matriculaGrado
885+
};
886+
887+
// Validar y renovar cada receta
888+
const nuevasRecetas = [];
889+
for (const recetaOriginal of recetasOriginales) {
890+
const estadoActual = recetaOriginal.estadoActual?.tipo;
891+
892+
// Solo se puede renovar si está finalizada o vencida
893+
if (!['finalizada', 'vencida'].includes(estadoActual)) {
894+
throw new RecetaNotEdit(
895+
`La receta ${recetaOriginal._id} está en estado "${estadoActual}" y no puede renovarse. Solo se permiten estados: finalizada, vencida`
896+
);
897+
}
898+
899+
// No se puede renovar si fue suspendida en algún momento
900+
const estuveSuspendida = recetaOriginal.estados?.some((e: any) => e.tipo === 'suspendida');
901+
if (estuveSuspendida) {
902+
throw new RecetaNotEdit(`La receta ${recetaOriginal._id} fue suspendida y no puede renovarse`);
903+
}
904+
905+
// No se puede renovar si hay recetas pendientes del mismo tratamiento prolongado
906+
if (recetaOriginal.idRegistro) {
907+
const pendiente = await Receta.findOne({
908+
idRegistro: recetaOriginal.idRegistro,
909+
'estadoActual.tipo': 'pendiente'
910+
});
911+
if (pendiente) {
912+
throw new RecetaNotEdit(
913+
`La receta ${recetaOriginal._id} tiene recetas pendientes del tratamiento prolongado y no puede renovarse`
914+
);
915+
}
916+
}
917+
918+
// Validar antigüedad ≤ 1 año
919+
const fechaLimiteRenovacion = moment().subtract(1, 'year').toDate();
920+
if (recetaOriginal.fechaRegistro < fechaLimiteRenovacion) {
921+
throw new RecetaNotEdit(
922+
`La receta ${recetaOriginal._id} tiene más de 1 año de antigüedad y no puede renovarse`
923+
);
924+
}
925+
926+
// Construir dataReceta a partir de la receta original
927+
const ahora = new Date();
928+
929+
recetaOriginal.profesional = profesionalData as any;
930+
recetaOriginal.organizacion = {
931+
id: Types.ObjectId(organizacion.id),
932+
nombre: organizacion.nombre
933+
} as any;
934+
recetaOriginal.fechaRegistro = ahora;
935+
936+
// Limpia origenExterno por si la receta original venía de otro lado
937+
recetaOriginal.origenExterno = undefined as any;
938+
939+
// Avanzar el estado a vigente y dejarlo sin dispensar
940+
recetaOriginal.estados.push({ tipo: 'vigente' } as any);
941+
recetaOriginal.estadosDispensa.push({ tipo: 'sin-dispensa', fecha: ahora } as any);
942+
943+
// Limpiar historial previo de dispensas y notificaciones para arrancar un ciclo limpio
944+
recetaOriginal.dispensa = [];
945+
recetaOriginal.appNotificada = [];
946+
947+
// Calcular número de renovación
948+
const MAX_RENOVACIONES = 12;
949+
const medicamento = recetaOriginal.medicamento;
950+
const esProlongado = medicamento?.tratamientoProlongado && medicamento?.tiempoTratamiento?.id != null;
951+
let numRenovacion: number;
952+
953+
if (recetaOriginal.numeroRenovacion != null) {
954+
// Renovación de una renovación: avanzar desde el último número registrado
955+
numRenovacion = recetaOriginal.numeroRenovacion + 1;
956+
} else if (esProlongado) {
957+
// Primera renovación de un tratamiento prolongado: salta por encima de los meses originales
958+
numRenovacion = parseInt(medicamento.tiempoTratamiento.id, 10) + 1;
959+
} else {
960+
// Primera renovación de receta simple
961+
numRenovacion = 1;
962+
}
963+
964+
if (numRenovacion > MAX_RENOVACIONES) {
965+
throw new RecetaNotEdit(
966+
`La receta ${recetaOriginal._id} ya alcanzó el máximo de ${MAX_RENOVACIONES} renovaciones`
967+
);
968+
}
969+
970+
// Mantenemos idRecetaOriginal si ya lo tenía, si no, se lo asignamos a sí mismo
971+
if (!recetaOriginal.idRecetaOriginal) {
972+
recetaOriginal.idRecetaOriginal = recetaOriginal._id.toString();
973+
}
974+
recetaOriginal.numeroRenovacion = numRenovacion;
975+
976+
Auth.audit(recetaOriginal as any, req);
977+
await recetaOriginal.save();
978+
979+
nuevasRecetas.push(recetaOriginal);
980+
}
981+
982+
return nuevasRecetas;
983+
984+
} catch (err) {
985+
createLog.error('renovarRecetas', { recetasIds, profBody, organizacion }, err, req);
986+
return err;
987+
}
988+
}

0 commit comments

Comments
 (0)