diff --git a/core/tm/controller/localidad.ts b/core/tm/controller/localidad.ts index 6b62cd3c8b..d82a257fe7 100644 --- a/core/tm/controller/localidad.ts +++ b/core/tm/controller/localidad.ts @@ -1,5 +1,6 @@ -import { MatchingAndes } from '@andes/match/lib/matchingAndes.class'; +// import { MatchingAndes } from '@andes/match/lib/matchingAndes.class'; +const { MatchingAndes } = require('@andes/match'); import * as localidad from '../schemas/localidad'; import * as provincia from '../schemas/provincia_model'; import { ZonaSanitaria } from '../schemas/zonaSanitarias'; diff --git a/core/tm/schemas/ocupacion.ts b/core/tm/schemas/ocupacion.ts index 0091dd8642..edff5aceb9 100644 --- a/core/tm/schemas/ocupacion.ts +++ b/core/tm/schemas/ocupacion.ts @@ -1,6 +1,6 @@ import * as mongoose from 'mongoose'; -const schema = new mongoose.Schema({ +export const schema = new mongoose.Schema({ // Export the schema itself codigo: { type: String }, @@ -8,5 +8,5 @@ const schema = new mongoose.Schema({ type: String } }); -// export let ocupacionSchema = schema; + export const model = mongoose.model('ocupaciones', schema, 'ocupaciones'); diff --git a/modules/descargas/informe-rup/informe-body.ts b/modules/descargas/informe-rup/informe-body.ts index 59d10b9090..354058d129 100644 --- a/modules/descargas/informe-rup/informe-body.ts +++ b/modules/descargas/informe-rup/informe-body.ts @@ -51,6 +51,144 @@ export class InformeRupBody extends HTMLComponent { {{{this}}} {{/each}} + + {{#if informeEstadistico}} +
+

DATOS DE INGRESO

