paperclip-plugin-autopilot-ops 0.1.32 → 0.1.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/manifest.js +9 -4
- package/dist/ui/index.js +9 -4
- package/dist/worker.js +526 -69
- package/package.json +1 -1
package/dist/manifest.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/manifest.ts
|
|
2
2
|
var PLUGIN_ID = "alephoo.autopilot-ops";
|
|
3
|
-
var PLUGIN_VERSION = "0.1.
|
|
3
|
+
var PLUGIN_VERSION = "0.1.34";
|
|
4
4
|
var DB_NAMESPACE_SLUG = "autopilot_ops";
|
|
5
5
|
var PAGE_ROUTE = "autopilot";
|
|
6
6
|
var TOOL_NAMES = {
|
|
@@ -68,13 +68,18 @@ var manifest = {
|
|
|
68
68
|
{
|
|
69
69
|
name: TOOL_NAMES.listarCasos,
|
|
70
70
|
displayName: "Listar casos",
|
|
71
|
-
description:
|
|
71
|
+
description: 'Lista casos del registro de negocio de la instituci\xF3n, abiertos y cerrados, del m\xE1s recientemente actualizado al m\xE1s viejo. Filtros opcionales: workflow (si el plugin no tiene esa funci\xF3n responde el error `workflow_desconocido`); estado (uno) o estados (varios, en un arreglo); referencia (todos los casos de ese objeto real, por ejemplo `rentabilidad:<prestacion_id>:<financiador_id>`, para saber si ya tiene caso sin recorrer el listado); limit (por defecto 50, m\xE1x. 500). Para recorrer todos sin perder ninguno aunque cambien mientras tanto: `orden: "id"` y, en cada p\xE1gina siguiente, `despues_de` con el id que indica la respuesta (\xABPuede haber m\xE1s casos\u2026\xBB); si vuelven menos casos que el limit, no hay m\xE1s. `offset` pagina el orden por actualizaci\xF3n (se corre si los casos cambian entre una p\xE1gina y otra). Solo lectura.',
|
|
72
72
|
parametersSchema: {
|
|
73
73
|
type: "object",
|
|
74
74
|
properties: {
|
|
75
75
|
workflow: { type: "string" },
|
|
76
76
|
estado: { type: "string" },
|
|
77
|
-
|
|
77
|
+
estados: { type: "array", items: { type: "string" }, description: "Varios estados a la vez (se ignora si viene estado)." },
|
|
78
|
+
referencia: { type: "string", description: "Solo los casos con esta referencia exacta." },
|
|
79
|
+
limit: { type: "number" },
|
|
80
|
+
orden: { type: "string", enum: ["actualizacion", "id"], description: '"id": orden estable para recorrer todo con despues_de. Por defecto, por \xFAltima actualizaci\xF3n.' },
|
|
81
|
+
despues_de: { type: "string", description: 'Id del \xFAltimo caso de la p\xE1gina anterior (con orden "id"): trae los que siguen.' },
|
|
82
|
+
offset: { type: "number" }
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
85
|
},
|
|
@@ -128,7 +133,7 @@ var manifest = {
|
|
|
128
133
|
{
|
|
129
134
|
name: TOOL_NAMES.abrirCaso,
|
|
130
135
|
displayName: "Abrir caso",
|
|
131
|
-
description: 'Abre un caso nuevo en un workflow, en su estado inicial. Workflows: `internacion_al_dia` (una internaci\xF3n abierta; referencia obligatoria `internacion:<id>`; datos: internacion_id), `honorario_en_cero` (referencia `honorarios:<profesional_id>`; datos: profesional_id, profesional), `debito_profesional` (referencia `debito:<profesional_id>:<nota_credito_id>`; datos: profesional_id, profesional, nota_credito_id, obra_social), `liquidacion_profesional` (referencia `liquidacion:<profesional_id>:<AAAA-MM-DD>`; datos: profesional_id, profesional, liquidado_el, institucion, mail_his), `cobranza_financiador` (un caso abierto por financiador; referencia obligatoria `cobranza:<obra_social_id>`; datos: obra_social (nombre como en el HIS), obra_social_id, institucion (nombre de la instituci\xF3n para el reclamo)), `circular_aranceles` (una circular de aranceles entera; referencia obligatoria `circular:<obra social>:<hash corto del archivo>`; datos: obra_social, convenio (nombre en el HIS), archivo {nombre, hash, paginas}, vigencia_desde AAAA-MM-DD, porcentaje_declarado y filas_declaradas si la circular los dice), `demanda_pendiente` (referencia obligatoria `orden:<id>`, `solicitud:<id>` o `control:<persona_id>:<regla_id>`; datos: regla_id (regla activa) y candidato tal cual lo devuelve buscar-demanda.mjs; el plugin verifica la regla con el candidato (si no la cumple, no abre) y fija regla_version, canal, intentos, vence_el; nunca nombre, documento ni tel\xE9fono del paciente),`actualizacion_arancel`, `tarea_operativa`, `recupero_turno` (datos: fecha AAAA-MM-DD, hora HH:MM, profesional, especialidad del turno libre; referencia "AAAA-MM-DD HH:MM \xB7 Profesional"), `cuenta_completa` (una prestaci\xF3n pendiente de facturar con faltantes; referencia obligatoria "bono:<n\xFAmero>", datos: bono_id, bono_numero, obra_social, plan, prestacion, fecha_prestacion AAAA-MM-DD, monto_pesos, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese. Pasar `idempotencyKey` para reintentos seguros.',
|
|
136
|
+
description: 'Abre un caso nuevo en un workflow, en su estado inicial. Workflows: `internacion_al_dia` (una internaci\xF3n abierta; referencia obligatoria `internacion:<id>`; datos: internacion_id), `honorario_en_cero` (referencia `honorarios:<profesional_id>`; datos: profesional_id, profesional), `debito_profesional` (referencia `debito:<profesional_id>:<nota_credito_id>`; datos: profesional_id, profesional, nota_credito_id, obra_social), `liquidacion_profesional` (referencia `liquidacion:<profesional_id>:<AAAA-MM-DD>`; datos: profesional_id, profesional, liquidado_el, institucion, mail_his), `cobranza_financiador` (un caso abierto por financiador; referencia obligatoria `cobranza:<obra_social_id>`; datos: obra_social (nombre como en el HIS), obra_social_id, institucion (nombre de la instituci\xF3n para el reclamo)), `circular_aranceles` (una circular de aranceles entera; referencia obligatoria `circular:<obra social>:<hash corto del archivo>`; datos: obra_social, convenio (nombre en el HIS), archivo {nombre, hash, paginas}, vigencia_desde AAAA-MM-DD, porcentaje_declarado y filas_declaradas si la circular los dice), `demanda_pendiente` (referencia obligatoria `orden:<id>`, `solicitud:<id>` o `control:<persona_id>:<regla_id>`; datos: regla_id (regla activa) y candidato tal cual lo devuelve buscar-demanda.mjs; el plugin verifica la regla con el candidato (si no la cumple, no abre) y fija regla_version, canal, intentos, vence_el; nunca nombre, documento ni tel\xE9fono del paciente),`actualizacion_arancel`, `tarea_operativa`, `recupero_turno` (datos: fecha AAAA-MM-DD, hora HH:MM, profesional, especialidad del turno libre; referencia "AAAA-MM-DD HH:MM \xB7 Profesional"), `cuenta_completa` (una prestaci\xF3n pendiente de facturar con faltantes; referencia obligatoria "bono:<n\xFAmero>", datos: bono_id, bono_numero, obra_social, plan, prestacion, fecha_prestacion AAAA-MM-DD, monto_pesos, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades), `rentabilidad_practica` (referencia obligatoria `rentabilidad:<prestacion_id>:<financiador_id>`; datos: prestacion_id, financiador_id, codigo, practica, financiador), `rentabilidad_internacion` (una internaci\xF3n egresada; referencia obligatoria `rentabilidad_internacion:<internacion_id>`; datos: internacion_id, financiador_id, financiador). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese (creado: false). En `rentabilidad_internacion` se devuelve el caso existente aunque est\xE9 cerrado: una internaci\xF3n se mide una sola vez. Pasar `idempotencyKey` para reintentos seguros.',
|
|
132
137
|
parametersSchema: {
|
|
133
138
|
type: "object",
|
|
134
139
|
properties: {
|
package/dist/ui/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
|
|
9
9
|
// src/manifest.ts
|
|
10
10
|
var PLUGIN_ID = "alephoo.autopilot-ops";
|
|
11
|
-
var PLUGIN_VERSION = "0.1.
|
|
11
|
+
var PLUGIN_VERSION = "0.1.34";
|
|
12
12
|
var DB_NAMESPACE_SLUG = "autopilot_ops";
|
|
13
13
|
var PAGE_ROUTE = "autopilot";
|
|
14
14
|
var TOOL_NAMES = {
|
|
@@ -76,13 +76,18 @@ var manifest = {
|
|
|
76
76
|
{
|
|
77
77
|
name: TOOL_NAMES.listarCasos,
|
|
78
78
|
displayName: "Listar casos",
|
|
79
|
-
description:
|
|
79
|
+
description: 'Lista casos del registro de negocio de la instituci\xF3n, abiertos y cerrados, del m\xE1s recientemente actualizado al m\xE1s viejo. Filtros opcionales: workflow (si el plugin no tiene esa funci\xF3n responde el error `workflow_desconocido`); estado (uno) o estados (varios, en un arreglo); referencia (todos los casos de ese objeto real, por ejemplo `rentabilidad:<prestacion_id>:<financiador_id>`, para saber si ya tiene caso sin recorrer el listado); limit (por defecto 50, m\xE1x. 500). Para recorrer todos sin perder ninguno aunque cambien mientras tanto: `orden: "id"` y, en cada p\xE1gina siguiente, `despues_de` con el id que indica la respuesta (\xABPuede haber m\xE1s casos\u2026\xBB); si vuelven menos casos que el limit, no hay m\xE1s. `offset` pagina el orden por actualizaci\xF3n (se corre si los casos cambian entre una p\xE1gina y otra). Solo lectura.',
|
|
80
80
|
parametersSchema: {
|
|
81
81
|
type: "object",
|
|
82
82
|
properties: {
|
|
83
83
|
workflow: { type: "string" },
|
|
84
84
|
estado: { type: "string" },
|
|
85
|
-
|
|
85
|
+
estados: { type: "array", items: { type: "string" }, description: "Varios estados a la vez (se ignora si viene estado)." },
|
|
86
|
+
referencia: { type: "string", description: "Solo los casos con esta referencia exacta." },
|
|
87
|
+
limit: { type: "number" },
|
|
88
|
+
orden: { type: "string", enum: ["actualizacion", "id"], description: '"id": orden estable para recorrer todo con despues_de. Por defecto, por \xFAltima actualizaci\xF3n.' },
|
|
89
|
+
despues_de: { type: "string", description: 'Id del \xFAltimo caso de la p\xE1gina anterior (con orden "id"): trae los que siguen.' },
|
|
90
|
+
offset: { type: "number" }
|
|
86
91
|
}
|
|
87
92
|
}
|
|
88
93
|
},
|
|
@@ -136,7 +141,7 @@ var manifest = {
|
|
|
136
141
|
{
|
|
137
142
|
name: TOOL_NAMES.abrirCaso,
|
|
138
143
|
displayName: "Abrir caso",
|
|
139
|
-
description: 'Abre un caso nuevo en un workflow, en su estado inicial. Workflows: `internacion_al_dia` (una internaci\xF3n abierta; referencia obligatoria `internacion:<id>`; datos: internacion_id), `honorario_en_cero` (referencia `honorarios:<profesional_id>`; datos: profesional_id, profesional), `debito_profesional` (referencia `debito:<profesional_id>:<nota_credito_id>`; datos: profesional_id, profesional, nota_credito_id, obra_social), `liquidacion_profesional` (referencia `liquidacion:<profesional_id>:<AAAA-MM-DD>`; datos: profesional_id, profesional, liquidado_el, institucion, mail_his), `cobranza_financiador` (un caso abierto por financiador; referencia obligatoria `cobranza:<obra_social_id>`; datos: obra_social (nombre como en el HIS), obra_social_id, institucion (nombre de la instituci\xF3n para el reclamo)), `circular_aranceles` (una circular de aranceles entera; referencia obligatoria `circular:<obra social>:<hash corto del archivo>`; datos: obra_social, convenio (nombre en el HIS), archivo {nombre, hash, paginas}, vigencia_desde AAAA-MM-DD, porcentaje_declarado y filas_declaradas si la circular los dice), `demanda_pendiente` (referencia obligatoria `orden:<id>`, `solicitud:<id>` o `control:<persona_id>:<regla_id>`; datos: regla_id (regla activa) y candidato tal cual lo devuelve buscar-demanda.mjs; el plugin verifica la regla con el candidato (si no la cumple, no abre) y fija regla_version, canal, intentos, vence_el; nunca nombre, documento ni tel\xE9fono del paciente),`actualizacion_arancel`, `tarea_operativa`, `recupero_turno` (datos: fecha AAAA-MM-DD, hora HH:MM, profesional, especialidad del turno libre; referencia "AAAA-MM-DD HH:MM \xB7 Profesional"), `cuenta_completa` (una prestaci\xF3n pendiente de facturar con faltantes; referencia obligatoria "bono:<n\xFAmero>", datos: bono_id, bono_numero, obra_social, plan, prestacion, fecha_prestacion AAAA-MM-DD, monto_pesos, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese. Pasar `idempotencyKey` para reintentos seguros.',
|
|
144
|
+
description: 'Abre un caso nuevo en un workflow, en su estado inicial. Workflows: `internacion_al_dia` (una internaci\xF3n abierta; referencia obligatoria `internacion:<id>`; datos: internacion_id), `honorario_en_cero` (referencia `honorarios:<profesional_id>`; datos: profesional_id, profesional), `debito_profesional` (referencia `debito:<profesional_id>:<nota_credito_id>`; datos: profesional_id, profesional, nota_credito_id, obra_social), `liquidacion_profesional` (referencia `liquidacion:<profesional_id>:<AAAA-MM-DD>`; datos: profesional_id, profesional, liquidado_el, institucion, mail_his), `cobranza_financiador` (un caso abierto por financiador; referencia obligatoria `cobranza:<obra_social_id>`; datos: obra_social (nombre como en el HIS), obra_social_id, institucion (nombre de la instituci\xF3n para el reclamo)), `circular_aranceles` (una circular de aranceles entera; referencia obligatoria `circular:<obra social>:<hash corto del archivo>`; datos: obra_social, convenio (nombre en el HIS), archivo {nombre, hash, paginas}, vigencia_desde AAAA-MM-DD, porcentaje_declarado y filas_declaradas si la circular los dice), `demanda_pendiente` (referencia obligatoria `orden:<id>`, `solicitud:<id>` o `control:<persona_id>:<regla_id>`; datos: regla_id (regla activa) y candidato tal cual lo devuelve buscar-demanda.mjs; el plugin verifica la regla con el candidato (si no la cumple, no abre) y fija regla_version, canal, intentos, vence_el; nunca nombre, documento ni tel\xE9fono del paciente),`actualizacion_arancel`, `tarea_operativa`, `recupero_turno` (datos: fecha AAAA-MM-DD, hora HH:MM, profesional, especialidad del turno libre; referencia "AAAA-MM-DD HH:MM \xB7 Profesional"), `cuenta_completa` (una prestaci\xF3n pendiente de facturar con faltantes; referencia obligatoria "bono:<n\xFAmero>", datos: bono_id, bono_numero, obra_social, plan, prestacion, fecha_prestacion AAAA-MM-DD, monto_pesos, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades), `rentabilidad_practica` (referencia obligatoria `rentabilidad:<prestacion_id>:<financiador_id>`; datos: prestacion_id, financiador_id, codigo, practica, financiador), `rentabilidad_internacion` (una internaci\xF3n egresada; referencia obligatoria `rentabilidad_internacion:<internacion_id>`; datos: internacion_id, financiador_id, financiador). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese (creado: false). En `rentabilidad_internacion` se devuelve el caso existente aunque est\xE9 cerrado: una internaci\xF3n se mide una sola vez. Pasar `idempotencyKey` para reintentos seguros.',
|
|
140
145
|
parametersSchema: {
|
|
141
146
|
type: "object",
|
|
142
147
|
properties: {
|
package/dist/worker.js
CHANGED
|
@@ -39932,7 +39932,7 @@ function startWorkerRpcHost(options) {
|
|
|
39932
39932
|
|
|
39933
39933
|
// src/manifest.ts
|
|
39934
39934
|
var PLUGIN_ID = "alephoo.autopilot-ops";
|
|
39935
|
-
var PLUGIN_VERSION = "0.1.
|
|
39935
|
+
var PLUGIN_VERSION = "0.1.34";
|
|
39936
39936
|
var DB_NAMESPACE_SLUG = "autopilot_ops";
|
|
39937
39937
|
var PAGE_ROUTE = "autopilot";
|
|
39938
39938
|
var TOOL_NAMES = {
|
|
@@ -40000,13 +40000,18 @@ var manifest = {
|
|
|
40000
40000
|
{
|
|
40001
40001
|
name: TOOL_NAMES.listarCasos,
|
|
40002
40002
|
displayName: "Listar casos",
|
|
40003
|
-
description:
|
|
40003
|
+
description: 'Lista casos del registro de negocio de la instituci\xF3n, abiertos y cerrados, del m\xE1s recientemente actualizado al m\xE1s viejo. Filtros opcionales: workflow (si el plugin no tiene esa funci\xF3n responde el error `workflow_desconocido`); estado (uno) o estados (varios, en un arreglo); referencia (todos los casos de ese objeto real, por ejemplo `rentabilidad:<prestacion_id>:<financiador_id>`, para saber si ya tiene caso sin recorrer el listado); limit (por defecto 50, m\xE1x. 500). Para recorrer todos sin perder ninguno aunque cambien mientras tanto: `orden: "id"` y, en cada p\xE1gina siguiente, `despues_de` con el id que indica la respuesta (\xABPuede haber m\xE1s casos\u2026\xBB); si vuelven menos casos que el limit, no hay m\xE1s. `offset` pagina el orden por actualizaci\xF3n (se corre si los casos cambian entre una p\xE1gina y otra). Solo lectura.',
|
|
40004
40004
|
parametersSchema: {
|
|
40005
40005
|
type: "object",
|
|
40006
40006
|
properties: {
|
|
40007
40007
|
workflow: { type: "string" },
|
|
40008
40008
|
estado: { type: "string" },
|
|
40009
|
-
|
|
40009
|
+
estados: { type: "array", items: { type: "string" }, description: "Varios estados a la vez (se ignora si viene estado)." },
|
|
40010
|
+
referencia: { type: "string", description: "Solo los casos con esta referencia exacta." },
|
|
40011
|
+
limit: { type: "number" },
|
|
40012
|
+
orden: { type: "string", enum: ["actualizacion", "id"], description: '"id": orden estable para recorrer todo con despues_de. Por defecto, por \xFAltima actualizaci\xF3n.' },
|
|
40013
|
+
despues_de: { type: "string", description: 'Id del \xFAltimo caso de la p\xE1gina anterior (con orden "id"): trae los que siguen.' },
|
|
40014
|
+
offset: { type: "number" }
|
|
40010
40015
|
}
|
|
40011
40016
|
}
|
|
40012
40017
|
},
|
|
@@ -40060,7 +40065,7 @@ var manifest = {
|
|
|
40060
40065
|
{
|
|
40061
40066
|
name: TOOL_NAMES.abrirCaso,
|
|
40062
40067
|
displayName: "Abrir caso",
|
|
40063
|
-
description: 'Abre un caso nuevo en un workflow, en su estado inicial. Workflows: `internacion_al_dia` (una internaci\xF3n abierta; referencia obligatoria `internacion:<id>`; datos: internacion_id), `honorario_en_cero` (referencia `honorarios:<profesional_id>`; datos: profesional_id, profesional), `debito_profesional` (referencia `debito:<profesional_id>:<nota_credito_id>`; datos: profesional_id, profesional, nota_credito_id, obra_social), `liquidacion_profesional` (referencia `liquidacion:<profesional_id>:<AAAA-MM-DD>`; datos: profesional_id, profesional, liquidado_el, institucion, mail_his), `cobranza_financiador` (un caso abierto por financiador; referencia obligatoria `cobranza:<obra_social_id>`; datos: obra_social (nombre como en el HIS), obra_social_id, institucion (nombre de la instituci\xF3n para el reclamo)), `circular_aranceles` (una circular de aranceles entera; referencia obligatoria `circular:<obra social>:<hash corto del archivo>`; datos: obra_social, convenio (nombre en el HIS), archivo {nombre, hash, paginas}, vigencia_desde AAAA-MM-DD, porcentaje_declarado y filas_declaradas si la circular los dice), `demanda_pendiente` (referencia obligatoria `orden:<id>`, `solicitud:<id>` o `control:<persona_id>:<regla_id>`; datos: regla_id (regla activa) y candidato tal cual lo devuelve buscar-demanda.mjs; el plugin verifica la regla con el candidato (si no la cumple, no abre) y fija regla_version, canal, intentos, vence_el; nunca nombre, documento ni tel\xE9fono del paciente),`actualizacion_arancel`, `tarea_operativa`, `recupero_turno` (datos: fecha AAAA-MM-DD, hora HH:MM, profesional, especialidad del turno libre; referencia "AAAA-MM-DD HH:MM \xB7 Profesional"), `cuenta_completa` (una prestaci\xF3n pendiente de facturar con faltantes; referencia obligatoria "bono:<n\xFAmero>", datos: bono_id, bono_numero, obra_social, plan, prestacion, fecha_prestacion AAAA-MM-DD, monto_pesos, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese. Pasar `idempotencyKey` para reintentos seguros.',
|
|
40068
|
+
description: 'Abre un caso nuevo en un workflow, en su estado inicial. Workflows: `internacion_al_dia` (una internaci\xF3n abierta; referencia obligatoria `internacion:<id>`; datos: internacion_id), `honorario_en_cero` (referencia `honorarios:<profesional_id>`; datos: profesional_id, profesional), `debito_profesional` (referencia `debito:<profesional_id>:<nota_credito_id>`; datos: profesional_id, profesional, nota_credito_id, obra_social), `liquidacion_profesional` (referencia `liquidacion:<profesional_id>:<AAAA-MM-DD>`; datos: profesional_id, profesional, liquidado_el, institucion, mail_his), `cobranza_financiador` (un caso abierto por financiador; referencia obligatoria `cobranza:<obra_social_id>`; datos: obra_social (nombre como en el HIS), obra_social_id, institucion (nombre de la instituci\xF3n para el reclamo)), `circular_aranceles` (una circular de aranceles entera; referencia obligatoria `circular:<obra social>:<hash corto del archivo>`; datos: obra_social, convenio (nombre en el HIS), archivo {nombre, hash, paginas}, vigencia_desde AAAA-MM-DD, porcentaje_declarado y filas_declaradas si la circular los dice), `demanda_pendiente` (referencia obligatoria `orden:<id>`, `solicitud:<id>` o `control:<persona_id>:<regla_id>`; datos: regla_id (regla activa) y candidato tal cual lo devuelve buscar-demanda.mjs; el plugin verifica la regla con el candidato (si no la cumple, no abre) y fija regla_version, canal, intentos, vence_el; nunca nombre, documento ni tel\xE9fono del paciente),`actualizacion_arancel`, `tarea_operativa`, `recupero_turno` (datos: fecha AAAA-MM-DD, hora HH:MM, profesional, especialidad del turno libre; referencia "AAAA-MM-DD HH:MM \xB7 Profesional"), `cuenta_completa` (una prestaci\xF3n pendiente de facturar con faltantes; referencia obligatoria "bono:<n\xFAmero>", datos: bono_id, bono_numero, obra_social, plan, prestacion, fecha_prestacion AAAA-MM-DD, monto_pesos, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades), `rentabilidad_practica` (referencia obligatoria `rentabilidad:<prestacion_id>:<financiador_id>`; datos: prestacion_id, financiador_id, codigo, practica, financiador), `rentabilidad_internacion` (una internaci\xF3n egresada; referencia obligatoria `rentabilidad_internacion:<internacion_id>`; datos: internacion_id, financiador_id, financiador). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese (creado: false). En `rentabilidad_internacion` se devuelve el caso existente aunque est\xE9 cerrado: una internaci\xF3n se mide una sola vez. Pasar `idempotencyKey` para reintentos seguros.',
|
|
40064
40069
|
parametersSchema: {
|
|
40065
40070
|
type: "object",
|
|
40066
40071
|
properties: {
|
|
@@ -43110,12 +43115,12 @@ var destinoRentabilidad = (caso, _datos, contexto) => {
|
|
|
43110
43115
|
var rentabilidadPractica = {
|
|
43111
43116
|
clave: "rentabilidad_practica",
|
|
43112
43117
|
nombre: "Rentabilidad por pr\xE1ctica",
|
|
43113
|
-
puesto: "
|
|
43118
|
+
puesto: "Costos y rentabilidad",
|
|
43114
43119
|
descripcion: "Calcula, para cada pr\xE1ctica y cada financiador, lo que deja: lo que paga el convenio menos el honorario del profesional (con la regla del HIS) y los insumos. Marca las que dan p\xE9rdida o quedan debajo del umbral de la instituci\xF3n, y las que no se pueden calcular porque faltan datos en el HIS. Decide la direcci\xF3n: renegociar con el financiador o aceptarla. El puesto no escribe en el sistema.",
|
|
43115
43120
|
inicial: "detectado",
|
|
43116
43121
|
estados: ["detectado", "pendiente_aprobacion", "datos_incompletos", "completado", "en_renegociacion", "resuelta", "aceptada", "rentable", "descartado"],
|
|
43117
|
-
finalesOk: ["resuelta"],
|
|
43118
|
-
finalesNoOk: ["
|
|
43122
|
+
finalesOk: ["resuelta", "rentable", "aceptada"],
|
|
43123
|
+
finalesNoOk: ["descartado"],
|
|
43119
43124
|
minutosAbsorbidos: 15,
|
|
43120
43125
|
transiciones: [
|
|
43121
43126
|
{
|
|
@@ -43165,7 +43170,123 @@ var rentabilidadPractica = {
|
|
|
43165
43170
|
}
|
|
43166
43171
|
]
|
|
43167
43172
|
};
|
|
43168
|
-
|
|
43173
|
+
function validarLecturaCostoInternacion(caso, datos, contexto) {
|
|
43174
|
+
const r = contexto.costoInternacion;
|
|
43175
|
+
const propia = texto4(caso.datos.internacion_id) ?? "";
|
|
43176
|
+
return [
|
|
43177
|
+
!r ? falla("lectura_valida", "falta lectura: la internaci\xF3n tal como la devuelve leer-internaciones.mjs") : "error" in r ? falla("lectura_valida", r.error) : ok("lectura_valida", `margen ${r.evaluacion.margen_pct ?? "sin calcular"} %`),
|
|
43178
|
+
r && !("error" in r) && r.internacionId !== propia ? falla("misma_internacion", `la lectura es de la internaci\xF3n ${r.internacionId} y el caso es de la ${propia}`) : ok("misma_internacion"),
|
|
43179
|
+
texto4(datos.lectura_id) ? ok("lectura_registrada") : falla("lectura_registrada", "hace falta lectura_id: la lectura de la base registrada con ops_registrar_accion")
|
|
43180
|
+
];
|
|
43181
|
+
}
|
|
43182
|
+
var DIAS_REVISION_CUENTA = 14;
|
|
43183
|
+
var centavos = (v) => typeof v === "number" && Number.isFinite(v) ? Math.round(v * 100) : null;
|
|
43184
|
+
var fechaCortaISO = (v) => {
|
|
43185
|
+
const iso5 = fechaAISO(v);
|
|
43186
|
+
return iso5 ? `${iso5.slice(8, 10)}/${iso5.slice(5, 7)}/${iso5.slice(0, 4)}` : null;
|
|
43187
|
+
};
|
|
43188
|
+
function baseRevisionCuenta(d) {
|
|
43189
|
+
const e = d.evaluacion && typeof d.evaluacion === "object" ? d.evaluacion : {};
|
|
43190
|
+
return {
|
|
43191
|
+
facturado: typeof d.revision_facturado === "number" ? d.revision_facturado : typeof e.facturado === "number" ? e.facturado : null,
|
|
43192
|
+
costo: typeof d.revision_costo === "number" ? d.revision_costo : typeof e.costo === "number" ? e.costo : null
|
|
43193
|
+
};
|
|
43194
|
+
}
|
|
43195
|
+
function resultadoRevisionCuenta(datosCaso, e, hoy) {
|
|
43196
|
+
const base = baseRevisionCuenta(datosCaso);
|
|
43197
|
+
const cambio2 = (antes, ahora) => antes !== null && centavos(antes) !== centavos(ahora);
|
|
43198
|
+
if (cambio2(base.facturado, e.facturado) || cambio2(base.costo, e.costo)) return "con_cambios";
|
|
43199
|
+
const vence = fechaAISO(datosCaso.revision_vence_el);
|
|
43200
|
+
return !vence || !hoy || hoy >= vence ? "sin_cambios" : null;
|
|
43201
|
+
}
|
|
43202
|
+
function notaRevisionCuenta(d, estado, e, hoy) {
|
|
43203
|
+
const pedida = fechaCortaISO(d.revision_pedida_el);
|
|
43204
|
+
const desde = pedida ? `Se pidi\xF3 revisar la cuenta el ${pedida}` : "Se pidi\xF3 revisar la cuenta";
|
|
43205
|
+
if (estado === "en_revision") {
|
|
43206
|
+
const vence = fechaCortaISO(d.revision_vence_el);
|
|
43207
|
+
return `${desde}: si no cambia, vuelve a la direcci\xF3n${vence ? ` el ${vence}` : ""}.`;
|
|
43208
|
+
}
|
|
43209
|
+
if (estado !== "pendiente_aprobacion" || typeof d.revisada_sin_cambios !== "boolean") return "";
|
|
43210
|
+
if (d.revisada_sin_cambios) {
|
|
43211
|
+
const dias = hoy ? diasEntre3(d.revision_pedida_el, hoy) : null;
|
|
43212
|
+
return `${desde} y${dias !== null && dias > 0 ? ` en ${dias} ${dias === 1 ? "d\xEDa" : "d\xEDas"}` : ""} no cambi\xF3.`;
|
|
43213
|
+
}
|
|
43214
|
+
const base = baseRevisionCuenta(d);
|
|
43215
|
+
const cambios = [
|
|
43216
|
+
base.facturado !== null && centavos(base.facturado) !== centavos(e.facturado) ? `lo facturado pas\xF3 de ${pesos2(base.facturado)} a ${pesos2(e.facturado)}` : null,
|
|
43217
|
+
base.costo !== null && centavos(base.costo) !== centavos(e.costo) ? `el costo pas\xF3 de ${pesos2(base.costo)} a ${pesos2(e.costo)}` : null
|
|
43218
|
+
].filter((x) => x !== null);
|
|
43219
|
+
const sigue = e.clase === "perdida" ? "sigue con p\xE9rdida" : "sigue debajo del margen m\xEDnimo";
|
|
43220
|
+
return `${desde}: ${cambios.length ? cambios.join(" y ") : "la cuenta cambi\xF3 desde entonces"}, y ${sigue}.`;
|
|
43221
|
+
}
|
|
43222
|
+
var destinoCostoInternacion = (caso, _datos, contexto) => {
|
|
43223
|
+
const r = contexto.costoInternacion;
|
|
43224
|
+
if (!r || "error" in r) return caso.estado;
|
|
43225
|
+
const clase2 = r.evaluacion.clase;
|
|
43226
|
+
if (clase2 === "datos_incompletos") return "datos_incompletos";
|
|
43227
|
+
if (clase2 === "rentable") return caso.estado === "detectado" ? "rentable" : "resuelta";
|
|
43228
|
+
if (caso.estado === "en_revision") return resultadoRevisionCuenta(caso.datos, r.evaluacion, contexto.hoy) ? "pendiente_aprobacion" : "en_revision";
|
|
43229
|
+
return "pendiente_aprobacion";
|
|
43230
|
+
};
|
|
43231
|
+
var rentabilidadInternacion = {
|
|
43232
|
+
clave: "rentabilidad_internacion",
|
|
43233
|
+
nombre: "Rentabilidad por internaci\xF3n",
|
|
43234
|
+
puesto: "Costos y rentabilidad",
|
|
43235
|
+
descripcion: "Calcula, para cada internaci\xF3n egresada, lo que dej\xF3: lo facturado contra lo que cost\xF3 (honorarios con la regla del HIS, insumos que farmacia le entreg\xF3 al paciente a costo de compra, d\xEDas cama y quir\xF3fano con los costos que fija la instituci\xF3n). Las que dan p\xE9rdida o quedan debajo del umbral van a la direcci\xF3n: revisar la cuenta (consumos sin facturar, renegociar el m\xF3dulo con el financiador) o aceptarla as\xED. El puesto no escribe en el sistema.",
|
|
43236
|
+
inicial: "detectado",
|
|
43237
|
+
estados: ["detectado", "pendiente_aprobacion", "datos_incompletos", "completado", "en_revision", "resuelta", "aceptada", "rentable", "descartado"],
|
|
43238
|
+
finalesOk: ["resuelta", "rentable", "aceptada"],
|
|
43239
|
+
finalesNoOk: ["descartado"],
|
|
43240
|
+
minutosAbsorbidos: 20,
|
|
43241
|
+
transiciones: [
|
|
43242
|
+
{
|
|
43243
|
+
via: "evaluar",
|
|
43244
|
+
de: ["detectado"],
|
|
43245
|
+
a: destinoCostoInternacion,
|
|
43246
|
+
actor: ["agente", "humano", "sistema"],
|
|
43247
|
+
nivelRequerido: "observa",
|
|
43248
|
+
descripcion: "Primera lectura de la internaci\xF3n. El plugin calcula el resultado y decide: pendiente de aprobaci\xF3n (p\xE9rdida o margen bajo), datos incompletos o rentable.",
|
|
43249
|
+
validar: validarLecturaCostoInternacion
|
|
43250
|
+
},
|
|
43251
|
+
{ via: "aprobar", de: ["pendiente_aprobacion"], a: "en_revision", actor: ["humano"], descripcion: `La direcci\xF3n pide revisar la cuenta: consumos que no se facturaron o el m\xF3dulo con el financiador. El puesto vuelve a medir: si la cuenta cambia y sigue con p\xE9rdida o margen bajo, o si pasan ${DIAS_REVISION_CUENTA} d\xEDas sin cambios, vuelve a la direcci\xF3n.` },
|
|
43252
|
+
{
|
|
43253
|
+
via: "rechazar",
|
|
43254
|
+
de: ["pendiente_aprobacion", "en_revision"],
|
|
43255
|
+
a: "aceptada",
|
|
43256
|
+
actor: ["humano"],
|
|
43257
|
+
descripcion: "La direcci\xF3n acepta el resultado de la internaci\xF3n as\xED. Queda con el motivo.",
|
|
43258
|
+
validar: (_caso, datos) => [texto4(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "rechazar requiere motivo: por qu\xE9 se acepta el resultado as\xED")]
|
|
43259
|
+
},
|
|
43260
|
+
{
|
|
43261
|
+
via: "completar",
|
|
43262
|
+
de: ["datos_incompletos"],
|
|
43263
|
+
a: "completado",
|
|
43264
|
+
actor: ["humano"],
|
|
43265
|
+
cargaManual: true,
|
|
43266
|
+
descripcion: "Una persona de la instituci\xF3n carg\xF3 lo que faltaba (el costo de un lote en el HIS, o el costo del d\xEDa cama o del quir\xF3fano en Opera). El puesto lo comprueba volviendo a leer.",
|
|
43267
|
+
validar: (_caso, datos) => [texto4(datos.accion_id) ? ok("accion_registrada") : falla("accion_registrada", "completar requiere accion_id de la carga registrada")]
|
|
43268
|
+
},
|
|
43269
|
+
{
|
|
43270
|
+
via: "comprobar",
|
|
43271
|
+
de: ["pendiente_aprobacion", "datos_incompletos", "completado", "en_revision"],
|
|
43272
|
+
a: destinoCostoInternacion,
|
|
43273
|
+
actor: ["agente", "humano", "sistema"],
|
|
43274
|
+
nivelRequerido: "observa",
|
|
43275
|
+
descripcion: `Volver a leer la internaci\xF3n. El plugin decide: resuelta (ya deja margen, por ejemplo porque se factur\xF3 lo que faltaba), pendiente de aprobaci\xF3n (sigue con p\xE9rdida o margen bajo; desde la revisi\xF3n, solo si la cuenta cambi\xF3 o pasaron ${DIAS_REVISION_CUENTA} d\xEDas sin cambios), sigue en revisi\xF3n o datos incompletos.`,
|
|
43276
|
+
validar: validarLecturaCostoInternacion
|
|
43277
|
+
},
|
|
43278
|
+
{
|
|
43279
|
+
via: "descartar",
|
|
43280
|
+
de: ["detectado", "pendiente_aprobacion", "datos_incompletos", "completado", "en_revision"],
|
|
43281
|
+
a: "descartado",
|
|
43282
|
+
actor: ["agente", "humano"],
|
|
43283
|
+
nivelRequerido: "observa",
|
|
43284
|
+
descripcion: "La internaci\xF3n se anul\xF3 o no corresponde medirla.",
|
|
43285
|
+
validar: (_caso, datos) => [texto4(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "descartar requiere motivo")]
|
|
43286
|
+
}
|
|
43287
|
+
]
|
|
43288
|
+
};
|
|
43289
|
+
var WORKFLOWS_SOLO_LECTURA = /* @__PURE__ */ new Set([cuentaCompleta.clave, facturacionPendiente.clave, credencialProfesional.clave, internacionAlDia.clave, honorarioEnCero.clave, rentabilidadPractica.clave, rentabilidadInternacion.clave]);
|
|
43169
43290
|
var WORKFLOWS = {
|
|
43170
43291
|
[actualizacionArancel.clave]: actualizacionArancel,
|
|
43171
43292
|
[tareaOperativa.clave]: tareaOperativa,
|
|
@@ -43180,7 +43301,8 @@ var WORKFLOWS = {
|
|
|
43180
43301
|
[honorarioEnCero.clave]: honorarioEnCero,
|
|
43181
43302
|
[debitoProfesional.clave]: debitoProfesional,
|
|
43182
43303
|
[liquidacionProfesional.clave]: liquidacionProfesional,
|
|
43183
|
-
[rentabilidadPractica.clave]: rentabilidadPractica
|
|
43304
|
+
[rentabilidadPractica.clave]: rentabilidadPractica,
|
|
43305
|
+
[rentabilidadInternacion.clave]: rentabilidadInternacion
|
|
43184
43306
|
};
|
|
43185
43307
|
function getWorkflow(clave) {
|
|
43186
43308
|
return WORKFLOWS[clave] ?? null;
|
|
@@ -43577,8 +43699,8 @@ async function requerirRegla(ctx, companyId, id3) {
|
|
|
43577
43699
|
if (!r) throw new OpsError("regla_inexistente", `no existe la regla ${id3} en esta instituci\xF3n`);
|
|
43578
43700
|
return r;
|
|
43579
43701
|
}
|
|
43580
|
-
function cambio(version2, actor,
|
|
43581
|
-
return { version: version2, fecha: (/* @__PURE__ */ new Date()).toISOString(), actorTipo: actor.tipo, actorId: actor.id, cambio:
|
|
43702
|
+
function cambio(version2, actor, texto10) {
|
|
43703
|
+
return { version: version2, fecha: (/* @__PURE__ */ new Date()).toISOString(), actorTipo: actor.tipo, actorId: actor.id, cambio: texto10 };
|
|
43582
43704
|
}
|
|
43583
43705
|
function soloPersonas(actor, que) {
|
|
43584
43706
|
if (actor.tipo !== "humano") throw new OpsError("actor_no_permitido", `${que} lo hace una persona de la instituci\xF3n desde el portal; el agente solo lee las reglas e informa la prueba`);
|
|
@@ -43767,7 +43889,7 @@ function evaluarRentabilidad(input2) {
|
|
|
43767
43889
|
if (l.insumos.length === 0) avisos.push("sin_insumos");
|
|
43768
43890
|
const debajo = [];
|
|
43769
43891
|
const honorarios = [];
|
|
43770
|
-
if (l.honorarios.length === 0) faltas.push("
|
|
43892
|
+
if (l.honorarios.length === 0) faltas.push("sin_profesional");
|
|
43771
43893
|
for (const h of l.honorarios) {
|
|
43772
43894
|
const nombre = h.profesional ?? h.profesional_id;
|
|
43773
43895
|
if (!h.regla) {
|
|
@@ -43817,6 +43939,7 @@ var NOMBRE_FALTA = {
|
|
|
43817
43939
|
sin_valor_convenio: "el convenio no tiene valor para esta pr\xE1ctica",
|
|
43818
43940
|
insumo_sin_precio: "hay insumos sin precio",
|
|
43819
43941
|
sin_regla_honorario: "hay profesionales sin regla de honorarios",
|
|
43942
|
+
sin_profesional: "se factur\xF3 sin un profesional (si es un medicamento o un descartable, no corresponde medirla como pr\xE1ctica)",
|
|
43820
43943
|
regla_sin_valor: "hay reglas de honorarios sin valor"
|
|
43821
43944
|
};
|
|
43822
43945
|
var porcentaje = (n) => `${n.toLocaleString("es-AR", { maximumFractionDigits: 1 })} %`;
|
|
@@ -43921,6 +44044,204 @@ async function insumosPorCodigo(ctx, companyId) {
|
|
|
43921
44044
|
return out;
|
|
43922
44045
|
}
|
|
43923
44046
|
|
|
44047
|
+
// src/costos_internacion.ts
|
|
44048
|
+
var texto7 = (v) => typeof v === "string" && v.trim() ? v.trim() : typeof v === "number" && Number.isFinite(v) ? String(v) : null;
|
|
44049
|
+
var num7 = (v) => {
|
|
44050
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() ? Number(v) : NaN;
|
|
44051
|
+
return Number.isFinite(n) ? n : null;
|
|
44052
|
+
};
|
|
44053
|
+
var redondear5 = (n) => Math.round(n * 100) / 100;
|
|
44054
|
+
var fechaHora2 = (v) => {
|
|
44055
|
+
const s = texto7(v);
|
|
44056
|
+
return s && /^\d{4}-\d{2}-\d{2}([ T]\d{2}:\d{2}(:\d{2})?)?/.test(s) ? s.replace(" ", "T").slice(0, 19) : null;
|
|
44057
|
+
};
|
|
44058
|
+
function leerLecturaCostoInternacion(v) {
|
|
44059
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return { error: "lectura: tiene que ser un objeto" };
|
|
44060
|
+
const r = v;
|
|
44061
|
+
const internacion_id = texto7(r.internacion_id);
|
|
44062
|
+
const financiador_id = texto7(r.financiador_id);
|
|
44063
|
+
const ingreso = fechaHora2(r.ingreso);
|
|
44064
|
+
if (!internacion_id) return { error: "lectura: falta internacion_id" };
|
|
44065
|
+
if (!financiador_id) return { error: "lectura: falta financiador_id" };
|
|
44066
|
+
if (!ingreso) return { error: "lectura.ingreso: fecha y hora AAAA-MM-DD HH:MM" };
|
|
44067
|
+
for (const k of ["eventos", "facturado", "consumos", "cirugias"]) if (!Array.isArray(r[k])) return { error: `lectura.${k}: tiene que ser una lista` };
|
|
44068
|
+
const eventos = [];
|
|
44069
|
+
for (const [i, x] of r.eventos.entries()) {
|
|
44070
|
+
const e = x ?? {};
|
|
44071
|
+
const fecha5 = fechaHora2(e.fecha);
|
|
44072
|
+
const tipo = texto7(e.tipo);
|
|
44073
|
+
if (!fecha5 || !tipo) return { error: `lectura.eventos[${i}]: faltan fecha o tipo` };
|
|
44074
|
+
eventos.push({ fecha: fecha5, tipo, servicio: texto7(e.servicio) });
|
|
44075
|
+
}
|
|
44076
|
+
const facturado = [];
|
|
44077
|
+
for (const [i, x] of r.facturado.entries()) {
|
|
44078
|
+
const f = x ?? {};
|
|
44079
|
+
const cantidad2 = num7(f.cantidad);
|
|
44080
|
+
const monto = num7(f.monto);
|
|
44081
|
+
if (cantidad2 === null || cantidad2 < 0 || monto === null || monto < 0) return { error: `lectura.facturado[${i}]: cantidad y monto tienen que ser n\xFAmeros \u2265 0` };
|
|
44082
|
+
if (f.regla !== null && f.regla !== void 0 && !leerRegla(f.regla)) return { error: `lectura.facturado[${i}].regla: tiene que ser {id, prof, inst} o null` };
|
|
44083
|
+
const noLiquidable = f.liquidable === false || f.liquidable === 0 || f.liquidable === "0" || f.liquidable === "false";
|
|
44084
|
+
facturado.push({ prestacion_id: texto7(f.prestacion_id) ?? String(i), nombre: texto7(f.nombre) ?? "", cantidad: cantidad2, monto: redondear5(monto), profesional_id: texto7(f.profesional_id), regla: leerRegla(f.regla), ...noLiquidable ? { liquidable: false } : {} });
|
|
44085
|
+
}
|
|
44086
|
+
const consumos = [];
|
|
44087
|
+
for (const [i, x] of r.consumos.entries()) {
|
|
44088
|
+
const c = x ?? {};
|
|
44089
|
+
const dispensado = num7(c.dispensado) ?? 0;
|
|
44090
|
+
const registrado = num7(c.registrado) ?? 0;
|
|
44091
|
+
const costo = num7(c.costo_unitario);
|
|
44092
|
+
if (dispensado < 0 && registrado <= 0) continue;
|
|
44093
|
+
if (costo !== null && costo < 0) return { error: `lectura.consumos[${i}].costo_unitario: no puede ser negativo` };
|
|
44094
|
+
consumos.push({ articulo_id: texto7(c.articulo_id) ?? String(i), articulo: texto7(c.articulo) ?? "", dispensado: Math.max(0, dispensado), registrado: Math.max(0, registrado), costo_unitario: costo === null || costo === 0 ? null : costo });
|
|
44095
|
+
}
|
|
44096
|
+
const cirugias = r.cirugias.map((x) => {
|
|
44097
|
+
const m = num7((x ?? {}).minutos);
|
|
44098
|
+
return { minutos: m !== null && m > 0 && m <= 24 * 60 ? Math.round(m) : null };
|
|
44099
|
+
});
|
|
44100
|
+
return { lectura: { internacion_id, financiador_id, financiador: texto7(r.financiador) ?? financiador_id, ingreso, egreso: fechaHora2(r.egreso), eventos, facturado, consumos, cirugias } };
|
|
44101
|
+
}
|
|
44102
|
+
var DIA_MS = 864e5;
|
|
44103
|
+
var ABREN = /* @__PURE__ */ new Set(["ingreso", "pase_cama", "reingreso"]);
|
|
44104
|
+
var CIERRAN = /* @__PURE__ */ new Set(["egreso"]);
|
|
44105
|
+
var ORDEN_EVENTO = { egreso: 0, ingreso: 1, reingreso: 1, pase_cama: 2 };
|
|
44106
|
+
var claveServicio = (s) => s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/\s+/g, " ").trim();
|
|
44107
|
+
function repartir2(pesos4, unidades) {
|
|
44108
|
+
const suma = pesos4.reduce((a, b) => a + b, 0);
|
|
44109
|
+
const exactas = pesos4.map((p) => suma > 0 ? p / suma * unidades : 0);
|
|
44110
|
+
const enteras = exactas.map((x) => Math.floor(x));
|
|
44111
|
+
let resto = unidades - enteras.reduce((a, b) => a + b, 0);
|
|
44112
|
+
const orden = pesos4.map((_, i) => i).sort((a, b) => exactas[b] - enteras[b] - (exactas[a] - enteras[a]) || pesos4[b] - pesos4[a] || a - b);
|
|
44113
|
+
for (const i of orden) {
|
|
44114
|
+
if (resto <= 0) break;
|
|
44115
|
+
enteras[i] += 1;
|
|
44116
|
+
resto -= 1;
|
|
44117
|
+
}
|
|
44118
|
+
return enteras;
|
|
44119
|
+
}
|
|
44120
|
+
function repartirDecimas(pesos4, total) {
|
|
44121
|
+
const decimas = Math.round(total * 10);
|
|
44122
|
+
if (pesos4.length > decimas) return repartir2(pesos4, decimas);
|
|
44123
|
+
const suma = pesos4.reduce((a, b) => a + b, 0);
|
|
44124
|
+
const excedentes = pesos4.map((p) => Math.max(0, p / suma * decimas - 1));
|
|
44125
|
+
return repartir2(excedentes, decimas - pesos4.length).map((d) => d + 1);
|
|
44126
|
+
}
|
|
44127
|
+
function diasPorServicio(l, hoy) {
|
|
44128
|
+
const inicio = Date.parse(l.ingreso);
|
|
44129
|
+
const fin = Math.max(inicio, Date.parse(l.egreso ?? `${hoy}T23:59:59`));
|
|
44130
|
+
const recortar = (t6) => Math.min(fin, Math.max(inicio, t6));
|
|
44131
|
+
const evs = l.eventos.filter((e) => ABREN.has(e.tipo) || CIERRAN.has(e.tipo)).map((e) => ({ ...e, t: recortar(Date.parse(e.fecha)) })).sort((a, b) => a.t - b.t || (ORDEN_EVENTO[a.tipo] ?? 1) - (ORDEN_EVENTO[b.tipo] ?? 1) || a.fecha.localeCompare(b.fecha));
|
|
44132
|
+
const tramos = /* @__PURE__ */ new Map();
|
|
44133
|
+
const sumar = (servicio, ms) => {
|
|
44134
|
+
const k = claveServicio(servicio);
|
|
44135
|
+
const t6 = tramos.get(k);
|
|
44136
|
+
if (t6) t6.ms += Math.max(0, ms);
|
|
44137
|
+
else tramos.set(k, { nombre: servicio, ms: Math.max(0, ms) });
|
|
44138
|
+
};
|
|
44139
|
+
let abierto = null;
|
|
44140
|
+
let ultimoServicio = "Sin servicio";
|
|
44141
|
+
for (const e of evs) {
|
|
44142
|
+
if (abierto) sumar(abierto.servicio, e.t - abierto.desde);
|
|
44143
|
+
abierto = null;
|
|
44144
|
+
if (ABREN.has(e.tipo)) {
|
|
44145
|
+
ultimoServicio = e.servicio ?? ultimoServicio;
|
|
44146
|
+
abierto = { desde: e.t, servicio: ultimoServicio };
|
|
44147
|
+
}
|
|
44148
|
+
}
|
|
44149
|
+
if (abierto) sumar(abierto.servicio, fin - abierto.desde);
|
|
44150
|
+
if (tramos.size === 0) sumar(ultimoServicio, fin - inicio);
|
|
44151
|
+
const totalMs = [...tramos.values()].reduce((a, t6) => a + t6.ms, 0);
|
|
44152
|
+
const total = Math.max(1, Math.ceil(totalMs / DIA_MS - 0.01));
|
|
44153
|
+
let activos = [...tramos.values()].filter((t6) => t6.ms > 0);
|
|
44154
|
+
if (activos.length === 0) activos = [{ ...tramos.get(claveServicio(ultimoServicio)) ?? { nombre: ultimoServicio }, ms: 1 }];
|
|
44155
|
+
const decimas = repartirDecimas(activos.map((t6) => t6.ms), total);
|
|
44156
|
+
const msActivos = activos.reduce((a, t6) => a + t6.ms, 0);
|
|
44157
|
+
const out = /* @__PURE__ */ new Map();
|
|
44158
|
+
const exactos = /* @__PURE__ */ new Map();
|
|
44159
|
+
activos.forEach((t6, i) => {
|
|
44160
|
+
out.set(t6.nombre, decimas[i] / 10);
|
|
44161
|
+
exactos.set(t6.nombre, t6.ms / msActivos * total);
|
|
44162
|
+
});
|
|
44163
|
+
return { tramos: out, total, exactos };
|
|
44164
|
+
}
|
|
44165
|
+
function evaluarCostoInternacion(input2) {
|
|
44166
|
+
const { lectura: l, hoy } = input2;
|
|
44167
|
+
const umbral = input2.umbral ?? UMBRAL_MARGEN_POR_DEFECTO;
|
|
44168
|
+
const faltas = [];
|
|
44169
|
+
const avisos = [];
|
|
44170
|
+
const facturado = redondear5(l.facturado.reduce((a, f) => a + f.monto * f.cantidad, 0));
|
|
44171
|
+
if (facturado <= 0) faltas.push("sin_facturar");
|
|
44172
|
+
if (!l.egreso) avisos.push("en_curso");
|
|
44173
|
+
let honorarios = 0;
|
|
44174
|
+
let sinRegla = 0;
|
|
44175
|
+
for (const f of l.facturado) {
|
|
44176
|
+
if (!f.profesional_id || f.liquidable === false) continue;
|
|
44177
|
+
if (!f.regla || f.regla.prof === null && f.regla.inst === null) sinRegla += 1;
|
|
44178
|
+
honorarios += honorarioDe({ regla: f.regla, monto: f.monto, cantidad: f.cantidad });
|
|
44179
|
+
}
|
|
44180
|
+
honorarios = redondear5(honorarios);
|
|
44181
|
+
if (sinRegla > 0) avisos.push("prestaciones_sin_regla");
|
|
44182
|
+
let insumos = 0;
|
|
44183
|
+
const sinCosto = [];
|
|
44184
|
+
for (const c of l.consumos) {
|
|
44185
|
+
const cantidad2 = Math.max(c.dispensado, c.registrado);
|
|
44186
|
+
if (cantidad2 <= 0) continue;
|
|
44187
|
+
if (c.costo_unitario === null) {
|
|
44188
|
+
sinCosto.push(c.articulo || c.articulo_id);
|
|
44189
|
+
continue;
|
|
44190
|
+
}
|
|
44191
|
+
insumos += cantidad2 * c.costo_unitario;
|
|
44192
|
+
}
|
|
44193
|
+
insumos = redondear5(insumos);
|
|
44194
|
+
if (sinCosto.length) faltas.push("insumo_sin_costo");
|
|
44195
|
+
const { tramos, total, exactos } = diasPorServicio(l, hoy);
|
|
44196
|
+
const porServicio = input2.costoDiaPorServicio ?? {};
|
|
44197
|
+
const porClave = /* @__PURE__ */ new Map();
|
|
44198
|
+
for (const [s, c] of Object.entries(porServicio)) if (!porClave.has(claveServicio(s))) porClave.set(claveServicio(s), c);
|
|
44199
|
+
const general = input2.costoDiaCama ?? null;
|
|
44200
|
+
let cama = 0;
|
|
44201
|
+
let faltaCama = false;
|
|
44202
|
+
const dias_por_servicio = [...tramos.entries()].map(([servicio, dias]) => {
|
|
44203
|
+
const costo_dia = porServicio[servicio] ?? porClave.get(claveServicio(servicio)) ?? general;
|
|
44204
|
+
if (costo_dia === null || costo_dia === void 0) faltaCama = true;
|
|
44205
|
+
else cama += (exactos.get(servicio) ?? dias) * costo_dia;
|
|
44206
|
+
return { servicio, dias, costo_dia: costo_dia ?? null };
|
|
44207
|
+
});
|
|
44208
|
+
cama = redondear5(cama);
|
|
44209
|
+
if (faltaCama) faltas.push("sin_costo_cama");
|
|
44210
|
+
const minutos = l.cirugias.reduce((a, c) => a + (c.minutos ?? 0), 0);
|
|
44211
|
+
let quirofano = 0;
|
|
44212
|
+
if (l.cirugias.length > 0) {
|
|
44213
|
+
if (input2.costoHoraQuirofano === null || input2.costoHoraQuirofano === void 0) faltas.push("sin_costo_quirofano");
|
|
44214
|
+
else quirofano = redondear5(minutos / 60 * input2.costoHoraQuirofano);
|
|
44215
|
+
}
|
|
44216
|
+
if (l.cirugias.some((c) => c.minutos === null)) faltas.push("cirugia_sin_duracion");
|
|
44217
|
+
const costo = redondear5(honorarios + insumos + cama + quirofano);
|
|
44218
|
+
const calculable = faltas.length === 0 && facturado > 0;
|
|
44219
|
+
const margen = calculable ? redondear5(facturado - costo) : null;
|
|
44220
|
+
const margen_pct = margen !== null ? Math.round(margen / facturado * 1e3) / 10 : null;
|
|
44221
|
+
let clase2;
|
|
44222
|
+
if (margen === null || margen_pct === null) clase2 = "datos_incompletos";
|
|
44223
|
+
else if (margen < 0) clase2 = "perdida";
|
|
44224
|
+
else if (margen_pct < umbral) clase2 = "margen_bajo";
|
|
44225
|
+
else clase2 = "rentable";
|
|
44226
|
+
return { clase: clase2, facturado, honorarios, insumos, cama, quirofano, costo, margen, margen_pct, umbral, dias: total, dias_por_servicio, minutos_quirofano: minutos, faltas, avisos, insumos_sin_costo: sinCosto, prestaciones_sin_regla: sinRegla };
|
|
44227
|
+
}
|
|
44228
|
+
var NOMBRE_FALTA2 = {
|
|
44229
|
+
sin_facturar: "no tiene nada facturado",
|
|
44230
|
+
insumo_sin_costo: "hay insumos sin costo de compra en el HIS",
|
|
44231
|
+
sin_costo_cama: "falta el costo del d\xEDa cama",
|
|
44232
|
+
sin_costo_quirofano: "falta el costo de la hora de quir\xF3fano",
|
|
44233
|
+
cirugia_sin_duracion: "hay cirug\xEDas sin la duraci\xF3n en el parte quir\xFArgico"
|
|
44234
|
+
};
|
|
44235
|
+
var porcentaje2 = (n) => `${n.toLocaleString("es-AR", { maximumFractionDigits: 1 })} %`;
|
|
44236
|
+
var enumerar = (xs) => xs.length > 1 ? `${xs.slice(0, -1).join(", ")} y ${xs[xs.length - 1]}` : xs[0] ?? "";
|
|
44237
|
+
function oracionCostoInternacion(l, e) {
|
|
44238
|
+
const quien = `La internaci\xF3n ${l.internacion_id} con ${l.financiador} (${e.dias} ${e.dias === 1 ? "d\xEDa" : "d\xEDas"})`;
|
|
44239
|
+
if (e.clase === "datos_incompletos") return `${quien}: no se puede calcular el resultado porque ${enumerar(e.faltas.map((f) => NOMBRE_FALTA2[f] ?? f.replace(/_/g, " ")))}.`;
|
|
44240
|
+
if (e.clase === "perdida") return `${quien} dio p\xE9rdida: se facturaron ${pesos2(e.facturado)} y cost\xF3 ${pesos2(e.costo)} (${pesos2(Math.abs(e.margen))} de p\xE9rdida).`;
|
|
44241
|
+
if (e.clase === "margen_bajo") return `${quien} dej\xF3 un margen de ${porcentaje2(e.margen_pct)}, debajo del ${porcentaje2(e.umbral)} de la instituci\xF3n: se facturaron ${pesos2(e.facturado)} y cost\xF3 ${pesos2(e.costo)}.`;
|
|
44242
|
+
return `${quien} dej\xF3 un margen de ${porcentaje2(e.margen_pct)}.`;
|
|
44243
|
+
}
|
|
44244
|
+
|
|
43924
44245
|
// src/ops.ts
|
|
43925
44246
|
var OpsError = class extends Error {
|
|
43926
44247
|
constructor(codigo, motivo, detalle) {
|
|
@@ -44062,6 +44383,11 @@ function describirOpciones(antes, despues) {
|
|
|
44062
44383
|
if (antes.hora_corte !== despues.hora_corte && despues.hora_corte !== void 0) partes.push(`hora de corte de la evoluci\xF3n: ${String(despues.hora_corte)}`);
|
|
44063
44384
|
if (antes.dias_sin_actividad !== despues.dias_sin_actividad && despues.dias_sin_actividad !== void 0) partes.push(`d\xEDas sin actividad para sospechar un egreso sin registrar: ${String(despues.dias_sin_actividad)}`);
|
|
44064
44385
|
if (antes.tope_variacion !== despues.tope_variacion && despues.tope_variacion !== void 0) partes.push(`tope de variaci\xF3n de las circulares: ${String(despues.tope_variacion)} %`);
|
|
44386
|
+
if (antes.umbral_margen !== despues.umbral_margen && despues.umbral_margen !== void 0) partes.push(`margen m\xEDnimo: ${String(despues.umbral_margen)} %`);
|
|
44387
|
+
if (antes.meses_precio !== despues.meses_precio && despues.meses_precio !== void 0) partes.push(`meses para avisar un precio de insumo viejo: ${String(despues.meses_precio)}`);
|
|
44388
|
+
if (antes.costo_dia_cama !== despues.costo_dia_cama && despues.costo_dia_cama !== void 0) partes.push(`costo del d\xEDa cama: ${despues.costo_dia_cama === null ? "sin fijar" : `$ ${String(despues.costo_dia_cama)}`}`);
|
|
44389
|
+
if (antes.costo_hora_quirofano !== despues.costo_hora_quirofano && despues.costo_hora_quirofano !== void 0) partes.push(`costo de la hora de quir\xF3fano: ${despues.costo_hora_quirofano === null ? "sin fijar" : `$ ${String(despues.costo_hora_quirofano)}`}`);
|
|
44390
|
+
if (JSON.stringify(antes.costo_dia_servicio ?? {}) !== JSON.stringify(despues.costo_dia_servicio ?? {}) && despues.costo_dia_servicio !== void 0) partes.push(`costo del d\xEDa cama por servicio: ${Object.entries(despues.costo_dia_servicio).map(([s, m]) => `${s} $ ${m}`).join(", ") || "ninguno"}`);
|
|
44065
44391
|
if (Boolean(antes.ofrecer_primero) !== Boolean(despues.ofrecer_primero)) partes.push(`consultar al paciente antes de asignar: ${despues.ofrecer_primero ? "s\xED" : "no"}`);
|
|
44066
44392
|
const plazosAntes = JSON.stringify(leerPlazos(antes.plazos));
|
|
44067
44393
|
const plazosDespues = leerPlazos(despues.plazos);
|
|
@@ -44074,6 +44400,22 @@ function describirOpciones(antes, despues) {
|
|
|
44074
44400
|
function conInsumosDeOpera(l, insumos) {
|
|
44075
44401
|
l.insumos = insumos.get(normalizarCodigo(l.codigo)) ?? [];
|
|
44076
44402
|
}
|
|
44403
|
+
function montoNoNegativo(v) {
|
|
44404
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() ? Number(v) : NaN;
|
|
44405
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 100) / 100 : null;
|
|
44406
|
+
}
|
|
44407
|
+
function leerCostosPorServicio(v) {
|
|
44408
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return {};
|
|
44409
|
+
const out = {};
|
|
44410
|
+
for (const [k, x] of Object.entries(v)) {
|
|
44411
|
+
const m = montoNoNegativo(x);
|
|
44412
|
+
if (k.trim() && k.length <= 80 && m !== null) out[k.trim()] = m;
|
|
44413
|
+
}
|
|
44414
|
+
return out;
|
|
44415
|
+
}
|
|
44416
|
+
function opcionesCostos(op) {
|
|
44417
|
+
return { umbral: enteroEntre(op.umbral_margen, 0, 90) ?? UMBRAL_MARGEN_POR_DEFECTO, costoDiaCama: montoNoNegativo(op.costo_dia_cama), costoDiaPorServicio: leerCostosPorServicio(op.costo_dia_servicio), costoHoraQuirofano: montoNoNegativo(op.costo_hora_quirofano) };
|
|
44418
|
+
}
|
|
44077
44419
|
function opcionesRentabilidad(op) {
|
|
44078
44420
|
return { umbral: enteroEntre(op.umbral_margen, 0, 90) ?? UMBRAL_MARGEN_POR_DEFECTO, mesesPrecio: enteroEntre(op.meses_precio, 1, 36) ?? MESES_PRECIO_POR_DEFECTO };
|
|
44079
44421
|
}
|
|
@@ -44143,6 +44485,23 @@ async function cambiarPolitica(ctx, input2) {
|
|
|
44143
44485
|
if (tope === null) throw new OpsError("validacion", `tope_variacion: tiene que ser un n\xFAmero entre ${LIMITES_TOPE[0]} y ${LIMITES_TOPE[1]}`);
|
|
44144
44486
|
opciones.tope_variacion = tope;
|
|
44145
44487
|
}
|
|
44488
|
+
for (const clave of ["umbral_margen", "meses_precio"]) {
|
|
44489
|
+
if (opciones[clave] === void 0) continue;
|
|
44490
|
+
const [min, max] = clave === "umbral_margen" ? [0, 90] : [1, 36];
|
|
44491
|
+
const v = enteroEntre(opciones[clave], min, max);
|
|
44492
|
+
if (v === null) throw new OpsError("validacion", `${clave}: tiene que ser un n\xFAmero entero entre ${min} y ${max}`);
|
|
44493
|
+
opciones[clave] = v;
|
|
44494
|
+
}
|
|
44495
|
+
for (const clave of ["costo_dia_cama", "costo_hora_quirofano"]) {
|
|
44496
|
+
if (opciones[clave] === void 0 || opciones[clave] === null || opciones[clave] === "") {
|
|
44497
|
+
if (opciones[clave] === "") opciones[clave] = null;
|
|
44498
|
+
continue;
|
|
44499
|
+
}
|
|
44500
|
+
const v = montoNoNegativo(opciones[clave]);
|
|
44501
|
+
if (v === null) throw new OpsError("validacion", `${clave}: tiene que ser un monto mayor o igual a 0`);
|
|
44502
|
+
opciones[clave] = v;
|
|
44503
|
+
}
|
|
44504
|
+
if (opciones.costo_dia_servicio !== void 0) opciones.costo_dia_servicio = leerCostosPorServicio(opciones.costo_dia_servicio);
|
|
44146
44505
|
const cambioOpciones = describirOpciones(opcionesAntes, opciones);
|
|
44147
44506
|
const motivo = [input2.motivo ?? null, cambioOpciones].filter(Boolean).join(" \xB7 ") || null;
|
|
44148
44507
|
const existe = await ctx.db.query(
|
|
@@ -44202,10 +44561,22 @@ async function leerCasoCompleto(ctx, companyId, casoId) {
|
|
|
44202
44561
|
async function listarCasos(ctx, input2) {
|
|
44203
44562
|
const params = [input2.companyId];
|
|
44204
44563
|
const where = ["company_id = $1"];
|
|
44564
|
+
const despuesDe = str2(input2.despuesDe);
|
|
44565
|
+
if (despuesDe && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(despuesDe)) throw new OpsError("validacion", `despues_de tiene que ser el id de un caso (el \xFAltimo de la p\xE1gina anterior); vino \xAB${despuesDe}\xBB`);
|
|
44566
|
+
const porId = input2.orden === "id" || despuesDe !== null;
|
|
44567
|
+
if (despuesDe) {
|
|
44568
|
+
params.push(despuesDe);
|
|
44569
|
+
where.push(`id > $${params.length}`);
|
|
44570
|
+
}
|
|
44205
44571
|
if (input2.workflow) {
|
|
44206
44572
|
params.push(input2.workflow);
|
|
44207
44573
|
where.push(`workflow = $${params.length}`);
|
|
44208
44574
|
}
|
|
44575
|
+
const referencia = str2(input2.referencia);
|
|
44576
|
+
if (referencia) {
|
|
44577
|
+
params.push(referencia);
|
|
44578
|
+
where.push(`referencia = $${params.length}`);
|
|
44579
|
+
}
|
|
44209
44580
|
if (input2.estado) {
|
|
44210
44581
|
params.push(input2.estado);
|
|
44211
44582
|
where.push(`estado = $${params.length}`);
|
|
@@ -44216,14 +44587,18 @@ async function listarCasos(ctx, input2) {
|
|
|
44216
44587
|
});
|
|
44217
44588
|
where.push(`estado IN (${marks.join(", ")})`);
|
|
44218
44589
|
}
|
|
44219
|
-
const limit = Math.min(Math.max(Number(input2.limit ?? 50) || 50, 1),
|
|
44590
|
+
const limit = Math.min(Math.max(Number(input2.limit ?? 50) || 50, 1), 500);
|
|
44591
|
+
const offset = Math.min(Math.max(Math.floor(Number(input2.offset ?? 0)) || 0, 0), 1e6);
|
|
44220
44592
|
params.push(limit);
|
|
44593
|
+
const pLimit = params.length;
|
|
44594
|
+
params.push(offset);
|
|
44221
44595
|
const rows = await ctx.db.query(
|
|
44222
|
-
`SELECT ${CASO_COLS} FROM ${t3(ctx, "caso")} WHERE ${where.join(" AND ")} ORDER BY updated_at DESC LIMIT $${params.length}`,
|
|
44596
|
+
`SELECT ${CASO_COLS} FROM ${t3(ctx, "caso")} WHERE ${where.join(" AND ")} ORDER BY ${porId ? "id" : "updated_at DESC, id"} LIMIT $${pLimit} OFFSET $${params.length}`,
|
|
44223
44597
|
params
|
|
44224
44598
|
);
|
|
44225
44599
|
return rows.map(mapCaso);
|
|
44226
44600
|
}
|
|
44601
|
+
var CLAVE_UNA_POR_REFERENCIA = "una-por-referencia:";
|
|
44227
44602
|
async function abrirCaso(ctx, input2) {
|
|
44228
44603
|
const wf = getWorkflow(input2.workflow);
|
|
44229
44604
|
if (!wf) throw new OpsError("workflow_desconocido", `workflow desconocido: ${input2.workflow}. Disponibles: ${listarWorkflows().map((w) => w.clave).join(", ")}`);
|
|
@@ -44280,6 +44655,11 @@ async function abrirCaso(ctx, input2) {
|
|
|
44280
44655
|
if (!pid || !/^\d{1,12}$/.test(pid) || !fid || !/^\d{1,12}$/.test(fid)) throw new OpsError("validacion", "rentabilidad_practica necesita prestacion_id y financiador_id (los ids en el HIS)");
|
|
44281
44656
|
if (referencia !== `rentabilidad:${pid}:${fid}`) throw new OpsError("referencia_requerida", `en rentabilidad_practica la referencia es obligatoria: rentabilidad:${pid}:${fid}`);
|
|
44282
44657
|
}
|
|
44658
|
+
if (wf.clave === "rentabilidad_internacion") {
|
|
44659
|
+
const id4 = str2(input2.datos?.internacion_id);
|
|
44660
|
+
if (!id4 || !/^\d{1,12}$/.test(id4)) throw new OpsError("validacion", "rentabilidad_internacion necesita internacion_id (el id de la internaci\xF3n en el HIS)");
|
|
44661
|
+
if (referencia !== `rentabilidad_internacion:${id4}`) throw new OpsError("referencia_requerida", `en rentabilidad_internacion la referencia es obligatoria: rentabilidad_internacion:${id4}`);
|
|
44662
|
+
}
|
|
44283
44663
|
if (wf.clave === "circular_aranceles") {
|
|
44284
44664
|
const d = input2.datos ?? {};
|
|
44285
44665
|
if (!referencia || !/^circular:\S+$/.test(referencia)) throw new OpsError("referencia_requerida", "en circular_aranceles la referencia es obligatoria: circular:<obra social>:<hash corto del archivo>");
|
|
@@ -44322,18 +44702,26 @@ async function abrirCaso(ctx, input2) {
|
|
|
44322
44702
|
extra.detectado_el = hoy;
|
|
44323
44703
|
}
|
|
44324
44704
|
if (referencia) {
|
|
44325
|
-
const
|
|
44326
|
-
|
|
44327
|
-
|
|
44705
|
+
const soloAbiertos = wf.clave !== "rentabilidad_internacion";
|
|
44706
|
+
const previos = await ctx.db.query(
|
|
44707
|
+
`SELECT ${CASO_COLS} FROM ${t3(ctx, "caso")} WHERE company_id = $1 AND workflow = $2 AND referencia = $3${soloAbiertos ? " AND cerrado_at IS NULL" : ""} ORDER BY created_at DESC LIMIT 1`,
|
|
44708
|
+
[input2.companyId, wf.clave, referencia]
|
|
44328
44709
|
);
|
|
44329
|
-
if (
|
|
44710
|
+
if (previos[0]) return { caso: mapCaso(previos[0]), creado: false };
|
|
44330
44711
|
}
|
|
44331
44712
|
const id3 = randomUUID3();
|
|
44332
44713
|
const datos = { ...input2.datos ?? {}, ...extra };
|
|
44333
|
-
|
|
44334
|
-
|
|
44335
|
-
|
|
44336
|
-
|
|
44714
|
+
const clave = wf.clave === "rentabilidad_internacion" && referencia ? `${CLAVE_UNA_POR_REFERENCIA}${referencia}` : idem;
|
|
44715
|
+
try {
|
|
44716
|
+
await ctx.db.execute(
|
|
44717
|
+
`INSERT INTO ${t3(ctx, "caso")} (id, company_id, workflow, estado, referencia, titulo, datos, abierto_por_tipo, abierto_por_id, agente_id, issue_id, idempotency_key) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8, $9, $10, $11, $12)`,
|
|
44718
|
+
[id3, input2.companyId, wf.clave, wf.inicial, referencia, titulo, JSON.stringify(datos), input2.actor.tipo, input2.actor.id, input2.agenteId ?? null, input2.issueId ?? null, clave]
|
|
44719
|
+
);
|
|
44720
|
+
} catch (err) {
|
|
44721
|
+
const ganador = clave ? await ctx.db.query(`SELECT ${CASO_COLS} FROM ${t3(ctx, "caso")} WHERE company_id = $1 AND idempotency_key = $2 LIMIT 1`, [input2.companyId, clave]) : [];
|
|
44722
|
+
if (ganador[0]) return { caso: mapCaso(ganador[0]), creado: false };
|
|
44723
|
+
throw err;
|
|
44724
|
+
}
|
|
44337
44725
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
44338
44726
|
return {
|
|
44339
44727
|
creado: true,
|
|
@@ -44402,6 +44790,14 @@ async function contextoDe(ctx, companyId, caso, via, datos) {
|
|
|
44402
44790
|
}
|
|
44403
44791
|
return contexto;
|
|
44404
44792
|
}
|
|
44793
|
+
if (caso.workflow === "rentabilidad_internacion") {
|
|
44794
|
+
if (via === "evaluar" || via === "comprobar") {
|
|
44795
|
+
const op = await obtenerOpciones(ctx, companyId, caso.workflow);
|
|
44796
|
+
const l = leerLecturaCostoInternacion(datos.lectura);
|
|
44797
|
+
contexto.costoInternacion = "error" in l ? { error: l.error } : { internacionId: l.lectura.internacion_id, evaluacion: evaluarCostoInternacion({ lectura: l.lectura, hoy, ...opcionesCostos(op) }) };
|
|
44798
|
+
}
|
|
44799
|
+
return contexto;
|
|
44800
|
+
}
|
|
44405
44801
|
if (caso.workflow === "cobranza_financiador") {
|
|
44406
44802
|
const op = await obtenerOpciones(ctx, companyId, caso.workflow);
|
|
44407
44803
|
contexto.plazosPago = leerPlazos(op.plazos_pago);
|
|
@@ -44640,6 +45036,46 @@ async function transicionar(ctx, input2) {
|
|
|
44640
45036
|
}
|
|
44641
45037
|
delete datos.lectura;
|
|
44642
45038
|
}
|
|
45039
|
+
if (caso.workflow === "rentabilidad_internacion") {
|
|
45040
|
+
for (const k of ["evaluacion", "oracion", "financiador", "ingreso", "egreso", "facturado", "consumos", "cirugias", "comprobado_el", "revisada_sin_cambios", "revision_pedida_el", "revision_vence_el", "revision_facturado", "revision_costo"]) delete datos[k];
|
|
45041
|
+
const hoy = contexto.hoy ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
45042
|
+
const r = contexto.costoInternacion;
|
|
45043
|
+
const l = leerLecturaCostoInternacion(datos.lectura);
|
|
45044
|
+
const nueva = r && !("error" in r) && !("error" in l) ? { lectura: l.lectura, evaluacion: r.evaluacion } : null;
|
|
45045
|
+
if (resultado.via === "aprobar") {
|
|
45046
|
+
const vista = caso.datos.evaluacion && typeof caso.datos.evaluacion === "object" ? caso.datos.evaluacion : {};
|
|
45047
|
+
Object.assign(datos, {
|
|
45048
|
+
revision_pedida_el: hoy,
|
|
45049
|
+
revision_vence_el: sumarDias4(hoy, DIAS_REVISION_CUENTA),
|
|
45050
|
+
revision_facturado: typeof vista.facturado === "number" ? vista.facturado : null,
|
|
45051
|
+
revision_costo: typeof vista.costo === "number" ? vista.costo : null,
|
|
45052
|
+
revisada_sin_cambios: null
|
|
45053
|
+
});
|
|
45054
|
+
} else if (nueva && resultado.a === "pendiente_aprobacion") {
|
|
45055
|
+
if (caso.estado === "en_revision") datos.revisada_sin_cambios = resultadoRevisionCuenta(caso.datos, nueva.evaluacion, hoy) === "sin_cambios";
|
|
45056
|
+
else if (caso.datos.revisada_sin_cambios === true && resultadoRevisionCuenta(caso.datos, nueva.evaluacion, hoy) === "con_cambios") datos.revisada_sin_cambios = false;
|
|
45057
|
+
}
|
|
45058
|
+
if (nueva) {
|
|
45059
|
+
Object.assign(datos, {
|
|
45060
|
+
financiador: nueva.lectura.financiador,
|
|
45061
|
+
ingreso: nueva.lectura.ingreso,
|
|
45062
|
+
egreso: nueva.lectura.egreso,
|
|
45063
|
+
facturado: nueva.lectura.facturado,
|
|
45064
|
+
consumos: nueva.lectura.consumos,
|
|
45065
|
+
cirugias: nueva.lectura.cirugias,
|
|
45066
|
+
evaluacion: nueva.evaluacion,
|
|
45067
|
+
comprobado_el: contexto.hoy ?? null
|
|
45068
|
+
});
|
|
45069
|
+
}
|
|
45070
|
+
const despues = { ...caso.datos, ...datos };
|
|
45071
|
+
const e = nueva?.evaluacion ?? (despues.evaluacion && typeof despues.evaluacion === "object" ? despues.evaluacion : null);
|
|
45072
|
+
if (e && typeof e.facturado === "number" && typeof e.costo === "number") {
|
|
45073
|
+
const quien = { internacion_id: str2(despues.internacion_id) ?? "", financiador: str2(despues.financiador) ?? "" };
|
|
45074
|
+
const nota = notaRevisionCuenta(despues, resultado.a, e, hoy);
|
|
45075
|
+
datos.oracion = `${oracionCostoInternacion(nueva?.lectura ?? quien, e)}${nota ? ` ${nota}` : ""}`;
|
|
45076
|
+
}
|
|
45077
|
+
delete datos.lectura;
|
|
45078
|
+
}
|
|
44643
45079
|
if (caso.workflow === "cobranza_financiador") {
|
|
44644
45080
|
for (const k of ["items", "resumen", "reclamo", "reclamos", "ultimo_reclamo", "proximo_reclamo", "nc_al_reclamar", "cobrado_el"]) delete datos[k];
|
|
44645
45081
|
const hoy = contexto.hoy ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -45403,7 +45839,7 @@ import { randomUUID as randomUUID4 } from "node:crypto";
|
|
|
45403
45839
|
function t4(ctx, table) {
|
|
45404
45840
|
return `${ctx.db.namespace}.${table}`;
|
|
45405
45841
|
}
|
|
45406
|
-
function
|
|
45842
|
+
function texto8(v) {
|
|
45407
45843
|
return typeof v === "string" && v.trim().length > 0 ? v.trim() : typeof v === "number" ? String(v) : null;
|
|
45408
45844
|
}
|
|
45409
45845
|
function iso3(v) {
|
|
@@ -45441,7 +45877,8 @@ var ESPERAN_PERSONA = {
|
|
|
45441
45877
|
credencial_profesional: ["observada"],
|
|
45442
45878
|
internacion_al_dia: ["con_pendientes"],
|
|
45443
45879
|
honorario_en_cero: ["para_cargar"],
|
|
45444
|
-
debito_profesional: ["aprobado", "con_diferencias"]
|
|
45880
|
+
debito_profesional: ["aprobado", "con_diferencias"],
|
|
45881
|
+
rentabilidad_internacion: ["en_revision"]
|
|
45445
45882
|
};
|
|
45446
45883
|
var CANALES_AVISO = ["mail", "whatsapp"];
|
|
45447
45884
|
var HORARIO_AVISOS = { desde: "08:00", hasta: "18:00" };
|
|
@@ -45462,7 +45899,7 @@ function pendientesDe(caso, hoy) {
|
|
|
45462
45899
|
const estados = ESPERAN_PERSONA[caso.workflow];
|
|
45463
45900
|
if (!estados || !estados.includes(caso.estado)) return [];
|
|
45464
45901
|
const d = caso.datos;
|
|
45465
|
-
const bono = `Bono ${
|
|
45902
|
+
const bono = `Bono ${texto8(d.bono_numero) ?? texto8(d.bono_id) ?? "sin n\xFAmero"}${texto8(d.obra_social) ? ` \xB7 ${texto8(d.obra_social)}` : ""}`;
|
|
45466
45903
|
const base = { casoId: caso.id, workflow: caso.workflow, titulo: caso.titulo };
|
|
45467
45904
|
if (caso.workflow === "cuenta_completa") {
|
|
45468
45905
|
const out = [];
|
|
@@ -45470,7 +45907,7 @@ function pendientesDe(caso, hoy) {
|
|
|
45470
45907
|
const nombreRegla = REGLAS_CUENTA[f.regla]?.nombre ?? f.regla;
|
|
45471
45908
|
if (f.regla === "epicrisis_faltante") {
|
|
45472
45909
|
const p = f.responsable?.tipo === "profesional" && f.responsable.id ? f.responsable : null;
|
|
45473
|
-
const internacion =
|
|
45910
|
+
const internacion = texto8(d.internacion_id) ? `Internaci\xF3n ${texto8(d.internacion_id)}` : bono;
|
|
45474
45911
|
out.push({
|
|
45475
45912
|
...base,
|
|
45476
45913
|
que: `${internacion}: falta la epicrisis`,
|
|
@@ -45484,7 +45921,7 @@ function pendientesDe(caso, hoy) {
|
|
|
45484
45921
|
}
|
|
45485
45922
|
return out;
|
|
45486
45923
|
}
|
|
45487
|
-
const profesionalH =
|
|
45924
|
+
const profesionalH = texto8(d.profesional) ?? (d.profesional && typeof d.profesional === "object" ? texto8(d.profesional.nombre) : null);
|
|
45488
45925
|
if (caso.workflow === "honorario_en_cero") {
|
|
45489
45926
|
const r = d.resumen && typeof d.resumen === "object" ? d.resumen : {};
|
|
45490
45927
|
const n = typeof r.prestaciones === "number" ? r.prestaciones : 0;
|
|
@@ -45493,22 +45930,22 @@ function pendientesDe(caso, hoy) {
|
|
|
45493
45930
|
if (caso.workflow === "debito_profesional") {
|
|
45494
45931
|
const r = d.resumen && typeof d.resumen === "object" ? d.resumen : {};
|
|
45495
45932
|
const total = typeof r.total === "number" ? r.total.toLocaleString("es-AR", { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : "";
|
|
45496
|
-
return [{ ...base, que: `${profesionalH ?? "Un profesional"}: cargar un d\xE9bito de $ ${total} aprobado por la direcci\xF3n (nota de cr\xE9dito de ${
|
|
45933
|
+
return [{ ...base, que: `${profesionalH ?? "Un profesional"}: cargar un d\xE9bito de $ ${total} aprobado por la direcci\xF3n (nota de cr\xE9dito de ${texto8(d.obra_social) ?? "la obra social"})`, responsable: rol("honorarios") }];
|
|
45497
45934
|
}
|
|
45498
45935
|
if (caso.workflow === "internacion_al_dia") {
|
|
45499
|
-
const internacion = `Internaci\xF3n ${
|
|
45936
|
+
const internacion = `Internaci\xF3n ${texto8(d.internacion_id) ?? "sin n\xFAmero"}${texto8(d.servicio) ? ` (${texto8(d.servicio)})` : ""}`;
|
|
45500
45937
|
const resp = d.responsable && typeof d.responsable === "object" ? d.responsable : null;
|
|
45501
|
-
const medicoId = resp ?
|
|
45938
|
+
const medicoId = resp ? texto8(resp.id) : null;
|
|
45502
45939
|
const out = [];
|
|
45503
45940
|
for (const p of Array.isArray(d.pendientes) ? d.pendientes : []) {
|
|
45504
|
-
const regla =
|
|
45941
|
+
const regla = texto8(p.regla);
|
|
45505
45942
|
if (regla === "evolucion_de_hoy") {
|
|
45506
|
-
const dia5 =
|
|
45943
|
+
const dia5 = texto8(p.dia);
|
|
45507
45944
|
if (!dia5 || hoy && dia5 !== hoy) continue;
|
|
45508
45945
|
out.push({
|
|
45509
45946
|
...base,
|
|
45510
45947
|
que: `${internacion}: todav\xEDa no tiene evoluci\xF3n hoy`,
|
|
45511
|
-
responsable: medicoId ? { clave: `profesional:${medicoId}`, nombre:
|
|
45948
|
+
responsable: medicoId ? { clave: `profesional:${medicoId}`, nombre: texto8(resp?.nombre) ?? `Profesional ${medicoId}`, reserva: "rol:internacion" } : rol("internacion"),
|
|
45512
45949
|
marca: `evolucion:${dia5}`
|
|
45513
45950
|
});
|
|
45514
45951
|
} else if (regla === "alta_sin_registrar") {
|
|
@@ -45520,13 +45957,17 @@ function pendientesDe(caso, hoy) {
|
|
|
45520
45957
|
return out;
|
|
45521
45958
|
}
|
|
45522
45959
|
if (caso.workflow === "facturacion_pendiente") {
|
|
45523
|
-
const vence =
|
|
45960
|
+
const vence = texto8(d.vence_el);
|
|
45524
45961
|
return [{ ...base, que: `${bono}: sin presentar${vence ? `, vence el ${vence.slice(8, 10)}/${vence.slice(5, 7)}` : ""}`, responsable: rol("facturacion") }];
|
|
45525
45962
|
}
|
|
45963
|
+
if (caso.workflow === "rentabilidad_internacion") {
|
|
45964
|
+
const vence = texto8(d.revision_vence_el);
|
|
45965
|
+
return [{ ...base, que: `${caso.titulo}: revisar la cuenta (consumos sin facturar o m\xF3dulo con el financiador)${vence ? `, antes del ${vence.slice(8, 10)}/${vence.slice(5, 7)}` : ""}`, responsable: rol("facturacion") }];
|
|
45966
|
+
}
|
|
45526
45967
|
if (caso.workflow === "credencial_profesional") {
|
|
45527
|
-
const id3 =
|
|
45528
|
-
const nombre =
|
|
45529
|
-
const obs =
|
|
45968
|
+
const id3 = texto8(d.profesional_id);
|
|
45969
|
+
const nombre = texto8(d.profesional) ?? (id3 ? `Profesional ${id3}` : "Profesional");
|
|
45970
|
+
const obs = texto8(d.observacion);
|
|
45530
45971
|
const que = `${nombre}: ${(obs && OBSERVACIONES_MATRICULA[obs]) ?? "la matr\xEDcula tiene una observaci\xF3n del registro nacional"}`;
|
|
45531
45972
|
return [{ ...base, que, responsable: id3 ? { clave: `profesional:${id3}`, nombre, reserva: "rol:personal" } : rol("personal") }];
|
|
45532
45973
|
}
|
|
@@ -45536,7 +45977,7 @@ var CLAVE_RE = /^(profesional:[A-Za-z0-9_-]{1,40}|financiador:\d{1,12}|rol:(admi
|
|
|
45536
45977
|
var MAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
45537
45978
|
var COLS_CONTACTO = "id, clave, nombre, mail, whatsapp, activo, updated_at";
|
|
45538
45979
|
function mapContacto(r) {
|
|
45539
|
-
return { id: String(r.id), clave: String(r.clave), nombre: String(r.nombre), mail:
|
|
45980
|
+
return { id: String(r.id), clave: String(r.clave), nombre: String(r.nombre), mail: texto8(r.mail), whatsapp: texto8(r.whatsapp), activo: r.activo !== false, actualizadoEn: iso3(r.updated_at) };
|
|
45540
45981
|
}
|
|
45541
45982
|
async function listarContactos(ctx, companyId) {
|
|
45542
45983
|
const rows = await ctx.db.query(`SELECT ${COLS_CONTACTO} FROM ${t4(ctx, "contacto")} WHERE company_id = $1 ORDER BY nombre ASC`, [companyId]);
|
|
@@ -45550,15 +45991,15 @@ function leerWhatsapp(v) {
|
|
|
45550
45991
|
}
|
|
45551
45992
|
async function guardarContacto(ctx, input2) {
|
|
45552
45993
|
if (input2.actor.tipo !== "humano") throw new OpsError("actor_no_permitido", "los contactos los carga una persona de la instituci\xF3n desde el portal");
|
|
45553
|
-
const clave =
|
|
45994
|
+
const clave = texto8(input2.clave) ?? "";
|
|
45554
45995
|
if (!CLAVE_RE.test(clave)) throw new OpsError("validacion", "clave: tiene que ser profesional:<id>, financiador:<id> o rol:<admision|aranceles|facturacion|internacion|personal>");
|
|
45555
|
-
const mail =
|
|
45996
|
+
const mail = texto8(input2.mail)?.toLowerCase() ?? null;
|
|
45556
45997
|
if (mail && !MAIL_RE.test(mail)) throw new OpsError("validacion", "mail: no tiene formato de correo");
|
|
45557
45998
|
const whatsapp = leerWhatsapp(input2.whatsapp);
|
|
45558
45999
|
const activo = input2.activo !== false;
|
|
45559
46000
|
if (activo && !mail && !whatsapp) throw new OpsError("validacion", "hace falta un mail o un WhatsApp para poder avisar");
|
|
45560
46001
|
const existente = (await ctx.db.query(`SELECT ${COLS_CONTACTO} FROM ${t4(ctx, "contacto")} WHERE company_id = $1 AND clave = $2 LIMIT 1`, [input2.companyId, clave]))[0];
|
|
45561
|
-
const nombre =
|
|
46002
|
+
const nombre = texto8(input2.nombre)?.slice(0, 120) ?? (existente ? String(existente.nombre) : clave.startsWith("rol:") ? ROLES[clave.slice(4)] ?? clave : null);
|
|
45562
46003
|
if (!nombre) throw new OpsError("validacion", "nombre: es obligatorio para un profesional nuevo");
|
|
45563
46004
|
if (existente) {
|
|
45564
46005
|
await ctx.db.execute(
|
|
@@ -45588,8 +46029,8 @@ function mapAviso(r) {
|
|
|
45588
46029
|
texto: String(r.texto),
|
|
45589
46030
|
estado: ["pendiente", "enviado", "fallido", "cancelado"].includes(String(r.estado)) ? String(r.estado) : "pendiente",
|
|
45590
46031
|
intentosEnvio: Number(r.intentos_envio) || 0,
|
|
45591
|
-
error:
|
|
45592
|
-
proveedorId:
|
|
46032
|
+
error: texto8(r.error),
|
|
46033
|
+
proveedorId: texto8(r.proveedor_id),
|
|
45593
46034
|
enviadoEn: iso3(r.enviado_en),
|
|
45594
46035
|
creadoEn: iso3(r.created_at)
|
|
45595
46036
|
};
|
|
@@ -45633,7 +46074,7 @@ function redactarAviso(nombre, institucion, pendientes) {
|
|
|
45633
46074
|
const repetidos = pendientes.filter((p) => p.intento > 1).length;
|
|
45634
46075
|
const asunto = `${n === 1 ? "1 pendiente" : `${n} pendientes`} para resolver en el HIS \xB7 ${institucion}`;
|
|
45635
46076
|
const lineas = pendientes.map((p) => `- ${p.que}${p.intento > 1 ? " (segundo aviso)" : ""}`);
|
|
45636
|
-
const
|
|
46077
|
+
const texto10 = [
|
|
45637
46078
|
`Hola ${nombre}:`,
|
|
45638
46079
|
"",
|
|
45639
46080
|
`Desde ${institucion}, Autopilot Opera te avisa que ${n === 1 ? "ten\xE9s 1 pendiente" : `ten\xE9s ${n} pendientes`} para resolver en el HIS:`,
|
|
@@ -45644,7 +46085,7 @@ function redactarAviso(nombre, institucion, pendientes) {
|
|
|
45644
46085
|
"",
|
|
45645
46086
|
"Autopilot Opera \xB7 avisos autom\xE1ticos de la administraci\xF3n"
|
|
45646
46087
|
].join("\n");
|
|
45647
|
-
return { asunto, texto:
|
|
46088
|
+
return { asunto, texto: texto10 };
|
|
45648
46089
|
}
|
|
45649
46090
|
async function generarAvisos(ctx, input2) {
|
|
45650
46091
|
const { hoy, hora } = ahoraEnHospital(input2.ahora);
|
|
@@ -45721,7 +46162,7 @@ async function informarResultadoAviso(ctx, input2) {
|
|
|
45721
46162
|
if (input2.resultado === "enviado") {
|
|
45722
46163
|
await ctx.db.execute(
|
|
45723
46164
|
`UPDATE ${t4(ctx, "aviso")} SET estado = $3, proveedor_id = $4, enviado_en = now(), intentos_envio = intentos_envio + 1, error = NULL, updated_at = now() WHERE company_id = $1 AND id = $2`,
|
|
45724
|
-
[input2.companyId, aviso.id, "enviado",
|
|
46165
|
+
[input2.companyId, aviso.id, "enviado", texto8(input2.proveedorId)]
|
|
45725
46166
|
);
|
|
45726
46167
|
for (const c of aviso.casos) {
|
|
45727
46168
|
await registrarAccion(ctx, {
|
|
@@ -45735,11 +46176,11 @@ async function informarResultadoAviso(ctx, input2) {
|
|
|
45735
46176
|
actor
|
|
45736
46177
|
});
|
|
45737
46178
|
}
|
|
45738
|
-
return { ...aviso, estado: "enviado", proveedorId:
|
|
46179
|
+
return { ...aviso, estado: "enviado", proveedorId: texto8(input2.proveedorId), enviadoEn: (/* @__PURE__ */ new Date()).toISOString(), intentosEnvio: aviso.intentosEnvio + 1, error: null };
|
|
45739
46180
|
}
|
|
45740
46181
|
const intentos = aviso.intentosEnvio + 1;
|
|
45741
46182
|
const estado = intentos >= MAX_INTENTOS_ENVIO ? "fallido" : "pendiente";
|
|
45742
|
-
const error62 =
|
|
46183
|
+
const error62 = texto8(input2.error)?.slice(0, 300) ?? "no se pudo enviar";
|
|
45743
46184
|
await ctx.db.execute(
|
|
45744
46185
|
`UPDATE ${t4(ctx, "aviso")} SET estado = $3, error = $4, intentos_envio = $5, updated_at = now() WHERE company_id = $1 AND id = $2`,
|
|
45745
46186
|
[input2.companyId, aviso.id, estado, error62, intentos]
|
|
@@ -45816,7 +46257,7 @@ function arr(v) {
|
|
|
45816
46257
|
}
|
|
45817
46258
|
return [];
|
|
45818
46259
|
}
|
|
45819
|
-
function
|
|
46260
|
+
function texto9(v, max, campo, problemas, obligatorio = false) {
|
|
45820
46261
|
if (v === void 0 || v === null || v === "") {
|
|
45821
46262
|
if (obligatorio) problemas.push(`${campo} es obligatorio`);
|
|
45822
46263
|
return void 0;
|
|
@@ -45927,24 +46368,24 @@ function validarWidgets(entrada) {
|
|
|
45927
46368
|
const a = ancho(o.ancho);
|
|
45928
46369
|
switch (tipo) {
|
|
45929
46370
|
case "indicador": {
|
|
45930
|
-
const etiqueta =
|
|
45931
|
-
const valor = typeof o.valor === "number" && Number.isFinite(o.valor) ? o.valor :
|
|
46371
|
+
const etiqueta = texto9(o.etiqueta, 80, `${ref}.etiqueta`, problemas, true) ?? "";
|
|
46372
|
+
const valor = typeof o.valor === "number" && Number.isFinite(o.valor) ? o.valor : texto9(o.valor, 40, `${ref}.valor`, problemas, true) ?? "";
|
|
45932
46373
|
const tono = typeof o.tono === "string" && TONOS.includes(o.tono) ? o.tono : void 0;
|
|
45933
46374
|
if (o.tono !== void 0 && !tono) problemas.push(`${ref}.tono debe ser uno de ${TONOS.join(", ")}`);
|
|
45934
|
-
const unidad =
|
|
46375
|
+
const unidad = texto9(o.unidad, 16, `${ref}.unidad`, problemas);
|
|
45935
46376
|
if (unidad === "%" && !ES_VARIACION.test(etiqueta)) controlarPorcentaje(valor, `${ref}.valor`, problemas);
|
|
45936
46377
|
const { calculo, origen } = leerCalculo(o.calculo, ref, problemas);
|
|
45937
|
-
salida.push({ tipo, etiqueta, valor, unidad, contexto:
|
|
46378
|
+
salida.push({ tipo, etiqueta, valor, unidad, contexto: texto9(o.contexto, 160, `${ref}.contexto`, problemas), tono, ancho: a, ...calculo ? { calculo } : {}, origen });
|
|
45938
46379
|
break;
|
|
45939
46380
|
}
|
|
45940
46381
|
case "barras": {
|
|
45941
|
-
const titulo =
|
|
46382
|
+
const titulo = texto9(o.titulo, 120, `${ref}.titulo`, problemas, true) ?? "";
|
|
45942
46383
|
const series = arr(o.series);
|
|
45943
46384
|
if (series.length === 0 || series.length > 40) problemas.push(`${ref}.series debe tener entre 1 y 40 barras`);
|
|
45944
|
-
const unidad =
|
|
46385
|
+
const unidad = texto9(o.unidad, 16, `${ref}.unidad`, problemas);
|
|
45945
46386
|
const barras = series.slice(0, 40).map((s, j) => {
|
|
45946
46387
|
const so = s && typeof s === "object" ? s : {};
|
|
45947
|
-
return { etiqueta:
|
|
46388
|
+
return { etiqueta: texto9(so.etiqueta, 60, `${ref}.series[${j}].etiqueta`, problemas, true) ?? "", valor: numero2(so.valor, `${ref}.series[${j}].valor`, problemas) };
|
|
45948
46389
|
});
|
|
45949
46390
|
if (unidad === "%" && !ES_VARIACION.test(titulo)) barras.forEach((b, j) => controlarPorcentaje(b.valor, `${ref}.series[${j}].valor`, problemas));
|
|
45950
46391
|
const { calculo, origen } = leerCalculo(o.calculo, ref, problemas);
|
|
@@ -45952,13 +46393,13 @@ function validarWidgets(entrada) {
|
|
|
45952
46393
|
break;
|
|
45953
46394
|
}
|
|
45954
46395
|
case "linea": {
|
|
45955
|
-
const titulo =
|
|
46396
|
+
const titulo = texto9(o.titulo, 120, `${ref}.titulo`, problemas, true) ?? "";
|
|
45956
46397
|
const puntos = arr(o.puntos);
|
|
45957
46398
|
if (puntos.length === 0 || puntos.length > 400) problemas.push(`${ref}.puntos debe tener entre 1 y 400 puntos`);
|
|
45958
|
-
const unidad =
|
|
46399
|
+
const unidad = texto9(o.unidad, 16, `${ref}.unidad`, problemas);
|
|
45959
46400
|
const serie2 = puntos.slice(0, 400).map((p, j) => {
|
|
45960
46401
|
const po = p && typeof p === "object" ? p : {};
|
|
45961
|
-
return { x:
|
|
46402
|
+
return { x: texto9(po.x, 40, `${ref}.puntos[${j}].x`, problemas, true) ?? "", y: numero2(po.y, `${ref}.puntos[${j}].y`, problemas) };
|
|
45962
46403
|
});
|
|
45963
46404
|
if (unidad === "%" && !ES_VARIACION.test(titulo)) serie2.forEach((p, j) => controlarPorcentaje(p.y, `${ref}.puntos[${j}].y`, problemas));
|
|
45964
46405
|
const { calculo, origen } = leerCalculo(o.calculo, ref, problemas);
|
|
@@ -45966,12 +46407,12 @@ function validarWidgets(entrada) {
|
|
|
45966
46407
|
break;
|
|
45967
46408
|
}
|
|
45968
46409
|
case "tabla": {
|
|
45969
|
-
const titulo =
|
|
46410
|
+
const titulo = texto9(o.titulo, 120, `${ref}.titulo`, problemas, true) ?? "";
|
|
45970
46411
|
const columnas = arr(o.columnas);
|
|
45971
46412
|
if (columnas.length === 0 || columnas.length > 12) problemas.push(`${ref}.columnas debe tener entre 1 y 12 columnas`);
|
|
45972
46413
|
const filas = arr(o.filas);
|
|
45973
46414
|
if (filas.length > 200) problemas.push(`${ref}.filas admite hasta 200 filas`);
|
|
45974
|
-
const cols = columnas.slice(0, 12).map((c, j) =>
|
|
46415
|
+
const cols = columnas.slice(0, 12).map((c, j) => texto9(c, 60, `${ref}.columnas[${j}]`, problemas, true) ?? "");
|
|
45975
46416
|
const celdasTabla = filas.slice(0, 200).map((f, j) => {
|
|
45976
46417
|
const celdas = arr(f);
|
|
45977
46418
|
if (celdas.length !== cols.length) problemas.push(`${ref}.filas[${j}] tiene ${celdas.length} celdas y la tabla ${cols.length} columnas`);
|
|
@@ -45987,7 +46428,7 @@ function validarWidgets(entrada) {
|
|
|
45987
46428
|
break;
|
|
45988
46429
|
}
|
|
45989
46430
|
case "texto": {
|
|
45990
|
-
salida.push({ tipo, titulo:
|
|
46431
|
+
salida.push({ tipo, titulo: texto9(o.titulo, 120, `${ref}.titulo`, problemas), texto: texto9(o.texto, 4e3, `${ref}.texto`, problemas, true) ?? "", ancho: a });
|
|
45991
46432
|
break;
|
|
45992
46433
|
}
|
|
45993
46434
|
}
|
|
@@ -46073,6 +46514,13 @@ function numOrNull(v) {
|
|
|
46073
46514
|
const n = typeof v === "number" ? v : typeof v === "string" ? Number(v) : NaN;
|
|
46074
46515
|
return Number.isFinite(n) ? n : null;
|
|
46075
46516
|
}
|
|
46517
|
+
function listaTextos(v) {
|
|
46518
|
+
const crudos = (Array.isArray(v) ? v : [v]).flatMap((x) => typeof x === "string" ? x.split(",") : []);
|
|
46519
|
+
return [...new Set(crudos.map(str3).filter((s) => s !== null))].slice(0, 20);
|
|
46520
|
+
}
|
|
46521
|
+
function ordenCasos(orden, despuesDe) {
|
|
46522
|
+
return orden === "id" || despuesDe !== null ? "id" : null;
|
|
46523
|
+
}
|
|
46076
46524
|
function companyFrom(params, fallback) {
|
|
46077
46525
|
const companyId = str3(params.companyId) ?? str3(fallback);
|
|
46078
46526
|
if (!companyId) throw new OpsError("company_requerida", "companyId es obligatorio");
|
|
@@ -46102,10 +46550,19 @@ async function conErrores(fn) {
|
|
|
46102
46550
|
function registrarHerramientas(ctx) {
|
|
46103
46551
|
ctx.tools.register(TOOL_NAMES.listarCasos, toolSchema(TOOL_NAMES.listarCasos), async (params, runCtx) => conErrores(async () => {
|
|
46104
46552
|
const p = obj(params);
|
|
46105
|
-
const
|
|
46553
|
+
const workflow = str3(p.workflow);
|
|
46554
|
+
if (workflow && !getWorkflow(workflow)) throw new OpsError("workflow_desconocido", `workflow desconocido: ${workflow}. Disponibles: ${listarWorkflows().map((w) => w.clave).join(", ")}`);
|
|
46555
|
+
const despuesDe = str3(p.despues_de);
|
|
46556
|
+
const porId = ordenCasos(p.orden, despuesDe) === "id";
|
|
46557
|
+
const casos = await listarCasos(ctx, { companyId: runCtx.companyId, workflow, estado: str3(p.estado), estados: listaTextos(p.estados), referencia: str3(p.referencia), limit: numOrNull(p.limit), offset: numOrNull(p.offset), orden: ordenCasos(p.orden, despuesDe), despuesDe });
|
|
46558
|
+
const limite = Math.min(Math.max(numOrNull(p.limit) || 50, 1), 500);
|
|
46559
|
+
const desde = Math.max(Math.floor(numOrNull(p.offset) ?? 0), 0);
|
|
46560
|
+
const mas = casos.length < limite ? "" : porId ? `
|
|
46561
|
+
Puede haber m\xE1s casos: ped\xED la p\xE1gina siguiente con los mismos filtros, orden "id" y despues_de "${casos[casos.length - 1].id}" (limit ${limite}).` : `
|
|
46562
|
+
Puede haber m\xE1s casos: ped\xED la p\xE1gina siguiente con offset ${desde + casos.length} (limit ${limite}).`;
|
|
46106
46563
|
return {
|
|
46107
|
-
content: casos.length ? casos.map(resumenCaso).join("\n") : "No hay casos que coincidan.",
|
|
46108
|
-
data: { casos }
|
|
46564
|
+
content: casos.length ? `${casos.map(resumenCaso).join("\n")}${mas}` : "No hay casos que coincidan.",
|
|
46565
|
+
data: { casos, ...mas && porId ? { despues_de: casos[casos.length - 1].id } : {} }
|
|
46109
46566
|
};
|
|
46110
46567
|
}));
|
|
46111
46568
|
ctx.tools.register(TOOL_NAMES.leerCaso, toolSchema(TOOL_NAMES.leerCaso), async (params, runCtx) => conErrores(async () => {
|
|
@@ -46196,8 +46653,8 @@ Transiciones registradas: ${detalle.transiciones.length}; acciones registradas:
|
|
|
46196
46653
|
const p = obj(params);
|
|
46197
46654
|
const quedo = p.quedo === true ? true : p.quedo === false ? false : null;
|
|
46198
46655
|
const r = await verificarEscritura(ctx, { companyId: runCtx.companyId, accionId: str3(p.accionId) ?? "", quedo, leido: obj(p.leido), detalle: str3(p.detalle), actor: actorAgente(runCtx) });
|
|
46199
|
-
const
|
|
46200
|
-
return { content: `Escritura ${r.accion.id}: ${
|
|
46656
|
+
const texto10 = quedo === true ? "qued\xF3 escrita: se da por hecha" : quedo === false ? "no se escribi\xF3: se puede reintentar" : r.paraPersona ? "no se pudo saber: pasa a una persona" : "no se pudo saber: volv\xE9 a verificar m\xE1s tarde";
|
|
46657
|
+
return { content: `Escritura ${r.accion.id}: ${texto10}`, data: r };
|
|
46201
46658
|
}));
|
|
46202
46659
|
ctx.tools.register(TOOL_NAMES.listarReglas, toolSchema(TOOL_NAMES.listarReglas), async (params, runCtx) => conErrores(async () => {
|
|
46203
46660
|
const p = obj(params);
|
|
@@ -46275,7 +46732,7 @@ function registrarDatosYAcciones(ctx) {
|
|
|
46275
46732
|
transiciones: w.transiciones.map((t6) => ({ via: t6.via, de: t6.de, a: typeof t6.a === "string" ? t6.a : "(seg\xFAn datos)", actor: t6.actor, nivelRequerido: t6.nivelRequerido ?? null, descripcion: t6.descripcion }))
|
|
46276
46733
|
})));
|
|
46277
46734
|
ctx.data.register(DATA_KEYS.tablero, async (params) => tablero(ctx, companyFrom(params), numOrNull(params.dias)));
|
|
46278
|
-
ctx.data.register(DATA_KEYS.casos, async (params) => listarCasos(ctx, { companyId: companyFrom(params), workflow: str3(params.workflow), estado: str3(params.estado), limit: numOrNull(params.limit) }));
|
|
46735
|
+
ctx.data.register(DATA_KEYS.casos, async (params) => listarCasos(ctx, { companyId: companyFrom(params), workflow: str3(params.workflow), estado: str3(params.estado), estados: listaTextos(params.estados), referencia: str3(params.referencia), limit: numOrNull(params.limit), offset: numOrNull(params.offset), orden: ordenCasos(params.orden, str3(params.despues_de)), despuesDe: str3(params.despues_de) }));
|
|
46279
46736
|
ctx.data.register(DATA_KEYS.caso, async (params) => {
|
|
46280
46737
|
const casoId = str3(params.casoId);
|
|
46281
46738
|
if (!casoId) throw new OpsError("caso_requerido", "casoId es obligatorio");
|
|
@@ -46341,7 +46798,7 @@ async function manejarApi(ctx, input2) {
|
|
|
46341
46798
|
case "tablero":
|
|
46342
46799
|
return respuesta(200, await tablero(ctx, companyId, numOrNull(qs("dias"))));
|
|
46343
46800
|
case "casos":
|
|
46344
|
-
return respuesta(200, { casos: await listarCasos(ctx, { companyId, workflow: qs("workflow"), estado: qs("estado"), limit: numOrNull(qs("limit")) }) });
|
|
46801
|
+
return respuesta(200, { casos: await listarCasos(ctx, { companyId, workflow: qs("workflow"), estado: qs("estado"), estados: listaTextos(q.estados), referencia: qs("referencia"), limit: numOrNull(qs("limit")), offset: numOrNull(qs("offset")), orden: ordenCasos(qs("orden"), qs("despues_de")), despuesDe: qs("despues_de") }) });
|
|
46345
46802
|
case "caso": {
|
|
46346
46803
|
const detalle = await leerCasoCompleto(ctx, companyId, input2.params.casoId ?? "");
|
|
46347
46804
|
return detalle ? respuesta(200, detalle) : respuesta(404, { error: "caso_inexistente" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paperclip-plugin-autopilot-ops",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Autopilot Care · operación autónoma gobernada para instituciones de salud: workflows determinísticos, validaciones, registro de negocio, políticas (observa/propone/ejecuta/corte) y tablero del director, como plugin de Paperclip.",
|
|
6
6
|
"files": [
|