From 580a1cfb5d236205acdb648db5af8ee7b54ff533 Mon Sep 17 00:00:00 2001 From: martinebucarey Date: Fri, 30 Oct 2026 14:34:52 -0300 Subject: [PATCH] feat(RUP): add patient condition handling in appointments and services --- ...icion-paciente-en-turnos-y-prestaciones.md | 77 +++++++++++++++++++ modules/rup/condicion-paciente.ts | 5 ++ modules/rup/prestaciones.interface.ts | 3 + modules/rup/routes/prestacion.ts | 13 ++++ modules/rup/schemas/prestacion.test.ts | 31 ++++++++ modules/rup/schemas/prestacion.ts | 11 ++- modules/turnos/controller/agenda.ts | 10 +++ modules/turnos/routes/agenda.ts | 3 + modules/turnos/routes/turno.ts | 13 ++++ modules/turnos/schemas/turno.ts | 6 ++ 10 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 docs/adr-condicion-paciente-en-turnos-y-prestaciones.md create mode 100644 modules/rup/condicion-paciente.ts create mode 100644 modules/rup/schemas/prestacion.test.ts diff --git a/docs/adr-condicion-paciente-en-turnos-y-prestaciones.md b/docs/adr-condicion-paciente-en-turnos-y-prestaciones.md new file mode 100644 index 0000000000..6a27199151 --- /dev/null +++ b/docs/adr-condicion-paciente-en-turnos-y-prestaciones.md @@ -0,0 +1,77 @@ +--- +tags: + - rup + - turnos +scope: + - api + - app +--- + +# ADR 0002: Condición del paciente en turnos y prestaciones + +- **Estado:** Aceptado +- **Fecha:** 2026-10-30 +- **Repos afectados:** `api` + +> **Este documento es vivo.** Los cambios que afecten el almacenamiento o la validación de +> `condicionPaciente` en turnos o prestaciones deben actualizar este ADR. + +## Contexto + +Los flujos de turnos y prestaciones necesitan registrar la condición del paciente asociada +a la atención. El dato requiere un conjunto de valores controlado, debe poder omitirse y no +debe modificarse en una prestación que ya fue validada. + +## Decisión + +Agregar el campo opcional `condicionPaciente` a los modelos de turnos y prestaciones, con +estos valores permitidos: + +- `ambulatorio` +- `internado` +- `guardia` + +La lista de valores y su validación se centralizan en `modules/rup/condicion-paciente.ts` +para reutilizarlas en los flujos de ambos módulos. Los esquemas de persistencia limitan el +campo a esos valores. + +Las rutas validan el dato antes de procesarlo: + +- La creación de prestaciones y su actualización mediante `estadoPush` rechazan valores + inválidos. En `estadoPush`, no se permite cambiar la condición si la prestación ya está + validada. +- La asignación de turnos y la creación de sobreturnos rechazan condiciones inválidas. En + los sobreturnos, los valores vacíos o nulos se tratan como dato no informado. +- La asignación de un turno guarda la condición recibida. Al liberar o reutilizar un turno, + se limpia la condición anterior para evitar que se herede en otra atención. + +## Alternativas consideradas + +Los cambios revisados no documentan otras alternativas consideradas. + +## Consecuencias + +**Positivas** + +- Prestaciones y turnos pueden guardar la condición sin hacer obligatorio el dato para + registros existentes o nuevas solicitudes que lo omitan. +- Los valores aceptados son consistentes entre validación de rutas y esquemas de persistencia. +- La condición no persiste accidentalmente al liberar o reutilizar un turno. + +**Negativas / a vigilar** + +- Los consumidores de estas rutas deben enviar uno de los valores definidos cuando informen + `condicionPaciente`. +- La condición de una prestación validada no puede modificarse mediante `estadoPush`. + +## Alcance y puntos abiertos + +El alcance documentado comprende la persistencia y validación de `condicionPaciente` en los +flujos de prestaciones y turnos descritos en este ADR. Los cambios TypeScript revisados no +identifican puntos abiertos adicionales. + +## Historial de cambios del documento + +| Fecha | Cambio | +|---|---| +| 2026-10-30 | Versión inicial, basada en los cambios TypeScript locales. | diff --git a/modules/rup/condicion-paciente.ts b/modules/rup/condicion-paciente.ts new file mode 100644 index 0000000000..418e541af7 --- /dev/null +++ b/modules/rup/condicion-paciente.ts @@ -0,0 +1,5 @@ +export const CONDICIONES_PACIENTE = ['ambulatorio', 'internado', 'guardia']; + +export function condicionPacienteValida(condicionPaciente: any): boolean { + return typeof condicionPaciente === 'string' && CONDICIONES_PACIENTE.indexOf(condicionPaciente) !== -1; +} diff --git a/modules/rup/prestaciones.interface.ts b/modules/rup/prestaciones.interface.ts index 9c4eb7aeb3..7a8e113aef 100644 --- a/modules/rup/prestaciones.interface.ts +++ b/modules/rup/prestaciones.interface.ts @@ -4,10 +4,13 @@ import { IFinanciador } from '../../core-v2/mpi/financiador'; import { IOrganizacion } from '../../core/tm/interfaces/IOrganizacion'; import { ISnomedConcept } from './schemas/snomed-concept'; +export type CondicionPaciente = 'ambulatorio' | 'internado' | 'guardia'; + export interface IPrestacion { id: ObjectId; trackId?: ObjectId; groupId?: ObjectId; + condicionPaciente?: CondicionPaciente; paciente?: { id: ObjectId; nombre: string; diff --git a/modules/rup/routes/prestacion.ts b/modules/rup/routes/prestacion.ts index 8a990b4ee6..1574d1c5fe 100755 --- a/modules/rup/routes/prestacion.ts +++ b/modules/rup/routes/prestacion.ts @@ -20,6 +20,7 @@ import { registrosProfundidad } from '../controllers/rup'; import { buscarYCrearSolicitudes } from '../controllers/solicitudes.controller'; import { IPrestacionDoc } from '../prestaciones.interface'; import { Prestacion } from '../schemas/prestacion'; +import { condicionPacienteValida } from '../condicion-paciente'; import { Auth } from './../../../auth/auth.class'; import { parseDate } from './../../../shared/parse'; import { Agenda } from '../../turnos/schemas/agenda'; @@ -591,6 +592,9 @@ router.get('/prestaciones', async (req: any, res, next) => { router.post('/prestaciones', async (req, res, next) => { try { const dto = parseDate(JSON.stringify(req.body)); + if (dto.condicionPaciente !== undefined && !condicionPacienteValida(dto.condicionPaciente)) { + return next('Condición del paciente inválida'); + } if (dto.inicio === 'top') { updateRegistroHistorialSolicitud(dto.solicitud, { op: 'creacion' }); @@ -638,6 +642,15 @@ router.patch('/prestaciones/:id', (req: Request, res, next) => { } break; case 'estadoPush': + if (req.body.condicionPaciente !== undefined) { + if (data.estadoActual.tipo === 'validada') { + return next('Prestación validada, no se puede modificar.'); + } + if (!condicionPacienteValida(req.body.condicionPaciente)) { + return next('Condición del paciente inválida'); + } + data.condicionPaciente = req.body.condicionPaciente; + } if (req.body.estado) { // SI LA SOLICITUD FUE ANULADA NO SE PUEDE REALIZAR OTRA OPERACION if (data.estadoActual.tipo === 'anulada') { diff --git a/modules/rup/schemas/prestacion.test.ts b/modules/rup/schemas/prestacion.test.ts new file mode 100644 index 0000000000..dc31094fff --- /dev/null +++ b/modules/rup/schemas/prestacion.test.ts @@ -0,0 +1,31 @@ +import { Prestacion } from './prestacion'; + +describe('PrestacionSchema condicionPaciente', () => { + const solicitud = { fecha: new Date() }; + + it('permite omitir la condición en prestaciones existentes o sin informar', () => { + const prestacion = new Prestacion({ noNominalizada: false, solicitud }); + + expect(prestacion.validateSync()?.errors.condicionPaciente).toBeUndefined(); + }); + + it.each(['ambulatorio', 'internado', 'guardia'])('acepta la condición %s', condicionPaciente => { + const prestacion = new Prestacion({ + noNominalizada: false, + solicitud, + condicionPaciente + }); + + expect(prestacion.validateSync()?.errors.condicionPaciente).toBeUndefined(); + }); + + it('rechaza valores fuera del conjunto permitido', () => { + const prestacion = new Prestacion({ + noNominalizada: false, + solicitud, + condicionPaciente: 'desconocido' + }); + + expect(prestacion.validateSync()?.errors.condicionPaciente).toBeDefined(); + }); +}); diff --git a/modules/rup/schemas/prestacion.ts b/modules/rup/schemas/prestacion.ts index 573068731b..63164a8baa 100644 --- a/modules/rup/schemas/prestacion.ts +++ b/modules/rup/schemas/prestacion.ts @@ -8,6 +8,10 @@ import { PrestacionSolicitudHistorialschema } from './prestacion.solicitud.histo import { SemanticTag } from './semantic-tag'; import { PacienteSubSchema } from '../../../core-v2/mpi/paciente/paciente.schema'; +import { CONDICIONES_PACIENTE } from '../condicion-paciente'; + +export { CONDICIONES_PACIENTE }; + export const PrestacionSchema = new Schema({ /** * ID de un EVENTO externo (internacion, guardia, embarazo) para agrupar un conjunto de prestaciones referidas a ese evento @@ -19,6 +23,12 @@ export const PrestacionSchema = new Schema({ */ groupId: { required: false, type: Schema.Types.ObjectId }, + condicionPaciente: { + type: String, + enum: CONDICIONES_PACIENTE, + required: false + }, + inicio: { type: String, enum: ['top', 'agenda', 'fuera-agenda', 'internacion', 'servicio-intermedio', 'plan-indicaciones'], @@ -373,4 +383,3 @@ PrestacionSchema.index( export const Prestacion = model('prestacion', PrestacionSchema, 'prestaciones'); export const PrestacionHistorial = model('prestacion-historial', PrestacionHistorialSchema, 'prestacion-historial'); - diff --git a/modules/turnos/controller/agenda.ts b/modules/turnos/controller/agenda.ts index 2406f574bb..7168d96549 100644 --- a/modules/turnos/controller/agenda.ts +++ b/modules/turnos/controller/agenda.ts @@ -18,6 +18,7 @@ import { getAccesosVirtuales } from '../../turnos/controller/turnosController'; import * as cie10 from './../../../core/term/schemas/cie10'; import { ECLQueriesCtr } from './../../../core/tm/eclqueries.routes'; import { handleHttpRequest } from '../../../utils/requestHandler'; +import { condicionPacienteValida } from '../../rup/condicion-paciente'; export async function getAgendaById(agendaId) { return await Agenda.findById(agendaId); @@ -135,6 +136,7 @@ export async function liberarTurno(req, data, turno) { turno.tipoTurno = undefined; turno.link = undefined; turno.motivoConsulta = undefined; + turno.condicionPaciente = undefined; turno.webexLinks = null; turno.updatedAt = new Date(); turno.updatedBy = req.user.usuario || req.user; @@ -434,6 +436,13 @@ export async function actualizarPaciente(pacienteTurno: any, req: any) { } // Agenda +export function condicionPacienteSobreturnoValida(sobreturno): boolean { + if (sobreturno && (sobreturno.condicionPaciente === null || sobreturno.condicionPaciente === '')) { + delete sobreturno.condicionPaciente; + } + return !sobreturno || sobreturno.condicionPaciente === undefined || condicionPacienteValida(sobreturno.condicionPaciente); +} + export async function agregarSobreturno(req, data) { const sobreturno = req.body.sobreturno; if (sobreturno) { @@ -1605,6 +1614,7 @@ export function agendaNueva(data, clon, req) { turno.fechaHoraDacion = undefined; turno.link = undefined; turno.motivoConsulta = undefined; + turno.condicionPaciente = undefined; turno.usuarioDacion = undefined; turno.profesional = undefined; turno.horaAsistencia = undefined; diff --git a/modules/turnos/routes/agenda.ts b/modules/turnos/routes/agenda.ts index 6954655e93..5873b6385d 100644 --- a/modules/turnos/routes/agenda.ts +++ b/modules/turnos/routes/agenda.ts @@ -566,6 +566,9 @@ router.patch('/agenda/:id*?', (req, res, next) => { } break; case 'agregarSobreturno': + if (!agendaCtrl.condicionPacienteSobreturnoValida(req.body.sobreturno)) { + return next({ status: 400, message: 'Condición del paciente inválida' }); + } event = { object: 'turno', accion: 'asignar', data: null }; event.data = await agendaCtrl.agregarSobreturno(req, data); break; diff --git a/modules/turnos/routes/turno.ts b/modules/turnos/routes/turno.ts index 1f633f6e0b..e0356552be 100644 --- a/modules/turnos/routes/turno.ts +++ b/modules/turnos/routes/turno.ts @@ -20,10 +20,15 @@ import { Agenda } from '../schemas/agenda'; import { Constantes } from '../../constantes/constantes.schema'; import { ValidateDarTurno } from './../../../utils/validateDarTurno'; import { userScheduler } from '../../../config.private'; +import { condicionPacienteValida } from '../../rup/condicion-paciente'; const router = express.Router(); const dbgTurno = debug('dbgTurno'); +function condicionPacienteTurnoInvalida(condicionPaciente: any): boolean { + return condicionPaciente !== undefined && condicionPaciente !== null && !condicionPacienteValida(condicionPaciente); +} + router.get('/turno/:id*?', async (req, res, next) => { try { @@ -55,6 +60,9 @@ function getCarpeta(nroDocumento, idOrganizacion) { } router.patch('/turno/agenda/:idAgenda', async (req, res, next) => { + if (condicionPacienteTurnoInvalida(req.body.condicionPaciente)) { + return next({ status: 400, message: 'Condición del paciente inválida' }); + } const continues = ValidateDarTurno.checkTurno(req.body); if (continues.valid) { @@ -82,6 +90,7 @@ router.patch('/turno/agenda/:idAgenda', async (req, res, next) => { nota: req.body.nota, link: req.body.link, motivoConsulta: req.body.motivoConsulta, + condicionPaciente: req.body.condicionPaciente || undefined, paciente: req.body.paciente, tipoPrestacion: req.body.tipoPrestacion, updatedAt: fecha, @@ -172,6 +181,9 @@ router.patch('/turno/agenda/:idAgenda', async (req, res, next) => { */ router.patch('/turno/:idTurno/bloque/:idBloque/agenda/:idAgenda/', async (req: any, res, next) => { + if (condicionPacienteTurnoInvalida(req.body.condicionPaciente)) { + return next({ status: 400, message: 'Condición del paciente inválida' }); + } const continues = ValidateDarTurno.checkTurno(req.body); const pacienteTurno = req.body.paciente; @@ -315,6 +327,7 @@ router.patch('/turno/:idTurno/bloque/:idBloque/agenda/:idAgenda/', async (req: a update[etiquetaLink] = req.body.link; update[etiquetaEmitidoPor] = req.body.emitidoPor ? req.body.emitidoPor : 'Gestión de pacientes'; update[etiquetaMotivoConsulta] = req.body.motivoConsulta; + update[bloqueTurno + '.condicionPaciente'] = req.body.condicionPaciente || undefined; update[estadoFacturacion] = req.body.estadoFacturacion; const etiquetaVideoConferencia = bloqueTurno + '.videoConferencia'; diff --git a/modules/turnos/schemas/turno.ts b/modules/turnos/schemas/turno.ts index b9fcfab3db..d10a396b02 100644 --- a/modules/turnos/schemas/turno.ts +++ b/modules/turnos/schemas/turno.ts @@ -3,6 +3,7 @@ import * as cie10 from '../../../core/term/schemas/cie10'; import { tipoPrestacionSchema } from '../../../core/tm/schemas/tipoPrestacion'; import { SnomedConcept } from '../../rup/schemas/snomed-concept'; import { PacienteSubSchema } from '../../../core-v2/mpi/paciente/paciente.schema'; +import { CONDICIONES_PACIENTE } from '../../rup/condicion-paciente'; const turnoSchema = new mongoose.Schema({ horaInicio: Date, @@ -64,6 +65,11 @@ const turnoSchema = new mongoose.Schema({ emitidoPor: String, // Agregamos para identificar desde donde se emitio el turno. Ej: appmobile. paciente: PacienteSubSchema, motivoConsulta: String, + condicionPaciente: { + type: String, + enum: [...CONDICIONES_PACIENTE, null], + required: false + }, tipoPrestacion: { type: tipoPrestacionSchema },