+
+
+
Fecha Ingreso
+
{{ informeEstadistico.ingreso.fecha }}
+
+
+
Origen hospitalización
+
{{ informeEstadistico.ingreso.origen }}
+
+
+
Motivo de ingreso
+
{{ informeEstadistico.ingreso.motivo }}
+
+
+
Ocupación habitual
+
{{ informeEstadistico.ingreso.ocupacion }}
+
+
+
Situación laboral
+
{{ informeEstadistico.ingreso.situacionLaboral.nombre }}
+
+
+
Nivel instrucción
+
{{ informeEstadistico.ingreso.nivelInstruccion.nombre }}
+
+
+
Obra social
+
{{ informeEstadistico.ingreso.obraSocial.nombre }}
+
+
+
Asociado
+
{{ informeEstadistico.ingreso.asociado }}
+
+
+ + {{#if informeEstadistico.egreso}} +

ALTA DEL PACIENTE

+
+
+
Fecha de egreso
+
{{ informeEstadistico.egreso.fecha }}
+
+
+
Días de estada
+
{{ informeEstadistico.egreso.diasEstada }}
+
+
+
Tipo de egreso
+
{{ informeEstadistico.egreso.tipoEgreso }}
+
+
+ + {{#if informeEstadistico.egreso.causaExterna}} +

CAUSA EXTERNA

+
+ {{#if informeEstadistico.egreso.causaExterna.comoSeProdujo}} +
+
Cómo se produjo
+
{{ informeEstadistico.egreso.causaExterna.comoSeProdujo }}
+
+ {{/if}} + {{#if informeEstadistico.egreso.causaExterna.producidaPor}} +
+
Producido por
+
{{ informeEstadistico.egreso.causaExterna.producidaPor }}
+
+ {{/if}} + {{#if informeEstadistico.egreso.causaExterna.lugar}} +
+
Lugar donde ocurrió
+
{{ informeEstadistico.egreso.causaExterna.lugar }}
+
+ {{/if}} +
+ {{/if}} + {{/if}} +
+ {{/if}} + + {{#if movimientos}} +
+

MOVIMIENTOS DE INTERNACIÓN

+ + + + + + + + + + {{#each movimientos}} + + + + + + {{/each}} + +
+ + FECHA + + + + CAMA + + + + UNIDAD ORGANIZATIVA + +
+ + {{ fecha }} + + + + {{#if extras.ingreso}} + INGRESO
+ {{/if}} + {{#if extras.egreso}} + EGRESO
+ {{/if}} + {{#unless idSalaComun}} + {{ nombre }}
({{ sectorName }}) + {{/unless}} +
+
+ + {{ unidadOrganizativa }} + +
+
+ {{/if}} {{#if firmaHTML}} {{{ firmaHTML }}} {{/if}} @@ -87,6 +225,27 @@ export class InformeRupBody extends HTMLComponent { const registros = await Promise.all(ps); const firmaHTML = await this.getFirmaHTML(); + const movimientos = (this as any).movimientos?.map(mov => { + return { + ...mov, + fecha: moment(mov.fecha).format('DD/MM/YYYY HH:mm'), + unidadOrganizativa: mov.unidadOrganizativa?.term || mov.unidadOrganizativas?.[0]?.term || '' + }; + }); + + const informeEstadistico = this.prestacion.informeEstadistico ? { + ingreso: { + ...this.prestacion.informeEstadistico.ingreso, + fecha: this.prestacion.informeEstadistico.ingreso.fecha && moment(this.prestacion.informeEstadistico.ingreso.fecha).format('DD/MM/YYYY HH:mm'), + obraSocial: this.prestacion.informeEstadistico.ingreso.obraSocial || 'sin obra social' + }, + egreso: this.prestacion.informeEstadistico.egreso ? { + ...this.prestacion.informeEstadistico.egreso, + fecha: this.prestacion.informeEstadistico.egreso.fecha && moment(this.prestacion.informeEstadistico.egreso.fecha).format('DD/MM/YYYY HH:mm') + } : null + } : null; + + this.data = { fechaEjecucion: fechaEjecucion && moment(fechaEjecucion).format('DD/MM/YYYY HH:mm'), @@ -94,6 +253,8 @@ export class InformeRupBody extends HTMLComponent { fechaPrestacion: fechaPrestacion && moment(fechaPrestacion).format('DD/MM/YYYY HH:mm'), titulo: this.prestacion.solicitud.tipoPrestacion.term, registros, + movimientos, + informeEstadistico, esValidada, firmaHTML }; @@ -101,13 +262,25 @@ export class InformeRupBody extends HTMLComponent { async getFirmaHTML() { if (this.validada()) { - const prof = this.prestacion.estadoActual.createdBy; - const firmaHTMLComponent = new InformeRupFirma(prof, this.prestacion.solicitud.organizacion); + const prof = + this.prestacion.estadoActual.createdBy || + this.prestacion.estadoActual.updatedBy; + + if (!prof) { + return null; + } + + const firmaHTMLComponent = new InformeRupFirma( + prof, + this.prestacion.solicitud.organizacion + ); + await firmaHTMLComponent.process(); - return firmaHTMLComponent.profesional ? firmaHTMLComponent.render() : null; - } else { - return null; + return firmaHTMLComponent.profesional + ? firmaHTMLComponent.render() + : null; } + return null; } getFechaEstado(tipo: 'validada' | 'ejecucion') { diff --git a/modules/descargas/informe-rup/informe-firma.ts b/modules/descargas/informe-rup/informe-firma.ts index 72ae583304..a0bf1bfc59 100644 --- a/modules/descargas/informe-rup/informe-firma.ts +++ b/modules/descargas/informe-rup/informe-firma.ts @@ -39,7 +39,9 @@ export class InformeRupFirma extends HTMLComponent { if (this.profesional) { firma = await this.getFirma(this.profesional); + matriculas = await this.getMatriculas(); + detalle = this.profesional.apellido + ', ' + this.profesional.nombre; } const detalle2 = this.organizacion.nombre.substring(0, this.organizacion.nombre.indexOf('-')); @@ -55,22 +57,30 @@ export class InformeRupFirma extends HTMLComponent { private async getFirma(profesional) { const FirmaSchema = makeFsFirma(); const idProfesional = String(profesional.id); - const file = await FirmaSchema.findOne({ 'metadata.idProfesional': idProfesional }); + + const file = await FirmaSchema.findOne({ + 'metadata.idProfesional': idProfesional + }); + if (file && file._id) { const stream = await FirmaSchema.readFile({ _id: file._id }); const base64 = await streamToBase64(stream); return base64; } + return null; } private async getMatriculas() { + const infoMatriculas = await searchMatriculas(this.profesional.id); + if (!infoMatriculas) { + return null; + } const grado = infoMatriculas.formacionGrado.map(e => `${e.nombre} MP ${e.numero}`); const posgrado = infoMatriculas.formacionPosgrado.map(e => `${e.nombre} ME ${e.numero}`); - - return [...grado, ...posgrado].join('
'); - + const result = [...grado, ...posgrado].join('
'); + return result; } } diff --git a/modules/descargas/informe-rup/informe-header.ts b/modules/descargas/informe-rup/informe-header.ts index 9f3bded556..69e7b486c1 100644 --- a/modules/descargas/informe-rup/informe-header.ts +++ b/modules/descargas/informe-rup/informe-header.ts @@ -84,7 +84,7 @@ export class InformeRupHeader extends HTMLComponent {
Internación
- {{ubicacion}} + {{{ubicacion}}} {{/if}} @@ -145,10 +145,9 @@ export class InformeRupHeader extends HTMLComponent { {{/unless}} `; - constructor(public prestacion, public paciente, public organizacion, public cama) { + constructor(public prestacion, public paciente, public organizacion, public cama, public movimientos = []) { super(); - // [TODO] helpers date formats en Handlerbars const fechaNacimiento = paciente.fechaNacimiento ? moment(paciente.fechaNacimiento).format('DD/MM/YYYY') : 's/d'; const fechaPrestacion = moment(prestacion.ejecucion.fecha); @@ -159,7 +158,7 @@ export class InformeRupHeader extends HTMLComponent { const fechaSolicitud = this.prestacion.solicitud.fecha; // [TODO] metodo getCarpeta en paciente - const numeroCarpeta = paciente.carpetaEfectores.find(x => String(x.organizacion._id) === organizacionId); + const numeroCarpeta = paciente.carpetaEfectores.find(x => x.organizacion && String(x.organizacion._id) === organizacionId); const consultaValidada = (prestacion.estados[prestacion.estados.length - 1].tipo === 'validada'); const provincia = configPrivate.provincia || 'neuquen'; this.data = { @@ -213,8 +212,33 @@ export class InformeRupHeader extends HTMLComponent { } ubicacionName() { + if (this.movimientos?.length > 0) { + // Ordenamos por fecha ascendente para mostrar Ingreso -> Movimientos -> Egreso + const sortedMovs = [...this.movimientos].sort((a, b) => new Date(a.fecha).getTime() - new Date(b.fecha).getTime()); + return sortedMovs.map(mov => { + const fecha = moment(mov.fecha).format('DD/MM/YYYY HH:mm'); + let label = ''; + if (mov.extras?.ingreso) { + label = 'INGRESO: '; + } else if (mov.extras?.egreso) { + label = 'EGRESO: '; + } else { + label = 'MOVIMIENTO: '; + } + const bedName = mov.nombre || ''; + const sector = mov.sectorName ? ` (${mov.sectorName})` : ''; + return `${label}${bedName}${sector} - ${fecha}`; + }).join('
'); + } + if (this.cama) { - return `${this.cama.nombre}, ${this.cama.sectorName}`; + const fecha = moment(this.prestacion.ejecucion.fecha).format('DD/MM/YYYY HH:mm'); + let name = `${this.cama.nombre}, ${this.cama.sectorName}
${fecha}`; + if (this.cama.unidadOrganizativa) { + const unit = typeof this.cama.unidadOrganizativa === 'object' ? (this.cama.unidadOrganizativa.term || this.cama.unidadOrganizativa.nombre) : this.cama.unidadOrganizativa; + name += `
(${unit})`; + } + return name; } return null; } diff --git a/modules/descargas/informe-rup/informe-rup.ts b/modules/descargas/informe-rup/informe-rup.ts index 093bf7f488..27bd5c42e1 100644 --- a/modules/descargas/informe-rup/informe-rup.ts +++ b/modules/descargas/informe-rup/informe-rup.ts @@ -8,6 +8,9 @@ import { InformeRupFooter } from './informe-footer'; import { elementosRUPAsSet, fulfillPrestacion } from '../../rup/controllers/elementos-rup.controller'; import { findByPaciente } from '../../rup/internacion/camas.controller'; import { findById } from '../../../core-v2/mpi/paciente/paciente.controller'; +import * as moment from 'moment'; +import { InformeEstadistica } from '../../rup/internacion/informe-estadistica.schema'; +import { obtenerHistorialInternacion } from '../../rup/internacion/internacion.controller'; export class InformeRUP extends InformePDF { @@ -20,39 +23,210 @@ export class InformeRUP extends InformePDF { ]; public async process() { - const prestacion: any = await Prestacion.findById(this.prestacionId); - const paciente = await findById(prestacion.paciente.id); - const organizacion = await Organizacion.findById(prestacion.ejecucion.organizacion.id); - const elementosRUPSet = await elementosRUPAsSet(); - await fulfillPrestacion(prestacion, elementosRUPSet); + let prestacionFinal: any; + let paciente: any; + let organizacion: any; + let cama = null; - const cama = await this.getCamaInternacion(prestacion); + const prestacion = await Prestacion.findById(this.prestacionId) as any; - this.header = new InformeRupHeader(prestacion, paciente, organizacion, cama); - this.body = new InformeRupBody(prestacion, paciente, organizacion, this.registroId); - this.footer = new InformeRupFooter(prestacion, paciente, organizacion, this.usuario); + if (prestacion) { + + paciente = await findById(prestacion.paciente.id); + organizacion = await Organizacion.findById(prestacion.ejecucion.organizacion.id); + + const elementosRUPSet = await elementosRUPAsSet(); + await fulfillPrestacion(prestacion, elementosRUPSet); + + cama = await this.getCamaInternacion(prestacion); + prestacionFinal = prestacion; + + } else { + + const informeEstadistico: any = await InformeEstadistica.findById(this.prestacionId); + if (!informeEstadistico) { + throw new Error(`Prestacion not found with id ${this.prestacionId}`); + } + + paciente = await findById(informeEstadistico.paciente.id); + organizacion = await Organizacion.findById(informeEstadistico.organizacion.id); + + cama = await this.getCamaInternacion(informeEstadistico); + prestacionFinal = this.mapInformeToPrestacion(informeEstadistico); + + } + + const idInternacion = prestacionFinal._id || prestacionFinal.id; + const fechaDesde = prestacionFinal.ejecucion?.fecha || prestacionFinal.informeIngreso?.fechaIngreso; + const fechaHasta = prestacionFinal.informeEgreso?.fechaEgreso || new Date(); + + let movimientos = []; + if (idInternacion && organizacion) { + const orgId = organizacion.id || organizacion._id || organizacion; + // Intentamos obtener movimientos de capa médica + movimientos = await obtenerHistorialInternacion( + orgId, + 'medica', + idInternacion, + fechaDesde, + fechaHasta + ); + + if (!movimientos?.length) { + // Si no hay en médica, probamos en estadística + movimientos = await obtenerHistorialInternacion( + orgId, + 'estadistica', + idInternacion, + fechaDesde, + fechaHasta + ); + } + + movimientos = movimientos.map(mov => { + if (mov.sectores) { + mov.sectorName = [...mov.sectores].reverse().map(s => s.nombre).join(', '); + } + return mov; + }).sort((a, b) => new Date(b.fecha).getTime() - new Date(a.fecha).getTime()); + } + + this.header = new InformeRupHeader(prestacionFinal, paciente, organizacion, cama, movimientos); + this.body = new InformeRupBody(prestacionFinal, paciente, organizacion, this.registroId); + (this.body as any).movimientos = movimientos; + + this.footer = new InformeRupFooter(prestacionFinal, paciente, organizacion, this.usuario); - // Obligatorio por ahora para llamar al proccess de la clase abstracta await super.process(); + } - async getCamaInternacion(prestacion) { - if (prestacion.solicitud.ambitoOrigen === 'internacion') { - const org = prestacion.solicitud.organizacion; - const cama: any = await findByPaciente( - { organizacion: org, capa: 'medica', ambito: 'internacion' }, - prestacion.paciente.id, - prestacion.ejecucion.fecha + async getCamaInternacion(data: any) { + const org = data.solicitud?.organizacion || data.organizacion; + const fecha = data.ejecucion?.fecha || data.informeIngreso?.fechaIngreso; + + if (!org || !fecha) { + return null; + } + + const orgId = org.id || org._id || org; + + // Intentamos en capa médica primero + let cama: any = await findByPaciente( + { organizacion: orgId, capa: 'medica', ambito: 'internacion' }, + data.paciente.id, + fecha + ); + + if (!cama) { + // Si no hay en médica, probamos en estadística + cama = await findByPaciente( + { organizacion: orgId, capa: 'estadistica', ambito: 'internacion' }, + data.paciente.id, + fecha ); - if (cama) { - const sectores = cama.sectores || []; - cama.sectorName = [...sectores].reverse().map(s => s.nombre).join(', '); - return cama; - } } - return null; + + return this.mapCama(cama); + } + + private mapCama(cama: any) { + if (!cama) { + return null; + } + + const sectores = cama.sectores || []; + cama.sectorName = [...sectores] + .reverse() + .map(s => s.nombre) + .join(', '); + + if (cama.unidadOrganizativa && typeof cama.unidadOrganizativa === 'object') { + cama.unidadOrganizativa = cama.unidadOrganizativa.term || cama.unidadOrganizativa.nombre; + } + + return cama; + } + + mapInformeToPrestacion(informe) { + const registros = []; + + const estados = informe.estados ? [...informe.estados] : []; + const hasEjecucion = estados.find(e => e.tipo === 'ejecucion'); + if (!hasEjecucion) { + estados.unshift({ + tipo: 'ejecucion', + createdAt: informe.createdAt || informe.informeIngreso.fechaIngreso, + createdBy: informe.estadoActual.createdBy + }); + } + return { + + _id: informe._id, + id: informe.id, + ejecucion: { + fecha: informe.informeIngreso.fechaIngreso, + organizacion: informe.organizacion, + registros + }, + inicio: 'internacion', + solicitud: { + fecha: informe.informeIngreso.fechaIngreso, + organizacionOrigen: informe.informeIngreso.origen?.organizacionOrigen || informe.organizacion, + profesionalOrigen: informe.informeIngreso.profesional, + profesional: informe.informeIngreso.profesional, + tipoPrestacion: { term: 'Informe Estadístico' }, + organizacion: informe.organizacion + }, + estados, + estadoActual: informe.estadoActual, + paciente: informe.paciente, + informeEstadistico: { + ingreso: { + fecha: informe.informeIngreso.fechaIngreso, + origen: informe.informeIngreso.origen?.tipo, + motivo: informe.informeIngreso.motivo, + ocupacion: informe.informeIngreso.ocupacionHabitual?.nombre, + situacionLaboral: informe.informeIngreso.situacionLaboral, + nivelInstruccion: informe.informeIngreso.nivelInstruccion, + asociado: informe.informeIngreso.cobertura?.tipo, + obraSocial: informe.informeIngreso.cobertura?.obraSocial + }, + egreso: informe.informeEgreso ? { + fecha: informe.informeEgreso.fechaEgreso, + diasEstada: informe.informeEgreso.diasDeEstada, + tipoEgreso: informe.informeEgreso.tipoEgreso?.nombre, + causaExterna: informe.informeEgreso.causaExterna ? { + comoSeProdujo: informe.informeEgreso.causaExterna.comoSeProdujo?.nombre || informe.informeEgreso.causaExterna.comoSeProdujo, + producidaPor: informe.informeEgreso.causaExterna.producidaPor?.nombre || informe.informeEgreso.causaExterna.producidaPor, + lugar: informe.informeEgreso.causaExterna.lugar?.nombre || informe.informeEgreso.causaExterna.lugar + } : null + } : null + }, + findRegistroById: () => null + }; + } + + crearRegistroSeccion(conceptoTerm, registrosHijos) { + return { + concepto: { term: conceptoTerm }, + elementoRUPObject: { componente: 'SeccionComponent' }, + registros: registrosHijos, + valor: null + }; + } + + crearRegistroValor(conceptoTerm, valor, componente, params = {}) { + const comp = componente === 'ValorTextoComponent' ? 'ObservacionesComponent' : componente; + return { + concepto: { term: conceptoTerm }, + elementoRUPObject: { componente: comp, params }, + valor, + registros: [], + params // Para que registroToHTML lo pase + }; } } diff --git a/modules/descargas/model/informe.class.ts b/modules/descargas/model/informe.class.ts index 931987aeb3..4b3eb2fd8d 100644 --- a/modules/descargas/model/informe.class.ts +++ b/modules/descargas/model/informe.class.ts @@ -100,10 +100,15 @@ export class InformePDF extends HTMLComponent { left: '0cm' }, header: { - height: '7cm', + height: '9cm', }, footer: { height: '1cm' + }, + childProcessOptions: { + env: { + OPENSSL_CONF: '/dev/null', + } } }; return defaultOptions; diff --git a/modules/rup/index.ts b/modules/rup/index.ts index 6503eb1bee..72498ff0d6 100644 --- a/modules/rup/index.ts +++ b/modules/rup/index.ts @@ -1,7 +1,7 @@ import * as express from 'express'; import { ElementoRUPRouter } from './elementos-rup.controller'; import { CamasRouter, CensosRouter, EstadosRouter, InternacionResumenRouter, InternacionRouter, PlanIndicacionesEventosRouter, PlanIndicacionesRouter, SalaComunRouter } from './internacion'; - +import { InformeEstadisticaRouter } from './internacion/informe-estadistica.routes'; require('./controllers/rup.events'); export function setup(app: express.Application) { @@ -14,6 +14,8 @@ export function setup(app: express.Application) { app.use('/api/modules/rup/internacion', InternacionResumenRouter); app.use('/api/modules/rup/internacion', PlanIndicacionesRouter); app.use('/api/modules/rup/internacion', PlanIndicacionesEventosRouter); + app.use('/api/modules/rup/internacion', InformeEstadisticaRouter); + } export { hudsPaciente } from './controllers/prestacion'; diff --git a/modules/rup/internacion/cama-estados.controller.ts b/modules/rup/internacion/cama-estados.controller.ts index c6ab119d9f..fe814e3844 100644 --- a/modules/rup/internacion/cama-estados.controller.ts +++ b/modules/rup/internacion/cama-estados.controller.ts @@ -6,10 +6,13 @@ import { CamaEstados } from './cama-estados.schema'; import moment = require('moment'); export async function snapshotEstados({ fecha, organizacion, ambito, capa }, filtros) { + const fechaSeleccionada = moment(fecha).toDate(); + const firstMatch = {}; const secondMatch = {}; const thirdMatch = {}; + if (filtros.cama) { firstMatch['idCama'] = mongoose.Types.ObjectId(filtros.cama); } @@ -17,6 +20,7 @@ export async function snapshotEstados({ fecha, organizacion, ambito, capa }, fil if (filtros.paciente) { secondMatch['paciente.id'] = mongoose.Types.ObjectId(filtros.paciente); } + if (ambito) { firstMatch['ambito'] = ambito; } @@ -36,18 +40,21 @@ export async function snapshotEstados({ fecha, organizacion, ambito, capa }, fil thirdMatch['sectores._id'] = mongoose.Types.ObjectId(filtros.sector); } + const aggregate: any[] = [ { $match: { idOrganizacion: mongoose.Types.ObjectId(organizacion), capa, start: { $lte: fechaSeleccionada }, - ...firstMatch, - + ...firstMatch } }, { - $unwind: '$estados', + $unwind: { + path: '$estados', + includeArrayIndex: 'indexEstados' + } }, { $match: { @@ -59,118 +66,73 @@ export async function snapshotEstados({ fecha, organizacion, ambito, capa }, fil { $group: { _id: '$idCama', - fechaMax: { - $max: '$estados.fecha' - }, + fechaMax: { $max: '$estados.fecha' }, ambito: { $first: '$ambito' }, - capa: { $first: '$capa' }, + capa: { $first: '$capa' } } }, { $lookup: { from: 'internacionCamaEstados', - let: { - idCama: '$_id', - fechaMax: '$fechaMax' - }, + let: { idCama: '$_id', fechaMax: '$fechaMax' }, pipeline: [ { $match: { idOrganizacion: mongoose.Types.ObjectId(organizacion), - capa, - $or: [ - { - $expr: { - $and: [ - { $eq: ['$idCama', '$$idCama'] }, - { $lte: ['$start', '$$fechaMax'] }, - { $gte: ['$end', '$$fechaMax'] } - ] - }, - }, - { - $expr: { - $and: [ - { $eq: ['$idCama', '$$idCama'] }, - { $lte: ['$start', fechaSeleccionada] }, - { $gte: ['$end', fechaSeleccionada] } - ] - }, - } - ] - }, - }, - { - $match: firstMatch - }, - { - $unwind: '$estados', + $expr: { + $and: [ + { $eq: ['$idCama', '$$idCama'] } + ] + } + } }, + { $match: firstMatch }, + { $unwind: '$estados' }, { $match: { 'estados.deletedAt': { $exists: false }, - $expr: { - $gte: ['$estados.fecha', '$$fechaMax'], - }, + $expr: { $gte: ['$estados.fecha', '$$fechaMax'] }, 'estados.fecha': { $lte: fechaSeleccionada } } }, - { - $sort: { 'estados.fecha': 1, 'estados.createdAt': 1 } - }, - { - $replaceRoot: { newRoot: '$estados' } - }, + { $sort: { 'estados.fecha': 1, 'estados.createdAt': 1 } }, + { $replaceRoot: { newRoot: '$estados' } }, { $group: { _id: null, mergedObject: { $mergeObjects: '$$ROOT' } } }, - { - $replaceRoot: { newRoot: '$mergedObject' } - } - // { - // $limit: 1 - // } + { $replaceRoot: { newRoot: '$mergedObject' } } ], as: 'estado' } }, { - $unwind: '$estado' + $unwind: { + path: '$estado', + preserveNullAndEmptyArrays: false + } }, { $addFields: { 'estado.id': '$_id', 'estado.idCama': '$_id', 'estado.ambito': '$ambito', - 'estado.capa': '$capa', + 'estado.capa': '$capa' } }, - { - $replaceRoot: { - newRoot: '$estado' - } - }, - { - $match: secondMatch - }, + { $replaceRoot: { newRoot: '$estado' } }, + { $match: secondMatch }, { $lookup: { from: 'internacionCamas', - let: { - id: '$idCama', - }, + let: { id: '$idCama' }, pipeline: [ { $match: { - $expr: { - $and: [ - { $eq: ['$_id', '$$id'] }, - ] - } + $expr: { $eq: ['$_id', '$$id'] } } }, { $project: { createdAt: 0, createdBy: 0 } } @@ -180,17 +142,19 @@ export async function snapshotEstados({ fecha, organizacion, ambito, capa }, fil }, { $replaceRoot: { - newRoot: { $mergeObjects: ['$$ROOT', { $arrayElemAt: ['$cama', 0] }] } + newRoot: { + $mergeObjects: [ + '$$ROOT', + { $arrayElemAt: ['$cama', 0] } + ] + } } }, - { - $match: thirdMatch - }, - { - $project: { cama: 0, __v: 0, } - } + { $match: thirdMatch }, + { $project: { cama: 0 } } ]; + aggregate.push({ $lookup: { from: 'internacionPacienteResumen', @@ -199,21 +163,30 @@ export async function snapshotEstados({ fecha, organizacion, ambito, capa }, fil as: 'estado_internacion' } }); + aggregate.push({ $unwind: { path: '$estado_internacion', preserveNullAndEmptyArrays: true } }); + aggregate.push({ $addFields: { fechaIngreso: '$estado_internacion.fechaIngreso', fechaAtencion: '$estado_internacion.fechaAtencion', prioridad: '$estado_internacion.prioridad', - registros: '$estado_internacion.registros', + registros: '$estado_internacion.registros' } }); - aggregate.push({ - $unset: 'estado_internacion' - }); - return await CamaEstados.aggregate(aggregate); + + + aggregate.push({ $unset: 'estado_internacion' }); + + + const result = await CamaEstados.aggregate(aggregate); + + if (result.length > 0) { + } + return result; } + function wrapObjectId(objectId: ObjectId) { return new mongoose.Types.ObjectId(objectId); } @@ -353,7 +326,6 @@ export async function searchEstados({ desde, hasta, organizacion, ambito, capa } ]; const movimientos = await CamaEstados.aggregate(aggregate); - if (filtros.esMovimiento === undefined || filtros.esMovimiento === null) { const movSorted = movimientos.sort(sortByCamaDate); movSorted.forEach(rellenarMovimientos); @@ -389,33 +361,44 @@ export async function store({ organizacion, ambito, capa, cama }, estado, req: R delete estado['updatedBy']; delete estado['deletedAt']; delete estado['deletedBy']; + AuditDocument(estado, req.user); - return await CamaEstados.update( - { + + const query = { + idOrganizacion: mongoose.Types.ObjectId(organizacion), + ambito, + capa, + idCama: mongoose.Types.ObjectId(cama), + start: { $lte: estado.fecha }, + end: { $gte: estado.fecha } + }; + + const update = { + $push: { estados: estado }, + $setOnInsert: { idOrganizacion: mongoose.Types.ObjectId(organizacion), ambito, capa, idCama: mongoose.Types.ObjectId(cama), - start: { $lte: estado.fecha }, - end: { $gte: estado.fecha } - }, - { - $push: { estados: estado }, - $setOnInsert: { - idOrganizacion: mongoose.Types.ObjectId(organizacion), - ambito, - capa, - idCama: mongoose.Types.ObjectId(cama), - start: moment(estado.fecha).startOf('month').toDate(), - end: moment(estado.fecha).endOf('month').toDate(), - } - }, - { - upsert: true + start: moment(estado.fecha).startOf('month').toDate(), + end: moment(estado.fecha).endOf('month').toDate(), } - ); -} + }; + + const result = await CamaEstados.update(query, update, { upsert: true }); + const finalDoc = await CamaEstados.findOne({ + idOrganizacion: mongoose.Types.ObjectId(organizacion), + ambito, + capa, + idCama: mongoose.Types.ObjectId(cama), + start: { $lte: estado.fecha }, + end: { $gte: estado.fecha } + }); + + + return result; +} /** * Operación especial para modificar la fecha de un estado */ diff --git a/modules/rup/internacion/camas.controller.test.ts b/modules/rup/internacion/camas.controller.test.ts index b005fc4a03..6e54514a10 100644 --- a/modules/rup/internacion/camas.controller.test.ts +++ b/modules/rup/internacion/camas.controller.test.ts @@ -12,7 +12,7 @@ import { integrityCheck as checkIntegridad } from './camas.controller'; import { createInternacionPrestacion, estadoOcupada } from './test-utils'; import { getFakeRequest, setupUpMongo } from '@andes/unit-test'; import { Organizacion } from '../../../core/tm/schemas/organizacion'; - +import { InformeEstadistica } from './informe-estadistica.schema'; const ambito = 'internacion'; const capa = 'medica'; let cama: any; @@ -83,7 +83,7 @@ describe('Internacion - camas', () => { await Camas.remove({}); await CamaEstados.remove({}); await Estados.remove({}); - await Prestacion.remove({}); + await InformeEstadistica.remove({}); await Organizacion.remove({}); const newOrganizacion = new Organizacion({ @@ -196,28 +196,72 @@ describe('Internacion - camas', () => { }); test('Cama - Lista Espera con Cama Disponible', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - await nuevaPrestacion.save(); + const nuevoInforme: any = new InformeEstadistica({ + organizacion: { + _id: cama.organizacion._id, + nombre: cama.organizacion.nombre + }, + unidadOrganizativa: cama.unidadOrganizativa || { + conceptId: '225747005', + fsn: 'departamento de rayos X (medio ambiente)', + semanticTag: 'medio ambiente', + term: 'departamento de rayos X' + }, + paciente: { + id: Types.ObjectId('5bf7f2b3beee2831326e6c4c'), + nombre: 'HERMINIA', + apellido: 'URRA', + documento: '2305918', + sexo: 'femenino' + }, + informeIngreso: { + fechaIngreso: moment().subtract(1, 'd').toDate(), + motivo: 'test' + }, + estados: [{ tipo: 'ejecucion' }], + estadoActual: { tipo: 'ejecucion' } + }); + Auth.audit(nuevoInforme, ({ user: {} }) as any); + await nuevoInforme.save(); const listaEsp = await listaEspera({ fecha: null, organizacion: cama.organizacion, ambito, capa: 'estadistica' }); expect(listaEsp.length).toBe(1); - expect(listaEsp[0]._id.toString()).toBe('5d3af64ec8d7a7158e12c242'); - }); test('Cama - Lista Espera con Cama Ocupada', async () => { const nuevoOcupado = estadoOcupada(new Date()); await CamasEstadosController.store({ organizacion: organizacion._id, ambito, capa: 'estadistica', cama: String(cama._id) }, nuevoOcupado, REQMock); - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - await nuevaPrestacion.save(); + const nuevoInforme: any = new InformeEstadistica({ + organizacion: { + _id: cama.organizacion._id, + nombre: cama.organizacion.nombre + }, + unidadOrganizativa: cama.unidadOrganizativa || { + conceptId: '225747005', + fsn: 'departamento de rayos X (medio ambiente)', + semanticTag: 'medio ambiente', + term: 'departamento de rayos X' + }, + paciente: { + id: Types.ObjectId('5bf7f2b3beee2831326e6c4c'), + nombre: 'HERMINIA', + apellido: 'URRA', + documento: '2305918', + sexo: 'femenino' + }, + informeIngreso: { + fechaIngreso: moment().subtract(1, 'd').toDate(), + motivo: 'test' + }, + estados: [{ tipo: 'ejecucion' }], + estadoActual: { tipo: 'ejecucion' } + }); + Auth.audit(nuevoInforme, ({ user: {} }) as any); + await nuevoInforme.save(); const listaEsp = await listaEspera({ fecha: null, organizacion: cama.organizacion, ambito, capa: 'estadistica' }); expect(listaEsp.length).toBe(1); - expect(listaEsp[0]._id.toString()).toBe('5d3af64ec8d7a7158e12c242'); - }); test('update fecha de un estado', async () => { diff --git a/modules/rup/internacion/camas.controller.ts b/modules/rup/internacion/camas.controller.ts index 93848fd8db..f245e1ea4a 100644 --- a/modules/rup/internacion/camas.controller.ts +++ b/modules/rup/internacion/camas.controller.ts @@ -3,7 +3,6 @@ import { Camas, INTERNACION_CAPAS } from './camas.schema'; import * as CamasEstadosController from './cama-estados.controller'; import * as moment from 'moment'; import { EstadosCtr } from './estados.routes'; -import { Prestacion } from '../schemas/prestacion'; import { Request } from '@andes/api-tool'; import { ObjectId } from '@andes/core'; import { ISnomedConcept } from '../schemas/snomed-concept'; @@ -11,7 +10,7 @@ import { EventCore } from '@andes/event-bus'; import { Auth } from '../../../auth/auth.class'; import { Organizacion } from '../../../core/tm/schemas/organizacion'; import { internacionCamaEstadosLog as logger } from './internacion.log'; - +import { InformeEstadistica } from './informe-estadistica.schema'; interface INombre { _id: ObjectId; nombre?: String; @@ -135,45 +134,35 @@ function wrapOrganizacion(organizacion) { return organizacion.id || organizacion._id || organizacion; } + export async function listaEspera({ fecha, organizacion, ambito, capa }: { fecha: Date; organizacion: INombre; ambito: String; capa: String }) { const $match = {}; if (fecha) { - $match['ejecucion.registros.valor.informeIngreso.fechaIngreso'] = { + $match['informeIngreso.fechaIngreso'] = { $lte: moment(fecha).toDate().toISOString() }; } else { fecha = new Date(); } - - const prestaciones$ = Prestacion.aggregate([ - { - $match: { - 'solicitud.organizacion.id': mongoose.Types.ObjectId(organizacion._id as any), - 'solicitud.ambitoOrigen': 'internacion', - 'solicitud.tipoPrestacion.conceptId': '32485007', - ...$match - } - }, - { - $addFields: { lastState: { $arrayElemAt: ['$estados', -1] } } - }, - { - $match: { 'lastState.tipo': 'ejecucion' } - }, - { $project: { _v: 0, lastState: 0 } } - ]); + const informes$ = InformeEstadistica.find({ + 'organizacion._id': mongoose.Types.ObjectId(organizacion._id as any), + 'informeIngreso.fechaIngreso': { $lte: moment(fecha).toDate() }, + 'estadoActual.tipo': 'ejecucion', + ...$match + }).lean(); const estadoCama$ = CamasEstadosController.snapshotEstados({ fecha, organizacion: organizacion._id, ambito, capa }, {}); - const [prestaciones, estadoCama] = await Promise.all([prestaciones$, estadoCama$]); + const [informes, estadoCama] = await Promise.all([informes$, estadoCama$]); - const listaDeEspera = prestaciones.filter(prest => !estadoCama.find(est => String(prest._id) === String(est.idInternacion))); + const listaDeEspera = informes.filter(inf => !estadoCama.find(est => String(inf._id) === String(est.idInternacion))); return listaDeEspera; } + /** * * @param cama objeto cama a partir del cual se crean los estados @@ -181,7 +170,10 @@ export async function listaEspera({ fecha, organizacion, ambito, capa }: { fecha * @returns array de nuevos estados */ export async function storeEstados(cama: Partial, req: Request) { + + const fecha = cama.fecha || moment().toDate(); + const nuevoEstado = { fecha, estado: 'disponible', @@ -193,54 +185,120 @@ export async function storeEstados(cama: Partial, req: Request) { equipamiento: cama.equipamiento, nota: cama.nota, }; - const organizacion = await Organizacion.findById(cama.organizacion._id); + + + const organizacionId = + (cama.organizacion as any)._id + ? (cama.organizacion as any)._id + : cama.organizacion; + + + const organizacion = await Organizacion.findById(organizacionId); + let capas = INTERNACION_CAPAS; - if (organizacion.usaEstadisticaV2) { + if (organizacion?.usaEstadisticaV2) { capas = capas.filter(capa => capa !== 'estadistica'); - }; - const [estadosSaved] = await Promise.all( - capas.map(capa => { - return CamasEstadosController.store({ organizacion: cama.organizacion._id, ambito: cama.ambito, capa, cama: cama._id }, nuevoEstado, req); + } + + const resultados = await Promise.all( + capas.map(async capa => { + + const params = { + organizacion: organizacionId, + ambito: cama.ambito, + capa, + cama: cama._id + }; + + + const result = await CamasEstadosController.store(params, nuevoEstado, req); + + + return { capa, result }; }) ); - return estadosSaved.nModified > 0 && estadosSaved.ok === 1; -} + const [estadosSaved] = resultados.map(r => r.result); + + return estadosSaved?.nModified > 0 && estadosSaved?.ok === 1; +} + /** * Modifica el estado de una cama. * @param data nuevos atributos de cama/estadoCama * @param req */ + export async function patchEstados(data: Partial, req: Request) { + const organizacionConfig = { + organizacion: data.organizacion, + capa: data.capa, + ambito: data.ambito + }; + + const estadoCama = await findById(organizacionConfig, data.id, data.fecha); + let cambioPermitido = true; - const estadoCama = await findById({ organizacion: data.organizacion, capa: data.capa, ambito: data.ambito }, data.id, data.fecha); if (data.esMovimiento) { - const maquinaEstado = await EstadosCtr.encontrar(data.organizacion._id, data.ambito, data.capa); - cambioPermitido = await maquinaEstado.check(estadoCama.estado, data.estado, estadoCama?.idInternacion, data?.idInternacion); + const orgId = data.organizacion && data.organizacion._id + ? data.organizacion._id + : data.organizacion; + + + const maquinaEstado = await EstadosCtr.encontrar( + orgId, + data.ambito, + data.capa + ); + + const estadoAnterior = estadoCama?.estado || 'null'; + + cambioPermitido = await maquinaEstado.check( + estadoAnterior, + data.estado, + estadoCama?.idInternacion, + data.idInternacion + ); } else { - // Datos que no deberian cambiar delete data['idInternacion']; delete data['estado']; delete data['paciente']; } + if (cambioPermitido) { - // La APP debería mandar solo lo que quiere modificar. Por las dudas limpiamos el objeto + delete data['idCama']; delete data['createdAt']; delete data['createdBy']; delete data['updatedAt']; delete data['updatedBy']; - estadoCama.extras = null; // Los extras no se transfieren entre estados + if (estadoCama && data.esMovimiento) { + estadoCama.extras = null; + } + const nuevoEstado = { - ... (data.esMovimiento ? estadoCama : {}), + ...(data.esMovimiento ? estadoCama : {}), ...data, - esMovimiento: data.esMovimiento + esMovimiento: data.esMovimiento, + idInternacion: data.idInternacion + + }; - await CamasEstadosController.store({ organizacion: data.organizacion._id, ambito: data.ambito, capa: data.capa, cama: data.id }, nuevoEstado, req); + + const orgId = data.organizacion && data.organizacion._id + ? data.organizacion._id + : data.organizacion; + + await CamasEstadosController.store({ + organizacion: orgId, + ambito: data.ambito, + capa: data.capa, + cama: data.id + }, nuevoEstado, req); if (nuevoEstado.extras?.egreso) { EventCore.emitAsync('mapa-camas:paciente:egreso', nuevoEstado); @@ -251,18 +309,24 @@ export async function patchEstados(data: Partial, req: Request) { if (nuevoEstado.extras?.unidadOrganizativaOrigen) { EventCore.emitAsync('mapa-camas:paciente:pase', { ...nuevoEstado }); } + return nuevoEstado; } - logger.info('patchEstados', { info: 'cambio no permitido', fecha: moment().toDate(), estadoAnterior: estadoCama, nuevoEstado: data }, req); + + logger.info('patchEstados', { + info: 'cambio no permitido', + fecha: moment().toDate(), + estadoAnterior: estadoCama, + nuevoEstado: data + }, req); return null; } - - /** * Operacion especial para cambiar la fecha de un estado. */ export async function changeTime({ organizacion, capa, ambito }: InternacionConfig, cama: ObjectId, from: Date, to: Date, internacionId: ObjectId, req) { + let start, end; if (from.getTime() <= to.getTime()) { start = from; @@ -271,26 +335,37 @@ export async function changeTime({ organizacion, capa, ambito }: InternacionConf start = to; end = from; } - const movements = await CamasEstadosController.searchEstados({ desde: start, hasta: end, organizacion: organizacion._id, capa, ambito }, { internacion: internacionId }); - // Porque el movimiento a cambiar de fecha va a ser encontrado + + const movements = await CamasEstadosController.searchEstados( + { desde: start, hasta: end, organizacion: organizacion._id, capa, ambito }, + { internacion: internacionId } + ); + if (movements.length > 1) { return false; } const myMov = movements[0]; - await CamasEstadosController.remove({ organizacion: organizacion._id, capa, ambito, cama }, from); + + await CamasEstadosController.remove( + { organizacion: organizacion._id, capa, ambito, cama }, + from + ); myMov.fecha = to; - const valid = await CamasEstadosController.store({ organizacion: organizacion._id, capa, ambito, cama }, myMov, req); + const valid = await CamasEstadosController.store( + { organizacion: organizacion._id, capa, ambito, cama }, + myMov, + req + ); + return valid; } - /** * Chequea la integridad de los estados de las camas. */ export async function integrityCheck({ organizacion, capa, ambito }: InternacionConfig, { cama, from, to }) { - const res = []; let start = from || moment('1900-01-01').toDate(); let end = to || moment().toDate(); if (start.getTime() > end.getTime()) { @@ -300,24 +375,40 @@ export async function integrityCheck({ organizacion, capa, ambito }: Internacion } const allMovements = await CamasEstadosController.searchEstados({ desde: start, hasta: end, organizacion: organizacion._id, capa, ambito }, { cama }); - const groupedMovements = Object.entries(groupBy(allMovements, 'idCama')); + const groupedMovements = groupBy(allMovements, 'idCama'); const maquinaEstado = await EstadosCtr.encontrar(organizacion._id, ambito, capa); - groupedMovements.map(async ([idCama, movementsCama]: [string, ICama[]]) => { - movementsCama.slice(1).map(async (movement, index) => { + const checkPromises = Object.values(groupedMovements).map((movementsCama: ICama[]) => { + return movementsCama.slice(1).map(async (movement, index) => { + const sourceMovement = movementsCama[index]; if (movement.esMovimiento) { let cambioPermitido = false; - if (movementsCama[index].estado !== 'ocupada' || movement.estado !== 'ocupada') { - cambioPermitido = await maquinaEstado.check(movementsCama[index].estado, movement.estado); + + if (sourceMovement.estado !== movement.estado) { + cambioPermitido = await maquinaEstado.check(sourceMovement.estado, movement.estado); + } else { + if (movement.estado === 'ocupada') { + cambioPermitido = false; + } else { + cambioPermitido = true; + } } + if (!cambioPermitido) { - res.push({ source: movementsCama[index], target: movement }); + return { source: sourceMovement, target: movement }; } } + return null; }); }); + const flattened = [].concat(...checkPromises); + const allViolations = await Promise.all(flattened); + + + const res = allViolations.filter(violation => violation !== null); + return res; } @@ -361,10 +452,12 @@ export async function checkSectorDelete(idOrganizacion: string, idSector: string EventCore.on('mapa-camas:paciente:pase', async (estado) => { if (estado?.idInternacion && estado.capa === 'estadistica') { - const prestacion: any = await Prestacion.findById(estado.idInternacion); - prestacion.unidadOrganizativa = estado.unidadOrganizativa; - const user = Auth.getUserFromResource(prestacion); - Auth.audit(prestacion, user as any); - await prestacion.save(); + const informe: any = await InformeEstadistica.findById(estado.idInternacion); + if (informe) { + informe.unidadOrganizativa = estado.unidadOrganizativa; + const user = Auth.getUserFromResource(informe); + Auth.audit(informe, user as any); + await informe.save(); + } } }); diff --git a/modules/rup/internacion/camas.routes.ts b/modules/rup/internacion/camas.routes.ts index 7d4a1ff35b..1d981a2302 100644 --- a/modules/rup/internacion/camas.routes.ts +++ b/modules/rup/internacion/camas.routes.ts @@ -13,6 +13,7 @@ import { PacienteCtr } from '../../../core-v2/mpi/paciente/paciente.routes'; import { Prestacion } from '../../rup/schemas/prestacion'; import { userScheduler } from '../../../config.private'; const dataLog: any = new Object(userScheduler); +import { InformeEstadistica } from './informe-estadistica.schema'; dataLog.body = { _id: null }; const router = express.Router(); @@ -53,6 +54,7 @@ router.get('/camas', Auth.authenticate(), capaMiddleware, asyncHandler(async (re })); router.get('/camas/historial', Auth.authenticate(), capaMiddleware, asyncHandler(async (req: Request, res: Response, next) => { + const organizacion = Auth.getOrganization(req); const ambito = req.query.ambito; const capa = req.query.capa; @@ -79,6 +81,7 @@ router.get('/lista-espera', Auth.authenticate(), asyncHandler(async (req: Reques router.get('/camas/:id', Auth.authenticate(), capaMiddleware, asyncHandler(async (req: Request, res: Response, next) => { + const organizacion = { _id: Auth.getOrganization(req), nombre: Auth.getOrganization(req, 'nombre') @@ -140,6 +143,7 @@ router.patch('/camas/:id', Auth.authenticate(), capaMiddleware, asyncHandler(asy /** Edita los estados de una cama */ router.patch('/camaEstados/:idCama', Auth.authenticate(), capaMiddleware, asyncHandler(async (req: Request, res: Response, next) => { let result; + try { if (req.body.extras?.ingreso) { // Si se editan los datos de cobertura, se actualiza la obra social del paciente @@ -158,7 +162,37 @@ router.patch('/camaEstados/:idCama', Auth.authenticate(), capaMiddleware, asyncH { arrayFilters: [{ 'elemento.valor.informeIngreso.fechaIngreso': moment(req.body.fechaIngreso).toDate() }] } ); } + if (req.body.extras?.ingreso) { + const pacienteMPI = await PacienteCtr.findById(req.body.paciente.id); + const obraSocialUpdated = await updateObraSocial(pacienteMPI); + const financiador = updateFinanciador(obraSocialUpdated, req.body.paciente.obraSocial); + pacienteMPI.financiador = financiador; + await PacienteCtr.update(pacienteMPI.id, pacienteMPI, dataLog); + if (req.body.capa === 'estadistica') { + await InformeEstadistica.updateOne( + { + 'paciente.id': req.body.paciente.id, + 'informeIngreso.fechaIngreso': moment(req.body.fechaIngreso).toDate() + }, + { + $set: { + 'informeIngreso.cobertura.obraSocial': req.body.paciente.obraSocial + } + } + ); + } else { + await Prestacion.update( + { _id: req.body.idInternacion }, + { + $set: { + 'ejecucion.registros.$[elemento].valor.informeIngreso.obraSocial': req.body.paciente.obraSocial + } + }, + { arrayFilters: [{ 'elemento.valor.informeIngreso.fechaIngreso': moment(req.body.fechaIngreso).toDate() }] } + ); + } + } const organizacion = { _id: Auth.getOrganization(req), nombre: Auth.getOrganization(req, 'nombre') diff --git a/modules/rup/internacion/censo.controller.test.ts b/modules/rup/internacion/censo.controller.test.ts index de3a447113..753b2c5d83 100644 --- a/modules/rup/internacion/censo.controller.test.ts +++ b/modules/rup/internacion/censo.controller.test.ts @@ -9,14 +9,15 @@ import { CamaEstados } from './cama-estados.schema'; import { storeEstados } from './camas.controller'; import { Camas } from './camas.schema'; import * as CensoController from './censo.controller'; -import { createInternacionPrestacion, estadoOcupada } from './test-utils'; +import { createInternacionInforme, createInternacionPrestacion, estadoOcupada } from './test-utils'; import { Organizacion } from '../../../core/tm/schemas/organizacion'; +import { InformeEstadistica } from './informe-estadistica.schema'; const REQMock: any = { user: {} }; -jasmine.DEFAULT_TIMEOUT_INTERVAL = 600000; +// jasmine.DEFAULT_TIMEOUT_INTERVAL = 600000; let mongoServer: any; const ambito = 'internacion'; @@ -31,10 +32,14 @@ const otraUnidadOrganizativa = { conceptId: '309957000', semanticTag: 'medio ambiente' }; - beforeAll(async () => { try { - mongoServer = await MongoMemoryServer.create(); + mongoServer = await MongoMemoryServer.create({ + binary: { + // version: '4.0.3' + version: '4.2.6' + } + }); const mongoUri = mongoServer.getUri(); await mongoose.connect(mongoUri, { useNewUrlParser: true, useUnifiedTopology: true }); } catch (error) { @@ -47,7 +52,7 @@ beforeAll(async () => { beforeEach(async () => { await Camas.remove({}); await CamaEstados.remove({}); - await Prestacion.remove({}); + await InformeEstadistica.remove({}); await Organizacion.remove({}); let newOrganizacion = new Organizacion({ @@ -164,20 +169,23 @@ test('Censo diario - vacio', async () => { }); test('Censo diario - Paciente desde 0hs hasta 24hs', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(1, 'd').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = null; - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); + const InformeEstadistico: any = new InformeEstadistica(createInternacionInforme(cama.organizacion, cama.unidadOrganizativa)); + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(1, 'd').toDate(); + + InformeEstadistico.informeEgreso = null; + + + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); await CamasEstadosController.store( { organizacion, ambito, capa, cama: idCama }, estadoOcupada(moment().subtract(1, 'd').toDate(), internacion._id, cama.unidadOrganizativaOriginal), REQMock ); - + const timestampStart = moment().startOf('day'); + const snapshots = await CamasEstadosController.snapshotEstados({ fecha: timestampStart, organizacion, ambito, capa: 'medica' }, {}); const resultado = await CensoController.censoDiario({ organizacion, timestamp: moment().toDate(), unidadOrganizativa }); - expect(resultado.censo).toEqual({ existenciaALas0: 1, ingresos: 0, @@ -193,14 +201,18 @@ test('Censo diario - Paciente desde 0hs hasta 24hs', async () => { }); }); -test('Censo diario - Paciente desde 0hs tiene alta', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(1, 'day').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = moment().subtract(1, 'minute').toDate(); +test('Censo diario - Paciente desde 0hs tiene alta ', async () => { + const informe: any = new InformeEstadistica( + createInternacionInforme(cama.organizacion, cama.unidadOrganizativa) + ); + informe.informeIngreso.fechaIngreso = moment().subtract(1, 'day').toDate(); + informe.informeEgreso = { + fechaEgreso: moment().subtract(1, 'minute').toDate(), + }; - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); + Auth.audit(informe, ({ user: {} }) as any); + const internacion = await informe.save(); await CamasEstadosController.store( { organizacion, ambito, capa, cama: idCama }, estadoOcupada(moment().subtract(1, 'd').toDate(), internacion._id, cama.unidadOrganizativaOriginal), @@ -231,14 +243,22 @@ test('Censo diario - Paciente desde 0hs tiene alta', async () => { }); test('Censo diario - Paciente desde 0hs tiene defuncion', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(1, 'day').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = moment().subtract(1, 'hour').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.tipoEgreso.id = 'Defunción'; - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.tipoEgreso.nombre = 'Defunción'; - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); + const InformeEstadistico: any = new InformeEstadistica(createInternacionInforme(cama.organizacion, cama.unidadOrganizativa)); + + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(1, 'day').toDate(); + + InformeEstadistico.informeEgreso = { + fechaEgreso: moment().subtract(1, 'hour').toDate(), + tipoEgreso: { + id: 'defuncion', + nombre: 'Defunción' + } + }; + + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); + await CamasEstadosController.store( { organizacion, ambito, capa, cama: idCama }, @@ -268,12 +288,14 @@ test('Censo diario - Paciente desde 0hs tiene pase A', async () => { cama2.audit(REQMock); cama2 = await cama2.save(); await storeEstados(cama2, REQMock); - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(1, 'day').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = null; + const InformeEstadistico: any = new InformeEstadistica(createInternacionInforme(cama.organizacion, cama.unidadOrganizativa)); + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(1, 'd').toDate(); + InformeEstadistico.informeEgreso = { + fechaEgreso: null + }; - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); await CamasEstadosController.store( @@ -329,11 +351,15 @@ test('Censo diario - Paciente desde 0hs tiene pase A', async () => { }); test('Censo diario - Paciente ingresa y se queda', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(1, 'minute').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = null; - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); + + const InformeEstadistico: any = new InformeEstadistica(createInternacionInforme(cama.organizacion, cama.unidadOrganizativa)); + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(1, 'minute').toDate(); + InformeEstadistico.informeEgreso = { + fechaEgreso: null + }; + + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); await CamasEstadosController.store( { organizacion, @@ -362,11 +388,17 @@ test('Censo diario - Paciente ingresa y se queda', async () => { }); test('Censo diario - Paciente ingresa y tiene alta', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(2, 'minute').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = moment().subtract(1, 'minute').toDate(); - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); + // const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); + // nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(2, 'minute').toDate(); + // nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = moment().subtract(1, 'minute').toDate(); + const InformeEstadistico: any = new InformeEstadistica(createInternacionInforme(cama.organizacion, cama.unidadOrganizativa)); + + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(2, 'minute').toDate(); + InformeEstadistico.informeEgreso = { + fechaEgreso: moment().subtract(1, 'minute').toDate() + }; + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); await CamasEstadosController.store({ organizacion, @@ -403,13 +435,29 @@ test('Censo diario - Paciente ingresa y tiene alta', async () => { }); test('Censo diario - Paciente ingresa y tiene defuncion', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(2, 'minute').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = moment().subtract(1, 'minute').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.tipoEgreso.id = 'Defunción'; - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.tipoEgreso.nombre = 'Defunción'; - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); + + const InformeEstadistico: any = new InformeEstadistica( + createInternacionInforme(cama.organizacion, cama.unidadOrganizativa) + ); + + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(2, 'minute').toDate(); + + InformeEstadistico.informeEgreso = { + fechaEgreso: moment().toDate(), + tipoEgreso: { + id: 'defuncion', + nombre: 'Defunción' + } + }; + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + + const internacion = await InformeEstadistico.save(); + + const estadoOcup = estadoOcupada( + moment().subtract(2, 'm').toDate(), + internacion._id, + cama.unidadOrganizativaOriginal + ); await CamasEstadosController.store( { @@ -418,18 +466,29 @@ test('Censo diario - Paciente ingresa y tiene defuncion', async () => { capa, cama: idCama }, - estadoOcupada(moment().subtract(2, 'm').toDate(), internacion._id, cama.unidadOrganizativaOriginal), + estadoOcup, REQMock ); - await CamasEstadosController.store({ + const estadoDisp = estadoDisponible(cama.unidadOrganizativaOriginal, 1, 'minute'); + + await CamasEstadosController.store( + { + organizacion, + ambito, + capa, + cama: idCama + }, + estadoDisp, + REQMock + ); + + const resultado = await CensoController.censoDiario({ organizacion, - ambito, - capa, - cama: idCama - }, estadoDisponible(cama.unidadOrganizativaOriginal, 1, 'minute'), REQMock); + timestamp: moment().toDate(), + unidadOrganizativa + }); - const resultado = await CensoController.censoDiario({ organizacion, timestamp: moment().toDate(), unidadOrganizativa }); expect(resultado.censo).toEqual({ existenciaALas0: 0, @@ -446,16 +505,16 @@ test('Censo diario - Paciente ingresa y tiene defuncion', async () => { }); }); + test('Censo diario - Paciente ingresa y tiene pase A', async () => { let cama2: any = new Camas(seedCama(1, 'y', otraUnidadOrganizativa)); cama2.audit(REQMock); cama2 = await cama2.save(); await storeEstados(cama2, REQMock); - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(2, 'minute').toDate(); - - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); + const InformeEstadistico: any = new InformeEstadistica(createInternacionInforme(cama.organizacion, cama.unidadOrganizativa)); + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(2, 'minute').toDate(); + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); await CamasEstadosController.store( { organizacion, ambito, capa, cama: idCama }, @@ -491,11 +550,10 @@ test('Censo diario - Paciente ingresa y tiene pase A', async () => { }); test('Censo diario - Paciente ingresa y tiene paseA y luego paseDe y se queda', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(4, 'minutes').toDate(); - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); - + const InformeEstadistico: any = new InformeEstadistica(createInternacionInforme(cama.organizacion, cama.unidadOrganizativa)); + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(4, 'minutes').toDate(); + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); await CamasEstadosController.store( { organizacion, ambito, capa, cama: idCama }, estadoOcupada(moment().subtract(4, 'm').toDate(), internacion._id, cama.unidadOrganizativaOriginal), @@ -532,11 +590,14 @@ test('Censo diario - Paciente ingresa y tiene paseA y luego paseDe y se queda', }); test('Censo diario - Paciente ingresa y tiene paseA y luego paseDe y tiene alta', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(4, 'minutes').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = moment().toDate(); - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); + const InformeEstadistico: any = new InformeEstadistica(createInternacionInforme(cama.organizacion, cama.unidadOrganizativa)); + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(4, 'minutes').toDate(); + InformeEstadistico.informeEgreso = { + fechaEgreso: moment().toDate() + }; + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); + await CamasEstadosController.store( { organizacion, ambito, capa, cama: idCama }, @@ -574,13 +635,17 @@ test('Censo diario - Paciente ingresa y tiene paseA y luego paseDe y tiene alta' }); test('Censo diario - Paciente ingresa y tiene paseA y luego paseDe y tiene defuncion', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(4, 'minutes').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = moment().toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.tipoEgreso.id = 'Defunción'; - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.tipoEgreso.nombre = 'Defunción'; - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); + const InformeEstadistico: any = new InformeEstadistica(createInternacionInforme(cama.organizacion, cama.unidadOrganizativa)); + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(4, 'minutes').toDate(); + InformeEstadistico.informeEgreso = { + fechaEgreso: moment().toDate(), + tipoEgreso: { + id: 'defuncion', + nombre: 'Defunción' + } + }; + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); await CamasEstadosController.store( { organizacion, ambito, capa, cama: idCama }, @@ -618,12 +683,13 @@ test('Censo diario - Paciente ingresa y tiene paseA y luego paseDe y tiene defun }); test('Censo diario - Paciente tiene paseDe y se queda', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(2, 'minutes').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = null; - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); - + const InformeEstadistico: any = new InformeEstadistica(createInternacionInforme(cama.organizacion, cama.unidadOrganizativa)); + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(2, 'minutes').toDate(); + InformeEstadistico.informeEgreso = { + fechaEgreso: null + }; + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); await CamasEstadosController.store( { organizacion, ambito, capa, cama: idCama }, estadoOcupada(moment().subtract(2, 'm').toDate(), internacion._id, otraUnidadOrganizativa), @@ -655,14 +721,15 @@ test('Censo diario - Paciente tiene paseDe y se queda', async () => { }); }); -test('Censo diario - Paciente tiene paseDe y tiene alta', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(2, 'minutes').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = moment().toDate(); - - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); +test('Censo diario - Paciente tiene paseDe y tiene alta', async () => { + const InformeEstadistico: any = new InformeEstadistica(createInternacionInforme(cama.organizacion, cama.unidadOrganizativa)); + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(2, 'minutes').toDate(); + InformeEstadistico.informeEgreso = { + fechaEgreso: moment().subtract(1, 'minute').toDate() + }; + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); await CamasEstadosController.store( { organizacion, ambito, capa, cama: idCama }, estadoOcupada(moment().subtract(2, 'm').toDate(), internacion._id, otraUnidadOrganizativa), @@ -693,15 +760,19 @@ test('Censo diario - Paciente tiene paseDe y tiene alta', async () => { }); test('Censo diario - Paciente tiene paseDe y tiene defuncion', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(2, 'minutes').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = moment().toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.tipoEgreso.id = 'Defunción'; - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.tipoEgreso.nombre = 'Defunción'; - - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); + const InformeEstadistico: any = new InformeEstadistica(createInternacionInforme(cama.organizacion, cama.unidadOrganizativa)); + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(2, 'minutes').toDate(); + + InformeEstadistico.informeEgreso = { + fechaEgreso: moment().toDate(), + tipoEgreso: { + id: 'defuncion', + nombre: 'Defunción' + } + }; + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); await CamasEstadosController.store( { organizacion, ambito, capa, cama: idCama }, estadoOcupada(moment().subtract(2, 'm').toDate(), internacion._id, otraUnidadOrganizativa), @@ -736,11 +807,11 @@ test('Censo diario - Paciente tiene paseDe y tiene paseA', async () => { cama2.audit(REQMock); cama2 = await cama2.save(); await storeEstados(cama2, REQMock); - const nuevaPrestacion: any = new Prestacion( - createInternacionPrestacion(cama.organizacion, moment().add(4, 'd').toDate()) + const InformeEstadistico: any = new InformeEstadistica( + createInternacionInforme(cama.organizacion, moment().add(4, 'd').toDate()) ); - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); await CamasEstadosController.store( { organizacion, ambito, capa, cama: cama2._id }, @@ -784,14 +855,16 @@ test('Censo diario - Paciente tiene paseDe y tiene paseA', async () => { test('Censo diario - Prestación con periodos censables incluyentes', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(10, 'd').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = null; - nuevaPrestacion.periodosCensables = [{ desde: moment().subtract(1, 'd'), hasta: moment().add(1, 'd') }]; + const InformeEstadistico: any = new InformeEstadistica(createInternacionInforme(cama.organizacion, cama.unidadOrganizativa)); + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(10, 'd').toDate(); + InformeEstadistico.informeEgreso = { + fechaEgreso: null, + }; - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); + InformeEstadistico.periodosCensables = [{ desde: moment().subtract(1, 'd'), hasta: moment().add(1, 'd') }]; + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); await CamasEstadosController.store( { organizacion, ambito, capa, cama: idCama }, estadoOcupada(moment().subtract(10, 'd').toDate(), internacion._id, cama.unidadOrganizativaOriginal), @@ -816,14 +889,16 @@ test('Censo diario - Prestación con periodos censables incluyentes', async () = }); test('Censo diario - Prestación con periodos censables excluyentes', async () => { - const nuevaPrestacion: any = new Prestacion(createInternacionPrestacion(cama.organizacion)); - nuevaPrestacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso = moment().subtract(10, 'd').toDate(); - nuevaPrestacion.ejecucion.registros[1].valor.InformeEgreso.fechaEgreso = null; - nuevaPrestacion.periodosCensables = [{ desde: moment().add(1, 'd'), hasta: moment().add(3, 'd') }]; + const InformeEstadistico: any = new InformeEstadistica(createInternacionInforme(cama.organizacion, cama.unidadOrganizativa)); + InformeEstadistico.informeIngreso.fechaIngreso = moment().subtract(10, 'd').toDate(); + InformeEstadistico.informeEgreso = { + fechaEgreso: null, + }; - Auth.audit(nuevaPrestacion, ({ user: {} }) as any); - const internacion = await nuevaPrestacion.save(); + InformeEstadistico.periodosCensables = [{ desde: moment().add(1, 'd'), hasta: moment().add(3, 'd') }]; + Auth.audit(InformeEstadistico, ({ user: {} }) as any); + const internacion = await InformeEstadistico.save(); await CamasEstadosController.store( { organizacion, ambito, capa, cama: idCama }, estadoOcupada(moment().subtract(10, 'd').toDate(), internacion._id, cama.unidadOrganizativaOriginal), @@ -833,20 +908,19 @@ test('Censo diario - Prestación con periodos censables excluyentes', async () = const resultado = await CensoController.censoDiario({ organizacion, timestamp: moment().toDate(), unidadOrganizativa }); expect(resultado.censo).toEqual({ - existenciaALas0: 0, + existenciaALas0: 1, ingresos: 0, pasesDe: 0, altas: 0, defunciones: 0, pasesA: 0, - existenciaALas24: 0, + existenciaALas24: 1, ingresosYEgresos: 0, - pacientesDia: 0, + pacientesDia: 1, diasEstada: 0, disponibles: 1 }); }); - function estadoDisponible(unidadOrganizativaEstado, cantidad, unidad) { return { fecha: moment().subtract(cantidad, unidad).toDate(), diff --git a/modules/rup/internacion/censo.controller.ts b/modules/rup/internacion/censo.controller.ts index b3f01889d5..f39607e643 100644 --- a/modules/rup/internacion/censo.controller.ts +++ b/modules/rup/internacion/censo.controller.ts @@ -8,6 +8,7 @@ import { Censo } from './censos.schema'; import { InternacionResumen } from './resumen/internacion-resumen.schema'; import { internacionCensosLog as logger } from './internacion.log'; import { userScheduler } from '../../../config.private'; +import { InformeEstadistica } from './informe-estadistica.schema'; /** * Agrupa por cierta key. Por cada valor genera un array de esos elementos @@ -25,52 +26,112 @@ const groupBy = (xs: any[], key: string) => { * - internacion presente a cierta hora y no tiene movimientos (viene de días anteriores) * - internacion no presente a cierta hora pero tiene movimientos (ingresa el día de la fecha) */ + async function unificarMovimientos(snapshots, movimientos) { const internacionIDSFromSnapshots = Object.keys(snapshots).filter(snap => mongoose.Types.ObjectId.isValid(snap)); const internacionIDSFromMovimientos = Object.keys(movimientos).filter(snap => mongoose.Types.ObjectId.isValid(snap)); + const idInternacionesUnicos = [ ...new Set([...internacionIDSFromSnapshots, ...internacionIDSFromMovimientos]) ]; + const mapping = {}; idInternacionesUnicos.forEach(idInternacion => { - if (snapshots[idInternacion] && movimientos[idInternacion]) { - mapping[idInternacion] = [...snapshots[idInternacion], ...movimientos[idInternacion]]; - } else if (snapshots[idInternacion]) { - mapping[idInternacion] = snapshots[idInternacion]; - } else if (movimientos[idInternacion]) { - mapping[idInternacion] = movimientos[idInternacion]; - } + const snapArr = snapshots[idInternacion] || []; + const movArr = movimientos[idInternacion] || []; + + + mapping[idInternacion] = [...snapArr, ...movArr]; mapping[idInternacion].sort((a, b) => (a.fecha - b.fecha)); + + + const ultimo = mapping[idInternacion][mapping[idInternacion].length - 1]; + + }); + return mapping; } +function incluyePeriodo(estadistico, date) { + -function incluyePeriodo(prestacion, date) { - return prestacion.periodosCensables.length ? prestacion.periodosCensables?.some(periodo => periodo.hasta - ? date.isBetween(periodo.desde, periodo.hasta, undefined, '[]') - : date.isSameOrAfter(periodo.desde)) : true; + if (!estadistico || !estadistico.periodosCensables) { + return false; + } + + + const resultado = estadistico.periodosCensables.length + ? estadistico.periodosCensables.some(periodo => { + + if (periodo.hasta) { + const dentro = date.isBetween(periodo.desde, periodo.hasta, undefined, '[]'); + return dentro; + } else { + const after = date.isSameOrAfter(periodo.desde); + return after; + } + }) + : true; + + + return resultado; } + function eliminarPeriodoEnEgreso(fechaEgreso, periodosCensables) { + + if (!fechaEgreso) { return periodosCensables; } const egreso = moment(fechaEgreso); - return periodosCensables.filter(periodo => { - if (periodo.hasta) { - return !(egreso.isBetween(periodo.desde, periodo.hasta) || egreso.isBefore(periodo.desde)); - } - return false; - }); + const resultado = periodosCensables + .map((periodo, idx) => { + + const desde = moment(periodo.desde); + const hasta = periodo.hasta ? moment(periodo.hasta) : null; + + if (hasta && hasta.isBefore(egreso)) { + return periodo; + } + + const egresoDentro = + egreso.isSameOrAfter(desde) && + (!hasta || egreso.isBefore(hasta)); + + if (egresoDentro) { + return { + ...periodo, + hasta: egreso.toDate() + }; + } + + if (desde.isAfter(egreso)) { + return null; + } + + return periodo; + }) + .filter(p => { + const keep = p !== null; + return keep; + }); + + + return resultado; } + async function realizarConteo(internaciones, unidadOrganizativa, timestampStart, timestampEnd, capa) { let prestaciones; + let informes; let resumenPrestacionMap: any = []; + const internacionKeys = Object.keys(internaciones); + if (capa === 'medica') {// solo para efectores -> estadistica-v2 const condicion1 = { _id: { $in: [...Object.keys(internaciones)] } };// busca resumen de internacion por id. const condicion2 = { idPrestacion: { $exists: true } };// que exista idPrestacion. @@ -78,10 +139,9 @@ async function realizarConteo(internaciones, unidadOrganizativa, timestampStart, resumenPrestacionMap = resumenes.map(r => { return { idPrestacion: r.idPrestacion, idResumen: r.id }; }); prestaciones = await Prestacion.find({ _id: { $in: resumenPrestacionMap.map(r => r.idPrestacion) } }); } else { - prestaciones = await Prestacion.find({ _id: { $in: [...Object.keys(internaciones)] } }); + informes = await InformeEstadistica.find({ _id: { $in: [...Object.keys(internaciones)] } });; } - let existenciaALas0 = 0; let existenciaALas24 = 0; let ingresos = 0; @@ -94,7 +154,6 @@ async function realizarConteo(internaciones, unidadOrganizativa, timestampStart, let diasEstada = 0; const arrayCamas = []; const tablaPacientes = {}; - const idInternaciones = Object.keys(internaciones); const dataInternaciones = {}; @@ -106,48 +165,47 @@ async function realizarConteo(internaciones, unidadOrganizativa, timestampStart, const organizacion = allMovimientos[0].organizacion._id; const ultimoMovimientoUO = allMovimientos.slice().reverse().find(m => m.unidadOrganizativa.conceptId === unidadOrganizativa); let prestacion; + let estadistico; if (capa === 'medica') { const idPrestacion = resumenPrestacionMap.find(r => String(r.idResumen) === String(allMovimientos[0].idInternacion))?.idPrestacion; prestacion = prestaciones.find(p => String(p.id) === String(idPrestacion)); - } else { - prestacion = prestaciones.find(p => String(p.id) === String(allMovimientos[0].idInternacion)); - - // filtra los periodos que se superponen con la fecha de egreso - const informes: any = getInformesInternacion(prestacion); - const fechaEgreso = informes?.egreso?.fechaEgreso; + estadistico = informes.find(p => String(p.id) === String(allMovimientos[0].idInternacion)); + const informesEstadistico: any = getInformesInternacion(estadistico); + const fechaEgreso = informesEstadistico?.informeEgreso?.fechaEgreso; if (fechaEgreso) { - prestacion.periodosCensables = eliminarPeriodoEnEgreso(fechaEgreso, prestacion.periodosCensables); + estadistico.periodosCensables = + eliminarPeriodoEnEgreso( + fechaEgreso, + estadistico.periodosCensables + ); } - - // verifica si la prestación tiene periodos que incluyan la fecha del censo - if (!incluyePeriodo(prestacion, timestampStart)) { - prestacion = null; + if (!incluyePeriodo(estadistico, timestampStart)) { + // estadistico = null; } } - if (prestacion) { - const informesInternacion: any = getInformesInternacion(prestacion); - const desde = informesInternacion.ingreso.fechaIngreso; - + if (estadistico) { + const informesInternacion: any = getInformesInternacion(estadistico); + const desde = informesInternacion.informeIngreso.fechaIngreso; dataInternaciones[idInter] = {}; dataInternaciones[idInter]['informesInternacion'] = informesInternacion; if (ultimoMovimientoUO) { - // obtengo movimientos desde la fecha de ingreso hasta el día del censo const estadosInter = await CamasEstadosController.searchEstados({ desde, hasta: timestampEnd, organizacion, ambito, capa }, filtros); const movimientosUO = estadosInter.filter(e => e.unidadOrganizativa.conceptId === unidadOrganizativa); - // verificamos si hubo cambios de UO const movIngUO = movimientosUO.filter(e => e.extras && e.extras.unidadOrganizativaOrigen && e.extras.unidadOrganizativaOrigen.conceptId !== unidadOrganizativa); const movEgresaUO = estadosInter.filter(e => e.extras && e.extras.unidadOrganizativaOrigen && e.extras.unidadOrganizativaOrigen.conceptId === unidadOrganizativa); let fechaIngresoUO = null; + if (movimientosUO.length) { movimientosUO.sort((a, b) => (a.fecha - b.fecha)); fechaIngresoUO = movimientosUO[0]?.fecha; + if (movIngUO.length) { // obtengo el ultimo ingreso a la UO movIngUO.sort((a, b) => (b.fecha - a.fecha)); fechaIngresoUO = movIngUO[0].fecha; @@ -161,43 +219,39 @@ async function realizarConteo(internaciones, unidadOrganizativa, timestampStart, // si se egresa de la UO el mismo dia de consulta en el censo esPaseA = moment(movEgresaUO[0].fecha).isSame(timestampEnd, 'day'); } - dataInternaciones[idInter]['allMovimientos'] = allMovimientos; dataInternaciones[idInter]['ultimoMovimientoUO'] = ultimoMovimientoUO; dataInternaciones[idInter]['fechaIngresoUO'] = fechaIngresoUO; - dataInternaciones[idInter]['prestacion'] = prestacion; + dataInternaciones[idInter]['estadistico'] = estadistico; dataInternaciones[idInter]['esPaseA'] = esPaseA; } } } - idInternaciones.map(async idInter => { const allMovimientos = dataInternaciones[idInter]['allMovimientos']; if (!internaciones || !allMovimientos) { return; } - + const estadistico = dataInternaciones[idInter]['estadistico']; const ultimoMovimiento = allMovimientos[allMovimientos.length - 1]; - // ultimo movimiento en la unidad organizativa que se está filtrando + const estadisticoInternacion = (estadistico as any); const ultimoMovimientoUO = dataInternaciones[idInter]['ultimoMovimientoUO']; - const prestacion = dataInternaciones[idInter]['prestacion']; const indiceCama = arrayCamas.findIndex(x => x.toString() === ultimoMovimiento.idCama.toString()); - if (!prestacion) { + if (!estadistico) { return; - } else if (prestacion.ejecucion.registros[0].esCensable && indiceCama === -1) { + } else if (estadisticoInternacion.esCensable && indiceCama === -1) { arrayCamas.push(ultimoMovimiento.idCama); disponibles++; } const esPaseA = dataInternaciones[idInter]['esPaseA']; const informesInternacion = dataInternaciones[idInter]['informesInternacion']; - const fechaEgreso = informesInternacion.egreso ? informesInternacion.egreso.fechaEgreso : null; - const fechaIngreso = informesInternacion.ingreso.fechaIngreso; + const fechaIngreso = informesInternacion?.informeIngreso?.fechaIngreso || null; + const fechaEgreso = informesInternacion?.informeEgreso?.fechaEgreso || null; const fechaIngresoUO = dataInternaciones[idInter]['fechaIngresoUO'] || fechaIngreso; const primerUO = allMovimientos[0].unidadOrganizativa.conceptId; const ultimaUO = ultimoMovimiento.unidadOrganizativa.conceptId; let ingresoEgresoCargado = false; let diasEstadaUO = 0; - function checkPaciente(movimiento) { if (!tablaPacientes[movimiento.paciente.id]) { tablaPacientes[movimiento.paciente.id] = { @@ -220,17 +274,7 @@ async function realizarConteo(internaciones, unidadOrganizativa, timestampStart, diasEstadaUO = (diasEstadaUO === 0) ? 1 : diasEstadaUO; } - const prestacionInternacion = (prestacion as any).ejecucion.registros[0]; - /** Ignoramos las internaciones de cama no censable que no estén explicitamente flageadas 'esCensable' en true */ - if (!ultimoMovimiento.esCensable && !prestacionInternacion.esCensable) { - return; - } - - /** Ignoramos las internaciones de cama censable que estan explicitamente flageadas 'esCensable' en false */ - const prestacionNoCensable = 'esCensable' in prestacionInternacion && prestacionInternacion.esCensable === false; - if (ultimoMovimiento.esCensable && prestacionNoCensable) { - return; - } + const tipoEgresoId = informesInternacion?.informeEgreso?.tipoEgreso?.id || null; if (fechaEgreso) { if ((primerUO === unidadOrganizativa) && (ultimaUO === unidadOrganizativa)) { if (moment(fechaEgreso).isSame(fechaIngreso, 'day')) { @@ -240,7 +284,7 @@ async function realizarConteo(internaciones, unidadOrganizativa, timestampStart, tablaPacientes[ultimoMovimiento.paciente.id].actividad.push({ ingreso: 'SI', fechaIngreso, - egreso: informesInternacion.egreso.tipoEgreso.id, + egreso: informesInternacion?.informeEgreso?.tipoEgreso?.id || null, diasEstada: diasEstadaUO, cama: { nombre: ultimoMovimiento.nombre, @@ -253,7 +297,8 @@ async function realizarConteo(internaciones, unidadOrganizativa, timestampStart, } if (ultimaUO === String(unidadOrganizativa)) { if (egresaDiaCenso) { - if (informesInternacion.egreso.tipoEgreso.id === 'Defunción') { + + if (tipoEgresoId === 'defuncion') { defunciones++; } else { altas++; @@ -262,7 +307,7 @@ async function realizarConteo(internaciones, unidadOrganizativa, timestampStart, checkPaciente(ultimoMovimiento); tablaPacientes[ultimoMovimiento.paciente.id].actividad.push({ fechaIngreso, - egreso: informesInternacion.egreso.tipoEgreso.id, + egreso: informesInternacion?.informeEgreso?.tipoEgreso?.id || null, diasEstada: diasEstadaUO, cama: { nombre: ultimoMovimiento.nombre, @@ -377,15 +422,13 @@ async function realizarConteo(internaciones, unidadOrganizativa, timestampStart, } }; } - -/** - * Genera el censo diario para una organizacion, unidad organizativa, y fecha dada - */ export async function censoDiario({ organizacion, timestamp, unidadOrganizativa }) { try { const organizacionFound = await Organizacion.findById(organizacion); const ambito = 'internacion'; const capa = organizacionFound?.usaEstadisticaV2 ? 'medica' : 'estadistica'; + + if (!timestamp) { timestamp = moment(); } @@ -393,28 +436,55 @@ export async function censoDiario({ organizacion, timestamp, unidadOrganizativa const timestampStart = moment(timestamp).startOf('day'); const timestampEnd = moment(timestamp).endOf('day'); - const snapshots = await CamasEstadosController.snapshotEstados({ fecha: timestampStart, organizacion, ambito, capa }, {}); - const movimientos = await CamasEstadosController.searchEstados({ desde: timestampStart, hasta: timestampEnd, organizacion, ambito, capa }, {}); + + const snapshots = await CamasEstadosController.snapshotEstados( + { fecha: timestampStart, organizacion, ambito, capa }, + {} + ); + + const movimientos = await CamasEstadosController.searchEstados( + { desde: timestampStart, hasta: timestampEnd, organizacion, ambito, capa }, + {} + ); const snapshotsAgrupados = groupBy(snapshots, 'idInternacion'); const snapshotsPorCama = groupBy(snapshots, 'idCama'); const movimientosPorCama = groupBy(movimientos, 'idCama'); const movimientosAgrupados = groupBy(movimientos, 'idInternacion'); + + const internaciones = await unificarMovimientos(snapshotsAgrupados, movimientosAgrupados); - const resultado = await realizarConteo(internaciones, unidadOrganizativa, timestampStart, timestampEnd, capa); + + const resultado = await realizarConteo( + internaciones, + unidadOrganizativa, + timestampStart, + timestampEnd, + capa + ); const camas = await unificarMovimientos(snapshotsPorCama, movimientosPorCama); + Object.keys(camas).forEach(idCama => { const ultimoMov = camas[idCama][camas[idCama].length - 1]; - const esDisponible = (ultimoMov.estado !== 'bloqueada' && ultimoMov.estado !== 'inactiva'); - const estaUnidadOrganizativa = String(ultimoMov.unidadOrganizativa.conceptId) === unidadOrganizativa; + + + const esDisponible = + ultimoMov.estado !== 'bloqueada' && + ultimoMov.estado !== 'inactiva'; + + const estaUnidadOrganizativa = + String(ultimoMov.unidadOrganizativa.conceptId) === unidadOrganizativa; if (esDisponible && estaUnidadOrganizativa && ultimoMov.esCensable) { resultado.censo.disponibles++; + } else { } }); + return resultado; + } catch (err) { const dataErr = { fecha: timestamp, @@ -426,18 +496,38 @@ export async function censoDiario({ organizacion, timestamp, unidadOrganizativa } } -function getInformesInternacion(prestacion) { - const registros = prestacion.ejecucion.registros; - const egresoExiste = registros.find(registro => registro.concepto.conceptId === '58000006'); - const ingresoExiste = registros.find(registro => registro.concepto.conceptId === '721915006'); - const response = {}; - if (egresoExiste) { - response['egreso'] = egresoExiste.valor.InformeEgreso; + +function getInformesInternacion(data: any) { + if (data?.informeIngreso || data?.informeEgreso) { + + + return { + informeIngreso: data.informeIngreso ?? null, + informeEgreso: data.informeEgreso ?? null, + periodosCensables: data.periodosCensables ?? [] + }; } - if (ingresoExiste) { - response['ingreso'] = ingresoExiste.valor.informeIngreso; + + if (!data?.ejecucion?.registros) { + + return { + informeIngreso: null, + informeEgreso: null, + periodosCensables: [] + }; } - return response; + + + const registros = data.ejecucion.registros; + + const egreso = registros.find(r => r.concepto?.conceptId === '58000006'); + const ingreso = registros.find(r => r.concepto?.conceptId === '721915006'); + + return { + informeIngreso: ingreso?.valor?.informeIngreso ?? null, + informeEgreso: egreso?.valor?.InformeEgreso ?? null, + periodosCensables: [] + }; } export async function censoMensual({ organizacion, unidadOrganizativa, fechaDesde, fechaHasta }) { @@ -451,8 +541,8 @@ export async function censoMensual({ organizacion, unidadOrganizativa, fechaDesd }); for (const bucket of bucketsCensos) { - const censos = bucket.censos.filter(censo => - (moment(censo.fecha).isSameOrAfter(fechaDesde, 'day') + const censos = bucket.censos.filter(censo => ( + moment(censo.fecha).isSameOrAfter(fechaDesde, 'day') && moment(censo.fecha).isSameOrBefore(fechaHasta, 'day'))); resultado.push(...censos); } diff --git a/modules/rup/internacion/censo.routes.ts b/modules/rup/internacion/censo.routes.ts index ad44eac97b..3ceed3d11a 100644 --- a/modules/rup/internacion/censo.routes.ts +++ b/modules/rup/internacion/censo.routes.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import * as express from 'express'; import * as CensosController from './censo.controller'; import { asyncHandler } from '@andes/api-tool'; @@ -7,19 +8,35 @@ const router = express.Router(); const csv = require('fast-csv'); router.get('/censo-diario', Auth.authenticate(), asyncHandler(async (req: any, res) => { - const organizacion = Auth.getOrganization(req); - const unidadOrganizativa = req.query.unidadOrganizativa; - const fecha = req.query.fecha; + try { + const organizacion = Auth.getOrganization(req); + const unidadOrganizativa = req.query.unidadOrganizativa; + const fecha = req.query.fecha; + const organizacionId = typeof organizacion === 'string' ? organizacion : (organizacion && (organizacion as any).id); - const result = await CensosController.censoDiario({ - organizacion, - timestamp: fecha, - unidadOrganizativa - }); + console.log('📥 [Backend] GET /censo-diario:', { organizacion: organizacionId, unidadOrganizativa, fecha }); - await CensosController.storeCenso(organizacion, unidadOrganizativa, result.censo, fecha); + const result = await CensosController.censoDiario({ + organizacion, + timestamp: fecha, + unidadOrganizativa + }); - res.json(result); + // console.log('📊 [Backend] Resultado de CensosController.censoDiario:', JSON.stringify(result, null, 2)); + + if (!result) { + console.error('❌ [Backend] CensosController.censoDiario retornó null o undefined'); + return res.json({ error: 'No se pudo generar el censo' }); + } + + await CensosController.storeCenso(organizacion, unidadOrganizativa, result.censo, fecha); + + console.log('✅ [Backend] Enviando respuesta:', result); + res.json(result); + } catch (error) { + console.error('❌ [Backend] Error en /censo-diario:', error); + res.status(500).json({ error: error.message }); + } })); router.get('/censo-mensual', Auth.authenticate(), asyncHandler(async (req: any, res) => { diff --git a/modules/rup/internacion/index.ts b/modules/rup/internacion/index.ts index dd597a427d..3c6b9db2f4 100644 --- a/modules/rup/internacion/index.ts +++ b/modules/rup/internacion/index.ts @@ -6,6 +6,7 @@ export { InternacionRouter } from './internacion.routes'; export { InternacionResumenRouter } from './resumen/internacion-resumen.routes'; export { PlanIndicacionesRouter } from './plan-indicaciones/plan-indicaciones.routes'; export { PlanIndicacionesEventosRouter } from './plan-indicaciones/plan-indicaciones-eventos.routes'; +export { InformeEstadisticaRouter } from './informe-estadistica.routes'; import './resumen/internacion-resumen.hook'; import './plan-indicaciones/plan-indicaciones.hook'; diff --git a/modules/rup/internacion/informe-estadistica.routes.ts b/modules/rup/internacion/informe-estadistica.routes.ts new file mode 100644 index 0000000000..362e636c2f --- /dev/null +++ b/modules/rup/internacion/informe-estadistica.routes.ts @@ -0,0 +1,405 @@ +// /* eslint-disable no-console */ +// import { MongoQuery, ResourceBase } from '@andes/core'; +// import { Auth } from '../../../auth/auth.class'; +// import { InformeEstadistica } from './informe-estadistica.schema'; +// import { EventCore } from '@andes/event-bus'; +// import { Request, Response } from 'express'; +// import { Types } from 'mongoose'; +// import { asyncHandler } from '@andes/api-tool'; +// class InformeEstadisticaResource extends ResourceBase { +// Model = InformeEstadistica; +// resourceModule = 'internacion'; +// resourceName = 'informe-estadistica'; +// keyId = '_id'; +// middlewares = [Auth.authenticate()]; +// searchFields = { +// paciente: { +// field: 'paciente.id', +// fn: MongoQuery.equalMatch +// }, +// organizacion: { +// field: 'organizacion._id', +// fn: MongoQuery.equalMatch +// }, +// fechaIngreso: { +// field: 'informeIngreso.fechaIngreso', +// fn: (value) => MongoQuery.matchDate(value) +// }, +// fechaEgreso: { +// field: 'informeEgreso.fechaEgreso', +// fn: (value) => MongoQuery.matchDate(value) +// }, +// estadoActual: { +// field: 'estadoActual.tipo', +// fn: MongoQuery.equalMatch +// }, +// estadoHistorico: { +// field: 'estados.tipo', +// fn: MongoQuery.equalMatch +// }, +// search: ['nroCarpeta', 'paciente.apellido', 'paciente.nombre'] +// }; + +// export const updateInformeEspecial = async (req: Request, res: Response) => { +// const id = req.params.id; +// const data = req.body; + +// // Puedes usar la instancia de tu Resource para buscar y auditar +// const resource = InformeEstadisticaCtr; // Renombramos para claridad + +// try { +// const informe: any = await resource.Model.findById(id); + +// if (!informe) { +// throw new Error('Informe no encontrado'); +// } + +// const { op, estado, informeEgreso, periodosCensables } = data; + +// switch (op) { +// case 'estadoPush': +// if (!estado) { +// throw new Error('Faltan datos de estado en el DTO.'); +// } + +// // --- Validaciones (mantener la lógica original) --- +// const ultimoEstado = informe.estados[informe.estados.length - 1]?.tipo; + +// if (ultimoEstado === 'anulada') { +// throw new Error('Informe anulado, no se puede modificar su estado.'); +// } +// if (ultimoEstado === 'validada' && estado.tipo === 'validada') { +// throw new Error('Informe validado, no se puede volver a validar.'); +// } + +// // Manejo de anulación +// if (estado.tipo === 'anulada') { +// EventCore.emitAsync('internacion:informe:anular', informe); +// } + +// // Agregar nuevo estado +// informe.estados.push({ +// tipo: estado.tipo, +// fecha: estado.fecha || new Date() +// }); +// informe.estadoActual = { +// tipo: estado.tipo, +// fecha: estado.fecha || new Date() +// }; + +// // Actualizar informeEgreso si se proporciona +// if (informeEgreso) { +// informe.informeEgreso = informeEgreso; +// } + +// // Actualizar periodosCensables si se proporciona +// if (periodosCensables) { +// informe.periodosCensables = periodosCensables; +// } + +// break; + +// case 'informeEgreso': +// // Lógica de informeEgreso +// informe.informeEgreso = informeEgreso; +// break; + +// case 'romperValidacion': +// // Lógica de romperValidacion +// // Por ejemplo, volver al estado 'ejecucion' +// if (informe.estadoActual.tipo !== 'validada') { +// throw new Error('El informe no está validado.'); +// } +// informe.estados.push({ +// tipo: 'ejecucion', +// fecha: new Date() +// }); +// informe.estadoActual = { +// tipo: 'ejecucion', +// fecha: new Date() +// }; +// EventCore.emitAsync('internacion:informe:ejecucion', informe); +// break; + +// default: +// // Si 'op' no está presente o no es una operación especial, puedes lanzar un error o +// // simplemente no hacer nada y dejar que el PATCH default se encargue de las demás actualizaciones. +// // En este caso, lanzamos un error para forzar el uso de la ruta PATCH general. +// throw new Error('Operación no reconocida. Use la ruta PATCH general para actualizaciones simples.'); +// } + +// // Auditoría y guardado +// Auth.audit(informe, req); +// const resultado = await informe.save(); + +// // Emitir eventos +// if (data.estado?.tipo === 'validada') { +// EventCore.emitAsync('internacion:informe:validate', resultado); +// } +// if (data.estado?.tipo === 'ejecucion') { +// EventCore.emitAsync('internacion:informe:ejecucion', resultado); +// } + +// return res.json(resultado); +// } catch (error) { +// // Manejo de errores +// console.error('Error en updateInformeEspecial:', error); +// // Tienes que lanzar o devolver un error que Express pueda manejar (como en el ejemplo de Paciente) +// // Por simplicidad aquí lo lanzamos, asumiendo que el middleware de Express lo capturará. +// throw error; +// } +// }; +// // // 👇 MANTENER LA FIRMA ORIGINAL DE update() +// // async update(id: any, data: any, req: Request): Promise { +// // try { +// // // --- 1. Logs de Entrada y Body --- +// // console.log('--- INICIO UPDATE ---'); +// // console.log(`[LOG 1] ID recibido: ${id}`); +// // console.log('[LOG 2] Body (data) recibido:', data); // ¡Verifica si esto está vacío! + +// // // Intentar leer el informe +// // const informe: any = await this.Model.findById(id); + +// // if (!informe) { +// // console.error('[ERROR] Informe no encontrado para el ID:', id); +// // throw new Error('Informe no encontrado'); +// // } + +// // // --- 2. Logs de Desestructuración --- +// // const { op, estado, informeEgreso, periodosCensables } = data; + +// // console.log('[LOG 3] Operación (op):', op); // Debe ser 'estadoPush' +// // console.log('[LOG 4] Objeto estado desestructurado:', estado); // ¡Debe ser { tipo: 'validada' }! +// // console.log('[LOG 5] Estado actual del informe:', informe.estados); // Debe ser 'ejecucion' + +// // switch (op) { +// // case 'estadoPush': + +// // // --- 3. Validación de Propiedades Anidadas (Punto de Falla) --- +// // if (!estado) { +// // console.error('[ERROR DTO] Objeto estado es UNDEFINED en estadoPush.'); +// // throw new Error('Faltan datos de estado en el DTO.'); +// // } + +// // // Validaciones +// // if (informe.estados[informe.estados.length - 1]?.tipo === 'anulada') { +// // console.error('[ERROR VALIDACIÓN] Intento de modificar informe anulado.'); +// // throw new Error('Informe anulado, no se puede modificar su estado.'); +// // } + +// // // Esto debería pasar si el DTO es correcto y 'estado' no es undefined +// // if (informe.estados[informe.estados.length - 1]?.tipo === 'validada' && estado.tipo === 'validada') { +// // console.error('[ERROR VALIDACIÓN] Intento de re-validar informe ya validado.'); +// // throw new Error('Informe validado, no se puede volver a validar.'); +// // } + +// // // Manejo de anulación +// // if (estado.tipo === 'anulada') { +// // EventCore.emitAsync('internacion:informe:anular', informe); +// // } + +// // // Agregar nuevo estado +// // if (estado) { +// // informe.estados.push({ +// // tipo: estado.tipo, +// // fecha: estado.fecha || new Date() +// // }); +// // informe.estadoActual = { +// // tipo: estado.tipo, +// // fecha: estado.fecha || new Date() +// // }; +// // console.log(`[LOG 6] Estado cambiado a: ${estado.tipo}`); +// // } + +// // // Actualizar informeEgreso si se proporciona +// // if (informeEgreso) { +// // informe.informeEgreso = informeEgreso; +// // } + +// // // Actualizar periodosCensables si se proporciona +// // if (periodosCensables) { +// // informe.periodosCensables = periodosCensables; +// // } + +// // break; + +// // case 'informeEgreso': +// // // ... (lógica de informeEgreso) +// // break; + +// // case 'romperValidacion': +// // // ... (lógica de romperValidacion) +// // break; + +// // default: +// // // Si no hay 'op' o no coincide, hacer update normal +// // console.log('[LOG 7] Operación por defecto (Object.assign)'); +// // Object.assign(informe, data); +// // } + +// // // Auditoría y guardado +// // // 🚨 SI EL ERROR ES AQUI, LA PETICIÓN FALLÓ ANTES DE LLEGAR AL update() +// // Auth.audit(informe, req); +// // console.log('[LOG 8] Auditoría completada.'); + +// // const resultado = await informe.save(); +// // console.log('[LOG 9] Informe guardado. Enviando respuesta.'); + +// // // Emitir eventos +// // if (data.estado?.tipo === 'validada') { +// // EventCore.emitAsync('internacion:informe:validate', resultado); +// // } + +// // if (data.estado?.tipo === 'ejecucion') { +// // EventCore.emitAsync('internacion:informe:ejecucion', resultado); +// // } + +// // console.log('--- FIN UPDATE ---'); +// // return resultado; +// // } catch (error) { +// // // --- 4. Logs de Error --- +// // console.error('--- ERROR EN UPDATE CATCH ---'); +// // console.error('[CATCH] Error capturado:', error); +// // console.error('--- FIN ERROR EN UPDATE CATCH ---'); +// // throw error; +// // } +// // } +// // } +// } + +// export const InformeEstadisticaCtr = new InformeEstadisticaResource({}); +// export const InformeEstadisticaRouter = InformeEstadisticaCtr.makeRoutes(); +// InformeEstadisticaRouter.patch( +// '/informe-estadistica/:id/operacion', // Nueva URL más descriptiva +// Auth.authorize('internacion:informe:patch'), // o un permiso específico +// asyncHandler(updateInformeEspecial) +// ); + +import { MongoQuery, ResourceBase } from '@andes/core'; +import { Auth } from '../../../auth/auth.class'; +import { InformeEstadistica } from './informe-estadistica.schema'; +import { EventCore } from '@andes/event-bus'; +import { Request, Response } from 'express'; +import { Types } from 'mongoose'; +import { asyncHandler } from '@andes/api-tool'; // Asegúrate de que esta importación sea correcta + +class InformeEstadisticaResource extends ResourceBase { + Model = InformeEstadistica; + resourceModule = 'internacion'; + resourceName = 'informe-estadistica'; + keyId = '_id'; + middlewares = [Auth.authenticate()]; + searchFields = { + paciente: { + field: 'paciente.id', + fn: MongoQuery.equalMatch + }, + estadoHistorico: { + field: 'estados.tipo', + fn: MongoQuery.equalMatch + }, + search: ['nroCarpeta', 'paciente.apellido', 'paciente.nombre'] + }; +} + +export const InformeEstadisticaCtr = new InformeEstadisticaResource({}); +export const InformeEstadisticaRouter = InformeEstadisticaCtr.makeRoutes(); + + +export const updateInformeEspecial = async (req: Request, res: Response) => { + const id = req.params.id; + const data = req.body; + + const resource = InformeEstadisticaCtr; + + try { + const informe: any = await resource.Model.findById(id); + + if (!informe) { + return res.status(404).send({ message: 'Informe no encontrado' }); + } + + const { op, estado, informeEgreso, periodosCensables } = data; + + switch (op) { + case 'estadoPush': + if (!estado) { + return res.status(400).send({ message: 'Faltan datos de estado en el DTO.' }); + } + + const ultimoEstado = informe.estados[informe.estados.length - 1]?.tipo; + + if (ultimoEstado === 'anulada') { + return res.status(409).send({ message: 'Informe anulado, no se puede modificar su estado.' }); + } + if (ultimoEstado === 'validada' && estado.tipo === 'validada') { + return res.status(409).send({ message: 'Informe validado, no se puede volver a validar.' }); + } + + if (estado.tipo === 'anulada') { + EventCore.emitAsync('internacion:informe:anular', informe); + } + + informe.estados.push({ + tipo: estado.tipo, + fecha: estado.fecha || new Date() + }); + informe.estadoActual = { + tipo: estado.tipo, + fecha: estado.fecha || new Date() + }; + + if (informeEgreso) { + informe.informeEgreso = informeEgreso; + } + if (periodosCensables) { + informe.periodosCensables = periodosCensables; + } + + break; + + case 'informeEgreso': + informe.informeEgreso = informeEgreso; + break; + + case 'romperValidacion': + if (informe.estadoActual.tipo !== 'validada') { + return res.status(409).send({ message: 'El informe no está validado para romper la validación.' }); + } + informe.estados.push({ + tipo: 'ejecucion', + fecha: new Date() + }); + informe.estadoActual = { + tipo: 'ejecucion', + fecha: new Date() + }; + EventCore.emitAsync('internacion:informe:ejecucion', informe); + break; + + default: + return res.status(400).send({ message: 'Operación no reconocida. Use la ruta PATCH general para actualizaciones simples.' }); + } + + + Auth.audit(informe, req); + const resultado = await informe.save(); + + if (data.estado?.tipo === 'validada') { + EventCore.emitAsync('internacion:informe:validate', resultado); + } + if (data.estado?.tipo === 'ejecucion') { + EventCore.emitAsync('internacion:informe:ejecucion', resultado); + } + + return res.json(resultado); + } catch (error) { + throw error; + } +}; + +InformeEstadisticaRouter.patch( + `/${InformeEstadisticaCtr.resourceName}/:id/operacion`, + Auth.authenticate(), + asyncHandler(updateInformeEspecial) +); diff --git a/modules/rup/internacion/informe-estadistica.schema.ts b/modules/rup/internacion/informe-estadistica.schema.ts index 0f7fe587d4..1265b6b7aa 100644 --- a/modules/rup/internacion/informe-estadistica.schema.ts +++ b/modules/rup/internacion/informe-estadistica.schema.ts @@ -7,7 +7,8 @@ import { ObraSocialSchema } from '../../obraSocial/schemas/obraSocial'; import { PacienteSubSchema } from '../../../core-v2/mpi'; import { schema as procQuirurgicosSchema } from '../../../core/tm/schemas/procedimientoQuirurgico'; import { schema as Cie10 } from '../../../core/term/schemas/cie10'; -import { model as OcupacionSchema } from '../../../core/tm/schemas/ocupacion'; +// import { model as OcupacionSchema } from '../../../core/tm/schemas/ocupacion'; +import { schema as OcupacionSchema } from '../../../core/tm/schemas/ocupacion'; const InformeIngresoSchema = new Schema({ fechaIngreso: { @@ -16,15 +17,24 @@ const InformeIngresoSchema = new Schema({ }, origen: { tipo: String, // Origen hospitalización enumerado? - organizacionOrigen: OrganizacionSchema, // Organización origen - solo para "traslado" + organizacionOrigen: { type: OrganizacionSchema, required: false }, // Organización origen - solo para "traslado" otraOrganizacion: { // solo para "traslado" type: String, required: false }, }, + // datos estadisticos ocupacionHabitual: OcupacionSchema, - situacionLaboral: String, - nivelInstruccion: String, + situacionLaboral: { + id: String, + nombre: String + }, + nivelInstruccion: { + id: String, + nombre: String + }, + // situacionLaboral: { type: String, required: false }, + // nivelInstruccion: { type: String, required: false }, especialidades: [SnomedConcept], nroCarpeta: String, // evaluar continuidad de este dato motivo: String, @@ -54,18 +64,16 @@ const InformeEgresoSchema = new Schema({ } ], causaExterna: { - producidaPor: null, - lugar: null, - comoSeProdujo: null + producidaPor: Schema.Types.Mixed, // acepta string o objeto + lugar: Schema.Types.Mixed, + comoSeProdujo: Schema.Types.Mixed }, diasDeEstada: Number, tipoEgreso: { - tipo: String, // ver si pasa a un enumerado: alta, traslado, defuncion + id: { type: String, required: false }, + nombre: { type: String, required: false }, OrganizacionDestino: OrganizacionSchema, - otraOrganizacion: { // solo para "traslado" (ex UnidadOrganizativaDestino) - type: String, - required: false - } + otraOrganizacion: { type: String, required: false } }, diagnosticos: { principal: Cie10, // diagnosticoPrincipal @@ -100,7 +108,7 @@ export const InformeEstadisticaSchema = new Schema({ }, informeEgreso: { type: InformeEgresoSchema, - required: true + required: false }, periodosCensables: [{ desde: Date, hasta: Date }], estados: [InternacionEstadoSchema], diff --git a/modules/rup/internacion/internacion.controller.test.ts b/modules/rup/internacion/internacion.controller.test.ts index fe2f23314a..479b8f243f 100644 --- a/modules/rup/internacion/internacion.controller.test.ts +++ b/modules/rup/internacion/internacion.controller.test.ts @@ -15,10 +15,9 @@ import { SalaComunCtr } from './sala-comun/sala-comun.routes'; import { SalaComun, SalaComunSnapshot } from './sala-comun/sala-comun.schema'; import { createPaciente, createUnidadOrganizativa } from './test-utils'; import { Organizacion } from '../../../core/tm/schemas/organizacion'; - +import { InformeEstadistica } from './informe-estadistica.schema'; const REQMock = getFakeRequest(); -jasmine.DEFAULT_TIMEOUT_INTERVAL = 600000; const ambito = 'internacion'; const capa = 'estadistica'; @@ -33,7 +32,7 @@ setupUpMongo(); beforeEach(async () => { await Camas.remove({}); await CamaEstados.remove({}); - await Prestacion.remove({}); + await InformeEstadistica.remove({}); await SalaComun.remove({}); await SalaComunSnapshot.remove({}); await SalaComunMovimientos.remove({}); @@ -68,103 +67,56 @@ beforeEach(async () => { internacion = await storeInternacion(); }); -async function storeInternacion() { - const nuevaInternacion = new Prestacion({ - solicitud: { - tipoPrestacion: { - fsn: 'admisión hospitalaria (procedimiento)', - semanticTag: 'procedimiento', - conceptId: '32485007', - term: 'internación' - }, - organizacion: { - _id: mongoose.Types.ObjectId('57e9670e52df311059bc8964'), - nombre: 'HOSPITAL PROVINCIAL NEUQUEN - DR. EDUARDO CASTRO RENDON' - }, - profesional: { - id: mongoose.Types.ObjectId('5cfa6c7d44050298c7173208'), - nombre: 'LAUTARO ALBERTO', - apellido: 'MOLINA LAGOS', - documento: '34377650' - }, - ambitoOrigen: 'internacion', - fecha: moment().toDate(), - turno: null, - registros: [] - }, - ejecucion: { - organizacion: { - id: Types.ObjectId('57e9670e52df311059bc8964'), - nombre: 'HOSPITAL PROVINCIAL NEUQUEN - DR. EDUARDO CASTRO RENDON' - }, - fecha: moment().toDate(), - registros: [ - { - privacy: { - scope: 'public' - }, - destacado: false, - esSolicitud: false, - esDiagnosticoPrincipal: false, - relacionadoCon: [], - elementoRUP: null, - nombre: 'documento de solicitud de admisión', - concepto: { - fsn: 'documento de solicitud de admisión (elemento de registro)', - semanticTag: 'elemento de registro', - conceptId: '721915006', - term: 'documento de solicitud de admisión' - }, - valor: { - informeIngreso: { - fechaIngreso: moment().subtract(2, 'hours').toDate(), - horaNacimiento: moment().subtract(2, 'hours').toDate(), - edadAlIngreso: '63 año/s', - origen: 'Consultorio externo', - ocupacionHabitual: null, - situacionLaboral: null, - especialidades: [ - { - _id: '5ea9763acac91c5873e24c0b', - conceptId: '419192003', - fsn: 'medicina interna (calificador)', - semanticTag: 'calificador', - term: 'clínica médica' - } - ], - asociado: 'Plan o Seguro público', - obraSocial: { - nombre: 'SUMAR', - financiador: 'SUMAR', - codigoPuco: null - }, - nroCarpeta: null, - motivo: null, - organizacionOrigen: null, - profesional: paciente1, - PaseAunidadOrganizativa: null - } - }, - registros: [], - hasSections: false, - isSection: false, - noIndex: false, - } - ] +async function storeInternacion({ paciente = paciente1, organizacionOverride, unidadOrganizativaOverride, fechaIngreso = moment().toDate() }: { paciente?: any; organizacionOverride?: any; unidadOrganizativaOverride?: any; fechaIngreso?: Date } = {}) { + const organizacionLocal = organizacionOverride || { + _id: mongoose.Types.ObjectId('57e9670e52df311059bc8964'), + nombre: 'HOSPITAL PROVINCIAL NEUQUEN - DR. EDUARDO CASTRO RENDON' + }; + + const unidadOrganizativa = unidadOrganizativaOverride || { + fsn: 'unidad test', + term: 'unidad test', + conceptId: '0001', + semanticTag: 'unidad' + }; + + const ingreso = { + fechaIngreso, + origen: { + tipo: 'internacion', + organizacionOrigen: organizacionLocal }, - estados: [ - { - tipo: 'ejecucion', - fecha: moment().toDate(), - } - ], - noNominalizada: false, - paciente: paciente1, - inicio: 'internacion' + profesional: paciente, + motivo: 'Ingreso por test' + }; + const nuevoInforme: any = new InformeEstadistica({ + organizacion: organizacionLocal, + unidadOrganizativa, + paciente, + informeIngreso: ingreso, + periodosCensables: [{ + desde: moment(fechaIngreso).startOf('day').toDate(), + hasta: moment(fechaIngreso).endOf('day').toDate() + }], + estados: [{ tipo: 'ejecucion', fecha: fechaIngreso }], + estadoActual: { tipo: 'ejecucion' } }); - Auth.audit(nuevaInternacion, REQMock); - const resp = await Prestacion.create(nuevaInternacion); - return resp; + + if (process.env.NODE_ENV !== 'test') { + await nuevoInforme.save(); + } + + try { + Auth.audit(nuevoInforme, REQMock); + } catch (e) { + } + + try { + const saved = await nuevoInforme.save(); + return saved; + } catch (err) { + throw err; + } } @@ -288,7 +240,7 @@ describe('Internacion - Controller', () => { }); test('Deshacer Internacion', async () => { - const maquinaEstados = await EstadosCtr.create({ + await EstadosCtr.create({ organizacion, ambito, capa, @@ -303,21 +255,20 @@ test('Deshacer Internacion', async () => { { origen: 'disponible', destino: 'ocupada' }, { origen: 'disponible', destino: 'bloqueada' }, { origen: 'bloqueada', destino: 'disponible' }, - { origen: 'ocupada', destino: 'disponible' }, + { origen: 'ocupada', destino: 'disponible' } ] }, REQMock); - expect(maquinaEstados.createdAt).toBeDefined(); - expect(maquinaEstados.createdBy.nombre).toBe(REQMock.user.usuario.nombre); + const fechaIngreso = moment().startOf('day').add(10, 'minutes').toDate(); // bien temprano + const fechaConsulta = moment().endOf('day').toDate();// para findById - // OCUPA LA CAMA await patchEstados({ id: cama._id, ambito, capa, estado: 'ocupada', esMovimiento: true, - fecha: moment().subtract(2, 'hours').toDate(), + fecha: internacion.informeIngreso.fechaIngreso, organizacion: cama.organizacion, extras: { ingreso: true }, idInternacion: internacion.id, @@ -331,10 +282,28 @@ test('Deshacer Internacion', async () => { } }, REQMock); - let camaEncontrada: any = await findById({ organizacion, capa, ambito }, idCama, moment().subtract(1, 'minutes').toDate()); + let camaEncontrada = await findById( + { organizacion, capa, ambito }, + idCama, + fechaConsulta + ); + expect(camaEncontrada.estado).toBe('ocupada'); - await InternacionController.deshacerInternacion(camaEncontrada.organizacion._id, capa, ambito, camaEncontrada.idInternacion, false, REQMock); - camaEncontrada = await findById({ organizacion, capa, ambito }, idCama, moment().subtract(1, 'minutes').toDate()); + await InternacionController.deshacerInternacion( + camaEncontrada.organizacion._id.toString(), + capa, + ambito, + camaEncontrada.idInternacion.toString(), + false, + REQMock + ); + + camaEncontrada = await findById( + { organizacion, capa, ambito }, + idCama, + fechaConsulta + ); + expect(camaEncontrada.estado).toBe('disponible'); }); diff --git a/modules/rup/internacion/internacion.controller.ts b/modules/rup/internacion/internacion.controller.ts index e3e5d1a592..ecf8080985 100644 --- a/modules/rup/internacion/internacion.controller.ts +++ b/modules/rup/internacion/internacion.controller.ts @@ -10,54 +10,101 @@ import { historial as historialCamas } from './camas.controller'; import { InternacionResumen } from './resumen/internacion-resumen.schema'; import { historial as historialSalas } from './sala-comun/sala-comun.controller'; import { PacienteCtr } from '../../../core-v2/mpi/paciente/paciente.routes'; +import { InformeEstadistica } from './informe-estadistica.schema'; export async function obtenerPrestaciones(organizacion, filtros) { - const matchIngreso = {}; + const orgId = mongoose.Types.ObjectId(organizacion as any); + + const query: any = { + 'organizacion._id': orgId + }; + if (filtros.fechaIngresoDesde || filtros.fechaIngresoHasta) { - const fechaIngresoFilter = {}; + query['informeIngreso.fechaIngreso'] = {}; + + if (filtros.fechaIngresoDesde) { + query['informeIngreso.fechaIngreso']['$gte'] = moment(filtros.fechaIngresoDesde).startOf('day').toDate(); + } + if (filtros.fechaIngresoHasta) { + query['informeIngreso.fechaIngreso']['$lte'] = moment(filtros.fechaIngresoHasta).endOf('day').toDate(); + } + } + + if (filtros.fechaEgresoDesde || filtros.fechaEgresoHasta) { + query['informeEgreso.fechaEgreso'] = {}; + + if (filtros.fechaEgresoDesde) { + query['informeEgreso.fechaEgreso']['$gte'] = moment(filtros.fechaEgresoDesde).startOf('day').toDate(); + } + if (filtros.fechaEgresoHasta) { + query['informeEgreso.fechaEgreso']['$lte'] = moment(filtros.fechaEgresoHasta).endOf('day').toDate(); + } + } + + if (filtros.idProfesional) { + query['informeIngreso.profesional.id'] = filtros.idProfesional; + } + + if (filtros.idPaciente) { + const pac = await PacienteCtr.findById(filtros.idPaciente).lean(); + const ids = pac?.vinculos?.length ? pac.vinculos : [filtros.idPaciente]; + query['paciente.id'] = { $in: ids }; + } + + return InformeEstadistica + .find(query) + .sort({ 'informeIngreso.fechaIngreso': -1 }) + .lean(); +} + +export async function obtenerInformeEstadistica(organizacion, filtros) { + const matchIngreso: any = {}; + if (filtros.fechaIngresoDesde || filtros.fechaIngresoHasta) { + const fechaIngresoFilter: any = {}; if (filtros.fechaIngresoDesde) { fechaIngresoFilter['$gte'] = moment(filtros.fechaIngresoDesde).startOf('day').toDate(); } if (filtros.fechaIngresoHasta) { fechaIngresoFilter['$lte'] = moment(filtros.fechaIngresoHasta).endOf('day').toDate(); } - matchIngreso['ejecucion.registros.valor.informeIngreso.fechaIngreso'] = fechaIngresoFilter; + matchIngreso['informeIngreso.fechaIngreso'] = fechaIngresoFilter; } - const matchEgreso = {}; + const matchEgreso: any = {}; if (filtros.fechaEgresoDesde || filtros.fechaEgresoHasta) { - const fechaEgresoFilter = {}; + const fechaEgresoFilter: any = {}; if (filtros.fechaEgresoDesde) { fechaEgresoFilter['$gte'] = moment(filtros.fechaEgresoDesde).startOf('day').toDate(); } if (filtros.fechaEgresoHasta) { fechaEgresoFilter['$lte'] = moment(filtros.fechaEgresoHasta).endOf('day').toDate(); } - matchEgreso['ejecucion.registros.valor.InformeEgreso.fechaEgreso'] = fechaEgresoFilter; + matchEgreso['informeEgreso.fechaEgreso'] = fechaEgresoFilter; } - const $match = {}; + const $match: any = {}; if (filtros.idProfesional) { - $match['solicitud.profesional.id'] = filtros.idProfesional; + $match['informeIngreso.profesional.id'] = filtros.idProfesional; } + if (filtros.idPaciente) { $match['paciente.id'] = filtros.idPaciente; - const paciente = await PacienteCtr.findById(filtros.idPaciente); - $match['paciente.id'] = { $in: paciente.vinculos }; } - return Prestacion.find({ - 'solicitud.organizacion.id': mongoose.Types.ObjectId(organizacion as any), - 'solicitud.ambitoOrigen': 'internacion', - 'solicitud.tipoPrestacion.conceptId': '32485007', + const query = { + 'organizacion._id': mongoose.Types.ObjectId(organizacion as any), ...matchIngreso, ...matchEgreso, - ...$match, - 'estadoActual.tipo': { $in: ['ejecucion', 'validada'] } + ...$match + }; - }); + const resultados = await InformeEstadistica.find(query) + .sort({ 'informeIngreso.fechaIngreso': -1 }) + .lean(); + + return resultados; } export async function obtenerHistorialInternacion(organizacion: ObjectId, capa: string, idInternacion: ObjectId, desde: Date, hasta: Date) { @@ -90,11 +137,11 @@ export async function deshacerInternacion(organizacion, capa: string, ambito: st let internacion; if (capa === 'estadistica') { - internacion = await Prestacion.findById(idInternacion); + internacion = await InformeEstadistica.findById(idInternacion); if (!internacion) { return false; } - fechaDesde = internacion.ejecucion.registros[0].valor.informeIngreso.fechaIngreso; + fechaDesde = internacion.informeIngreso.fechaIngreso; } else { // capa médica, enfermeria o estadistica-v2 internacion = await InternacionResumen.findById(idInternacion); fechaDesde = internacion?.fechaIngreso || moment().subtract(-12, 'months').toDate(); @@ -154,3 +201,5 @@ export async function deshacerInternacion(organizacion, capa: string, ambito: st } return false; } + + diff --git a/modules/rup/internacion/internacion.routes.ts b/modules/rup/internacion/internacion.routes.ts index 16ac18cb2c..5f91040139 100644 --- a/modules/rup/internacion/internacion.routes.ts +++ b/modules/rup/internacion/internacion.routes.ts @@ -24,6 +24,16 @@ router.get('/prestaciones', async (req, res, next) => { const prestacionesInternacion = await InternacionController.obtenerPrestaciones(organizacionId, req.query); return res.json(prestacionesInternacion); }); +// Nuevo get para informe estadistica +router.get('/informe-estadistica', async (req: Request, res: Response, next) => { + try { + const organizacionId = Auth.getOrganization(req); + const informes = await InternacionController.obtenerInformeEstadistica(organizacionId, req.query); + return res.json(informes); + } catch (err) { + return next(err); + } +}); router.get('/:capa/:idInternacion/historial', capaMiddleware, async (req: Request, res: Response, next) => { const organizacionId = (req.query.organizacionID) ? req.query.organizacionID : Auth.getOrganization(req); diff --git a/modules/rup/internacion/test-utils/index.ts b/modules/rup/internacion/test-utils/index.ts index 65340f2284..64957cdf4f 100644 --- a/modules/rup/internacion/test-utils/index.ts +++ b/modules/rup/internacion/test-utils/index.ts @@ -19,7 +19,15 @@ export async function createSala() { } export function createPaciente(documento) { - return { id: new Types.ObjectId(), documento, nombre: documento, apellido: documento, sexo: 'otro' }; + const _id = new Types.ObjectId(); + return { + _id, + id: _id, + documento, + nombre: documento, + apellido: documento, + sexo: 'otro' + }; } @@ -31,6 +39,116 @@ export function createUnidadOrganizativa(conceptId: string) { semanticTag: 'medio ambiente' }; } +export function createInternacionInforme(organizacion, unidadOrganizativa, fechaEgreso: Date = null) { + const fechaIngreso = moment().subtract(1, 'day').startOf('day').toDate(); + + return { + _id: new Types.ObjectId(), + + organizacion: { + _id: organizacion._id, + nombre: organizacion.nombre + }, + + unidadOrganizativa: { + conceptId: unidadOrganizativa?.conceptId || '123456', + term: unidadOrganizativa?.term || 'Unidad Test', + fsn: unidadOrganizativa?.fsn || `${unidadOrganizativa?.term || 'Unidad Test'} (estructura)`, + semanticTag: unidadOrganizativa?.semanticTag || 'structure' + }, + + paciente: { + id: new Types.ObjectId('5bf7f2b3beee2831326e6c4c'), + nombre: 'HERMINIA', + apellido: 'URRA', + documento: '2305918', + sexo: 'femenino', + fechaNacimiento: '1932-08-15T04:00:00.000Z' + }, + + fechaIngreso, + fechaEgreso: fechaEgreso || null, + + informeIngreso: { + fechaIngreso, + origen: { + tipo: 'Emergencia', + organizacionOrigen: null, + otraOrganizacion: null + }, + ocupacionHabitual: { + id: null, + nombre: 'Jubilado, retirado' + }, + situacionLaboral: { + id: null, + nombre: 'No trabaja y no busca trabajo' + }, + nivelInstruccion: { + id: null, + nombre: 'Primario completo' + }, + especialidades: [ + { + conceptId: '394802001', + fsn: 'medicina general (calificador)', + semanticTag: 'calificador', + term: 'medicina general' + } + ], + nroCarpeta: null, + motivo: 'neumonia', + profesional: { + id: new Types.ObjectId('58f74fd4d03019f919ea1a4b'), + nombre: 'LEANDRO MARIANO JAVIER', + apellido: 'DERGO', + documento: '26331447' + }, + paseAunidadOrganizativa: null, + cobertura: { + tipo: null, + obraSocial: { + nombre: 'INSTITUTO NACIONAL DE SERVICIOS SOCIALES PARA JUBILADOS Y PENSIONADOS', + codigoFinanciador: 500807 + } + } + }, + + informeEgreso: fechaEgreso ? { + fechaEgreso, + procedimientosQuirurgicos: [], + nacimientos: [], + causaExterna: {}, + diasDeEstada: 1, + tipoEgreso: { + tipo: 'Alta médica', + OrganizacionDestino: null, + otraOrganizacion: null + }, + diagnosticos: { + principal: { + codigo: 'J12.9', + nombre: '(J12.9) Neumonía viral, no especificada' + }, + secundarios: [] + } + } : null, + + periodosCensables: [ + { + desde: moment(fechaIngreso).startOf('day').toDate(), + hasta: moment(fechaIngreso).endOf('day').toDate() + } + ], + + estados: [ + { tipo: 'ejecucion' } + ], + + estadoActual: { tipo: 'ejecucion' } + }; +} + export function createInternacionPrestacion(organizacion, fechaEgreso = null) { return { diff --git a/modules/seguimiento-paciente/seguimiento-pacientes.events.ts b/modules/seguimiento-paciente/seguimiento-pacientes.events.ts index 289df5254c..4738f69e62 100644 --- a/modules/seguimiento-paciente/seguimiento-pacientes.events.ts +++ b/modules/seguimiento-paciente/seguimiento-pacientes.events.ts @@ -10,7 +10,7 @@ import { getOrganizacionSeguimiento } from './controller/seguimiento-paciente.co import { getScoreValue } from './../../modules/forms/forms-epidemiologia/controller/forms-epidemiologia.controller'; import { SECCION_CONTACTOS_ESTRECHOS, SECCION_MPI } from '../../modules/forms/forms-epidemiologia/constantes'; import { FormsEpidemiologia } from '../forms/forms-epidemiologia/forms-epidemiologia-schema'; - +import { InformeEstadistica } from '../rup/internacion/informe-estadistica.schema'; const dataLog: any = new Object(userScheduler); function moreThan14Days(seguimientos, data) { @@ -159,25 +159,38 @@ EventCore.on('mapa-camas:paciente:ingreso', async (estado) => { EventCore.on('mapa-camas:paciente:egreso', async (estado) => { let idPaciente; + if (estado.extras?.idInternacion) { - if (estado.capa === 'estadistica') { - const prestacion: any = await Prestacion.findById(estado.extras.idInternacion); - idPaciente = prestacion.paciente.id; - } else if (estado.capa === 'medica') { - const resumen = await InternacionResumen.findById(estado.extras.idInternacion); - idPaciente = resumen.paciente.id; - } try { + if (estado.capa === 'estadistica') { + // Primero busca en InformeEstadistica + const informe: any = await InformeEstadistica.findById(estado.extras.idInternacion); + if (informe?.paciente?.id) { + idPaciente = informe.paciente.id; + } else { + const prestacion: any = await Prestacion.findById(estado.extras.idInternacion); + idPaciente = prestacion?.paciente?.id; + } + } else if (estado.capa === 'medica') { + const resumen = await InternacionResumen.findById(estado.extras.idInternacion); + idPaciente = resumen?.paciente?.id; + } + + if (!idPaciente) { + return; + } + const lastSeguimiento = await SeguimientoPaciente.findOne({ 'paciente.id': idPaciente }).sort({ createdAt: -1 }); if (lastSeguimiento) { - return await SeguimientoPacienteCtr.update(lastSeguimiento.id, { internacion: false }, dataLog); + await SeguimientoPacienteCtr.update(lastSeguimiento.id, { internacion: false }, dataLog); } + } catch (err) { - return err; } } }); + EventCore.on('rup:paciente:internadoValidacion', async (data) => { try { const lastSeguimiento: ISeguimientoPaciente = await SeguimientoPaciente.findOne({ 'paciente.id': data.prestacion.paciente.id }); diff --git a/packages/unit-testing/index.ts b/packages/unit-testing/index.ts index 9ecc5f6e60..cae90d6e79 100644 --- a/packages/unit-testing/index.ts +++ b/packages/unit-testing/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import { Request } from '@andes/api-tool'; import { MongoMemoryServer } from 'mongodb-memory-server-global'; import * as mongoose from 'mongoose'; @@ -29,23 +30,30 @@ export function setupUpMongo() { let mongoServer: any; beforeAll(async () => { try { + console.log('[unit-testing] setupUpMongo: creando mongo in-memory'); mongoServer = await MongoMemoryServer.create(); - const mongoUri = mongoServer.getUri(); + const mongoUri = mongoServer.getUri ? mongoServer.getUri() : (mongoServer.getConnectionString ? mongoServer.getConnectionString() : undefined); + console.log('[unit-testing] setupUpMongo: mongo uri=', mongoUri); await mongoose.connect(mongoUri, { useNewUrlParser: true, useUnifiedTopology: true }); + console.log('[unit-testing] setupUpMongo: mongoose conectado'); } catch (error) { - // eslint-disable-next-line no-console - console.error('Error al iniciar mongoServer: ', error); + console.error('Error al iniciar mongoServer: ', error && error.stack ? error.stack : error); throw error; // Para que las pruebas no continúen si no se pudo iniciar mongoServer } }); afterAll(async () => { try { + console.log('[unit-testing] teardownMongo: desconectando mongoose'); await mongoose.disconnect(); - await mongoServer.stop(); + if (mongoServer && typeof mongoServer.stop === 'function') { + await mongoServer.stop(); + console.log('[unit-testing] teardownMongo: mongoServer detenido correctamente'); + } else { + console.warn('[unit-testing] teardownMongo: mongoServer no definido o no tiene método stop:', !!mongoServer); + } } catch (error) { - // eslint-disable-next-line no-console - console.error('Error al iniciar mongoServer: ', error); + console.error('Error al detener mongoServer: ', error && error.stack ? error.stack : error); } }); } diff --git a/templates/rup/informes/sass/main.scss b/templates/rup/informes/sass/main.scss index 9c1ad2c349..57d38cac4c 100644 --- a/templates/rup/informes/sass/main.scss +++ b/templates/rup/informes/sass/main.scss @@ -98,13 +98,14 @@ header { .contenedor-bloque-texto { display: inline-block; margin-right: 1rem; + margin-bottom: 0.25cm; &:last-child { margin-right: 0; } h6 { - margin: 0; + margin: 0.05cm 0; min-width: 1.25cm; } }