@@ -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