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