paperclip-plugin-autopilot-ops 0.1.13 → 0.1.14
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 +81 -4
- package/dist/ui/index.js +81 -4
- package/dist/worker.js +870 -182
- package/migrations/004_reglas.sql +25 -0
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -844,14 +844,14 @@ function promiseAllObject(promisesObj) {
|
|
|
844
844
|
}
|
|
845
845
|
function randomString(length = 10) {
|
|
846
846
|
const chars = "abcdefghijklmnopqrstuvwxyz";
|
|
847
|
-
let
|
|
847
|
+
let str4 = "";
|
|
848
848
|
for (let i = 0; i < length; i++) {
|
|
849
|
-
|
|
849
|
+
str4 += chars[Math.floor(Math.random() * chars.length)];
|
|
850
850
|
}
|
|
851
|
-
return
|
|
851
|
+
return str4;
|
|
852
852
|
}
|
|
853
|
-
function esc(
|
|
854
|
-
return JSON.stringify(
|
|
853
|
+
function esc(str4) {
|
|
854
|
+
return JSON.stringify(str4);
|
|
855
855
|
}
|
|
856
856
|
function slugify(input2) {
|
|
857
857
|
return input2.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -913,8 +913,8 @@ function numKeys(data) {
|
|
|
913
913
|
return keyCount;
|
|
914
914
|
}
|
|
915
915
|
var getParsedType = (data) => {
|
|
916
|
-
const
|
|
917
|
-
switch (
|
|
916
|
+
const t4 = typeof data;
|
|
917
|
+
switch (t4) {
|
|
918
918
|
case "undefined":
|
|
919
919
|
return "undefined";
|
|
920
920
|
case "string":
|
|
@@ -953,7 +953,7 @@ var getParsedType = (data) => {
|
|
|
953
953
|
}
|
|
954
954
|
return "object";
|
|
955
955
|
default:
|
|
956
|
-
throw new Error(`Unknown data type: ${
|
|
956
|
+
throw new Error(`Unknown data type: ${t4}`);
|
|
957
957
|
}
|
|
958
958
|
};
|
|
959
959
|
var propertyKeyTypes = /* @__PURE__ */ new Set(["string", "number", "symbol"]);
|
|
@@ -965,8 +965,8 @@ var primitiveTypes = /* @__PURE__ */ new Set([
|
|
|
965
965
|
"symbol",
|
|
966
966
|
"undefined"
|
|
967
967
|
]);
|
|
968
|
-
function escapeRegex(
|
|
969
|
-
return
|
|
968
|
+
function escapeRegex(str4) {
|
|
969
|
+
return str4.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
970
970
|
}
|
|
971
971
|
function clone(inst, def2, params) {
|
|
972
972
|
const cl = new inst._zod.constr(def2 ?? inst._zod.def);
|
|
@@ -1232,13 +1232,13 @@ function getSizableOrigin(input2) {
|
|
|
1232
1232
|
return "unknown";
|
|
1233
1233
|
}
|
|
1234
1234
|
var highSurrogate = /[\uD800-\uDBFF]/;
|
|
1235
|
-
function codePointLength(
|
|
1236
|
-
const units =
|
|
1237
|
-
if (!highSurrogate.test(
|
|
1235
|
+
function codePointLength(str4) {
|
|
1236
|
+
const units = str4.length;
|
|
1237
|
+
if (!highSurrogate.test(str4))
|
|
1238
1238
|
return units;
|
|
1239
1239
|
let count = units;
|
|
1240
1240
|
for (let i = 0; i < units - 1; i++) {
|
|
1241
|
-
if ((
|
|
1241
|
+
if ((str4.charCodeAt(i) & 64512) === 55296 && (str4.charCodeAt(i + 1) & 64512) === 56320) {
|
|
1242
1242
|
count--;
|
|
1243
1243
|
i++;
|
|
1244
1244
|
}
|
|
@@ -1253,8 +1253,8 @@ function getLengthableOrigin(input2) {
|
|
|
1253
1253
|
return "unknown";
|
|
1254
1254
|
}
|
|
1255
1255
|
function parsedType(data) {
|
|
1256
|
-
const
|
|
1257
|
-
switch (
|
|
1256
|
+
const t4 = typeof data;
|
|
1257
|
+
switch (t4) {
|
|
1258
1258
|
case "number": {
|
|
1259
1259
|
return Number.isNaN(data) ? "nan" : "number";
|
|
1260
1260
|
}
|
|
@@ -1271,7 +1271,7 @@ function parsedType(data) {
|
|
|
1271
1271
|
}
|
|
1272
1272
|
}
|
|
1273
1273
|
}
|
|
1274
|
-
return
|
|
1274
|
+
return t4;
|
|
1275
1275
|
}
|
|
1276
1276
|
function issue(...args) {
|
|
1277
1277
|
const [iss, input2, inst] = args;
|
|
@@ -3495,7 +3495,7 @@ function handleCatchall(proms, input2, payload, ctx, def2, inst, abortEarly) {
|
|
|
3495
3495
|
const unrecognized = [];
|
|
3496
3496
|
const keySet = def2.keySet;
|
|
3497
3497
|
const _catchall = def2.catchall._zod;
|
|
3498
|
-
const
|
|
3498
|
+
const t4 = _catchall.def.type;
|
|
3499
3499
|
const optin = _catchall.optin;
|
|
3500
3500
|
const optout = _catchall.optout;
|
|
3501
3501
|
let seen = 0;
|
|
@@ -3508,11 +3508,11 @@ function handleCatchall(proms, input2, payload, ctx, def2, inst, abortEarly) {
|
|
|
3508
3508
|
if (keySet.has(key))
|
|
3509
3509
|
continue;
|
|
3510
3510
|
if (key === "__proto__") {
|
|
3511
|
-
if (
|
|
3511
|
+
if (t4 === "never")
|
|
3512
3512
|
unrecognized.push(key);
|
|
3513
3513
|
continue;
|
|
3514
3514
|
}
|
|
3515
|
-
if (
|
|
3515
|
+
if (t4 === "never") {
|
|
3516
3516
|
unrecognized.push(key);
|
|
3517
3517
|
continue;
|
|
3518
3518
|
}
|
|
@@ -7748,16 +7748,16 @@ var error20 = () => {
|
|
|
7748
7748
|
number: { unit: "", shortLabel: "\u05E7\u05D8\u05DF", longLabel: "\u05D2\u05D3\u05D5\u05DC" }
|
|
7749
7749
|
// no unit
|
|
7750
7750
|
};
|
|
7751
|
-
const typeEntry = (
|
|
7752
|
-
const typeLabel = (
|
|
7753
|
-
const e = typeEntry(
|
|
7751
|
+
const typeEntry = (t4) => t4 ? TypeNames[t4] : void 0;
|
|
7752
|
+
const typeLabel = (t4) => {
|
|
7753
|
+
const e = typeEntry(t4);
|
|
7754
7754
|
if (e)
|
|
7755
7755
|
return e.label;
|
|
7756
|
-
return
|
|
7756
|
+
return t4 ?? TypeNames.unknown.label;
|
|
7757
7757
|
};
|
|
7758
|
-
const withDefinite = (
|
|
7759
|
-
const verbFor = (
|
|
7760
|
-
const e = typeEntry(
|
|
7758
|
+
const withDefinite = (t4) => `\u05D4${typeLabel(t4)}`;
|
|
7759
|
+
const verbFor = (t4) => {
|
|
7760
|
+
const e = typeEntry(t4);
|
|
7761
7761
|
const gender = e?.gender ?? "m";
|
|
7762
7762
|
return gender === "f" ? "\u05E6\u05E8\u05D9\u05DB\u05D4 \u05DC\u05D4\u05D9\u05D5\u05EA" : "\u05E6\u05E8\u05D9\u05DA \u05DC\u05D4\u05D9\u05D5\u05EA";
|
|
7763
7763
|
};
|
|
@@ -16638,7 +16638,7 @@ function stringifyKeyNames(bySchema, json2, visited) {
|
|
|
16638
16638
|
json2 = { ...json2, [keyword]: mapped };
|
|
16639
16639
|
}
|
|
16640
16640
|
const types = Array.isArray(json2.type) ? json2.type : [json2.type];
|
|
16641
|
-
const numericType = !types.includes("string") && types.some((
|
|
16641
|
+
const numericType = !types.includes("string") && types.some((t4) => t4 === "number" || t4 === "integer");
|
|
16642
16642
|
const values = json2.enum ?? (json2.const !== void 0 ? [json2.const] : void 0);
|
|
16643
16643
|
if (!numericType && !values?.some((v) => typeof v === "number"))
|
|
16644
16644
|
return json2;
|
|
@@ -19141,8 +19141,8 @@ function convertBaseSchema(schema, ctx) {
|
|
|
19141
19141
|
}
|
|
19142
19142
|
const type = schema.type;
|
|
19143
19143
|
if (Array.isArray(type)) {
|
|
19144
|
-
const typeSchemas = type.map((
|
|
19145
|
-
const typeSchema = { ...schema, type:
|
|
19144
|
+
const typeSchemas = type.map((t4) => {
|
|
19145
|
+
const typeSchema = { ...schema, type: t4 };
|
|
19146
19146
|
return convertBaseSchema(typeSchema, ctx);
|
|
19147
19147
|
});
|
|
19148
19148
|
if (typeSchemas.length === 0) {
|
|
@@ -31178,7 +31178,7 @@ var pluginManifestV1Schema = external_exports.object({
|
|
|
31178
31178
|
}
|
|
31179
31179
|
}
|
|
31180
31180
|
if (manifest2.tools) {
|
|
31181
|
-
const toolNames = manifest2.tools.map((
|
|
31181
|
+
const toolNames = manifest2.tools.map((t4) => t4.name);
|
|
31182
31182
|
const duplicates = toolNames.filter((name, i) => toolNames.indexOf(name) !== i);
|
|
31183
31183
|
if (duplicates.length > 0) {
|
|
31184
31184
|
ctx.addIssue({
|
|
@@ -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.14";
|
|
39936
39936
|
var DB_NAMESPACE_SLUG = "autopilot_ops";
|
|
39937
39937
|
var PAGE_ROUTE = "autopilot";
|
|
39938
39938
|
var TOOL_NAMES = {
|
|
@@ -39943,7 +39943,9 @@ var TOOL_NAMES = {
|
|
|
39943
39943
|
registrarAccion: "ops_registrar_accion",
|
|
39944
39944
|
consultarIndicador: "ops_consultar_indicador",
|
|
39945
39945
|
publicarTablero: "ops_publicar_tablero",
|
|
39946
|
-
listarTableros: "ops_listar_tableros"
|
|
39946
|
+
listarTableros: "ops_listar_tableros",
|
|
39947
|
+
listarReglas: "ops_listar_reglas",
|
|
39948
|
+
informarPruebaRegla: "ops_informar_prueba_regla"
|
|
39947
39949
|
};
|
|
39948
39950
|
var DATA_KEYS = {
|
|
39949
39951
|
tablero: "tablero",
|
|
@@ -40003,6 +40005,33 @@ var manifest = {
|
|
|
40003
40005
|
}
|
|
40004
40006
|
}
|
|
40005
40007
|
},
|
|
40008
|
+
{
|
|
40009
|
+
name: TOOL_NAMES.listarReglas,
|
|
40010
|
+
displayName: "Listar reglas",
|
|
40011
|
+
description: "Lista las reglas que el hospital arm\xF3 para un puesto (demanda_pendiente): tipo, par\xE1metros, contacto (canal, intentos, horario), cierre, estado y versi\xF3n. Filtro opcional: estado (activa). Solo lectura: las reglas las crea y activa el hospital desde el portal.",
|
|
40012
|
+
parametersSchema: {
|
|
40013
|
+
type: "object",
|
|
40014
|
+
properties: {
|
|
40015
|
+
workflow: { type: "string" },
|
|
40016
|
+
estado: { type: "string", enum: ["borrador", "activa", "pausada"] }
|
|
40017
|
+
}
|
|
40018
|
+
}
|
|
40019
|
+
},
|
|
40020
|
+
{
|
|
40021
|
+
name: TOOL_NAMES.informarPruebaRegla,
|
|
40022
|
+
displayName: "Informar prueba de regla",
|
|
40023
|
+
description: "Informa el resultado de probar una regla sobre los datos reales: a cu\xE1ntos pacientes alcanza hoy (alcanza) y hasta 10 referencias de muestra (orden:<id>, solicitud:<id>, control:<persona>:<regla>; nunca datos personales). Sin esta prueba de la versi\xF3n vigente, el hospital no puede activar la regla.",
|
|
40024
|
+
parametersSchema: {
|
|
40025
|
+
type: "object",
|
|
40026
|
+
properties: {
|
|
40027
|
+
reglaId: { type: "string" },
|
|
40028
|
+
alcanza: { type: "number" },
|
|
40029
|
+
muestra: { type: "array", items: { type: "string" } },
|
|
40030
|
+
consulta: { type: "string", description: "La consulta que se corri\xF3 (opcional, para auditor\xEDa)." }
|
|
40031
|
+
},
|
|
40032
|
+
required: ["reglaId", "alcanza"]
|
|
40033
|
+
}
|
|
40034
|
+
},
|
|
40006
40035
|
{
|
|
40007
40036
|
name: TOOL_NAMES.leerCaso,
|
|
40008
40037
|
displayName: "Leer caso",
|
|
@@ -40016,7 +40045,7 @@ var manifest = {
|
|
|
40016
40045
|
{
|
|
40017
40046
|
name: TOOL_NAMES.abrirCaso,
|
|
40018
40047
|
displayName: "Abrir caso",
|
|
40019
|
-
description: 'Abre un caso nuevo en un workflow, en su estado inicial. Workflows: `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), `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 del hospital), 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.',
|
|
40048
|
+
description: 'Abre un caso nuevo en un workflow, en su estado inicial. Workflows: `demanda_pendiente` (referencia obligatoria `orden:<id>`, `solicitud:<id>` o `control:<persona_id>:<regla_id>`; datos: regla_id obligatorio y la regla tiene que estar activa, persona_id, tipo de origen, estudio o especialidad o prestacion, fecha_origen AAAA-MM-DD, dias; el plugin 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), `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 del hospital), 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.',
|
|
40020
40049
|
parametersSchema: {
|
|
40021
40050
|
type: "object",
|
|
40022
40051
|
properties: {
|
|
@@ -40033,7 +40062,7 @@ var manifest = {
|
|
|
40033
40062
|
{
|
|
40034
40063
|
name: TOOL_NAMES.transicionar,
|
|
40035
40064
|
displayName: "Transicionar caso",
|
|
40036
|
-
description: 'Aplica una transici\xF3n del workflow al caso (p. ej. `proponer`, `aplicar`, `verificar`). El plugin valida contra la tabla de transiciones, la pol\xEDtica vigente (observa / propone / ejecuta / corte) y las reglas de negocio; si algo falla, el caso no cambia y se devuelve el motivo. Las transiciones humanas (aprobar, rechazar, corregir) no est\xE1n disponibles para agentes. Cuando una persona del hospital carga a mano lo aprobado, el caso queda en `aplicado`: el agente lo comprueba releyendo el sistema y llamando `verificar` con `datos.valor_leido`. En `recupero_turno`: `proponer` con solicitud_id, solicitud_estado, solicitud_especialidad, cola (la lista de espera pendiente de esa especialidad tal como se vio: [{solicitud_id, prioridad, alta}]; el plugin la ordena y exige justificaci\xF3n para no elegir la primera) y fuente; si la instituci\xF3n pide consulta previa (ops_leer_caso lo dice), `ofrecer` con canal y esperar `aceptar`/`no_acepta`; **antes de escribir en el sistema, `autorizar`** (sin esa autorizaci\xF3n no se escribe); despu\xE9s de escribir, `asignar` con accion_id; `verificar` con leido_fecha, leido_hora, leido_profesional y leido_solicitud_id; `sin_candidato` y `descartar` con motivo. En `cuenta_completa` el agente solo lee: `observar` con bono_id, bono_estado (no_confirmado, confirmado o prefacturado), faltantes [{regla, detalle, item?}] con reglas del cat\xE1logo (autorizacion_faltante, validacion_faltante, afiliado_faltante, orden_faltante, prestacion_sin_valor, epicrisis_faltante), lectura_id (la lectura registrada con ops_registrar_accion, conector "base") y fuente; `comprobar` con faltantes_leidos (lista vac\xEDa si ya est\xE1 completo), bono_estado_leido y lectura_id; `descartar` con motivo si el bono se elimin\xF3 o anul\xF3. En `facturacion_pendiente` (solo lee): `observar` con bono_id, bono_estado, fecha_prestacion, obra_social, lectura_id (conector "base") y fuente; `comprobar` con bono_estado_leido y lectura_id (facturado/refacturado \u2192 facturada; prefacturado \u2192 en prefactura; si no, sigue por presentar); `descartar` con motivo. En `credencial_profesional` (solo lee): `verificar` con profesional_id, matricula y tipo_matricula (nacional|provincial) si hay matr\xEDcula, resultado = el objeto que devuelve verificar-matriculas.mjs tal cual ({sin_matricula, encontrada, coincide_nombre, situacion, sancion, profesion, provincia, emisor}), lectura_id (conector "api", la consulta al registro) y fuente; el plugin decide vigente u observada y fija la observaci\xF3n (sin_matricula, no_figura, otra_persona, inhabilitada, sancion); `descartar` con motivo si el profesional ya no est\xE1 activo.',
|
|
40065
|
+
description: 'Aplica una transici\xF3n del workflow al caso (p. ej. `proponer`, `aplicar`, `verificar`). El plugin valida contra la tabla de transiciones, la pol\xEDtica vigente (observa / propone / ejecuta / corte) y las reglas de negocio; si algo falla, el caso no cambia y se devuelve el motivo. Las transiciones humanas (aprobar, rechazar, corregir) no est\xE1n disponibles para agentes. Cuando una persona del hospital carga a mano lo aprobado, el caso queda en `aplicado`: el agente lo comprueba releyendo el sistema y llamando `verificar` con `datos.valor_leido`. En `recupero_turno`: `proponer` con solicitud_id, solicitud_estado, solicitud_especialidad, cola (la lista de espera pendiente de esa especialidad tal como se vio: [{solicitud_id, prioridad, alta}]; el plugin la ordena y exige justificaci\xF3n para no elegir la primera) y fuente; si la instituci\xF3n pide consulta previa (ops_leer_caso lo dice), `ofrecer` con canal y esperar `aceptar`/`no_acepta`; **antes de escribir en el sistema, `autorizar`** (sin esa autorizaci\xF3n no se escribe); despu\xE9s de escribir, `asignar` con accion_id; `verificar` con leido_fecha, leido_hora, leido_profesional y leido_solicitud_id; `sin_candidato` y `descartar` con motivo. En `cuenta_completa` el agente solo lee: `observar` con bono_id, bono_estado (no_confirmado, confirmado o prefacturado), faltantes [{regla, detalle, item?}] con reglas del cat\xE1logo (autorizacion_faltante, validacion_faltante, afiliado_faltante, orden_faltante, prestacion_sin_valor, epicrisis_faltante), lectura_id (la lectura registrada con ops_registrar_accion, conector "base") y fuente; `comprobar` con faltantes_leidos (lista vac\xEDa si ya est\xE1 completo), bono_estado_leido y lectura_id; `descartar` con motivo si el bono se elimin\xF3 o anul\xF3. En `facturacion_pendiente` (solo lee): `observar` con bono_id, bono_estado, fecha_prestacion, obra_social, lectura_id (conector "base") y fuente; `comprobar` con bono_estado_leido y lectura_id (facturado/refacturado \u2192 facturada; prefacturado \u2192 en prefactura; si no, sigue por presentar); `descartar` con motivo. En `demanda_pendiente`: `contactar` (nivel ejecuta; datos: canal igual al de la regla y accion_id de la escritura registrada; el plugin controla regla activa, intentos, espera entre intentos y horario), `responder` (respuesta: acepta|rechaza|no_contesta), `comprobar` (lectura_id de la relectura de Alephoo, turno_id y turno_estado del turno encontrado), `cerrar` (motivo; solo con los intentos agotados o el plazo vencido), `descartar` (motivo). `credencial_profesional` (solo lee): `verificar` con profesional_id, matricula y tipo_matricula (nacional|provincial) si hay matr\xEDcula, resultado = el objeto que devuelve verificar-matriculas.mjs tal cual ({sin_matricula, encontrada, coincide_nombre, situacion, sancion, profesion, provincia, emisor}), lectura_id (conector "api", la consulta al registro) y fuente; el plugin decide vigente u observada y fija la observaci\xF3n (sin_matricula, no_figura, otra_persona, inhabilitada, sancion); `descartar` con motivo si el profesional ya no est\xE1 activo.',
|
|
40037
40066
|
parametersSchema: {
|
|
40038
40067
|
type: "object",
|
|
40039
40068
|
properties: {
|
|
@@ -40232,6 +40261,54 @@ var manifest = {
|
|
|
40232
40261
|
auth: "board",
|
|
40233
40262
|
capability: "api.routes.register",
|
|
40234
40263
|
companyResolution: { from: "query", key: "companyId" }
|
|
40264
|
+
},
|
|
40265
|
+
{
|
|
40266
|
+
routeKey: "reglas",
|
|
40267
|
+
method: "GET",
|
|
40268
|
+
path: "/reglas",
|
|
40269
|
+
auth: "board",
|
|
40270
|
+
capability: "api.routes.register",
|
|
40271
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
40272
|
+
},
|
|
40273
|
+
{
|
|
40274
|
+
routeKey: "crear-regla",
|
|
40275
|
+
method: "POST",
|
|
40276
|
+
path: "/reglas",
|
|
40277
|
+
auth: "board",
|
|
40278
|
+
capability: "api.routes.register",
|
|
40279
|
+
companyResolution: { from: "body", key: "companyId" }
|
|
40280
|
+
},
|
|
40281
|
+
{
|
|
40282
|
+
routeKey: "editar-regla",
|
|
40283
|
+
method: "POST",
|
|
40284
|
+
path: "/reglas/:reglaId",
|
|
40285
|
+
auth: "board",
|
|
40286
|
+
capability: "api.routes.register",
|
|
40287
|
+
companyResolution: { from: "body", key: "companyId" }
|
|
40288
|
+
},
|
|
40289
|
+
{
|
|
40290
|
+
routeKey: "estado-regla",
|
|
40291
|
+
method: "POST",
|
|
40292
|
+
path: "/reglas/:reglaId/estado",
|
|
40293
|
+
auth: "board",
|
|
40294
|
+
capability: "api.routes.register",
|
|
40295
|
+
companyResolution: { from: "body", key: "companyId" }
|
|
40296
|
+
},
|
|
40297
|
+
{
|
|
40298
|
+
routeKey: "probar-regla",
|
|
40299
|
+
method: "POST",
|
|
40300
|
+
path: "/reglas/:reglaId/probar",
|
|
40301
|
+
auth: "board",
|
|
40302
|
+
capability: "api.routes.register",
|
|
40303
|
+
companyResolution: { from: "body", key: "companyId" }
|
|
40304
|
+
},
|
|
40305
|
+
{
|
|
40306
|
+
routeKey: "contacto",
|
|
40307
|
+
method: "POST",
|
|
40308
|
+
path: "/casos/:casoId/contacto",
|
|
40309
|
+
auth: "board",
|
|
40310
|
+
capability: "api.routes.register",
|
|
40311
|
+
companyResolution: { from: "body", key: "companyId" }
|
|
40235
40312
|
}
|
|
40236
40313
|
],
|
|
40237
40314
|
ui: {
|
|
@@ -40256,7 +40333,7 @@ var manifest = {
|
|
|
40256
40333
|
var manifest_default = manifest;
|
|
40257
40334
|
|
|
40258
40335
|
// src/ops.ts
|
|
40259
|
-
import { randomUUID } from "node:crypto";
|
|
40336
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
40260
40337
|
|
|
40261
40338
|
// src/workflows.ts
|
|
40262
40339
|
var MODOS = ["corte", "observa", "propone", "ejecuta", "autonomo"];
|
|
@@ -40459,8 +40536,8 @@ function fechaAISO(v) {
|
|
|
40459
40536
|
if (fechaISO(s.slice(0, 10))) return s.slice(0, 10);
|
|
40460
40537
|
const m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})/.exec(s);
|
|
40461
40538
|
if (!m) return null;
|
|
40462
|
-
const
|
|
40463
|
-
return fechaISO(
|
|
40539
|
+
const iso4 = `${m[3]}-${m[2].padStart(2, "0")}-${m[1].padStart(2, "0")}`;
|
|
40540
|
+
return fechaISO(iso4) ? iso4 : null;
|
|
40464
40541
|
}
|
|
40465
40542
|
function leerCola(v) {
|
|
40466
40543
|
if (!Array.isArray(v) || v.length === 0) return null;
|
|
@@ -41043,6 +41120,151 @@ var credencialProfesional = {
|
|
|
41043
41120
|
}
|
|
41044
41121
|
]
|
|
41045
41122
|
};
|
|
41123
|
+
var TIPOS_REGLA_DEMANDA = ["orden_sin_turno", "solicitud_pendiente", "control_periodico"];
|
|
41124
|
+
var CANALES_CONTACTO = ["whatsapp", "sms", "mail", "llamada"];
|
|
41125
|
+
var RESPUESTAS_CONTACTO = ["acepta", "rechaza", "no_contesta"];
|
|
41126
|
+
var TURNO_ATENDIDO = ["M", "R", "V"];
|
|
41127
|
+
function diasEntre(desde, hasta) {
|
|
41128
|
+
const a = fechaAISO(desde);
|
|
41129
|
+
const b = fechaAISO(hasta);
|
|
41130
|
+
if (!a || !b) return null;
|
|
41131
|
+
return Math.round((Date.parse(`${b}T00:00:00Z`) - Date.parse(`${a}T00:00:00Z`)) / 864e5);
|
|
41132
|
+
}
|
|
41133
|
+
function horaDentro(hora, desde, hasta) {
|
|
41134
|
+
const h = horaHM(hora);
|
|
41135
|
+
const d = horaHM(desde);
|
|
41136
|
+
const t4 = horaHM(hasta);
|
|
41137
|
+
if (!h || !d || !t4) return false;
|
|
41138
|
+
return h >= d && h <= t4;
|
|
41139
|
+
}
|
|
41140
|
+
function codigoContacto(v) {
|
|
41141
|
+
const s = texto(v);
|
|
41142
|
+
return s ? s.toLowerCase().replace(/\s+/g, "_") : null;
|
|
41143
|
+
}
|
|
41144
|
+
function respuestaContacto(v) {
|
|
41145
|
+
const s = codigoContacto(v);
|
|
41146
|
+
return s && RESPUESTAS_CONTACTO.includes(s) ? s : null;
|
|
41147
|
+
}
|
|
41148
|
+
function canalContacto(v) {
|
|
41149
|
+
const s = codigoContacto(v);
|
|
41150
|
+
return s && CANALES_CONTACTO.includes(s) ? s : null;
|
|
41151
|
+
}
|
|
41152
|
+
var demandaPendiente = {
|
|
41153
|
+
clave: "demanda_pendiente",
|
|
41154
|
+
nombre: "Demanda pendiente",
|
|
41155
|
+
puesto: "Pacientes y agenda",
|
|
41156
|
+
descripcion: "Mueve la demanda que el hospital ya gener\xF3 y no se ejecut\xF3: \xF3rdenes de estudio sin turno, solicitudes de turno pendientes y controles peri\xF3dicos vencidos. Las reglas las arma el hospital en el portal; el plugin las aplica en cada contacto; el caso se cierra cuando el turno queda agendado.",
|
|
41157
|
+
inicial: "detectado",
|
|
41158
|
+
estados: [
|
|
41159
|
+
"detectado",
|
|
41160
|
+
"contactado",
|
|
41161
|
+
"aceptado",
|
|
41162
|
+
"turno_agendado",
|
|
41163
|
+
"realizado",
|
|
41164
|
+
"no_quiere",
|
|
41165
|
+
"cerrado_sin_turno",
|
|
41166
|
+
"no_corresponde",
|
|
41167
|
+
"descartado"
|
|
41168
|
+
],
|
|
41169
|
+
finalesOk: ["turno_agendado", "realizado"],
|
|
41170
|
+
finalesNoOk: ["no_quiere", "cerrado_sin_turno", "no_corresponde", "descartado"],
|
|
41171
|
+
minutosAbsorbidos: 12,
|
|
41172
|
+
transiciones: [
|
|
41173
|
+
{
|
|
41174
|
+
via: "contactar",
|
|
41175
|
+
de: ["detectado", "contactado"],
|
|
41176
|
+
a: "contactado",
|
|
41177
|
+
actor: ["agente", "humano"],
|
|
41178
|
+
nivelRequerido: "ejecuta",
|
|
41179
|
+
descripcion: "Contactar al paciente por el canal de la regla (el agente por el CRM de Alephoo; una persona del equipo, por donde lo hizo). Exige la escritura registrada (accion_id).",
|
|
41180
|
+
validar: (_caso, datos, contexto) => {
|
|
41181
|
+
const out = [];
|
|
41182
|
+
const regla = contexto.regla ?? null;
|
|
41183
|
+
const agente = contexto.actor === "agente";
|
|
41184
|
+
out.push(regla && regla.estado === "activa" ? ok("regla_activa") : falla("regla_activa", regla ? `la regla est\xE1 ${regla.estado}: no se contacta hasta que se active` : "la regla que abri\xF3 el caso ya no existe"));
|
|
41185
|
+
const canal = canalContacto(datos.canal);
|
|
41186
|
+
out.push(canal ? ok("canal_informado") : falla("canal_informado", `canal es obligatorio: ${CANALES_CONTACTO.join(", ")}`));
|
|
41187
|
+
if (agente && regla && canal) {
|
|
41188
|
+
out.push(canal === regla.contacto.canal ? ok("canal_de_la_regla") : falla("canal_de_la_regla", `la regla contacta por ${regla.contacto.canal}, no por ${canal}`));
|
|
41189
|
+
}
|
|
41190
|
+
if (regla) {
|
|
41191
|
+
const hechos = contexto.intentosContacto ?? 0;
|
|
41192
|
+
out.push(hechos < regla.contacto.max_intentos ? ok("intentos_disponibles", `${hechos} de ${regla.contacto.max_intentos}`) : falla("intentos_disponibles", `ya se hicieron ${hechos} contactos (tope ${regla.contacto.max_intentos}): cerrar sin turno`));
|
|
41193
|
+
if (agente) {
|
|
41194
|
+
const desdeUltimo = contexto.ultimoContacto ? diasEntre(contexto.ultimoContacto, contexto.hoy) : null;
|
|
41195
|
+
out.push(desdeUltimo === null || desdeUltimo >= regla.contacto.dias_entre_intentos ? ok("espera_entre_intentos") : falla("espera_entre_intentos", `el \xFAltimo contacto fue hace ${desdeUltimo} d\xEDas y la regla pide esperar ${regla.contacto.dias_entre_intentos}`));
|
|
41196
|
+
out.push(horaDentro(contexto.horaActual, regla.contacto.hora_desde, regla.contacto.hora_hasta) ? ok("horario_permitido") : falla("horario_permitido", `son las ${contexto.horaActual ?? "?"} y la regla contacta de ${regla.contacto.hora_desde} a ${regla.contacto.hora_hasta}`));
|
|
41197
|
+
}
|
|
41198
|
+
}
|
|
41199
|
+
out.push(texto(datos.accion_id) ? ok("accion_registrada") : falla("accion_registrada", "contactar requiere accion_id: la escritura (mensaje enviado o llamada hecha) registrada antes"));
|
|
41200
|
+
return out;
|
|
41201
|
+
}
|
|
41202
|
+
},
|
|
41203
|
+
{
|
|
41204
|
+
via: "responder",
|
|
41205
|
+
de: ["contactado"],
|
|
41206
|
+
a: (_caso, datos) => {
|
|
41207
|
+
const r = respuestaContacto(datos.respuesta);
|
|
41208
|
+
return r === "acepta" ? "aceptado" : r === "rechaza" ? "no_quiere" : "contactado";
|
|
41209
|
+
},
|
|
41210
|
+
actor: ["agente", "humano"],
|
|
41211
|
+
nivelRequerido: "observa",
|
|
41212
|
+
descripcion: "Registrar la respuesta del paciente: acepta (va a sacar turno), rechaza o no_contesta (queda para el pr\xF3ximo intento).",
|
|
41213
|
+
validar: (_caso, datos) => [
|
|
41214
|
+
respuestaContacto(datos.respuesta) ? ok("respuesta_valida") : falla("respuesta_valida", `respuesta debe ser ${RESPUESTAS_CONTACTO.join(", ")}`)
|
|
41215
|
+
]
|
|
41216
|
+
},
|
|
41217
|
+
{
|
|
41218
|
+
via: "comprobar",
|
|
41219
|
+
de: ["detectado", "contactado", "aceptado"],
|
|
41220
|
+
a: (_caso, datos) => TURNO_ATENDIDO.includes(String(datos.turno_estado ?? "").toUpperCase()) ? "realizado" : "turno_agendado",
|
|
41221
|
+
actor: ["agente", "humano"],
|
|
41222
|
+
nivelRequerido: "observa",
|
|
41223
|
+
descripcion: "Releer Alephoo y encontrar el turno de esa orden, solicitud o control: con el turno, el caso se cierra como movido.",
|
|
41224
|
+
validar: (_caso, datos) => [
|
|
41225
|
+
texto(datos.lectura_id) ? ok("lectura_registrada") : falla("lectura_registrada", "comprobar requiere lectura_id: la relectura de Alephoo registrada antes"),
|
|
41226
|
+
refExterna(datos.turno_id) ? ok("turno_presente") : falla("turno_presente", "comprobar requiere turno_id: el turno encontrado en Alephoo; sin turno no hay nada que comprobar")
|
|
41227
|
+
]
|
|
41228
|
+
},
|
|
41229
|
+
{
|
|
41230
|
+
via: "cerrar",
|
|
41231
|
+
de: ["detectado", "contactado"],
|
|
41232
|
+
a: "cerrado_sin_turno",
|
|
41233
|
+
actor: ["agente", "humano"],
|
|
41234
|
+
nivelRequerido: "observa",
|
|
41235
|
+
descripcion: "Cerrar sin turno: solo cuando se agotaron los intentos o venci\xF3 el plazo de la regla.",
|
|
41236
|
+
validar: (_caso, datos, contexto) => {
|
|
41237
|
+
const out = [texto(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "cerrar requiere motivo")];
|
|
41238
|
+
const regla = contexto.regla ?? null;
|
|
41239
|
+
if (regla) {
|
|
41240
|
+
const hechos = contexto.intentosContacto ?? 0;
|
|
41241
|
+
const dias = contexto.diasAbierto ?? 0;
|
|
41242
|
+
const agotados = hechos >= regla.contacto.max_intentos;
|
|
41243
|
+
const vencido = dias >= regla.cierre.dias_para_cerrar;
|
|
41244
|
+
out.push(agotados || vencido ? ok("cierre_habilitado", agotados ? `${hechos} contactos hechos` : `${dias} d\xEDas abierto`) : falla("cierre_habilitado", `van ${hechos} de ${regla.contacto.max_intentos} contactos y ${dias} de ${regla.cierre.dias_para_cerrar} d\xEDas: todav\xEDa no se cierra`));
|
|
41245
|
+
}
|
|
41246
|
+
return out;
|
|
41247
|
+
}
|
|
41248
|
+
},
|
|
41249
|
+
{
|
|
41250
|
+
via: "descartar",
|
|
41251
|
+
de: ["detectado", "contactado", "aceptado"],
|
|
41252
|
+
a: "descartado",
|
|
41253
|
+
actor: ["agente", "humano"],
|
|
41254
|
+
nivelRequerido: "observa",
|
|
41255
|
+
descripcion: "Descartar con motivo: el dato era err\xF3neo, el paciente ya lo hizo afuera, falleci\xF3 o pidi\xF3 no ser contactado.",
|
|
41256
|
+
validar: (_caso, datos) => [texto(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "descartar requiere motivo")]
|
|
41257
|
+
},
|
|
41258
|
+
{
|
|
41259
|
+
via: "no_corresponde",
|
|
41260
|
+
de: ["detectado", "contactado", "aceptado"],
|
|
41261
|
+
a: "no_corresponde",
|
|
41262
|
+
actor: ["humano"],
|
|
41263
|
+
descripcion: "Una persona del hospital decide que este caso no corresponde a la regla.",
|
|
41264
|
+
validar: (_caso, datos) => [texto(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "no_corresponde requiere motivo")]
|
|
41265
|
+
}
|
|
41266
|
+
]
|
|
41267
|
+
};
|
|
41046
41268
|
var WORKFLOWS_SOLO_LECTURA = /* @__PURE__ */ new Set([cuentaCompleta.clave, facturacionPendiente.clave, credencialProfesional.clave]);
|
|
41047
41269
|
var WORKFLOWS = {
|
|
41048
41270
|
[actualizacionArancel.clave]: actualizacionArancel,
|
|
@@ -41050,7 +41272,8 @@ var WORKFLOWS = {
|
|
|
41050
41272
|
[recuperoTurno.clave]: recuperoTurno,
|
|
41051
41273
|
[cuentaCompleta.clave]: cuentaCompleta,
|
|
41052
41274
|
[facturacionPendiente.clave]: facturacionPendiente,
|
|
41053
|
-
[credencialProfesional.clave]: credencialProfesional
|
|
41275
|
+
[credencialProfesional.clave]: credencialProfesional,
|
|
41276
|
+
[demandaPendiente.clave]: demandaPendiente
|
|
41054
41277
|
};
|
|
41055
41278
|
function getWorkflow(clave) {
|
|
41056
41279
|
return WORKFLOWS[clave] ?? null;
|
|
@@ -41072,9 +41295,9 @@ function evaluarTransicion(input2) {
|
|
|
41072
41295
|
if (!wf) {
|
|
41073
41296
|
return { ok: false, codigo: "workflow_desconocido", motivo: `workflow desconocido: ${caso.workflow}`, validaciones: [] };
|
|
41074
41297
|
}
|
|
41075
|
-
const def2 = wf.transiciones.find((
|
|
41298
|
+
const def2 = wf.transiciones.find((t4) => t4.via === via);
|
|
41076
41299
|
if (!def2) {
|
|
41077
|
-
const disponibles = wf.transiciones.filter((
|
|
41300
|
+
const disponibles = wf.transiciones.filter((t4) => t4.de.includes(caso.estado)).map((t4) => t4.via);
|
|
41078
41301
|
return { ok: false, codigo: "via_desconocida", motivo: `la transici\xF3n "${via}" no existe en ${wf.clave}; desde "${caso.estado}" se puede: ${disponibles.join(", ") || "ninguna"}`, validaciones: [] };
|
|
41079
41302
|
}
|
|
41080
41303
|
if (!def2.de.includes(caso.estado)) {
|
|
@@ -41117,15 +41340,333 @@ function evaluarTransicion(input2) {
|
|
|
41117
41340
|
function transicionesDisponibles(caso, actor) {
|
|
41118
41341
|
const wf = getWorkflow(caso.workflow);
|
|
41119
41342
|
if (!wf) return [];
|
|
41120
|
-
return wf.transiciones.filter((
|
|
41343
|
+
return wf.transiciones.filter((t4) => t4.de.includes(caso.estado) && (!actor || t4.actor.includes(actor)));
|
|
41121
41344
|
}
|
|
41122
41345
|
function transicionCargaManual(caso) {
|
|
41123
|
-
return transicionesDisponibles(caso, "humano").find((
|
|
41346
|
+
return transicionesDisponibles(caso, "humano").find((t4) => t4.cargaManual) ?? null;
|
|
41124
41347
|
}
|
|
41125
41348
|
function esModo(v) {
|
|
41126
41349
|
return typeof v === "string" && MODOS.includes(v);
|
|
41127
41350
|
}
|
|
41128
41351
|
|
|
41352
|
+
// src/reglas.ts
|
|
41353
|
+
import { randomUUID } from "node:crypto";
|
|
41354
|
+
var ESTADOS_REGLA = ["borrador", "activa", "pausada"];
|
|
41355
|
+
var LIMITES_REGLA = {
|
|
41356
|
+
dias_desde: [1, 365, 30],
|
|
41357
|
+
cada_meses: [1, 60, 12],
|
|
41358
|
+
max_intentos: [1, 5, 2],
|
|
41359
|
+
dias_entre_intentos: [1, 30, 7],
|
|
41360
|
+
dias_para_cerrar: [7, 180, 45],
|
|
41361
|
+
edad: [0, 120, 0]
|
|
41362
|
+
};
|
|
41363
|
+
var SIN_QUE = ["turno", "informe"];
|
|
41364
|
+
function t(ctx, table) {
|
|
41365
|
+
return `${ctx.db.namespace}.${table}`;
|
|
41366
|
+
}
|
|
41367
|
+
function str(v) {
|
|
41368
|
+
return typeof v === "string" && v.trim().length > 0 ? v.trim().replace(/\s+/g, " ") : null;
|
|
41369
|
+
}
|
|
41370
|
+
function iso(v) {
|
|
41371
|
+
if (v instanceof Date) return v.toISOString();
|
|
41372
|
+
if (typeof v === "string") return v;
|
|
41373
|
+
return "";
|
|
41374
|
+
}
|
|
41375
|
+
function jsonb(v) {
|
|
41376
|
+
if (v && typeof v === "object" && !Array.isArray(v)) return v;
|
|
41377
|
+
if (typeof v === "string") {
|
|
41378
|
+
try {
|
|
41379
|
+
const parsed = JSON.parse(v);
|
|
41380
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
41381
|
+
} catch {
|
|
41382
|
+
return {};
|
|
41383
|
+
}
|
|
41384
|
+
}
|
|
41385
|
+
return {};
|
|
41386
|
+
}
|
|
41387
|
+
function jsonArr(v) {
|
|
41388
|
+
if (Array.isArray(v)) return v;
|
|
41389
|
+
if (typeof v === "string") {
|
|
41390
|
+
try {
|
|
41391
|
+
const parsed = JSON.parse(v);
|
|
41392
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
41393
|
+
} catch {
|
|
41394
|
+
return [];
|
|
41395
|
+
}
|
|
41396
|
+
}
|
|
41397
|
+
return [];
|
|
41398
|
+
}
|
|
41399
|
+
function entero(v) {
|
|
41400
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
|
|
41401
|
+
return Number.isInteger(n) ? n : null;
|
|
41402
|
+
}
|
|
41403
|
+
function numeroEn(problemas, campo, v, nombre = campo) {
|
|
41404
|
+
const [min, max, def2] = LIMITES_REGLA[campo];
|
|
41405
|
+
if (v === void 0 || v === null || v === "") return def2;
|
|
41406
|
+
const n = entero(v);
|
|
41407
|
+
if (n === null || n < min || n > max) {
|
|
41408
|
+
problemas.push(`${nombre}: debe ser un entero entre ${min} y ${max}`);
|
|
41409
|
+
return def2;
|
|
41410
|
+
}
|
|
41411
|
+
return n;
|
|
41412
|
+
}
|
|
41413
|
+
function textoCorto(problemas, campo, v, max, obligatorio) {
|
|
41414
|
+
if (v === void 0 || v === null || v === "") {
|
|
41415
|
+
if (obligatorio) problemas.push(`${campo}: es obligatorio`);
|
|
41416
|
+
return null;
|
|
41417
|
+
}
|
|
41418
|
+
const s = typeof v === "string" ? v.trim().replace(/\s+/g, " ") : "";
|
|
41419
|
+
if (!s) {
|
|
41420
|
+
problemas.push(`${campo}: debe ser un texto`);
|
|
41421
|
+
return null;
|
|
41422
|
+
}
|
|
41423
|
+
if (s.length > max) {
|
|
41424
|
+
problemas.push(`${campo}: como m\xE1ximo ${max} caracteres`);
|
|
41425
|
+
return s.slice(0, max);
|
|
41426
|
+
}
|
|
41427
|
+
return s;
|
|
41428
|
+
}
|
|
41429
|
+
function poblacion(problemas, p) {
|
|
41430
|
+
const out = {};
|
|
41431
|
+
if (p.edad_desde !== void 0 && p.edad_desde !== null && p.edad_desde !== "") out.edad_desde = numeroEn(problemas, "edad", p.edad_desde, "edad_desde");
|
|
41432
|
+
if (p.edad_hasta !== void 0 && p.edad_hasta !== null && p.edad_hasta !== "") out.edad_hasta = numeroEn(problemas, "edad", p.edad_hasta, "edad_hasta");
|
|
41433
|
+
if (typeof out.edad_desde === "number" && typeof out.edad_hasta === "number" && out.edad_desde > out.edad_hasta) problemas.push("edad_desde: no puede ser mayor que edad_hasta");
|
|
41434
|
+
if (p.sexo !== void 0 && p.sexo !== null && p.sexo !== "") {
|
|
41435
|
+
const s = String(p.sexo).trim().toUpperCase();
|
|
41436
|
+
if (s === "F" || s === "M") out.sexo = s;
|
|
41437
|
+
else problemas.push("sexo: debe ser F o M");
|
|
41438
|
+
}
|
|
41439
|
+
if (p.obras_sociales !== void 0 && p.obras_sociales !== null) {
|
|
41440
|
+
const lista = Array.isArray(p.obras_sociales) ? p.obras_sociales.map((x) => str(x)).filter((x) => x !== null) : [];
|
|
41441
|
+
if (!Array.isArray(p.obras_sociales)) problemas.push("obras_sociales: debe ser una lista de nombres");
|
|
41442
|
+
else if (lista.length > 20) problemas.push("obras_sociales: como m\xE1ximo 20");
|
|
41443
|
+
else if (lista.length > 0) out.obras_sociales = lista.map((x) => x.slice(0, 80));
|
|
41444
|
+
}
|
|
41445
|
+
return out;
|
|
41446
|
+
}
|
|
41447
|
+
function validarRegla(input2) {
|
|
41448
|
+
const problemas = [];
|
|
41449
|
+
const nombre = textoCorto(problemas, "nombre", input2.nombre, 80, true) ?? "";
|
|
41450
|
+
if (nombre && nombre.length < 3) problemas.push("nombre: al menos 3 caracteres");
|
|
41451
|
+
const tipoCrudo = str(input2.tipo);
|
|
41452
|
+
const tipo = tipoCrudo && TIPOS_REGLA_DEMANDA.includes(tipoCrudo) ? tipoCrudo : null;
|
|
41453
|
+
if (!tipo) problemas.push(`tipo: debe ser ${TIPOS_REGLA_DEMANDA.join(", ")}`);
|
|
41454
|
+
const p = jsonb(input2.parametros);
|
|
41455
|
+
const parametros = poblacion(problemas, p);
|
|
41456
|
+
if (tipo === "orden_sin_turno") {
|
|
41457
|
+
const estudio = textoCorto(problemas, "estudio", p.estudio, 80, false);
|
|
41458
|
+
if (estudio) parametros.estudio = estudio;
|
|
41459
|
+
parametros.dias_desde = numeroEn(problemas, "dias_desde", p.dias_desde);
|
|
41460
|
+
const sin = Array.isArray(p.sin) ? p.sin.map((x) => String(x).trim().toLowerCase()) : [...SIN_QUE];
|
|
41461
|
+
if (sin.length === 0 || sin.some((x) => !SIN_QUE.includes(x))) problemas.push(`sin: lista con ${SIN_QUE.join(" y/o ")}`);
|
|
41462
|
+
parametros.sin = [...new Set(sin)].filter((x) => SIN_QUE.includes(x));
|
|
41463
|
+
} else if (tipo === "solicitud_pendiente") {
|
|
41464
|
+
const especialidad = textoCorto(problemas, "especialidad", p.especialidad, 80, false);
|
|
41465
|
+
if (especialidad) parametros.especialidad = especialidad;
|
|
41466
|
+
const prioridad = textoCorto(problemas, "prioridad", p.prioridad, 40, false);
|
|
41467
|
+
if (prioridad) parametros.prioridad = prioridad;
|
|
41468
|
+
parametros.dias_desde = numeroEn(problemas, "dias_desde", p.dias_desde);
|
|
41469
|
+
} else if (tipo === "control_periodico") {
|
|
41470
|
+
const diagnostico = textoCorto(problemas, "diagnostico", p.diagnostico, 10, true);
|
|
41471
|
+
if (diagnostico) {
|
|
41472
|
+
if (!/^[A-Za-z][0-9]{2}(\.[0-9A-Za-z]{1,2})?$/.test(diagnostico) && !/^[A-Za-z][0-9]{1,2}$/.test(diagnostico)) problemas.push("diagnostico: un c\xF3digo CIE-10 o su prefijo (p. ej. E11 o I10)");
|
|
41473
|
+
else parametros.diagnostico = diagnostico.toUpperCase();
|
|
41474
|
+
}
|
|
41475
|
+
const prestacion = textoCorto(problemas, "prestacion", p.prestacion, 80, true);
|
|
41476
|
+
if (prestacion) parametros.prestacion = prestacion;
|
|
41477
|
+
parametros.cada_meses = numeroEn(problemas, "cada_meses", p.cada_meses);
|
|
41478
|
+
}
|
|
41479
|
+
const c = jsonb(input2.contacto);
|
|
41480
|
+
const canalCrudo = str(c.canal)?.toLowerCase() ?? null;
|
|
41481
|
+
const canal = canalCrudo && CANALES_CONTACTO.includes(canalCrudo) ? canalCrudo : null;
|
|
41482
|
+
if (!canal) problemas.push(`contacto.canal: debe ser ${CANALES_CONTACTO.join(", ")}`);
|
|
41483
|
+
const plantilla = textoCorto(problemas, "contacto.plantilla", c.plantilla, 600, true) ?? "";
|
|
41484
|
+
if (plantilla && plantilla.length < 10) problemas.push("contacto.plantilla: al menos 10 caracteres");
|
|
41485
|
+
const maxIntentos = numeroEn(problemas, "max_intentos", c.max_intentos, "contacto.max_intentos");
|
|
41486
|
+
const diasEntreIntentos = numeroEn(problemas, "dias_entre_intentos", c.dias_entre_intentos, "contacto.dias_entre_intentos");
|
|
41487
|
+
const horaDesde = c.hora_desde === void 0 || c.hora_desde === null || c.hora_desde === "" ? "09:00" : horaHM(c.hora_desde);
|
|
41488
|
+
const horaHasta = c.hora_hasta === void 0 || c.hora_hasta === null || c.hora_hasta === "" ? "19:00" : horaHM(c.hora_hasta);
|
|
41489
|
+
if (!horaDesde) problemas.push("contacto.hora_desde: hora HH:MM");
|
|
41490
|
+
if (!horaHasta) problemas.push("contacto.hora_hasta: hora HH:MM");
|
|
41491
|
+
if (horaDesde && horaHasta && horaDesde >= horaHasta) problemas.push("contacto.hora_desde: tiene que ser anterior a hora_hasta");
|
|
41492
|
+
const ci = jsonb(input2.cierre);
|
|
41493
|
+
const diasParaCerrar = numeroEn(problemas, "dias_para_cerrar", ci.dias_para_cerrar, "cierre.dias_para_cerrar");
|
|
41494
|
+
if (problemas.length > 0) throw new OpsError("validacion", problemas.join("; "), { problemas });
|
|
41495
|
+
return {
|
|
41496
|
+
nombre,
|
|
41497
|
+
tipo,
|
|
41498
|
+
parametros,
|
|
41499
|
+
contacto: { canal, plantilla, max_intentos: maxIntentos, dias_entre_intentos: diasEntreIntentos, hora_desde: horaDesde, hora_hasta: horaHasta },
|
|
41500
|
+
cierre: { dias_para_cerrar: diasParaCerrar }
|
|
41501
|
+
};
|
|
41502
|
+
}
|
|
41503
|
+
function reglaParaContexto(r) {
|
|
41504
|
+
return {
|
|
41505
|
+
id: r.id,
|
|
41506
|
+
estado: r.estado,
|
|
41507
|
+
version: r.version,
|
|
41508
|
+
contacto: { canal: r.contacto.canal, max_intentos: r.contacto.max_intentos, dias_entre_intentos: r.contacto.dias_entre_intentos, hora_desde: r.contacto.hora_desde, hora_hasta: r.contacto.hora_hasta },
|
|
41509
|
+
cierre: { dias_para_cerrar: r.cierre.dias_para_cerrar }
|
|
41510
|
+
};
|
|
41511
|
+
}
|
|
41512
|
+
var COLS = "id, company_id, workflow, nombre, tipo, parametros, contacto, cierre, estado, version, historial, ultima_prueba, actor_tipo, actor_id, created_at, updated_at";
|
|
41513
|
+
function mapRegla(r) {
|
|
41514
|
+
const contacto = jsonb(r.contacto);
|
|
41515
|
+
const cierre = jsonb(r.cierre);
|
|
41516
|
+
const prueba = r.ultima_prueba ? jsonb(r.ultima_prueba) : null;
|
|
41517
|
+
return {
|
|
41518
|
+
id: String(r.id),
|
|
41519
|
+
companyId: String(r.company_id),
|
|
41520
|
+
workflow: String(r.workflow),
|
|
41521
|
+
nombre: String(r.nombre),
|
|
41522
|
+
tipo: String(r.tipo),
|
|
41523
|
+
parametros: jsonb(r.parametros),
|
|
41524
|
+
contacto: {
|
|
41525
|
+
canal: String(contacto.canal ?? "whatsapp"),
|
|
41526
|
+
plantilla: String(contacto.plantilla ?? ""),
|
|
41527
|
+
max_intentos: Number(contacto.max_intentos ?? LIMITES_REGLA.max_intentos[2]),
|
|
41528
|
+
dias_entre_intentos: Number(contacto.dias_entre_intentos ?? LIMITES_REGLA.dias_entre_intentos[2]),
|
|
41529
|
+
hora_desde: String(contacto.hora_desde ?? "09:00"),
|
|
41530
|
+
hora_hasta: String(contacto.hora_hasta ?? "19:00")
|
|
41531
|
+
},
|
|
41532
|
+
cierre: { dias_para_cerrar: Number(cierre.dias_para_cerrar ?? LIMITES_REGLA.dias_para_cerrar[2]) },
|
|
41533
|
+
estado: ESTADOS_REGLA.includes(String(r.estado)) ? String(r.estado) : "borrador",
|
|
41534
|
+
version: Number(r.version ?? 1),
|
|
41535
|
+
historial: jsonArr(r.historial).map((h) => {
|
|
41536
|
+
const x = jsonb(h);
|
|
41537
|
+
return { version: Number(x.version ?? 0), fecha: String(x.fecha ?? ""), actorTipo: String(x.actorTipo ?? ""), actorId: x.actorId ?? null, cambio: String(x.cambio ?? "") };
|
|
41538
|
+
}),
|
|
41539
|
+
ultimaPrueba: prueba && (prueba.estado === "pedida" || prueba.estado === "lista") ? {
|
|
41540
|
+
estado: prueba.estado,
|
|
41541
|
+
version: Number(prueba.version ?? 0),
|
|
41542
|
+
fecha: String(prueba.fecha ?? ""),
|
|
41543
|
+
...prueba.alcanza !== void 0 ? { alcanza: Number(prueba.alcanza) } : {},
|
|
41544
|
+
...Array.isArray(prueba.muestra) ? { muestra: prueba.muestra.map((m) => String(m)) } : {},
|
|
41545
|
+
...prueba.consulta !== void 0 ? { consulta: prueba.consulta ?? null } : {},
|
|
41546
|
+
...prueba.por !== void 0 ? { por: prueba.por ?? null } : {}
|
|
41547
|
+
} : null,
|
|
41548
|
+
actorTipo: String(r.actor_tipo),
|
|
41549
|
+
actorId: r.actor_id ?? null,
|
|
41550
|
+
createdAt: iso(r.created_at),
|
|
41551
|
+
updatedAt: iso(r.updated_at)
|
|
41552
|
+
};
|
|
41553
|
+
}
|
|
41554
|
+
async function listarReglas(ctx, companyId, filtro = {}) {
|
|
41555
|
+
const params = [companyId];
|
|
41556
|
+
let where = "company_id = $1";
|
|
41557
|
+
if (filtro.workflow) {
|
|
41558
|
+
params.push(filtro.workflow);
|
|
41559
|
+
where += ` AND workflow = $${params.length}`;
|
|
41560
|
+
}
|
|
41561
|
+
if (filtro.estado) {
|
|
41562
|
+
params.push(filtro.estado);
|
|
41563
|
+
where += ` AND estado = $${params.length}`;
|
|
41564
|
+
}
|
|
41565
|
+
const rows = await ctx.db.query(`SELECT ${COLS} FROM ${t(ctx, "regla")} WHERE ${where} ORDER BY created_at ASC`, params);
|
|
41566
|
+
return rows.map(mapRegla);
|
|
41567
|
+
}
|
|
41568
|
+
async function leerRegla(ctx, companyId, id) {
|
|
41569
|
+
const rows = await ctx.db.query(`SELECT ${COLS} FROM ${t(ctx, "regla")} WHERE company_id = $1 AND id = $2 LIMIT 1`, [companyId, id]);
|
|
41570
|
+
return rows[0] ? mapRegla(rows[0]) : null;
|
|
41571
|
+
}
|
|
41572
|
+
async function requerirRegla(ctx, companyId, id) {
|
|
41573
|
+
const r = await leerRegla(ctx, companyId, id);
|
|
41574
|
+
if (!r) throw new OpsError("regla_inexistente", `no existe la regla ${id} en esta instituci\xF3n`);
|
|
41575
|
+
return r;
|
|
41576
|
+
}
|
|
41577
|
+
function cambio(version2, actor, texto3) {
|
|
41578
|
+
return { version: version2, fecha: (/* @__PURE__ */ new Date()).toISOString(), actorTipo: actor.tipo, actorId: actor.id, cambio: texto3 };
|
|
41579
|
+
}
|
|
41580
|
+
function soloPersonas(actor, que) {
|
|
41581
|
+
if (actor.tipo !== "humano") throw new OpsError("actor_no_permitido", `${que} lo hace una persona del hospital desde el portal; el agente solo lee las reglas e informa la prueba`);
|
|
41582
|
+
}
|
|
41583
|
+
async function crearRegla(ctx, input2) {
|
|
41584
|
+
soloPersonas(input2.actor, "crear una regla");
|
|
41585
|
+
if (input2.workflow !== "demanda_pendiente") throw new OpsError("workflow_sin_reglas", `el puesto ${input2.workflow} no tiene reglas configurables`);
|
|
41586
|
+
const v = validarRegla(input2);
|
|
41587
|
+
const id = randomUUID();
|
|
41588
|
+
const historial = [cambio(1, input2.actor, "creada")];
|
|
41589
|
+
await ctx.db.execute(
|
|
41590
|
+
`INSERT INTO ${t(ctx, "regla")} (id, company_id, workflow, nombre, tipo, parametros, contacto, cierre, estado, version, historial, actor_tipo, actor_id) VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7::jsonb, $8::jsonb, $9, $10, $11::jsonb, $12, $13)`,
|
|
41591
|
+
[id, input2.companyId, input2.workflow, v.nombre, v.tipo, JSON.stringify(v.parametros), JSON.stringify(v.contacto), JSON.stringify(v.cierre), "borrador", 1, JSON.stringify(historial), input2.actor.tipo, input2.actor.id]
|
|
41592
|
+
);
|
|
41593
|
+
await ctx.activity.log({ companyId: input2.companyId, message: `Autopilot Care: regla \xAB${v.nombre}\xBB creada (${v.tipo}, borrador)`, metadata: { reglaId: id, workflow: input2.workflow } });
|
|
41594
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
41595
|
+
return { id, companyId: input2.companyId, workflow: input2.workflow, ...v, estado: "borrador", version: 1, historial, ultimaPrueba: null, actorTipo: input2.actor.tipo, actorId: input2.actor.id, createdAt: now, updatedAt: now };
|
|
41596
|
+
}
|
|
41597
|
+
async function editarRegla(ctx, input2) {
|
|
41598
|
+
soloPersonas(input2.actor, "editar una regla");
|
|
41599
|
+
const actual = await requerirRegla(ctx, input2.companyId, input2.id);
|
|
41600
|
+
const v = validarRegla({
|
|
41601
|
+
nombre: input2.nombre ?? actual.nombre,
|
|
41602
|
+
tipo: input2.tipo ?? actual.tipo,
|
|
41603
|
+
parametros: input2.parametros ?? actual.parametros,
|
|
41604
|
+
contacto: input2.contacto ?? actual.contacto,
|
|
41605
|
+
cierre: input2.cierre ?? actual.cierre
|
|
41606
|
+
});
|
|
41607
|
+
const version2 = actual.version + 1;
|
|
41608
|
+
const estado = actual.estado === "activa" ? "pausada" : actual.estado;
|
|
41609
|
+
const detalle = `editada${input2.motivo ? `: ${str(input2.motivo)}` : ""}${actual.estado === "activa" ? " \xB7 pausada hasta probar y activar la versi\xF3n nueva" : ""}`;
|
|
41610
|
+
const historial = [...actual.historial, cambio(version2, input2.actor, detalle)];
|
|
41611
|
+
await ctx.db.execute(
|
|
41612
|
+
`UPDATE ${t(ctx, "regla")} SET nombre = $3, tipo = $4, parametros = $5::jsonb, contacto = $6::jsonb, cierre = $7::jsonb, estado = $8, version = $9, historial = $10::jsonb, actor_tipo = $11, actor_id = $12, updated_at = now() WHERE company_id = $1 AND id = $2`,
|
|
41613
|
+
[input2.companyId, input2.id, v.nombre, v.tipo, JSON.stringify(v.parametros), JSON.stringify(v.contacto), JSON.stringify(v.cierre), estado, version2, JSON.stringify(historial), input2.actor.tipo, input2.actor.id]
|
|
41614
|
+
);
|
|
41615
|
+
await ctx.activity.log({ companyId: input2.companyId, message: `Autopilot Care: regla \xAB${v.nombre}\xBB editada (versi\xF3n ${version2}, ${estado})`, metadata: { reglaId: input2.id } });
|
|
41616
|
+
return { ...actual, ...v, estado, version: version2, historial, actorTipo: input2.actor.tipo, actorId: input2.actor.id, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
41617
|
+
}
|
|
41618
|
+
async function cambiarEstadoRegla(ctx, input2) {
|
|
41619
|
+
soloPersonas(input2.actor, "activar o pausar una regla");
|
|
41620
|
+
const actual = await requerirRegla(ctx, input2.companyId, input2.id);
|
|
41621
|
+
const estado = str(input2.estado);
|
|
41622
|
+
if (estado !== "activa" && estado !== "pausada") throw new OpsError("estado_invalido", "estado debe ser activa o pausada");
|
|
41623
|
+
if (estado === actual.estado) return actual;
|
|
41624
|
+
if (estado === "activa") {
|
|
41625
|
+
const prueba = actual.ultimaPrueba;
|
|
41626
|
+
if (!prueba || prueba.estado !== "lista" || prueba.version !== actual.version) {
|
|
41627
|
+
throw new OpsError("prueba_requerida", `la versi\xF3n ${actual.version} de \xAB${actual.nombre}\xBB no se prob\xF3 todav\xEDa: pedir la prueba y activar cuando llegue el resultado`);
|
|
41628
|
+
}
|
|
41629
|
+
}
|
|
41630
|
+
if (estado === "pausada" && actual.estado !== "activa") throw new OpsError("estado_invalido", `una regla en ${actual.estado} no se pausa`);
|
|
41631
|
+
const historial = [...actual.historial, cambio(actual.version, input2.actor, `${estado}${input2.motivo ? `: ${str(input2.motivo)}` : ""}`)];
|
|
41632
|
+
await ctx.db.execute(
|
|
41633
|
+
`UPDATE ${t(ctx, "regla")} SET estado = $3, historial = $4::jsonb, actor_tipo = $5, actor_id = $6, updated_at = now() WHERE company_id = $1 AND id = $2`,
|
|
41634
|
+
[input2.companyId, input2.id, estado, JSON.stringify(historial), input2.actor.tipo, input2.actor.id]
|
|
41635
|
+
);
|
|
41636
|
+
await ctx.activity.log({ companyId: input2.companyId, message: `Autopilot Care: regla \xAB${actual.nombre}\xBB ${estado} (versi\xF3n ${actual.version})`, metadata: { reglaId: input2.id } });
|
|
41637
|
+
return { ...actual, estado, historial, actorTipo: input2.actor.tipo, actorId: input2.actor.id, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
41638
|
+
}
|
|
41639
|
+
async function pedirPruebaRegla(ctx, input2) {
|
|
41640
|
+
soloPersonas(input2.actor, "pedir la prueba de una regla");
|
|
41641
|
+
const actual = await requerirRegla(ctx, input2.companyId, input2.id);
|
|
41642
|
+
const prueba = { estado: "pedida", version: actual.version, fecha: (/* @__PURE__ */ new Date()).toISOString() };
|
|
41643
|
+
await ctx.db.execute(`UPDATE ${t(ctx, "regla")} SET ultima_prueba = $3::jsonb, updated_at = now() WHERE company_id = $1 AND id = $2`, [input2.companyId, input2.id, JSON.stringify(prueba)]);
|
|
41644
|
+
return { ...actual, ultimaPrueba: prueba, updatedAt: prueba.fecha };
|
|
41645
|
+
}
|
|
41646
|
+
var REF_MUESTRA = /^(orden|solicitud|control):[A-Za-z0-9:_-]{1,80}$/;
|
|
41647
|
+
async function informarPruebaRegla(ctx, input2) {
|
|
41648
|
+
const actual = await requerirRegla(ctx, input2.companyId, input2.id);
|
|
41649
|
+
const alcanza = entero(input2.alcanza);
|
|
41650
|
+
if (alcanza === null || alcanza < 0) throw new OpsError("validacion", "alcanza: entero mayor o igual a 0 (pacientes que la regla alcanza hoy)");
|
|
41651
|
+
const crudos = Array.isArray(input2.muestra) ? input2.muestra : [];
|
|
41652
|
+
if (crudos.length > 10) throw new OpsError("validacion", "muestra: como m\xE1ximo 10 referencias");
|
|
41653
|
+
const muestra = crudos.map((m) => String(m).trim());
|
|
41654
|
+
const mala = muestra.find((m) => !REF_MUESTRA.test(m));
|
|
41655
|
+
if (mala) throw new OpsError("validacion", `muestra: solo referencias orden:<id>, solicitud:<id> o control:<persona>:<regla>; \xAB${mala.slice(0, 40)}\xBB no lo es (sin datos personales)`);
|
|
41656
|
+
const prueba = {
|
|
41657
|
+
estado: "lista",
|
|
41658
|
+
version: actual.version,
|
|
41659
|
+
fecha: (/* @__PURE__ */ new Date()).toISOString(),
|
|
41660
|
+
alcanza,
|
|
41661
|
+
muestra,
|
|
41662
|
+
consulta: str(input2.consulta)?.slice(0, 4e3) ?? null,
|
|
41663
|
+
por: input2.actor.id
|
|
41664
|
+
};
|
|
41665
|
+
await ctx.db.execute(`UPDATE ${t(ctx, "regla")} SET ultima_prueba = $3::jsonb, updated_at = now() WHERE company_id = $1 AND id = $2`, [input2.companyId, input2.id, JSON.stringify(prueba)]);
|
|
41666
|
+
await ctx.activity.log({ companyId: input2.companyId, message: `Autopilot Care: prueba de la regla \xAB${actual.nombre}\xBB (versi\xF3n ${actual.version}): alcanza a ${alcanza}`, metadata: { reglaId: input2.id, alcanza } });
|
|
41667
|
+
return { ...actual, ultimaPrueba: prueba, updatedAt: prueba.fecha };
|
|
41668
|
+
}
|
|
41669
|
+
|
|
41129
41670
|
// src/ops.ts
|
|
41130
41671
|
var OpsError = class extends Error {
|
|
41131
41672
|
constructor(codigo, motivo, detalle) {
|
|
@@ -41140,18 +41681,18 @@ var OpsError = class extends Error {
|
|
|
41140
41681
|
/** Mensaje sin el prefijo de código. */
|
|
41141
41682
|
motivo;
|
|
41142
41683
|
};
|
|
41143
|
-
function
|
|
41684
|
+
function t2(ctx, table) {
|
|
41144
41685
|
return `${ctx.db.namespace}.${table}`;
|
|
41145
41686
|
}
|
|
41146
|
-
function
|
|
41687
|
+
function str2(v) {
|
|
41147
41688
|
return typeof v === "string" && v.trim().length > 0 ? v.trim() : null;
|
|
41148
41689
|
}
|
|
41149
|
-
function
|
|
41690
|
+
function iso2(v) {
|
|
41150
41691
|
if (v instanceof Date) return v.toISOString();
|
|
41151
41692
|
if (typeof v === "string") return v;
|
|
41152
41693
|
return "";
|
|
41153
41694
|
}
|
|
41154
|
-
function
|
|
41695
|
+
function jsonb2(v) {
|
|
41155
41696
|
if (v && typeof v === "object" && !Array.isArray(v)) return v;
|
|
41156
41697
|
if (typeof v === "string") {
|
|
41157
41698
|
try {
|
|
@@ -41163,7 +41704,7 @@ function jsonb(v) {
|
|
|
41163
41704
|
}
|
|
41164
41705
|
return {};
|
|
41165
41706
|
}
|
|
41166
|
-
function
|
|
41707
|
+
function jsonArr2(v) {
|
|
41167
41708
|
if (Array.isArray(v)) return v;
|
|
41168
41709
|
if (typeof v === "string") {
|
|
41169
41710
|
try {
|
|
@@ -41183,15 +41724,15 @@ function mapCaso(r) {
|
|
|
41183
41724
|
estado: String(r.estado),
|
|
41184
41725
|
referencia: r.referencia ?? null,
|
|
41185
41726
|
titulo: String(r.titulo),
|
|
41186
|
-
datos:
|
|
41727
|
+
datos: jsonb2(r.datos),
|
|
41187
41728
|
abiertoPorTipo: String(r.abierto_por_tipo),
|
|
41188
41729
|
abiertoPorId: r.abierto_por_id ?? null,
|
|
41189
41730
|
agenteId: r.agente_id ?? null,
|
|
41190
41731
|
issueId: r.issue_id ?? null,
|
|
41191
41732
|
minutosAbsorbidos: Number(r.minutos_absorbidos ?? 0),
|
|
41192
|
-
createdAt:
|
|
41193
|
-
updatedAt:
|
|
41194
|
-
cerradoAt: r.cerrado_at ?
|
|
41733
|
+
createdAt: iso2(r.created_at),
|
|
41734
|
+
updatedAt: iso2(r.updated_at),
|
|
41735
|
+
cerradoAt: r.cerrado_at ? iso2(r.cerrado_at) : null
|
|
41195
41736
|
};
|
|
41196
41737
|
}
|
|
41197
41738
|
function mapTransicion(r) {
|
|
@@ -41204,9 +41745,9 @@ function mapTransicion(r) {
|
|
|
41204
41745
|
actorTipo: String(r.actor_tipo),
|
|
41205
41746
|
actorId: r.actor_id ?? null,
|
|
41206
41747
|
motivo: r.motivo ?? null,
|
|
41207
|
-
datos:
|
|
41208
|
-
validaciones:
|
|
41209
|
-
createdAt:
|
|
41748
|
+
datos: jsonb2(r.datos),
|
|
41749
|
+
validaciones: jsonArr2(r.validaciones),
|
|
41750
|
+
createdAt: iso2(r.created_at)
|
|
41210
41751
|
};
|
|
41211
41752
|
}
|
|
41212
41753
|
function mapAccion(r) {
|
|
@@ -41217,17 +41758,17 @@ function mapAccion(r) {
|
|
|
41217
41758
|
conector: String(r.conector),
|
|
41218
41759
|
objetivo: String(r.objetivo),
|
|
41219
41760
|
resultado: String(r.resultado),
|
|
41220
|
-
evidencia:
|
|
41761
|
+
evidencia: jsonb2(r.evidencia),
|
|
41221
41762
|
detalle: r.detalle ?? null,
|
|
41222
41763
|
actorTipo: String(r.actor_tipo),
|
|
41223
41764
|
actorId: r.actor_id ?? null,
|
|
41224
|
-
createdAt:
|
|
41765
|
+
createdAt: iso2(r.created_at)
|
|
41225
41766
|
};
|
|
41226
41767
|
}
|
|
41227
41768
|
var CASO_COLS = "id, company_id, workflow, estado, referencia, titulo, datos, abierto_por_tipo, abierto_por_id, agente_id, issue_id, minutos_absorbidos, created_at, updated_at, cerrado_at";
|
|
41228
41769
|
async function obtenerModo(ctx, companyId, workflow) {
|
|
41229
41770
|
const rows = await ctx.db.query(
|
|
41230
|
-
`SELECT modo FROM ${
|
|
41771
|
+
`SELECT modo FROM ${t2(ctx, "politica")} WHERE company_id = $1 AND workflow = $2 LIMIT 1`,
|
|
41231
41772
|
[companyId, workflow]
|
|
41232
41773
|
);
|
|
41233
41774
|
const modo = rows[0]?.modo;
|
|
@@ -41235,14 +41776,14 @@ async function obtenerModo(ctx, companyId, workflow) {
|
|
|
41235
41776
|
}
|
|
41236
41777
|
async function obtenerOpciones(ctx, companyId, workflow) {
|
|
41237
41778
|
const rows = await ctx.db.query(
|
|
41238
|
-
`SELECT opciones FROM ${
|
|
41779
|
+
`SELECT opciones FROM ${t2(ctx, "politica")} WHERE company_id = $1 AND workflow = $2 LIMIT 1`,
|
|
41239
41780
|
[companyId, workflow]
|
|
41240
41781
|
);
|
|
41241
|
-
return
|
|
41782
|
+
return jsonb2(rows[0]?.opciones);
|
|
41242
41783
|
}
|
|
41243
41784
|
async function listarPoliticas(ctx, companyId) {
|
|
41244
41785
|
const rows = await ctx.db.query(
|
|
41245
|
-
`SELECT workflow, modo, motivo, opciones, updated_at FROM ${
|
|
41786
|
+
`SELECT workflow, modo, motivo, opciones, updated_at FROM ${t2(ctx, "politica")} WHERE company_id = $1`,
|
|
41246
41787
|
[companyId]
|
|
41247
41788
|
);
|
|
41248
41789
|
const porWorkflow = new Map(rows.map((r) => [String(r.workflow), r]));
|
|
@@ -41254,9 +41795,9 @@ async function listarPoliticas(ctx, companyId) {
|
|
|
41254
41795
|
puesto: wf.puesto,
|
|
41255
41796
|
modo: r ? String(r.modo) : MODO_POR_DEFECTO,
|
|
41256
41797
|
motivo: r ? r.motivo ?? null : null,
|
|
41257
|
-
actualizadaEn: r ?
|
|
41798
|
+
actualizadaEn: r ? iso2(r.updated_at) : null,
|
|
41258
41799
|
porDefecto: !r,
|
|
41259
|
-
opciones: r ?
|
|
41800
|
+
opciones: r ? jsonb2(r.opciones) : {}
|
|
41260
41801
|
};
|
|
41261
41802
|
});
|
|
41262
41803
|
}
|
|
@@ -41290,23 +41831,23 @@ async function cambiarPolitica(ctx, input2) {
|
|
|
41290
41831
|
const cambioOpciones = describirOpciones(opcionesAntes, opciones);
|
|
41291
41832
|
const motivo = [input2.motivo ?? null, cambioOpciones].filter(Boolean).join(" \xB7 ") || null;
|
|
41292
41833
|
const existe = await ctx.db.query(
|
|
41293
|
-
`SELECT id FROM ${
|
|
41834
|
+
`SELECT id FROM ${t2(ctx, "politica")} WHERE company_id = $1 AND workflow = $2 LIMIT 1`,
|
|
41294
41835
|
[input2.companyId, input2.workflow]
|
|
41295
41836
|
);
|
|
41296
41837
|
if (existe.length > 0) {
|
|
41297
41838
|
await ctx.db.execute(
|
|
41298
|
-
`UPDATE ${
|
|
41839
|
+
`UPDATE ${t2(ctx, "politica")} SET modo = $3, motivo = $4, actor_tipo = $5, actor_id = $6, opciones = $7::jsonb, updated_at = now() WHERE company_id = $1 AND workflow = $2`,
|
|
41299
41840
|
[input2.companyId, input2.workflow, input2.modo, motivo, input2.actor.tipo, input2.actor.id, JSON.stringify(opciones)]
|
|
41300
41841
|
);
|
|
41301
41842
|
} else {
|
|
41302
41843
|
await ctx.db.execute(
|
|
41303
|
-
`INSERT INTO ${
|
|
41304
|
-
[
|
|
41844
|
+
`INSERT INTO ${t2(ctx, "politica")} (id, company_id, workflow, modo, motivo, actor_tipo, actor_id, opciones) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb)`,
|
|
41845
|
+
[randomUUID2(), input2.companyId, input2.workflow, input2.modo, motivo, input2.actor.tipo, input2.actor.id, JSON.stringify(opciones)]
|
|
41305
41846
|
);
|
|
41306
41847
|
}
|
|
41307
41848
|
await ctx.db.execute(
|
|
41308
|
-
`INSERT INTO ${
|
|
41309
|
-
[
|
|
41849
|
+
`INSERT INTO ${t2(ctx, "politica_historial")} (id, company_id, workflow, modo_anterior, modo_nuevo, motivo, actor_tipo, actor_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
|
41850
|
+
[randomUUID2(), input2.companyId, input2.workflow, anterior, input2.modo, motivo, input2.actor.tipo, input2.actor.id]
|
|
41310
41851
|
);
|
|
41311
41852
|
await ctx.activity.log({
|
|
41312
41853
|
companyId: input2.companyId,
|
|
@@ -41318,7 +41859,7 @@ async function cambiarPolitica(ctx, input2) {
|
|
|
41318
41859
|
}
|
|
41319
41860
|
async function leerCaso(ctx, companyId, casoId) {
|
|
41320
41861
|
const rows = await ctx.db.query(
|
|
41321
|
-
`SELECT ${CASO_COLS} FROM ${
|
|
41862
|
+
`SELECT ${CASO_COLS} FROM ${t2(ctx, "caso")} WHERE company_id = $1 AND id = $2 LIMIT 1`,
|
|
41322
41863
|
[companyId, casoId]
|
|
41323
41864
|
);
|
|
41324
41865
|
return rows[0] ? mapCaso(rows[0]) : null;
|
|
@@ -41328,11 +41869,11 @@ async function leerCasoCompleto(ctx, companyId, casoId) {
|
|
|
41328
41869
|
if (!caso) return null;
|
|
41329
41870
|
const [transiciones, acciones] = await Promise.all([
|
|
41330
41871
|
ctx.db.query(
|
|
41331
|
-
`SELECT id, caso_id, de_estado, a_estado, via, actor_tipo, actor_id, motivo, datos, validaciones, created_at FROM ${
|
|
41872
|
+
`SELECT id, caso_id, de_estado, a_estado, via, actor_tipo, actor_id, motivo, datos, validaciones, created_at FROM ${t2(ctx, "transicion")} WHERE company_id = $1 AND caso_id = $2 ORDER BY created_at ASC`,
|
|
41332
41873
|
[companyId, casoId]
|
|
41333
41874
|
),
|
|
41334
41875
|
ctx.db.query(
|
|
41335
|
-
`SELECT id, caso_id, tipo, conector, objetivo, resultado, evidencia, detalle, actor_tipo, actor_id, created_at FROM ${
|
|
41876
|
+
`SELECT id, caso_id, tipo, conector, objetivo, resultado, evidencia, detalle, actor_tipo, actor_id, created_at FROM ${t2(ctx, "accion")} WHERE company_id = $1 AND caso_id = $2 ORDER BY created_at ASC`,
|
|
41336
41877
|
[companyId, casoId]
|
|
41337
41878
|
)
|
|
41338
41879
|
]);
|
|
@@ -41363,7 +41904,7 @@ async function listarCasos(ctx, input2) {
|
|
|
41363
41904
|
const limit = Math.min(Math.max(Number(input2.limit ?? 50) || 50, 1), 100);
|
|
41364
41905
|
params.push(limit);
|
|
41365
41906
|
const rows = await ctx.db.query(
|
|
41366
|
-
`SELECT ${CASO_COLS} FROM ${
|
|
41907
|
+
`SELECT ${CASO_COLS} FROM ${t2(ctx, "caso")} WHERE ${where.join(" AND ")} ORDER BY updated_at DESC LIMIT $${params.length}`,
|
|
41367
41908
|
params
|
|
41368
41909
|
);
|
|
41369
41910
|
return rows.map(mapCaso);
|
|
@@ -41371,21 +41912,21 @@ async function listarCasos(ctx, input2) {
|
|
|
41371
41912
|
async function abrirCaso(ctx, input2) {
|
|
41372
41913
|
const wf = getWorkflow(input2.workflow);
|
|
41373
41914
|
if (!wf) throw new OpsError("workflow_desconocido", `workflow desconocido: ${input2.workflow}. Disponibles: ${listarWorkflows().map((w) => w.clave).join(", ")}`);
|
|
41374
|
-
const titulo =
|
|
41915
|
+
const titulo = str2(input2.titulo);
|
|
41375
41916
|
if (!titulo) throw new OpsError("titulo_requerido", "titulo es obligatorio");
|
|
41376
41917
|
if (input2.actor.tipo === "agente") {
|
|
41377
41918
|
const modo = await obtenerModo(ctx, input2.companyId, input2.workflow);
|
|
41378
41919
|
if (modo === "corte") throw new OpsError("politica", `el workflow ${input2.workflow} est\xE1 cortado para agentes en esta instituci\xF3n`);
|
|
41379
41920
|
}
|
|
41380
|
-
const idem =
|
|
41921
|
+
const idem = str2(input2.idempotencyKey);
|
|
41381
41922
|
if (idem) {
|
|
41382
41923
|
const prev = await ctx.db.query(
|
|
41383
|
-
`SELECT ${CASO_COLS} FROM ${
|
|
41924
|
+
`SELECT ${CASO_COLS} FROM ${t2(ctx, "caso")} WHERE company_id = $1 AND idempotency_key = $2 LIMIT 1`,
|
|
41384
41925
|
[input2.companyId, idem]
|
|
41385
41926
|
);
|
|
41386
41927
|
if (prev[0]) return { caso: mapCaso(prev[0]), creado: false };
|
|
41387
41928
|
}
|
|
41388
|
-
const referencia =
|
|
41929
|
+
const referencia = str2(input2.referencia);
|
|
41389
41930
|
if (wf.clave === "cuenta_completa" || wf.clave === "facturacion_pendiente") {
|
|
41390
41931
|
const bono = refExterna(input2.datos?.bono_id);
|
|
41391
41932
|
if (!referencia || !/^bono:\S+$/.test(referencia)) throw new OpsError("referencia_requerida", `en ${wf.clave} la referencia es obligatoria: bono:<n\xFAmero de bono>`);
|
|
@@ -41396,17 +41937,36 @@ async function abrirCaso(ctx, input2) {
|
|
|
41396
41937
|
if (!referencia || !/^profesional:\S+$/.test(referencia)) throw new OpsError("referencia_requerida", "en credencial_profesional la referencia es obligatoria: profesional:<id del profesional>");
|
|
41397
41938
|
if (prof && referencia !== `profesional:${prof}`) throw new OpsError("referencia_requerida", `la referencia ${referencia} no coincide con el profesional_id ${prof}`);
|
|
41398
41939
|
}
|
|
41940
|
+
const extra = {};
|
|
41941
|
+
if (wf.clave === "demanda_pendiente") {
|
|
41942
|
+
if (!referencia || !/^(orden|solicitud|control):\S+$/.test(referencia)) {
|
|
41943
|
+
throw new OpsError("referencia_requerida", "en demanda_pendiente la referencia es obligatoria: orden:<id>, solicitud:<id> o control:<persona_id>:<regla_id>");
|
|
41944
|
+
}
|
|
41945
|
+
const reglaId = str2(input2.datos?.regla_id);
|
|
41946
|
+
if (!reglaId) throw new OpsError("regla_requerida", "en demanda_pendiente datos.regla_id es obligatorio: la regla activa que detect\xF3 el caso");
|
|
41947
|
+
const regla = await leerRegla(ctx, input2.companyId, reglaId);
|
|
41948
|
+
if (!regla || regla.workflow !== wf.clave) throw new OpsError("regla_inexistente", `no existe la regla ${reglaId} en esta instituci\xF3n`);
|
|
41949
|
+
if (regla.estado !== "activa") throw new OpsError("regla_inactiva", `la regla \xAB${regla.nombre}\xBB est\xE1 ${regla.estado}: no abre casos hasta que se active`);
|
|
41950
|
+
const { hoy } = ahoraEnHospital();
|
|
41951
|
+
extra.regla_nombre = regla.nombre;
|
|
41952
|
+
extra.regla_version = regla.version;
|
|
41953
|
+
extra.tipo = regla.tipo;
|
|
41954
|
+
extra.canal = regla.contacto.canal;
|
|
41955
|
+
extra.intentos = 0;
|
|
41956
|
+
extra.vence_el = sumarDias(hoy, regla.cierre.dias_para_cerrar);
|
|
41957
|
+
extra.detectado_el = hoy;
|
|
41958
|
+
}
|
|
41399
41959
|
if (referencia) {
|
|
41400
41960
|
const abiertos = await ctx.db.query(
|
|
41401
|
-
`SELECT ${CASO_COLS} FROM ${
|
|
41961
|
+
`SELECT ${CASO_COLS} FROM ${t2(ctx, "caso")} WHERE company_id = $1 AND workflow = $2 AND referencia = $3 AND cerrado_at IS NULL ORDER BY created_at DESC LIMIT 1`,
|
|
41402
41962
|
[input2.companyId, input2.workflow, referencia]
|
|
41403
41963
|
);
|
|
41404
41964
|
if (abiertos[0]) return { caso: mapCaso(abiertos[0]), creado: false };
|
|
41405
41965
|
}
|
|
41406
|
-
const id =
|
|
41407
|
-
const datos = input2.datos ?? {};
|
|
41966
|
+
const id = randomUUID2();
|
|
41967
|
+
const datos = { ...input2.datos ?? {}, ...extra };
|
|
41408
41968
|
await ctx.db.execute(
|
|
41409
|
-
`INSERT INTO ${
|
|
41969
|
+
`INSERT INTO ${t2(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)`,
|
|
41410
41970
|
[id, input2.companyId, wf.clave, wf.inicial, referencia, titulo, JSON.stringify(datos), input2.actor.tipo, input2.actor.id, input2.agenteId ?? null, input2.issueId ?? null, idem]
|
|
41411
41971
|
);
|
|
41412
41972
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -41445,11 +42005,24 @@ async function contextoDe(ctx, companyId, caso, via, datos) {
|
|
|
41445
42005
|
contexto.plazosPresentacion = leerPlazos((await obtenerOpciones(ctx, companyId, caso.workflow)).plazos);
|
|
41446
42006
|
return contexto;
|
|
41447
42007
|
}
|
|
42008
|
+
if (caso.workflow === "demanda_pendiente") {
|
|
42009
|
+
const reglaId = str2(caso.datos.regla_id);
|
|
42010
|
+
const regla = reglaId ? await leerRegla(ctx, companyId, reglaId) : null;
|
|
42011
|
+
contexto.regla = regla ? reglaParaContexto(regla) : null;
|
|
42012
|
+
const contactos = (await ctx.db.query(
|
|
42013
|
+
`SELECT caso_id, via, created_at FROM ${t2(ctx, "transicion")} WHERE company_id = $1 AND caso_id = $2 AND via = $3 ORDER BY created_at DESC LIMIT 50`,
|
|
42014
|
+
[companyId, caso.id, "contactar"]
|
|
42015
|
+
)).filter((r) => String(r.caso_id) === caso.id && String(r.via) === "contactar");
|
|
42016
|
+
contexto.intentosContacto = contactos.length;
|
|
42017
|
+
contexto.ultimoContacto = contactos[0]?.created_at ? ahoraEnHospital(new Date(iso2(contactos[0].created_at))).hoy : null;
|
|
42018
|
+
contexto.diasAbierto = caso.createdAt ? Math.max(0, diasEntre(ahoraEnHospital(new Date(caso.createdAt)).hoy, hoy) ?? 0) : 0;
|
|
42019
|
+
return contexto;
|
|
42020
|
+
}
|
|
41448
42021
|
if (caso.workflow !== "recupero_turno") return contexto;
|
|
41449
42022
|
contexto.ofrecerPrimero = (await obtenerOpciones(ctx, companyId, caso.workflow)).ofrecer_primero === true;
|
|
41450
42023
|
if (via !== "proponer") return contexto;
|
|
41451
42024
|
const otros = await ctx.db.query(
|
|
41452
|
-
`SELECT id, datos->>'solicitud_id' AS solicitud_id FROM ${
|
|
42025
|
+
`SELECT id, datos->>'solicitud_id' AS solicitud_id FROM ${t2(ctx, "caso")} WHERE company_id = $1 AND workflow = $2 AND id <> $3 AND datos->>'solicitud_id' IS NOT NULL AND estado NOT IN ($4, $5, $6) LIMIT 1000`,
|
|
41453
42026
|
[companyId, "recupero_turno", caso.id, "sin_candidato", "rechazado", "cancelado"]
|
|
41454
42027
|
);
|
|
41455
42028
|
contexto.solicitudesEnCurso = [...new Set(otros.map((r) => String(r.solicitud_id)))];
|
|
@@ -41457,7 +42030,7 @@ async function contextoDe(ctx, companyId, caso, via, datos) {
|
|
|
41457
42030
|
const mismo = solicitud ? otros.find((r) => String(r.solicitud_id) === solicitud) : void 0;
|
|
41458
42031
|
contexto.solicitudEnOtroCaso = mismo ? String(mismo.id) : null;
|
|
41459
42032
|
const asignados = await ctx.db.query(
|
|
41460
|
-
`SELECT count(*)::int AS cantidad FROM ${
|
|
42033
|
+
`SELECT count(*)::int AS cantidad FROM ${t2(ctx, "transicion")} WHERE company_id = $1 AND via = $2 AND created_at >= $3`,
|
|
41461
42034
|
[companyId, "asignar", inicioDelDia.toISOString()]
|
|
41462
42035
|
);
|
|
41463
42036
|
contexto.asignacionesHoy = Number(asignados[0]?.cantidad ?? 0);
|
|
@@ -41466,10 +42039,10 @@ async function contextoDe(ctx, companyId, caso, via, datos) {
|
|
|
41466
42039
|
async function transicionar(ctx, input2) {
|
|
41467
42040
|
const caso = await leerCaso(ctx, input2.companyId, input2.casoId);
|
|
41468
42041
|
if (!caso) throw new OpsError("caso_inexistente", `no existe el caso ${input2.casoId} en esta instituci\xF3n`);
|
|
41469
|
-
const idem =
|
|
42042
|
+
const idem = str2(input2.idempotencyKey);
|
|
41470
42043
|
if (idem) {
|
|
41471
42044
|
const prev = await ctx.db.query(
|
|
41472
|
-
`SELECT id, caso_id, de_estado, a_estado, via, actor_tipo, actor_id, motivo, datos, validaciones, created_at FROM ${
|
|
42045
|
+
`SELECT id, caso_id, de_estado, a_estado, via, actor_tipo, actor_id, motivo, datos, validaciones, created_at FROM ${t2(ctx, "transicion")} WHERE company_id = $1 AND idempotency_key = $2 LIMIT 1`,
|
|
41473
42046
|
[input2.companyId, idem]
|
|
41474
42047
|
);
|
|
41475
42048
|
if (prev[0]) return { caso, transicion: mapTransicion(prev[0]), repetida: true };
|
|
@@ -41482,10 +42055,10 @@ async function transicionar(ctx, input2) {
|
|
|
41482
42055
|
if (!resultado.ok) {
|
|
41483
42056
|
throw new OpsError(resultado.codigo, resultado.motivo, { validaciones: resultado.validaciones, estado: caso.estado });
|
|
41484
42057
|
}
|
|
41485
|
-
const accionId =
|
|
42058
|
+
const accionId = str2(datos.accion_id);
|
|
41486
42059
|
if (accionId) {
|
|
41487
42060
|
const rows = await ctx.db.query(
|
|
41488
|
-
`SELECT id, caso_id, tipo, resultado FROM ${
|
|
42061
|
+
`SELECT id, caso_id, tipo, resultado FROM ${t2(ctx, "accion")} WHERE company_id = $1 AND id = $2 LIMIT 1`,
|
|
41489
42062
|
[input2.companyId, accionId]
|
|
41490
42063
|
);
|
|
41491
42064
|
const accion = rows[0];
|
|
@@ -41494,21 +42067,21 @@ async function transicionar(ctx, input2) {
|
|
|
41494
42067
|
throw new OpsError("validacion", `accion_registrada: ${problema}`, { validaciones: [{ regla: "accion_registrada", ok: false, detalle: problema }], estado: caso.estado });
|
|
41495
42068
|
}
|
|
41496
42069
|
}
|
|
41497
|
-
const lecturaId =
|
|
42070
|
+
const lecturaId = str2(datos.lectura_id);
|
|
41498
42071
|
if (lecturaId) {
|
|
41499
42072
|
const rows = await ctx.db.query(
|
|
41500
|
-
`SELECT id, caso_id, tipo, resultado, created_at FROM ${
|
|
42073
|
+
`SELECT id, caso_id, tipo, resultado, created_at FROM ${t2(ctx, "accion")} WHERE company_id = $1 AND id = $2 LIMIT 1`,
|
|
41501
42074
|
[input2.companyId, lecturaId]
|
|
41502
42075
|
);
|
|
41503
42076
|
const lectura = rows[0];
|
|
41504
42077
|
let vieja = false;
|
|
41505
42078
|
if (lectura && resultado.via === "comprobar") {
|
|
41506
42079
|
const ultima = await ctx.db.query(
|
|
41507
|
-
`SELECT max(created_at) AS ultima FROM ${
|
|
42080
|
+
`SELECT max(created_at) AS ultima FROM ${t2(ctx, "transicion")} WHERE company_id = $1 AND caso_id = $2`,
|
|
41508
42081
|
[input2.companyId, caso.id]
|
|
41509
42082
|
);
|
|
41510
|
-
const desde = ultima[0]?.ultima ? Date.parse(
|
|
41511
|
-
vieja = Number.isFinite(desde) && Date.parse(
|
|
42083
|
+
const desde = ultima[0]?.ultima ? Date.parse(iso2(ultima[0].ultima)) : NaN;
|
|
42084
|
+
vieja = Number.isFinite(desde) && Date.parse(iso2(lectura.created_at)) <= desde;
|
|
41512
42085
|
}
|
|
41513
42086
|
const problema = !lectura ? `lectura_id ${lecturaId} no existe en esta instituci\xF3n` : String(lectura.caso_id) !== caso.id ? `la lectura ${lecturaId} pertenece a otro caso` : !["lectura", "verificacion"].includes(String(lectura.tipo)) ? `la acci\xF3n ${lecturaId} es de tipo ${String(lectura.tipo)}; hace falta una lectura o verificaci\xF3n` : String(lectura.resultado) !== "ok" ? `la lectura ${lecturaId} termin\xF3 con resultado ${String(lectura.resultado)}` : vieja ? `la lectura ${lecturaId} es anterior al \xFAltimo cambio del caso: volver a leer el bono y registrar esa lectura` : null;
|
|
41514
42087
|
if (problema) {
|
|
@@ -41535,6 +42108,23 @@ async function transicionar(ctx, input2) {
|
|
|
41535
42108
|
const fecha = String(datos.fecha_prestacion ?? caso.datos.fecha_prestacion ?? "");
|
|
41536
42109
|
Object.assign(datos, calcularVencimiento(fecha, datos.obra_social ?? caso.datos.obra_social, contexto));
|
|
41537
42110
|
}
|
|
42111
|
+
if (caso.workflow === "demanda_pendiente") {
|
|
42112
|
+
delete datos.intentos;
|
|
42113
|
+
delete datos.proximo_contacto;
|
|
42114
|
+
if (resultado.via === "contactar") {
|
|
42115
|
+
const hechos = (contexto.intentosContacto ?? 0) + 1;
|
|
42116
|
+
const regla = contexto.regla ?? null;
|
|
42117
|
+
datos.intentos = hechos;
|
|
42118
|
+
datos.ultimo_contacto = contexto.hoy ?? null;
|
|
42119
|
+
datos.canal = canalContacto(datos.canal);
|
|
42120
|
+
datos.proximo_contacto = regla && hechos < regla.contacto.max_intentos && contexto.hoy ? sumarDias(contexto.hoy, regla.contacto.dias_entre_intentos) : null;
|
|
42121
|
+
}
|
|
42122
|
+
if (resultado.via === "responder") {
|
|
42123
|
+
datos.respuesta = respuestaContacto(datos.respuesta);
|
|
42124
|
+
datos.respondio_el = contexto.hoy ?? null;
|
|
42125
|
+
}
|
|
42126
|
+
if (resultado.via === "comprobar") datos.turno_encontrado_el = contexto.hoy ?? null;
|
|
42127
|
+
}
|
|
41538
42128
|
if (caso.workflow === "recupero_turno" && resultado.via === "no_acepta") {
|
|
41539
42129
|
const previas = Array.isArray(caso.datos.declinaron) ? caso.datos.declinaron.map((v) => refExterna(v)).filter((v) => v !== null) : [];
|
|
41540
42130
|
const esta = refExterna(caso.datos.solicitud_id);
|
|
@@ -41551,18 +42141,18 @@ async function transicionar(ctx, input2) {
|
|
|
41551
42141
|
datos.largo_cola = total;
|
|
41552
42142
|
const cola = leerCola(datos.cola);
|
|
41553
42143
|
const elegida = cola?.find((e) => e.solicitud_id === refExterna(datos.solicitud_id));
|
|
41554
|
-
if (elegida?.prioridad && !
|
|
42144
|
+
if (elegida?.prioridad && !str2(datos.solicitud_prioridad)) datos.solicitud_prioridad = elegida.prioridad;
|
|
41555
42145
|
if (cola) datos.cola = ordenarCola(cola).map((e) => enCurso.has(e.solicitud_id) && e.solicitud_id !== elegida?.solicitud_id ? { ...e, en_otro_recupero: true } : e);
|
|
41556
42146
|
}
|
|
41557
|
-
const transicionId =
|
|
42147
|
+
const transicionId = randomUUID2();
|
|
41558
42148
|
await ctx.db.execute(
|
|
41559
|
-
`INSERT INTO ${
|
|
42149
|
+
`INSERT INTO ${t2(ctx, "transicion")} (id, company_id, caso_id, de_estado, a_estado, via, actor_tipo, actor_id, run_id, motivo, datos, validaciones, idempotency_key) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11::jsonb, $12::jsonb, $13)`,
|
|
41560
42150
|
[transicionId, input2.companyId, caso.id, resultado.de, resultado.a, resultado.via, input2.actor.tipo, input2.actor.id, input2.actor.runId ?? null, resultado.autoaprobado ? input2.motivo ?? "Aprobado autom\xE1ticamente: pol\xEDtica aut\xF3noma, sin excepciones" : input2.motivo ?? null, JSON.stringify(resultado.autoaprobado ? { ...datos, autoaprobado: true } : datos), JSON.stringify(resultado.validaciones), idem]
|
|
41561
42151
|
);
|
|
41562
42152
|
const nuevosDatos = { ...caso.datos, ...datos };
|
|
41563
42153
|
delete nuevosDatos.motivo;
|
|
41564
42154
|
await ctx.db.execute(
|
|
41565
|
-
`UPDATE ${
|
|
42155
|
+
`UPDATE ${t2(ctx, "caso")} SET estado = $3, datos = $4::jsonb, minutos_absorbidos = minutos_absorbidos + $5::int, cerrado_at = CASE WHEN $6::boolean THEN now() ELSE NULL END, updated_at = now() WHERE company_id = $1 AND id = $2`,
|
|
41566
42156
|
[input2.companyId, caso.id, resultado.a, JSON.stringify(nuevosDatos), resultado.minutosAbsorbidos, resultado.esFinal]
|
|
41567
42157
|
);
|
|
41568
42158
|
await ctx.activity.log({
|
|
@@ -41598,7 +42188,7 @@ async function registrarAccion(ctx, input2) {
|
|
|
41598
42188
|
if (!TIPOS_ACCION.includes(input2.tipo)) throw new OpsError("tipo_invalido", `tipo debe ser: ${TIPOS_ACCION.join(", ")}`);
|
|
41599
42189
|
if (!CONECTORES.includes(input2.conector)) throw new OpsError("conector_invalido", `conector debe ser: ${CONECTORES.join(", ")}`);
|
|
41600
42190
|
if (!RESULTADOS_ACCION.includes(input2.resultado)) throw new OpsError("resultado_invalido", `resultado debe ser: ${RESULTADOS_ACCION.join(", ")}`);
|
|
41601
|
-
const objetivo =
|
|
42191
|
+
const objetivo = str2(input2.objetivo);
|
|
41602
42192
|
if (!objetivo) throw new OpsError("objetivo_requerido", "objetivo es obligatorio (qu\xE9 pantalla, endpoint o archivo se toc\xF3)");
|
|
41603
42193
|
if (WORKFLOWS_SOLO_LECTURA.has(caso.workflow) && input2.tipo === "escritura" && input2.actor.tipo === "agente") {
|
|
41604
42194
|
throw new OpsError("politica", `en ${caso.workflow} el puesto solo lee: no registra escrituras sobre el sistema del hospital; eso lo hace el equipo`);
|
|
@@ -41607,18 +42197,18 @@ async function registrarAccion(ctx, input2) {
|
|
|
41607
42197
|
const modo = await obtenerModo(ctx, input2.companyId, caso.workflow);
|
|
41608
42198
|
if (!modoHabilita(modo, "ejecuta")) throw new OpsError("politica", `la pol\xEDtica vigente de ${caso.workflow} es "${modo}": un agente no puede registrar escrituras sobre el sistema del hospital`);
|
|
41609
42199
|
}
|
|
41610
|
-
const id =
|
|
42200
|
+
const id = randomUUID2();
|
|
41611
42201
|
const evidencia = { ...input2.evidencia ?? {} };
|
|
41612
42202
|
delete evidencia.fuera_de_flujo;
|
|
41613
42203
|
if (caso.workflow === "recupero_turno" && input2.tipo === "escritura" && input2.actor.tipo === "agente" && caso.estado !== "asignando") {
|
|
41614
42204
|
evidencia.fuera_de_flujo = true;
|
|
41615
42205
|
}
|
|
41616
42206
|
await ctx.db.execute(
|
|
41617
|
-
`INSERT INTO ${
|
|
42207
|
+
`INSERT INTO ${t2(ctx, "accion")} (id, company_id, caso_id, tipo, conector, objetivo, resultado, evidencia, detalle, actor_tipo, actor_id, run_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9, $10, $11, $12)`,
|
|
41618
42208
|
[id, input2.companyId, caso.id, input2.tipo, input2.conector, objetivo, input2.resultado, JSON.stringify(evidencia), input2.detalle ?? null, input2.actor.tipo, input2.actor.id, input2.actor.runId ?? null]
|
|
41619
42209
|
);
|
|
41620
42210
|
await ctx.db.execute(
|
|
41621
|
-
`UPDATE ${
|
|
42211
|
+
`UPDATE ${t2(ctx, "caso")} SET updated_at = now() WHERE company_id = $1 AND id = $2`,
|
|
41622
42212
|
[input2.companyId, caso.id]
|
|
41623
42213
|
);
|
|
41624
42214
|
return {
|
|
@@ -41642,10 +42232,10 @@ function numero(v) {
|
|
|
41642
42232
|
}
|
|
41643
42233
|
function motivoSinCarga(caso) {
|
|
41644
42234
|
const wf = getWorkflow(caso.workflow);
|
|
41645
|
-
if (!wf || !wf.transiciones.some((
|
|
42235
|
+
if (!wf || !wf.transiciones.some((t4) => t4.cargaManual)) return `el workflow ${caso.workflow} no tiene carga manual en el sistema de gesti\xF3n`;
|
|
41646
42236
|
if (wf.finalesOk.includes(caso.estado)) return "ya est\xE1 cargado y comprobado";
|
|
41647
42237
|
if (wf.finalesNoOk.includes(caso.estado)) return `est\xE1 ${caso.estado}: no se carga`;
|
|
41648
|
-
if (wf.transiciones.some((
|
|
42238
|
+
if (wf.transiciones.some((t4) => t4.cargaManual && t4.a === caso.estado)) return "ya figura como cargado; falta la comprobaci\xF3n del puesto";
|
|
41649
42239
|
return "todav\xEDa no est\xE1 aprobado: no hay nada para cargar";
|
|
41650
42240
|
}
|
|
41651
42241
|
async function registrarCargaManual(ctx, input2) {
|
|
@@ -41654,10 +42244,10 @@ async function registrarCargaManual(ctx, input2) {
|
|
|
41654
42244
|
}
|
|
41655
42245
|
const caso = await leerCaso(ctx, input2.companyId, input2.casoId);
|
|
41656
42246
|
if (!caso) throw new OpsError("caso_inexistente", `no existe el caso ${input2.casoId} en esta instituci\xF3n`);
|
|
41657
|
-
const idem =
|
|
42247
|
+
const idem = str2(input2.idempotencyKey);
|
|
41658
42248
|
if (idem) {
|
|
41659
42249
|
const prev = await ctx.db.query(
|
|
41660
|
-
`SELECT id, caso_id, de_estado, a_estado, via, actor_tipo, actor_id, motivo, datos, validaciones, created_at FROM ${
|
|
42250
|
+
`SELECT id, caso_id, de_estado, a_estado, via, actor_tipo, actor_id, motivo, datos, validaciones, created_at FROM ${t2(ctx, "transicion")} WHERE company_id = $1 AND idempotency_key = $2 LIMIT 1`,
|
|
41661
42251
|
[input2.companyId, idem]
|
|
41662
42252
|
);
|
|
41663
42253
|
if (prev[0]) {
|
|
@@ -41675,8 +42265,8 @@ async function registrarCargaManual(ctx, input2) {
|
|
|
41675
42265
|
const contextoPrevio = await contextoDe(ctx, input2.companyId, caso, def2.via, {});
|
|
41676
42266
|
const previa = evaluarTransicion({ caso, via: def2.via, actor: "humano", datos: { accion_id: "(por registrar)" }, modo: "ejecuta", contexto: contextoPrevio });
|
|
41677
42267
|
if (!previa.ok) throw new OpsError(previa.codigo, previa.motivo, { validaciones: previa.validaciones, estado: caso.estado });
|
|
41678
|
-
const persona =
|
|
41679
|
-
const comentario =
|
|
42268
|
+
const persona = str2(input2.persona);
|
|
42269
|
+
const comentario = str2(input2.comentario);
|
|
41680
42270
|
const accion = await registrarAccion(ctx, {
|
|
41681
42271
|
companyId: input2.companyId,
|
|
41682
42272
|
casoId: caso.id,
|
|
@@ -41710,7 +42300,7 @@ async function registrarCargaManual(ctx, input2) {
|
|
|
41710
42300
|
} catch (err) {
|
|
41711
42301
|
if (idem) {
|
|
41712
42302
|
const prev = await ctx.db.query(
|
|
41713
|
-
`SELECT id, caso_id, de_estado, a_estado, via, actor_tipo, actor_id, motivo, datos, validaciones, created_at FROM ${
|
|
42303
|
+
`SELECT id, caso_id, de_estado, a_estado, via, actor_tipo, actor_id, motivo, datos, validaciones, created_at FROM ${t2(ctx, "transicion")} WHERE company_id = $1 AND idempotency_key = $2 LIMIT 1`,
|
|
41714
42304
|
[input2.companyId, idem]
|
|
41715
42305
|
);
|
|
41716
42306
|
if (prev[0]) {
|
|
@@ -41726,11 +42316,75 @@ async function registrarRespuestaPaciente(ctx, input2) {
|
|
|
41726
42316
|
const caso = await leerCaso(ctx, input2.companyId, input2.casoId);
|
|
41727
42317
|
if (!caso) throw new OpsError("caso_inexistente", `no existe el caso ${input2.casoId} en esta instituci\xF3n`);
|
|
41728
42318
|
if (caso.workflow !== "recupero_turno") throw new OpsError("estado_invalido", "solo las citas ofrecidas al paciente llevan respuesta");
|
|
41729
|
-
const persona =
|
|
41730
|
-
const comentario =
|
|
42319
|
+
const persona = str2(input2.persona);
|
|
42320
|
+
const comentario = str2(input2.comentario);
|
|
41731
42321
|
const datos = input2.acepto ? { respuesta: comentario ?? "Acept\xF3 la cita", ...persona ? { registrado_por: persona } : {} } : { motivo: comentario ?? "No acept\xF3 la cita", ...persona ? { registrado_por: persona } : {} };
|
|
41732
42322
|
return transicionar(ctx, { companyId: input2.companyId, casoId: caso.id, via: input2.acepto ? "aceptar" : "no_acepta", datos, motivo: comentario, idempotencyKey: input2.idempotencyKey, actor: input2.actor });
|
|
41733
42323
|
}
|
|
42324
|
+
async function registrarContacto(ctx, input2) {
|
|
42325
|
+
if (input2.actor.tipo !== "humano") throw new OpsError("actor_no_permitido", "el contacto manual lo declara una persona del hospital; un agente registra la escritura y usa ops_transicionar con contactar");
|
|
42326
|
+
const caso = await leerCaso(ctx, input2.companyId, input2.casoId);
|
|
42327
|
+
if (!caso) throw new OpsError("caso_inexistente", `no existe el caso ${input2.casoId} en esta instituci\xF3n`);
|
|
42328
|
+
if (caso.workflow !== "demanda_pendiente") throw new OpsError("estado_invalido", "solo los casos de demanda pendiente llevan contacto");
|
|
42329
|
+
const idem = str2(input2.idempotencyKey);
|
|
42330
|
+
if (idem) {
|
|
42331
|
+
const prev = await ctx.db.query(
|
|
42332
|
+
`SELECT id, caso_id, de_estado, a_estado, via, actor_tipo, actor_id, motivo, datos, validaciones, created_at FROM ${t2(ctx, "transicion")} WHERE company_id = $1 AND idempotency_key = $2 LIMIT 1`,
|
|
42333
|
+
[input2.companyId, idem]
|
|
42334
|
+
);
|
|
42335
|
+
if (prev[0]) return { caso, transicion: mapTransicion(prev[0]), accion: null, respondio: null, repetida: true };
|
|
42336
|
+
}
|
|
42337
|
+
const canal = canalContacto(input2.canal);
|
|
42338
|
+
const respuesta2 = input2.respuesta === void 0 || input2.respuesta === null || input2.respuesta === "" ? null : respuestaContacto(input2.respuesta);
|
|
42339
|
+
if (input2.respuesta !== void 0 && input2.respuesta !== null && input2.respuesta !== "" && !respuesta2) {
|
|
42340
|
+
throw new OpsError("validacion", "respuesta_valida: respuesta debe ser acepta, rechaza o no_contesta", { estado: caso.estado });
|
|
42341
|
+
}
|
|
42342
|
+
const contextoPrevio = await contextoDe(ctx, input2.companyId, caso, "contactar", {});
|
|
42343
|
+
const previa = evaluarTransicion({ caso, via: "contactar", actor: "humano", datos: { accion_id: "(por registrar)", canal }, modo: "ejecuta", contexto: contextoPrevio });
|
|
42344
|
+
if (!previa.ok) throw new OpsError(previa.codigo, previa.motivo, { validaciones: previa.validaciones, estado: caso.estado });
|
|
42345
|
+
if (respuesta2) {
|
|
42346
|
+
const despues = evaluarTransicion({ caso: { ...caso, estado: "contactado" }, via: "responder", actor: "humano", datos: { respuesta: respuesta2 }, modo: "ejecuta", contexto: contextoPrevio });
|
|
42347
|
+
if (!despues.ok) throw new OpsError(despues.codigo, despues.motivo, { validaciones: despues.validaciones, estado: caso.estado });
|
|
42348
|
+
}
|
|
42349
|
+
const persona = str2(input2.persona);
|
|
42350
|
+
const comentario = str2(input2.comentario);
|
|
42351
|
+
const accion = await registrarAccion(ctx, {
|
|
42352
|
+
companyId: input2.companyId,
|
|
42353
|
+
casoId: caso.id,
|
|
42354
|
+
tipo: "escritura",
|
|
42355
|
+
conector: "manual",
|
|
42356
|
+
objetivo: `Paciente \xB7 ${caso.titulo}${caso.referencia ? ` (ref ${caso.referencia})` : ""}`,
|
|
42357
|
+
resultado: "ok",
|
|
42358
|
+
evidencia: { canal, ...persona ? { contactado_por: persona } : {}, ...respuesta2 ? { respuesta: respuesta2 } : {}, ...comentario ? { nota: comentario } : {} },
|
|
42359
|
+
detalle: comentario,
|
|
42360
|
+
actor: input2.actor
|
|
42361
|
+
});
|
|
42362
|
+
const r = await transicionar(ctx, {
|
|
42363
|
+
companyId: input2.companyId,
|
|
42364
|
+
casoId: caso.id,
|
|
42365
|
+
via: "contactar",
|
|
42366
|
+
datos: { accion_id: accion.id, canal, ...persona ? { contactado_por: persona } : {} },
|
|
42367
|
+
motivo: comentario,
|
|
42368
|
+
idempotencyKey: idem,
|
|
42369
|
+
actor: input2.actor
|
|
42370
|
+
});
|
|
42371
|
+
let respondio = null;
|
|
42372
|
+
let final = r.caso;
|
|
42373
|
+
if (respuesta2) {
|
|
42374
|
+
const r2 = await transicionar(ctx, {
|
|
42375
|
+
companyId: input2.companyId,
|
|
42376
|
+
casoId: caso.id,
|
|
42377
|
+
via: "responder",
|
|
42378
|
+
datos: { respuesta: respuesta2, ...persona ? { registrado_por: persona } : {} },
|
|
42379
|
+
motivo: comentario,
|
|
42380
|
+
idempotencyKey: idem ? `${idem}:respuesta` : null,
|
|
42381
|
+
actor: input2.actor
|
|
42382
|
+
});
|
|
42383
|
+
respondio = r2.transicion;
|
|
42384
|
+
final = r2.caso;
|
|
42385
|
+
}
|
|
42386
|
+
return { caso: final, transicion: r.transicion, accion, respondio, repetida: false };
|
|
42387
|
+
}
|
|
41734
42388
|
function finalesOk() {
|
|
41735
42389
|
return [...new Set(listarWorkflows().flatMap((w) => w.finalesOk))];
|
|
41736
42390
|
}
|
|
@@ -41741,20 +42395,20 @@ async function tablero(ctx, companyId, diasInput) {
|
|
|
41741
42395
|
const dias = Math.min(Math.max(Number(diasInput ?? 30) || 30, 1), 365);
|
|
41742
42396
|
const p1 = [companyId];
|
|
41743
42397
|
const porEstadoRows = await ctx.db.query(
|
|
41744
|
-
`SELECT workflow, estado, count(*)::int AS cantidad FROM ${
|
|
42398
|
+
`SELECT workflow, estado, count(*)::int AS cantidad FROM ${t2(ctx, "caso")} WHERE company_id = $1 GROUP BY workflow, estado ORDER BY workflow, estado`,
|
|
41745
42399
|
p1
|
|
41746
42400
|
);
|
|
41747
42401
|
const p2 = [companyId, dias];
|
|
41748
42402
|
const cerradosRows = await ctx.db.query(
|
|
41749
|
-
`SELECT workflow, estado, count(*)::int AS cantidad, coalesce(sum(minutos_absorbidos), 0)::int AS minutos FROM ${
|
|
42403
|
+
`SELECT workflow, estado, count(*)::int AS cantidad, coalesce(sum(minutos_absorbidos), 0)::int AS minutos FROM ${t2(ctx, "caso")} WHERE company_id = $1 AND cerrado_at IS NOT NULL AND cerrado_at >= now() - ($2::int * interval '1 day') GROUP BY workflow, estado`,
|
|
41750
42404
|
p2
|
|
41751
42405
|
);
|
|
41752
42406
|
const totalRows = await ctx.db.query(
|
|
41753
|
-
`SELECT coalesce(sum(minutos_absorbidos), 0)::int AS minutos FROM ${
|
|
42407
|
+
`SELECT coalesce(sum(minutos_absorbidos), 0)::int AS minutos FROM ${t2(ctx, "caso")} WHERE company_id = $1`,
|
|
41754
42408
|
p1
|
|
41755
42409
|
);
|
|
41756
42410
|
const accionesRows = await ctx.db.query(
|
|
41757
|
-
`SELECT tipo, count(*)::int AS cantidad FROM ${
|
|
42411
|
+
`SELECT tipo, count(*)::int AS cantidad FROM ${t2(ctx, "accion")} WHERE company_id = $1 AND created_at >= now() - ($2::int * interval '1 day') GROUP BY tipo`,
|
|
41758
42412
|
p2
|
|
41759
42413
|
);
|
|
41760
42414
|
const [pendientes, recientes, politicas] = await Promise.all([
|
|
@@ -41826,15 +42480,15 @@ async function serie(ctx, companyId, diasInput) {
|
|
|
41826
42480
|
const p = [companyId, dias];
|
|
41827
42481
|
const [abiertos, cerrados, acciones] = await Promise.all([
|
|
41828
42482
|
ctx.db.query(
|
|
41829
|
-
`SELECT to_char(created_at, 'YYYY-MM-DD') AS dia, count(*)::int AS cantidad FROM ${
|
|
42483
|
+
`SELECT to_char(created_at, 'YYYY-MM-DD') AS dia, count(*)::int AS cantidad FROM ${t2(ctx, "caso")} WHERE company_id = $1 AND created_at >= now() - ($2::int * interval '1 day') GROUP BY 1 ORDER BY 1`,
|
|
41830
42484
|
p
|
|
41831
42485
|
),
|
|
41832
42486
|
ctx.db.query(
|
|
41833
|
-
`SELECT to_char(cerrado_at, 'YYYY-MM-DD') AS dia, estado, count(*)::int AS cantidad, coalesce(sum(minutos_absorbidos), 0)::int AS minutos FROM ${
|
|
42487
|
+
`SELECT to_char(cerrado_at, 'YYYY-MM-DD') AS dia, estado, count(*)::int AS cantidad, coalesce(sum(minutos_absorbidos), 0)::int AS minutos FROM ${t2(ctx, "caso")} WHERE company_id = $1 AND cerrado_at IS NOT NULL AND cerrado_at >= now() - ($2::int * interval '1 day') GROUP BY 1, 2 ORDER BY 1`,
|
|
41834
42488
|
p
|
|
41835
42489
|
),
|
|
41836
42490
|
ctx.db.query(
|
|
41837
|
-
`SELECT to_char(created_at, 'YYYY-MM-DD') AS dia, count(*)::int AS cantidad FROM ${
|
|
42491
|
+
`SELECT to_char(created_at, 'YYYY-MM-DD') AS dia, count(*)::int AS cantidad FROM ${t2(ctx, "accion")} WHERE company_id = $1 AND created_at >= now() - ($2::int * interval '1 day') GROUP BY 1 ORDER BY 1`,
|
|
41838
42492
|
p
|
|
41839
42493
|
)
|
|
41840
42494
|
]);
|
|
@@ -41868,7 +42522,7 @@ async function serie(ctx, companyId, diasInput) {
|
|
|
41868
42522
|
async function historialPoliticas(ctx, companyId, limitInput) {
|
|
41869
42523
|
const limit = Math.min(Math.max(Number(limitInput ?? 50) || 50, 1), 200);
|
|
41870
42524
|
const rows = await ctx.db.query(
|
|
41871
|
-
`SELECT id, workflow, modo_anterior, modo_nuevo, motivo, actor_tipo, actor_id, created_at FROM ${
|
|
42525
|
+
`SELECT id, workflow, modo_anterior, modo_nuevo, motivo, actor_tipo, actor_id, created_at FROM ${t2(ctx, "politica_historial")} WHERE company_id = $1 ORDER BY created_at DESC LIMIT $2`,
|
|
41872
42526
|
[companyId, limit]
|
|
41873
42527
|
);
|
|
41874
42528
|
return rows.map((r) => ({
|
|
@@ -41879,7 +42533,7 @@ async function historialPoliticas(ctx, companyId, limitInput) {
|
|
|
41879
42533
|
motivo: r.motivo ?? null,
|
|
41880
42534
|
actorTipo: String(r.actor_tipo),
|
|
41881
42535
|
actorId: r.actor_id ?? null,
|
|
41882
|
-
createdAt:
|
|
42536
|
+
createdAt: iso2(r.created_at)
|
|
41883
42537
|
}));
|
|
41884
42538
|
}
|
|
41885
42539
|
async function indicador(ctx, input2) {
|
|
@@ -41931,14 +42585,14 @@ function metricaPorId(id) {
|
|
|
41931
42585
|
}
|
|
41932
42586
|
|
|
41933
42587
|
// src/tableros.ts
|
|
41934
|
-
import { randomUUID as
|
|
42588
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
41935
42589
|
var TIPOS = ["indicador", "barras", "linea", "tabla", "texto"];
|
|
41936
42590
|
var TONOS = ["neutro", "ambar", "senal", "corte"];
|
|
41937
42591
|
var MAX_WIDGETS = 24;
|
|
41938
|
-
function
|
|
42592
|
+
function t3(ctx, table) {
|
|
41939
42593
|
return `${ctx.db.namespace}.${table}`;
|
|
41940
42594
|
}
|
|
41941
|
-
function
|
|
42595
|
+
function iso3(v) {
|
|
41942
42596
|
if (v instanceof Date) return v.toISOString();
|
|
41943
42597
|
if (typeof v === "string") return v;
|
|
41944
42598
|
return "";
|
|
@@ -42150,18 +42804,18 @@ function mapTablero(r) {
|
|
|
42150
42804
|
publicadoPorTipo: String(r.publicado_por_tipo),
|
|
42151
42805
|
publicadoPorId: r.publicado_por_id ?? null,
|
|
42152
42806
|
versiones: Number(r.versiones ?? 1),
|
|
42153
|
-
createdAt:
|
|
42154
|
-
updatedAt:
|
|
42807
|
+
createdAt: iso3(r.created_at),
|
|
42808
|
+
updatedAt: iso3(r.updated_at)
|
|
42155
42809
|
};
|
|
42156
42810
|
}
|
|
42157
|
-
var
|
|
42811
|
+
var COLS2 = "id, company_id, clave, titulo, descripcion, pedido, periodo, fuentes, widgets, agente_id, issue_id, publicado_por_tipo, publicado_por_id, versiones, created_at, updated_at";
|
|
42158
42812
|
var CLAVE_RE = /^[a-z0-9][a-z0-9-]{1,60}$/;
|
|
42159
42813
|
async function listarTableros(ctx, companyId) {
|
|
42160
|
-
const rows = await ctx.db.query(`SELECT ${
|
|
42814
|
+
const rows = await ctx.db.query(`SELECT ${COLS2} FROM ${t3(ctx, "tablero")} WHERE company_id = $1 ORDER BY updated_at DESC`, [companyId]);
|
|
42161
42815
|
return rows.map(mapTablero);
|
|
42162
42816
|
}
|
|
42163
42817
|
async function leerTablero(ctx, companyId, clave) {
|
|
42164
|
-
const rows = await ctx.db.query(`SELECT ${
|
|
42818
|
+
const rows = await ctx.db.query(`SELECT ${COLS2} FROM ${t3(ctx, "tablero")} WHERE company_id = $1 AND clave = $2 LIMIT 1`, [companyId, clave]);
|
|
42165
42819
|
return rows[0] ? mapTablero(rows[0]) : null;
|
|
42166
42820
|
}
|
|
42167
42821
|
async function publicarTablero(ctx, input2) {
|
|
@@ -42176,7 +42830,7 @@ async function publicarTablero(ctx, input2) {
|
|
|
42176
42830
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
42177
42831
|
if (previo) {
|
|
42178
42832
|
await ctx.db.execute(
|
|
42179
|
-
`UPDATE ${
|
|
42833
|
+
`UPDATE ${t3(ctx, "tablero")} SET titulo = $3, descripcion = $4, pedido = COALESCE($5, pedido), periodo = $6, fuentes = $7::jsonb, widgets = $8::jsonb, agente_id = COALESCE($9, agente_id), issue_id = COALESCE($10, issue_id), publicado_por_tipo = $11, publicado_por_id = $12, versiones = versiones + 1, updated_at = now() WHERE company_id = $1 AND clave = $2`,
|
|
42180
42834
|
[input2.companyId, clave, titulo, limpio(input2.descripcion, 600), limpio(input2.pedido, 1e3), limpio(input2.periodo, 80), JSON.stringify(fuentes), JSON.stringify(widgets), input2.agenteId ?? null, input2.issueId ?? null, input2.actor.tipo, input2.actor.id]
|
|
42181
42835
|
);
|
|
42182
42836
|
await ctx.activity.log({ companyId: input2.companyId, message: `Autopilot Care: tablero \xAB${titulo}\xBB actualizado (${clave}, versi\xF3n ${previo.versiones + 1})`, metadata: { clave, widgets: widgets.length, agentId: input2.agenteId ?? null, issueId: input2.issueId ?? null } });
|
|
@@ -42185,9 +42839,9 @@ async function publicarTablero(ctx, input2) {
|
|
|
42185
42839
|
tablero: { ...previo, titulo, descripcion: limpio(input2.descripcion, 600), pedido: limpio(input2.pedido, 1e3) ?? previo.pedido, periodo: limpio(input2.periodo, 80), fuentes, widgets, agenteId: input2.agenteId ?? previo.agenteId, issueId: input2.issueId ?? previo.issueId, publicadoPorTipo: input2.actor.tipo, publicadoPorId: input2.actor.id, versiones: previo.versiones + 1, updatedAt: now }
|
|
42186
42840
|
};
|
|
42187
42841
|
}
|
|
42188
|
-
const id =
|
|
42842
|
+
const id = randomUUID3();
|
|
42189
42843
|
await ctx.db.execute(
|
|
42190
|
-
`INSERT INTO ${
|
|
42844
|
+
`INSERT INTO ${t3(ctx, "tablero")} (id, company_id, clave, titulo, descripcion, pedido, periodo, fuentes, widgets, agente_id, issue_id, publicado_por_tipo, publicado_por_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9::jsonb, $10, $11, $12, $13)`,
|
|
42191
42845
|
[id, input2.companyId, clave, titulo, limpio(input2.descripcion, 600), limpio(input2.pedido, 1e3), limpio(input2.periodo, 80), JSON.stringify(fuentes), JSON.stringify(widgets), input2.agenteId ?? null, input2.issueId ?? null, input2.actor.tipo, input2.actor.id]
|
|
42192
42846
|
);
|
|
42193
42847
|
await ctx.activity.log({ companyId: input2.companyId, message: `Autopilot Care: tablero \xAB${titulo}\xBB publicado (${clave})`, metadata: { clave, widgets: widgets.length, agentId: input2.agenteId ?? null, issueId: input2.issueId ?? null } });
|
|
@@ -42198,14 +42852,14 @@ async function publicarTablero(ctx, input2) {
|
|
|
42198
42852
|
}
|
|
42199
42853
|
|
|
42200
42854
|
// src/worker.ts
|
|
42201
|
-
function
|
|
42855
|
+
function str3(v) {
|
|
42202
42856
|
return typeof v === "string" && v.trim().length > 0 ? v.trim() : null;
|
|
42203
42857
|
}
|
|
42204
42858
|
function obj(v) {
|
|
42205
42859
|
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
42206
42860
|
}
|
|
42207
42861
|
function uuidOrNull(v) {
|
|
42208
|
-
const s =
|
|
42862
|
+
const s = str3(v);
|
|
42209
42863
|
return s && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s) ? s : null;
|
|
42210
42864
|
}
|
|
42211
42865
|
function numOrNull(v) {
|
|
@@ -42213,12 +42867,12 @@ function numOrNull(v) {
|
|
|
42213
42867
|
return Number.isFinite(n) ? n : null;
|
|
42214
42868
|
}
|
|
42215
42869
|
function companyFrom(params, fallback) {
|
|
42216
|
-
const companyId =
|
|
42870
|
+
const companyId = str3(params.companyId) ?? str3(fallback);
|
|
42217
42871
|
if (!companyId) throw new OpsError("company_requerida", "companyId es obligatorio");
|
|
42218
42872
|
return companyId;
|
|
42219
42873
|
}
|
|
42220
42874
|
function toolSchema(name) {
|
|
42221
|
-
const decl = manifest_default.tools?.find((
|
|
42875
|
+
const decl = manifest_default.tools?.find((t4) => t4.name === name);
|
|
42222
42876
|
return {
|
|
42223
42877
|
displayName: decl?.displayName ?? name,
|
|
42224
42878
|
description: decl?.description ?? "",
|
|
@@ -42241,7 +42895,7 @@ async function conErrores(fn) {
|
|
|
42241
42895
|
function registrarHerramientas(ctx) {
|
|
42242
42896
|
ctx.tools.register(TOOL_NAMES.listarCasos, toolSchema(TOOL_NAMES.listarCasos), async (params, runCtx) => conErrores(async () => {
|
|
42243
42897
|
const p = obj(params);
|
|
42244
|
-
const casos = await listarCasos(ctx, { companyId: runCtx.companyId, workflow:
|
|
42898
|
+
const casos = await listarCasos(ctx, { companyId: runCtx.companyId, workflow: str3(p.workflow), estado: str3(p.estado), limit: numOrNull(p.limit) });
|
|
42245
42899
|
return {
|
|
42246
42900
|
content: casos.length ? casos.map(resumenCaso).join("\n") : "No hay casos que coincidan.",
|
|
42247
42901
|
data: { casos }
|
|
@@ -42249,7 +42903,7 @@ function registrarHerramientas(ctx) {
|
|
|
42249
42903
|
}));
|
|
42250
42904
|
ctx.tools.register(TOOL_NAMES.leerCaso, toolSchema(TOOL_NAMES.leerCaso), async (params, runCtx) => conErrores(async () => {
|
|
42251
42905
|
const p = obj(params);
|
|
42252
|
-
const casoId =
|
|
42906
|
+
const casoId = str3(p.casoId);
|
|
42253
42907
|
if (!casoId) throw new OpsError("caso_requerido", "casoId es obligatorio");
|
|
42254
42908
|
const detalle = await leerCasoCompleto(ctx, runCtx.companyId, casoId);
|
|
42255
42909
|
if (!detalle) throw new OpsError("caso_inexistente", `no existe el caso ${casoId} en esta instituci\xF3n`);
|
|
@@ -42268,11 +42922,11 @@ Transiciones registradas: ${detalle.transiciones.length}; acciones registradas:
|
|
|
42268
42922
|
const p = obj(params);
|
|
42269
42923
|
const { caso, creado } = await abrirCaso(ctx, {
|
|
42270
42924
|
companyId: runCtx.companyId,
|
|
42271
|
-
workflow:
|
|
42272
|
-
titulo:
|
|
42273
|
-
referencia:
|
|
42925
|
+
workflow: str3(p.workflow) ?? "",
|
|
42926
|
+
titulo: str3(p.titulo) ?? "",
|
|
42927
|
+
referencia: str3(p.referencia),
|
|
42274
42928
|
datos: obj(p.datos),
|
|
42275
|
-
idempotencyKey:
|
|
42929
|
+
idempotencyKey: str3(p.idempotencyKey),
|
|
42276
42930
|
actor: actorAgente(runCtx),
|
|
42277
42931
|
agenteId: runCtx.agentId,
|
|
42278
42932
|
issueId: uuidOrNull(p.issueId)
|
|
@@ -42284,16 +42938,16 @@ Transiciones registradas: ${detalle.transiciones.length}; acciones registradas:
|
|
|
42284
42938
|
}));
|
|
42285
42939
|
ctx.tools.register(TOOL_NAMES.transicionar, toolSchema(TOOL_NAMES.transicionar), async (params, runCtx) => conErrores(async () => {
|
|
42286
42940
|
const p = obj(params);
|
|
42287
|
-
const casoId =
|
|
42288
|
-
const via =
|
|
42941
|
+
const casoId = str3(p.casoId);
|
|
42942
|
+
const via = str3(p.via);
|
|
42289
42943
|
if (!casoId || !via) throw new OpsError("parametros", "casoId y via son obligatorios");
|
|
42290
42944
|
const r = await transicionar(ctx, {
|
|
42291
42945
|
companyId: runCtx.companyId,
|
|
42292
42946
|
casoId,
|
|
42293
42947
|
via,
|
|
42294
42948
|
datos: obj(p.datos),
|
|
42295
|
-
motivo:
|
|
42296
|
-
idempotencyKey:
|
|
42949
|
+
motivo: str3(p.motivo),
|
|
42950
|
+
idempotencyKey: str3(p.idempotencyKey),
|
|
42297
42951
|
actor: actorAgente(runCtx)
|
|
42298
42952
|
});
|
|
42299
42953
|
return {
|
|
@@ -42305,32 +42959,45 @@ Transiciones registradas: ${detalle.transiciones.length}; acciones registradas:
|
|
|
42305
42959
|
const p = obj(params);
|
|
42306
42960
|
const accion = await registrarAccion(ctx, {
|
|
42307
42961
|
companyId: runCtx.companyId,
|
|
42308
|
-
casoId:
|
|
42309
|
-
tipo:
|
|
42310
|
-
conector:
|
|
42311
|
-
objetivo:
|
|
42312
|
-
resultado:
|
|
42962
|
+
casoId: str3(p.casoId) ?? "",
|
|
42963
|
+
tipo: str3(p.tipo) ?? "",
|
|
42964
|
+
conector: str3(p.conector) ?? "",
|
|
42965
|
+
objetivo: str3(p.objetivo) ?? "",
|
|
42966
|
+
resultado: str3(p.resultado) ?? "",
|
|
42313
42967
|
evidencia: obj(p.evidencia),
|
|
42314
|
-
detalle:
|
|
42968
|
+
detalle: str3(p.detalle),
|
|
42315
42969
|
actor: actorAgente(runCtx)
|
|
42316
42970
|
});
|
|
42317
42971
|
return { content: `Acci\xF3n registrada ${accion.id} (${accion.tipo} \xB7 ${accion.conector} \xB7 ${accion.resultado})`, data: { accion } };
|
|
42318
42972
|
}));
|
|
42973
|
+
ctx.tools.register(TOOL_NAMES.listarReglas, toolSchema(TOOL_NAMES.listarReglas), async (params, runCtx) => conErrores(async () => {
|
|
42974
|
+
const p = obj(params);
|
|
42975
|
+
const reglas = await listarReglas(ctx, runCtx.companyId, { workflow: str3(p.workflow) ?? "demanda_pendiente", estado: str3(p.estado) });
|
|
42976
|
+
const resumen = reglas.map((r) => `${r.id} \xB7 ${r.nombre} \xB7 ${r.tipo} \xB7 ${r.estado} \xB7 v${r.version}${r.ultimaPrueba?.estado === "pedida" && r.ultimaPrueba.version === r.version ? " \xB7 PRUEBA PEDIDA" : ""}`);
|
|
42977
|
+
return { content: resumen.length ? resumen.join("\n") : "No hay reglas.", data: { reglas } };
|
|
42978
|
+
}));
|
|
42979
|
+
ctx.tools.register(TOOL_NAMES.informarPruebaRegla, toolSchema(TOOL_NAMES.informarPruebaRegla), async (params, runCtx) => conErrores(async () => {
|
|
42980
|
+
const p = obj(params);
|
|
42981
|
+
const reglaId = str3(p.reglaId);
|
|
42982
|
+
if (!reglaId) throw new OpsError("regla_requerida", "reglaId es obligatorio");
|
|
42983
|
+
const regla = await informarPruebaRegla(ctx, { companyId: runCtx.companyId, id: reglaId, alcanza: p.alcanza, muestra: p.muestra, consulta: str3(p.consulta), actor: actorAgente(runCtx) });
|
|
42984
|
+
return { content: `Prueba informada: la regla \xAB${regla.nombre}\xBB (versi\xF3n ${regla.version}) alcanza hoy a ${regla.ultimaPrueba?.alcanza ?? 0}`, data: { regla } };
|
|
42985
|
+
}));
|
|
42319
42986
|
ctx.tools.register(TOOL_NAMES.consultarIndicador, toolSchema(TOOL_NAMES.consultarIndicador), async (params, runCtx) => conErrores(async () => {
|
|
42320
42987
|
const p = obj(params);
|
|
42321
|
-
const nombre =
|
|
42322
|
-
const data = await indicador(ctx, { companyId: runCtx.companyId, indicador: nombre, workflow:
|
|
42988
|
+
const nombre = str3(p.indicador) ?? "resumen";
|
|
42989
|
+
const data = await indicador(ctx, { companyId: runCtx.companyId, indicador: nombre, workflow: str3(p.workflow), dias: numOrNull(p.dias) });
|
|
42323
42990
|
return { content: JSON.stringify(data, null, 2), data };
|
|
42324
42991
|
}));
|
|
42325
42992
|
ctx.tools.register(TOOL_NAMES.publicarTablero, toolSchema(TOOL_NAMES.publicarTablero), async (params, runCtx) => conErrores(async () => {
|
|
42326
42993
|
const p = obj(params);
|
|
42327
42994
|
const r = await publicarTablero(ctx, {
|
|
42328
42995
|
companyId: runCtx.companyId,
|
|
42329
|
-
clave:
|
|
42330
|
-
titulo:
|
|
42331
|
-
descripcion:
|
|
42332
|
-
pedido:
|
|
42333
|
-
periodo:
|
|
42996
|
+
clave: str3(p.clave) ?? "",
|
|
42997
|
+
titulo: str3(p.titulo) ?? "",
|
|
42998
|
+
descripcion: str3(p.descripcion),
|
|
42999
|
+
pedido: str3(p.pedido),
|
|
43000
|
+
periodo: str3(p.periodo),
|
|
42334
43001
|
fuentes: p.fuentes,
|
|
42335
43002
|
widgets: p.widgets,
|
|
42336
43003
|
actor: actorAgente(runCtx),
|
|
@@ -42366,12 +43033,12 @@ function registrarDatosYAcciones(ctx) {
|
|
|
42366
43033
|
finalesOk: w.finalesOk,
|
|
42367
43034
|
finalesNoOk: w.finalesNoOk,
|
|
42368
43035
|
minutosAbsorbidos: w.minutosAbsorbidos,
|
|
42369
|
-
transiciones: w.transiciones.map((
|
|
43036
|
+
transiciones: w.transiciones.map((t4) => ({ via: t4.via, de: t4.de, a: typeof t4.a === "string" ? t4.a : "(seg\xFAn datos)", actor: t4.actor, nivelRequerido: t4.nivelRequerido ?? null, descripcion: t4.descripcion }))
|
|
42370
43037
|
})));
|
|
42371
43038
|
ctx.data.register(DATA_KEYS.tablero, async (params) => tablero(ctx, companyFrom(params), numOrNull(params.dias)));
|
|
42372
|
-
ctx.data.register(DATA_KEYS.casos, async (params) => listarCasos(ctx, { companyId: companyFrom(params), workflow:
|
|
43039
|
+
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) }));
|
|
42373
43040
|
ctx.data.register(DATA_KEYS.caso, async (params) => {
|
|
42374
|
-
const casoId =
|
|
43041
|
+
const casoId = str3(params.casoId);
|
|
42375
43042
|
if (!casoId) throw new OpsError("caso_requerido", "casoId es obligatorio");
|
|
42376
43043
|
return leerCasoCompleto(ctx, companyFrom(params), casoId);
|
|
42377
43044
|
});
|
|
@@ -42379,40 +43046,40 @@ function registrarDatosYAcciones(ctx) {
|
|
|
42379
43046
|
ctx.data.register(DATA_KEYS.serie, async (params) => serie(ctx, companyFrom(params), numOrNull(params.dias)));
|
|
42380
43047
|
ctx.data.register(DATA_KEYS.historialPoliticas, async (params) => historialPoliticas(ctx, companyFrom(params), numOrNull(params.limit)));
|
|
42381
43048
|
ctx.actions.register(ACTION_KEYS.aprobar, async (params, context) => {
|
|
42382
|
-
const casoId =
|
|
43049
|
+
const casoId = str3(params.casoId);
|
|
42383
43050
|
if (!casoId) throw new OpsError("caso_requerido", "casoId es obligatorio");
|
|
42384
|
-
return transicionar(ctx, { companyId: companyFrom(params, context.companyId), casoId, via: "aprobar", motivo:
|
|
43051
|
+
return transicionar(ctx, { companyId: companyFrom(params, context.companyId), casoId, via: "aprobar", motivo: str3(params.motivo), datos: obj(params.datos), actor: actorHumano(context) });
|
|
42385
43052
|
});
|
|
42386
43053
|
ctx.actions.register(ACTION_KEYS.rechazar, async (params, context) => {
|
|
42387
|
-
const casoId =
|
|
43054
|
+
const casoId = str3(params.casoId);
|
|
42388
43055
|
if (!casoId) throw new OpsError("caso_requerido", "casoId es obligatorio");
|
|
42389
|
-
return transicionar(ctx, { companyId: companyFrom(params, context.companyId), casoId, via: "rechazar", motivo:
|
|
43056
|
+
return transicionar(ctx, { companyId: companyFrom(params, context.companyId), casoId, via: "rechazar", motivo: str3(params.motivo), datos: obj(params.datos), actor: actorHumano(context) });
|
|
42390
43057
|
});
|
|
42391
43058
|
ctx.actions.register(ACTION_KEYS.marcarCargado, async (params, context) => {
|
|
42392
|
-
const casoId =
|
|
43059
|
+
const casoId = str3(params.casoId);
|
|
42393
43060
|
if (!casoId) throw new OpsError("caso_requerido", "casoId es obligatorio");
|
|
42394
43061
|
return registrarCargaManual(ctx, {
|
|
42395
43062
|
companyId: companyFrom(params, context.companyId),
|
|
42396
43063
|
casoId,
|
|
42397
43064
|
valorCargado: params.valorCargado,
|
|
42398
|
-
comentario:
|
|
42399
|
-
persona:
|
|
43065
|
+
comentario: str3(params.comentario),
|
|
43066
|
+
persona: str3(params.cargadoPor),
|
|
42400
43067
|
actor: actorHumano(context)
|
|
42401
43068
|
});
|
|
42402
43069
|
});
|
|
42403
43070
|
ctx.actions.register(ACTION_KEYS.cambiarPolitica, async (params, context) => {
|
|
42404
|
-
const workflow =
|
|
43071
|
+
const workflow = str3(params.workflow);
|
|
42405
43072
|
const modo = params.modo;
|
|
42406
43073
|
if (!workflow) throw new OpsError("workflow_requerido", "workflow es obligatorio");
|
|
42407
43074
|
if (!esModo(modo)) throw new OpsError("modo_invalido", "modo debe ser corte, observa, propone, ejecuta o autonomo");
|
|
42408
|
-
return cambiarPolitica(ctx, { companyId: companyFrom(params, context.companyId), workflow, modo, motivo:
|
|
43075
|
+
return cambiarPolitica(ctx, { companyId: companyFrom(params, context.companyId), workflow, modo, motivo: str3(params.motivo), actor: actorHumano(context) });
|
|
42409
43076
|
});
|
|
42410
43077
|
ctx.actions.register(ACTION_KEYS.abrirCaso, async (params, context) => {
|
|
42411
43078
|
return abrirCaso(ctx, {
|
|
42412
43079
|
companyId: companyFrom(params, context.companyId),
|
|
42413
|
-
workflow:
|
|
42414
|
-
titulo:
|
|
42415
|
-
referencia:
|
|
43080
|
+
workflow: str3(params.workflow) ?? "",
|
|
43081
|
+
titulo: str3(params.titulo) ?? "",
|
|
43082
|
+
referencia: str3(params.referencia),
|
|
42416
43083
|
datos: obj(params.datos),
|
|
42417
43084
|
actor: actorHumano(context)
|
|
42418
43085
|
});
|
|
@@ -42425,10 +43092,10 @@ async function manejarApi(ctx, input2) {
|
|
|
42425
43092
|
const companyId = input2.companyId;
|
|
42426
43093
|
const body = obj(input2.body);
|
|
42427
43094
|
const q = input2.query;
|
|
42428
|
-
const qs = (k) => Array.isArray(q[k]) ?
|
|
43095
|
+
const qs = (k) => Array.isArray(q[k]) ? str3(q[k][0]) : str3(q[k]);
|
|
42429
43096
|
const actor = input2.actor.actorType === "agent" ? { tipo: "agente", id: input2.actor.agentId ?? input2.actor.actorId, runId: input2.actor.runId ?? null } : { tipo: "humano", id: input2.actor.userId ?? input2.actor.actorId };
|
|
42430
43097
|
const quien = obj(body.decididoPor);
|
|
42431
|
-
const decididoPor =
|
|
43098
|
+
const decididoPor = str3(quien.nombre) ? `${str3(quien.nombre)}${str3(quien.email) ? ` (${str3(quien.email)})` : ""}` : null;
|
|
42432
43099
|
const conPersona = (datos) => decididoPor ? { ...datos, decidido_por: decididoPor } : datos;
|
|
42433
43100
|
const motivoConPersona = (motivo) => decididoPor ? `${motivo ? `${motivo} \xB7 ` : ""}decidido por ${decididoPor}` : motivo ?? null;
|
|
42434
43101
|
switch (input2.routeKey) {
|
|
@@ -42441,20 +43108,20 @@ async function manejarApi(ctx, input2) {
|
|
|
42441
43108
|
return detalle ? respuesta(200, detalle) : respuesta(404, { error: "caso_inexistente" });
|
|
42442
43109
|
}
|
|
42443
43110
|
case "aprobar":
|
|
42444
|
-
return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "aprobar", motivo:
|
|
43111
|
+
return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "aprobar", motivo: str3(body.motivo), datos: conPersona(obj(body.datos)), actor }));
|
|
42445
43112
|
case "rechazar":
|
|
42446
|
-
return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "rechazar", motivo:
|
|
43113
|
+
return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "rechazar", motivo: str3(body.motivo), datos: conPersona(obj(body.datos)), actor }));
|
|
42447
43114
|
case "marcar-cargado":
|
|
42448
|
-
return respuesta(200, await registrarCargaManual(ctx, { companyId, casoId: input2.params.casoId ?? "", valorCargado: body.valorCargado, comentario:
|
|
43115
|
+
return respuesta(200, await registrarCargaManual(ctx, { companyId, casoId: input2.params.casoId ?? "", valorCargado: body.valorCargado, comentario: str3(body.comentario), persona: decididoPor, idempotencyKey: str3(body.idempotencyKey), actor }));
|
|
42449
43116
|
case "respuesta-paciente":
|
|
42450
|
-
return respuesta(200, await registrarRespuestaPaciente(ctx, { companyId, casoId: input2.params.casoId ?? "", acepto: body.acepto === true, comentario:
|
|
43117
|
+
return respuesta(200, await registrarRespuestaPaciente(ctx, { companyId, casoId: input2.params.casoId ?? "", acepto: body.acepto === true, comentario: str3(body.comentario), persona: decididoPor, idempotencyKey: str3(body.idempotencyKey), actor }));
|
|
42451
43118
|
case "no-corresponde": {
|
|
42452
43119
|
if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "no_corresponde lo decide una persona del hospital" });
|
|
42453
|
-
if (!
|
|
42454
|
-
return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "no_corresponde", motivo: motivoConPersona(
|
|
43120
|
+
if (!str3(body.motivo)) return respuesta(422, { error: "validacion", motivo: "motivo_presente: no_corresponde requiere motivo" });
|
|
43121
|
+
return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "no_corresponde", motivo: motivoConPersona(str3(body.motivo)), datos: conPersona(obj(body.datos)), idempotencyKey: str3(body.idempotencyKey), actor }));
|
|
42455
43122
|
}
|
|
42456
43123
|
case "version":
|
|
42457
|
-
return respuesta(200, { version: manifest_default.version, capacidades: ["carga-manual", "recupero-turnos", "ofrecer-primero", "cuenta-completa", "metricas-certificadas", "facturacion-pendiente", "credenciales"], workflows: listarWorkflows().map((w) => w.clave) });
|
|
43124
|
+
return respuesta(200, { version: manifest_default.version, capacidades: ["carga-manual", "recupero-turnos", "ofrecer-primero", "cuenta-completa", "metricas-certificadas", "facturacion-pendiente", "credenciales", "demanda-pendiente", "reglas"], workflows: listarWorkflows().map((w) => w.clave) });
|
|
42458
43125
|
case "politicas":
|
|
42459
43126
|
return respuesta(200, { politicas: await listarPoliticas(ctx, companyId) });
|
|
42460
43127
|
case "serie":
|
|
@@ -42469,12 +43136,33 @@ async function manejarApi(ctx, input2) {
|
|
|
42469
43136
|
const tb = await leerTablero(ctx, companyId, (input2.params.clave ?? "").toLowerCase());
|
|
42470
43137
|
return tb ? respuesta(200, tb) : respuesta(404, { error: "tablero_inexistente" });
|
|
42471
43138
|
}
|
|
43139
|
+
case "reglas":
|
|
43140
|
+
return respuesta(200, { reglas: await listarReglas(ctx, companyId, { workflow: qs("workflow") ?? "demanda_pendiente", estado: qs("estado") }) });
|
|
43141
|
+
case "crear-regla": {
|
|
43142
|
+
if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "las reglas las crea una persona del hospital" });
|
|
43143
|
+
const workflow = str3(body.workflow) ?? "demanda_pendiente";
|
|
43144
|
+
return respuesta(200, { regla: await crearRegla(ctx, { companyId, workflow, nombre: body.nombre, tipo: body.tipo, parametros: body.parametros, contacto: body.contacto, cierre: body.cierre, actor: { tipo: "humano", id: decididoPor ?? actor.id } }) });
|
|
43145
|
+
}
|
|
43146
|
+
case "editar-regla": {
|
|
43147
|
+
if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "las reglas las edita una persona del hospital" });
|
|
43148
|
+
return respuesta(200, { regla: await editarRegla(ctx, { companyId, id: input2.params.reglaId ?? "", nombre: body.nombre, tipo: body.tipo, parametros: body.parametros, contacto: body.contacto, cierre: body.cierre, motivo: str3(body.motivo), actor: { tipo: "humano", id: decididoPor ?? actor.id } }) });
|
|
43149
|
+
}
|
|
43150
|
+
case "estado-regla": {
|
|
43151
|
+
if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "activar o pausar una regla lo hace una persona del hospital" });
|
|
43152
|
+
return respuesta(200, { regla: await cambiarEstadoRegla(ctx, { companyId, id: input2.params.reglaId ?? "", estado: body.estado, motivo: str3(body.motivo), actor: { tipo: "humano", id: decididoPor ?? actor.id } }) });
|
|
43153
|
+
}
|
|
43154
|
+
case "probar-regla": {
|
|
43155
|
+
if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "la prueba la pide una persona del hospital; el agente la informa con ops_informar_prueba_regla" });
|
|
43156
|
+
return respuesta(200, { regla: await pedirPruebaRegla(ctx, { companyId, id: input2.params.reglaId ?? "", actor: { tipo: "humano", id: decididoPor ?? actor.id } }) });
|
|
43157
|
+
}
|
|
43158
|
+
case "contacto":
|
|
43159
|
+
return respuesta(200, await registrarContacto(ctx, { companyId, casoId: input2.params.casoId ?? "", canal: body.canal, respuesta: body.respuesta, comentario: str3(body.comentario), persona: decididoPor, idempotencyKey: str3(body.idempotencyKey), actor }));
|
|
42472
43160
|
case "cambiar-politica": {
|
|
42473
|
-
const workflow =
|
|
43161
|
+
const workflow = str3(body.workflow);
|
|
42474
43162
|
if (!workflow) return respuesta(400, { error: "workflow_requerido" });
|
|
42475
43163
|
if (!esModo(body.modo)) return respuesta(400, { error: "modo_invalido", detalle: "modo debe ser corte, observa, propone, ejecuta o autonomo" });
|
|
42476
43164
|
const opciones = body.opciones && typeof body.opciones === "object" && !Array.isArray(body.opciones) ? body.opciones : null;
|
|
42477
|
-
return respuesta(200, await cambiarPolitica(ctx, { companyId, workflow, modo: body.modo, motivo: motivoConPersona(
|
|
43165
|
+
return respuesta(200, await cambiarPolitica(ctx, { companyId, workflow, modo: body.modo, motivo: motivoConPersona(str3(body.motivo)), opciones, actor }));
|
|
42478
43166
|
}
|
|
42479
43167
|
default:
|
|
42480
43168
|
return respuesta(404, { error: "ruta_desconocida", routeKey: input2.routeKey });
|