paperclip-plugin-autopilot-ops 0.1.18 → 0.1.20
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 +42 -2
- package/dist/ui/index.js +42 -2
- package/dist/worker.js +468 -85
- package/migrations/005_contactos_avisos.sql +44 -0
- package/package.json +1 -1
package/dist/manifest.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/manifest.ts
|
|
2
2
|
var PLUGIN_ID = "alephoo.autopilot-ops";
|
|
3
|
-
var PLUGIN_VERSION = "0.1.
|
|
3
|
+
var PLUGIN_VERSION = "0.1.20";
|
|
4
4
|
var DB_NAMESPACE_SLUG = "autopilot_ops";
|
|
5
5
|
var PAGE_ROUTE = "autopilot";
|
|
6
6
|
var TOOL_NAMES = {
|
|
@@ -113,7 +113,7 @@ var manifest = {
|
|
|
113
113
|
{
|
|
114
114
|
name: TOOL_NAMES.abrirCaso,
|
|
115
115
|
displayName: "Abrir caso",
|
|
116
|
-
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 de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese. Pasar `idempotencyKey` para reintentos seguros.',
|
|
116
|
+
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, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese. Pasar `idempotencyKey` para reintentos seguros.',
|
|
117
117
|
parametersSchema: {
|
|
118
118
|
type: "object",
|
|
119
119
|
properties: {
|
|
@@ -370,6 +370,46 @@ var manifest = {
|
|
|
370
370
|
capability: "api.routes.register",
|
|
371
371
|
companyResolution: { from: "body", key: "companyId" }
|
|
372
372
|
},
|
|
373
|
+
{
|
|
374
|
+
routeKey: "contactos",
|
|
375
|
+
method: "GET",
|
|
376
|
+
path: "/contactos",
|
|
377
|
+
auth: "board",
|
|
378
|
+
capability: "api.routes.register",
|
|
379
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
routeKey: "guardar-contacto",
|
|
383
|
+
method: "POST",
|
|
384
|
+
path: "/contactos",
|
|
385
|
+
auth: "board",
|
|
386
|
+
capability: "api.routes.register",
|
|
387
|
+
companyResolution: { from: "body", key: "companyId" }
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
routeKey: "avisos-pendientes",
|
|
391
|
+
method: "GET",
|
|
392
|
+
path: "/avisos/pendientes",
|
|
393
|
+
auth: "board",
|
|
394
|
+
capability: "api.routes.register",
|
|
395
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
routeKey: "avisos",
|
|
399
|
+
method: "GET",
|
|
400
|
+
path: "/avisos",
|
|
401
|
+
auth: "board",
|
|
402
|
+
capability: "api.routes.register",
|
|
403
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
routeKey: "resultado-aviso",
|
|
407
|
+
method: "POST",
|
|
408
|
+
path: "/avisos/:avisoId/resultado",
|
|
409
|
+
auth: "board",
|
|
410
|
+
capability: "api.routes.register",
|
|
411
|
+
companyResolution: { from: "body", key: "companyId" }
|
|
412
|
+
},
|
|
373
413
|
{
|
|
374
414
|
routeKey: "contacto",
|
|
375
415
|
method: "POST",
|
package/dist/ui/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
|
|
9
9
|
// src/manifest.ts
|
|
10
10
|
var PLUGIN_ID = "alephoo.autopilot-ops";
|
|
11
|
-
var PLUGIN_VERSION = "0.1.
|
|
11
|
+
var PLUGIN_VERSION = "0.1.20";
|
|
12
12
|
var DB_NAMESPACE_SLUG = "autopilot_ops";
|
|
13
13
|
var PAGE_ROUTE = "autopilot";
|
|
14
14
|
var TOOL_NAMES = {
|
|
@@ -121,7 +121,7 @@ var manifest = {
|
|
|
121
121
|
{
|
|
122
122
|
name: TOOL_NAMES.abrirCaso,
|
|
123
123
|
displayName: "Abrir caso",
|
|
124
|
-
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 de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese. Pasar `idempotencyKey` para reintentos seguros.',
|
|
124
|
+
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, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese. Pasar `idempotencyKey` para reintentos seguros.',
|
|
125
125
|
parametersSchema: {
|
|
126
126
|
type: "object",
|
|
127
127
|
properties: {
|
|
@@ -378,6 +378,46 @@ var manifest = {
|
|
|
378
378
|
capability: "api.routes.register",
|
|
379
379
|
companyResolution: { from: "body", key: "companyId" }
|
|
380
380
|
},
|
|
381
|
+
{
|
|
382
|
+
routeKey: "contactos",
|
|
383
|
+
method: "GET",
|
|
384
|
+
path: "/contactos",
|
|
385
|
+
auth: "board",
|
|
386
|
+
capability: "api.routes.register",
|
|
387
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
routeKey: "guardar-contacto",
|
|
391
|
+
method: "POST",
|
|
392
|
+
path: "/contactos",
|
|
393
|
+
auth: "board",
|
|
394
|
+
capability: "api.routes.register",
|
|
395
|
+
companyResolution: { from: "body", key: "companyId" }
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
routeKey: "avisos-pendientes",
|
|
399
|
+
method: "GET",
|
|
400
|
+
path: "/avisos/pendientes",
|
|
401
|
+
auth: "board",
|
|
402
|
+
capability: "api.routes.register",
|
|
403
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
routeKey: "avisos",
|
|
407
|
+
method: "GET",
|
|
408
|
+
path: "/avisos",
|
|
409
|
+
auth: "board",
|
|
410
|
+
capability: "api.routes.register",
|
|
411
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
routeKey: "resultado-aviso",
|
|
415
|
+
method: "POST",
|
|
416
|
+
path: "/avisos/:avisoId/resultado",
|
|
417
|
+
auth: "board",
|
|
418
|
+
capability: "api.routes.register",
|
|
419
|
+
companyResolution: { from: "body", key: "companyId" }
|
|
420
|
+
},
|
|
381
421
|
{
|
|
382
422
|
routeKey: "contacto",
|
|
383
423
|
method: "POST",
|
package/dist/worker.js
CHANGED
|
@@ -913,8 +913,8 @@ function numKeys(data) {
|
|
|
913
913
|
return keyCount;
|
|
914
914
|
}
|
|
915
915
|
var getParsedType = (data) => {
|
|
916
|
-
const
|
|
917
|
-
switch (
|
|
916
|
+
const t5 = typeof data;
|
|
917
|
+
switch (t5) {
|
|
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: ${t5}`);
|
|
957
957
|
}
|
|
958
958
|
};
|
|
959
959
|
var propertyKeyTypes = /* @__PURE__ */ new Set(["string", "number", "symbol"]);
|
|
@@ -1253,8 +1253,8 @@ function getLengthableOrigin(input2) {
|
|
|
1253
1253
|
return "unknown";
|
|
1254
1254
|
}
|
|
1255
1255
|
function parsedType(data) {
|
|
1256
|
-
const
|
|
1257
|
-
switch (
|
|
1256
|
+
const t5 = typeof data;
|
|
1257
|
+
switch (t5) {
|
|
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 t5;
|
|
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 t5 = _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 (t5 === "never")
|
|
3512
3512
|
unrecognized.push(key);
|
|
3513
3513
|
continue;
|
|
3514
3514
|
}
|
|
3515
|
-
if (
|
|
3515
|
+
if (t5 === "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 = (t5) => t5 ? TypeNames[t5] : void 0;
|
|
7752
|
+
const typeLabel = (t5) => {
|
|
7753
|
+
const e = typeEntry(t5);
|
|
7754
7754
|
if (e)
|
|
7755
7755
|
return e.label;
|
|
7756
|
-
return
|
|
7756
|
+
return t5 ?? TypeNames.unknown.label;
|
|
7757
7757
|
};
|
|
7758
|
-
const withDefinite = (
|
|
7759
|
-
const verbFor = (
|
|
7760
|
-
const e = typeEntry(
|
|
7758
|
+
const withDefinite = (t5) => `\u05D4${typeLabel(t5)}`;
|
|
7759
|
+
const verbFor = (t5) => {
|
|
7760
|
+
const e = typeEntry(t5);
|
|
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((t5) => t5 === "number" || t5 === "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((t5) => {
|
|
19145
|
+
const typeSchema = { ...schema, type: t5 };
|
|
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((t5) => t5.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.20";
|
|
39936
39936
|
var DB_NAMESPACE_SLUG = "autopilot_ops";
|
|
39937
39937
|
var PAGE_ROUTE = "autopilot";
|
|
39938
39938
|
var TOOL_NAMES = {
|
|
@@ -40045,7 +40045,7 @@ var manifest = {
|
|
|
40045
40045
|
{
|
|
40046
40046
|
name: TOOL_NAMES.abrirCaso,
|
|
40047
40047
|
displayName: "Abrir caso",
|
|
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 de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese. Pasar `idempotencyKey` para reintentos seguros.',
|
|
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, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese. Pasar `idempotencyKey` para reintentos seguros.',
|
|
40049
40049
|
parametersSchema: {
|
|
40050
40050
|
type: "object",
|
|
40051
40051
|
properties: {
|
|
@@ -40302,6 +40302,46 @@ var manifest = {
|
|
|
40302
40302
|
capability: "api.routes.register",
|
|
40303
40303
|
companyResolution: { from: "body", key: "companyId" }
|
|
40304
40304
|
},
|
|
40305
|
+
{
|
|
40306
|
+
routeKey: "contactos",
|
|
40307
|
+
method: "GET",
|
|
40308
|
+
path: "/contactos",
|
|
40309
|
+
auth: "board",
|
|
40310
|
+
capability: "api.routes.register",
|
|
40311
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
40312
|
+
},
|
|
40313
|
+
{
|
|
40314
|
+
routeKey: "guardar-contacto",
|
|
40315
|
+
method: "POST",
|
|
40316
|
+
path: "/contactos",
|
|
40317
|
+
auth: "board",
|
|
40318
|
+
capability: "api.routes.register",
|
|
40319
|
+
companyResolution: { from: "body", key: "companyId" }
|
|
40320
|
+
},
|
|
40321
|
+
{
|
|
40322
|
+
routeKey: "avisos-pendientes",
|
|
40323
|
+
method: "GET",
|
|
40324
|
+
path: "/avisos/pendientes",
|
|
40325
|
+
auth: "board",
|
|
40326
|
+
capability: "api.routes.register",
|
|
40327
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
40328
|
+
},
|
|
40329
|
+
{
|
|
40330
|
+
routeKey: "avisos",
|
|
40331
|
+
method: "GET",
|
|
40332
|
+
path: "/avisos",
|
|
40333
|
+
auth: "board",
|
|
40334
|
+
capability: "api.routes.register",
|
|
40335
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
40336
|
+
},
|
|
40337
|
+
{
|
|
40338
|
+
routeKey: "resultado-aviso",
|
|
40339
|
+
method: "POST",
|
|
40340
|
+
path: "/avisos/:avisoId/resultado",
|
|
40341
|
+
auth: "board",
|
|
40342
|
+
capability: "api.routes.register",
|
|
40343
|
+
companyResolution: { from: "body", key: "companyId" }
|
|
40344
|
+
},
|
|
40305
40345
|
{
|
|
40306
40346
|
routeKey: "contacto",
|
|
40307
40347
|
method: "POST",
|
|
@@ -40536,8 +40576,8 @@ function fechaAISO(v) {
|
|
|
40536
40576
|
if (fechaISO(s.slice(0, 10))) return s.slice(0, 10);
|
|
40537
40577
|
const m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})/.exec(s);
|
|
40538
40578
|
if (!m) return null;
|
|
40539
|
-
const
|
|
40540
|
-
return fechaISO(
|
|
40579
|
+
const iso5 = `${m[3]}-${m[2].padStart(2, "0")}-${m[1].padStart(2, "0")}`;
|
|
40580
|
+
return fechaISO(iso5) ? iso5 : null;
|
|
40541
40581
|
}
|
|
40542
40582
|
function leerCola(v) {
|
|
40543
40583
|
if (!Array.isArray(v) || v.length === 0) return null;
|
|
@@ -40828,6 +40868,17 @@ var REGLAS_CUENTA = {
|
|
|
40828
40868
|
};
|
|
40829
40869
|
var BONO_POR_FACTURAR = ["no_confirmado", "confirmado", "prefacturado"];
|
|
40830
40870
|
var BONO_FACTURADO = ["facturado", "refacturado"];
|
|
40871
|
+
function leerResponsable(v) {
|
|
40872
|
+
if (!v || typeof v !== "object") return null;
|
|
40873
|
+
const r = v;
|
|
40874
|
+
const tipo = texto(r.tipo);
|
|
40875
|
+
if (tipo !== "profesional" && tipo !== "rol") return null;
|
|
40876
|
+
const id = refExterna(r.id);
|
|
40877
|
+
const nombre = texto(r.nombre)?.slice(0, 120) ?? null;
|
|
40878
|
+
if (tipo === "profesional" && !id) return null;
|
|
40879
|
+
if (tipo === "rol" && !id) return null;
|
|
40880
|
+
return { tipo, id, nombre };
|
|
40881
|
+
}
|
|
40831
40882
|
function leerFaltantes(v) {
|
|
40832
40883
|
if (!Array.isArray(v)) return null;
|
|
40833
40884
|
const out = [];
|
|
@@ -40837,17 +40888,18 @@ function leerFaltantes(v) {
|
|
|
40837
40888
|
const regla = texto(r.regla);
|
|
40838
40889
|
const detalle = texto(r.detalle);
|
|
40839
40890
|
if (!regla || !REGLAS_CUENTA[regla] || !detalle) return null;
|
|
40840
|
-
|
|
40891
|
+
const responsable = leerResponsable(r.responsable);
|
|
40892
|
+
out.push(responsable ? { regla, detalle, item: refExterna(r.item), responsable } : { regla, detalle, item: refExterna(r.item) });
|
|
40841
40893
|
}
|
|
40842
40894
|
return out;
|
|
40843
40895
|
}
|
|
40844
40896
|
function validarFaltantes(campo, v, alMenosUno) {
|
|
40845
|
-
const
|
|
40846
|
-
if (!
|
|
40897
|
+
const lista2 = leerFaltantes(v);
|
|
40898
|
+
if (!lista2) {
|
|
40847
40899
|
return falla("faltantes_validos", `${campo} tiene que ser una lista [{regla, detalle, item?}] con reglas del cat\xE1logo: ${Object.keys(REGLAS_CUENTA).join(", ")}`);
|
|
40848
40900
|
}
|
|
40849
|
-
if (alMenosUno &&
|
|
40850
|
-
return ok("faltantes_validos",
|
|
40901
|
+
if (alMenosUno && lista2.length === 0) return falla("faltantes_validos", `${campo} est\xE1 vac\xEDo: una cuenta sin faltantes no se abre`);
|
|
40902
|
+
return ok("faltantes_validos", lista2.length === 0 ? "sin faltantes" : lista2.length === 1 ? "1 faltante" : `${lista2.length} faltantes`);
|
|
40851
40903
|
}
|
|
40852
40904
|
function estadoBono(v) {
|
|
40853
40905
|
const s = texto(v);
|
|
@@ -41133,9 +41185,9 @@ function diasEntre(desde, hasta) {
|
|
|
41133
41185
|
function horaDentro(hora, desde, hasta) {
|
|
41134
41186
|
const h = horaHM(hora);
|
|
41135
41187
|
const d = horaHM(desde);
|
|
41136
|
-
const
|
|
41137
|
-
if (!h || !d || !
|
|
41138
|
-
return h >= d && h <=
|
|
41188
|
+
const t5 = horaHM(hasta);
|
|
41189
|
+
if (!h || !d || !t5) return false;
|
|
41190
|
+
return h >= d && h <= t5;
|
|
41139
41191
|
}
|
|
41140
41192
|
function codigoContacto(v) {
|
|
41141
41193
|
const s = texto(v);
|
|
@@ -41295,9 +41347,9 @@ function evaluarTransicion(input2) {
|
|
|
41295
41347
|
if (!wf) {
|
|
41296
41348
|
return { ok: false, codigo: "workflow_desconocido", motivo: `workflow desconocido: ${caso.workflow}`, validaciones: [] };
|
|
41297
41349
|
}
|
|
41298
|
-
const def2 = wf.transiciones.find((
|
|
41350
|
+
const def2 = wf.transiciones.find((t5) => t5.via === via);
|
|
41299
41351
|
if (!def2) {
|
|
41300
|
-
const disponibles = wf.transiciones.filter((
|
|
41352
|
+
const disponibles = wf.transiciones.filter((t5) => t5.de.includes(caso.estado)).map((t5) => t5.via);
|
|
41301
41353
|
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: [] };
|
|
41302
41354
|
}
|
|
41303
41355
|
if (!def2.de.includes(caso.estado)) {
|
|
@@ -41340,10 +41392,10 @@ function evaluarTransicion(input2) {
|
|
|
41340
41392
|
function transicionesDisponibles(caso, actor) {
|
|
41341
41393
|
const wf = getWorkflow(caso.workflow);
|
|
41342
41394
|
if (!wf) return [];
|
|
41343
|
-
return wf.transiciones.filter((
|
|
41395
|
+
return wf.transiciones.filter((t5) => t5.de.includes(caso.estado) && (!actor || t5.actor.includes(actor)));
|
|
41344
41396
|
}
|
|
41345
41397
|
function transicionCargaManual(caso) {
|
|
41346
|
-
return transicionesDisponibles(caso, "humano").find((
|
|
41398
|
+
return transicionesDisponibles(caso, "humano").find((t5) => t5.cargaManual) ?? null;
|
|
41347
41399
|
}
|
|
41348
41400
|
function esModo(v) {
|
|
41349
41401
|
return typeof v === "string" && MODOS.includes(v);
|
|
@@ -41438,10 +41490,10 @@ function poblacion(problemas, p) {
|
|
|
41438
41490
|
else problemas.push("sexo: debe ser F o M");
|
|
41439
41491
|
}
|
|
41440
41492
|
if (p.obras_sociales !== void 0 && p.obras_sociales !== null) {
|
|
41441
|
-
const
|
|
41493
|
+
const lista2 = Array.isArray(p.obras_sociales) ? p.obras_sociales.map((x) => str(x)).filter((x) => x !== null) : [];
|
|
41442
41494
|
if (!Array.isArray(p.obras_sociales)) problemas.push("obras_sociales: debe ser una lista de nombres");
|
|
41443
|
-
else if (
|
|
41444
|
-
else if (
|
|
41495
|
+
else if (lista2.length > 20) problemas.push("obras_sociales: como m\xE1ximo 20");
|
|
41496
|
+
else if (lista2.length > 0) out.obras_sociales = lista2.map((x) => x.slice(0, 80));
|
|
41445
41497
|
}
|
|
41446
41498
|
return out;
|
|
41447
41499
|
}
|
|
@@ -41670,8 +41722,8 @@ async function requerirRegla(ctx, companyId, id) {
|
|
|
41670
41722
|
if (!r) throw new OpsError("regla_inexistente", `no existe la regla ${id} en esta instituci\xF3n`);
|
|
41671
41723
|
return r;
|
|
41672
41724
|
}
|
|
41673
|
-
function cambio(version2, actor,
|
|
41674
|
-
return { version: version2, fecha: (/* @__PURE__ */ new Date()).toISOString(), actorTipo: actor.tipo, actorId: actor.id, cambio:
|
|
41725
|
+
function cambio(version2, actor, texto4) {
|
|
41726
|
+
return { version: version2, fecha: (/* @__PURE__ */ new Date()).toISOString(), actorTipo: actor.tipo, actorId: actor.id, cambio: texto4 };
|
|
41675
41727
|
}
|
|
41676
41728
|
function soloPersonas(actor, que) {
|
|
41677
41729
|
if (actor.tipo !== "humano") throw new OpsError("actor_no_permitido", `${que} lo hace una persona de la instituci\xF3n desde el portal; el agente solo lee las reglas e informa la prueba`);
|
|
@@ -41903,8 +41955,8 @@ function describirOpciones(antes, despues) {
|
|
|
41903
41955
|
const plazosAntes = JSON.stringify(leerPlazos(antes.plazos));
|
|
41904
41956
|
const plazosDespues = leerPlazos(despues.plazos);
|
|
41905
41957
|
if (plazosAntes !== JSON.stringify(plazosDespues)) {
|
|
41906
|
-
const
|
|
41907
|
-
partes.push(`plazos de presentaci\xF3n: ${
|
|
41958
|
+
const lista2 = Object.entries(plazosDespues).map(([os, d]) => `${os} ${d} d\xEDas`);
|
|
41959
|
+
partes.push(`plazos de presentaci\xF3n: ${lista2.length ? lista2.join(", ") : "ninguno fijado (se usa el habitual)"}`);
|
|
41908
41960
|
}
|
|
41909
41961
|
return partes.length ? partes.join(" \xB7 ") : null;
|
|
41910
41962
|
}
|
|
@@ -42275,8 +42327,8 @@ async function transicionar(ctx, input2) {
|
|
|
42275
42327
|
}
|
|
42276
42328
|
};
|
|
42277
42329
|
}
|
|
42278
|
-
var TIPOS_ACCION = ["lectura", "escritura", "verificacion"];
|
|
42279
|
-
var CONECTORES = ["api", "base", "archivo", "pantalla", "manual"];
|
|
42330
|
+
var TIPOS_ACCION = ["lectura", "escritura", "verificacion", "aviso"];
|
|
42331
|
+
var CONECTORES = ["api", "base", "archivo", "pantalla", "manual", "mail", "whatsapp"];
|
|
42280
42332
|
var RESULTADOS_ACCION = ["ok", "error", "pendiente"];
|
|
42281
42333
|
async function registrarAccion(ctx, input2) {
|
|
42282
42334
|
const caso = await leerCaso(ctx, input2.companyId, input2.casoId);
|
|
@@ -42328,10 +42380,10 @@ function numero(v) {
|
|
|
42328
42380
|
}
|
|
42329
42381
|
function motivoSinCarga(caso) {
|
|
42330
42382
|
const wf = getWorkflow(caso.workflow);
|
|
42331
|
-
if (!wf || !wf.transiciones.some((
|
|
42383
|
+
if (!wf || !wf.transiciones.some((t5) => t5.cargaManual)) return `el workflow ${caso.workflow} no tiene carga manual en el sistema de gesti\xF3n`;
|
|
42332
42384
|
if (wf.finalesOk.includes(caso.estado)) return "ya est\xE1 cargado y comprobado";
|
|
42333
42385
|
if (wf.finalesNoOk.includes(caso.estado)) return `est\xE1 ${caso.estado}: no se carga`;
|
|
42334
|
-
if (wf.transiciones.some((
|
|
42386
|
+
if (wf.transiciones.some((t5) => t5.cargaManual && t5.a === caso.estado)) return "ya figura como cargado; falta la comprobaci\xF3n del puesto";
|
|
42335
42387
|
return "todav\xEDa no est\xE1 aprobado: no hay nada para cargar";
|
|
42336
42388
|
}
|
|
42337
42389
|
async function registrarCargaManual(ctx, input2) {
|
|
@@ -42596,8 +42648,8 @@ async function serie(ctx, companyId, diasInput) {
|
|
|
42596
42648
|
puntos.set(d, { dia: d, abiertos: 0, cerradosOk: 0, rechazados: 0, minutos: 0, acciones: 0 });
|
|
42597
42649
|
}
|
|
42598
42650
|
const descartado = { dia: "", abiertos: 0, cerradosOk: 0, rechazados: 0, minutos: 0, acciones: 0 };
|
|
42599
|
-
const punto = (
|
|
42600
|
-
const k = typeof
|
|
42651
|
+
const punto = (dia2) => {
|
|
42652
|
+
const k = typeof dia2 === "string" ? dia2.slice(0, 10) : "";
|
|
42601
42653
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(k)) return descartado;
|
|
42602
42654
|
if (!puntos.has(k)) puntos.set(k, { dia: k, abiertos: 0, cerradosOk: 0, rechazados: 0, minutos: 0, acciones: 0 });
|
|
42603
42655
|
return puntos.get(k);
|
|
@@ -42654,6 +42706,322 @@ function errorParaAgente(err) {
|
|
|
42654
42706
|
return { error: `error_interno: ${message2}`, data: { codigo: "error_interno" } };
|
|
42655
42707
|
}
|
|
42656
42708
|
|
|
42709
|
+
// src/avisos.ts
|
|
42710
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
42711
|
+
function t3(ctx, table) {
|
|
42712
|
+
return `${ctx.db.namespace}.${table}`;
|
|
42713
|
+
}
|
|
42714
|
+
function texto2(v) {
|
|
42715
|
+
return typeof v === "string" && v.trim().length > 0 ? v.trim() : typeof v === "number" ? String(v) : null;
|
|
42716
|
+
}
|
|
42717
|
+
function iso3(v) {
|
|
42718
|
+
if (v instanceof Date) return v.toISOString();
|
|
42719
|
+
if (typeof v === "string" && v) return v;
|
|
42720
|
+
return null;
|
|
42721
|
+
}
|
|
42722
|
+
function dia(v) {
|
|
42723
|
+
if (v instanceof Date) return v.toISOString().slice(0, 10);
|
|
42724
|
+
return String(v ?? "").slice(0, 10);
|
|
42725
|
+
}
|
|
42726
|
+
function lista(v) {
|
|
42727
|
+
if (Array.isArray(v)) return v;
|
|
42728
|
+
if (typeof v === "string") {
|
|
42729
|
+
try {
|
|
42730
|
+
const p = JSON.parse(v);
|
|
42731
|
+
return Array.isArray(p) ? p : [];
|
|
42732
|
+
} catch {
|
|
42733
|
+
return [];
|
|
42734
|
+
}
|
|
42735
|
+
}
|
|
42736
|
+
return [];
|
|
42737
|
+
}
|
|
42738
|
+
var ROLES = {
|
|
42739
|
+
admision: "Admisi\xF3n",
|
|
42740
|
+
aranceles: "Aranceles y convenios",
|
|
42741
|
+
facturacion: "Facturaci\xF3n",
|
|
42742
|
+
internacion: "Internaci\xF3n",
|
|
42743
|
+
personal: "Administraci\xF3n de personal"
|
|
42744
|
+
};
|
|
42745
|
+
var ESPERAN_PERSONA = {
|
|
42746
|
+
cuenta_completa: ["para_completar", "con_faltantes"],
|
|
42747
|
+
facturacion_pendiente: ["por_presentar"],
|
|
42748
|
+
credencial_profesional: ["observada"]
|
|
42749
|
+
};
|
|
42750
|
+
var CANALES_AVISO = ["mail", "whatsapp"];
|
|
42751
|
+
var HORARIO_AVISOS = { desde: "08:00", hasta: "18:00" };
|
|
42752
|
+
var DIAS_ENTRE_AVISOS = 3;
|
|
42753
|
+
var MAX_AVISOS = 2;
|
|
42754
|
+
var MAX_INTENTOS_ENVIO = 3;
|
|
42755
|
+
var DIAS_VENTANA = 45;
|
|
42756
|
+
var OBSERVACIONES_MATRICULA = {
|
|
42757
|
+
sin_matricula: "falta cargar la matr\xEDcula",
|
|
42758
|
+
no_figura: "la matr\xEDcula no figura en el registro nacional",
|
|
42759
|
+
otra_persona: "la matr\xEDcula figura a nombre de otra persona",
|
|
42760
|
+
inhabilitada: "la matr\xEDcula est\xE1 inhabilitada",
|
|
42761
|
+
sancion: "la matr\xEDcula tiene una sanci\xF3n registrada"
|
|
42762
|
+
};
|
|
42763
|
+
var rol = (clave) => ({ clave: `rol:${clave}`, nombre: ROLES[clave] ?? clave, reserva: null });
|
|
42764
|
+
function pendientesDe(caso) {
|
|
42765
|
+
const estados = ESPERAN_PERSONA[caso.workflow];
|
|
42766
|
+
if (!estados || !estados.includes(caso.estado)) return [];
|
|
42767
|
+
const d = caso.datos;
|
|
42768
|
+
const bono = `Bono ${texto2(d.bono_numero) ?? texto2(d.bono_id) ?? "sin n\xFAmero"}${texto2(d.obra_social) ? ` \xB7 ${texto2(d.obra_social)}` : ""}`;
|
|
42769
|
+
const base = { casoId: caso.id, workflow: caso.workflow, titulo: caso.titulo };
|
|
42770
|
+
if (caso.workflow === "cuenta_completa") {
|
|
42771
|
+
const out = [];
|
|
42772
|
+
for (const f of leerFaltantes(d.faltantes) ?? []) {
|
|
42773
|
+
const nombreRegla = REGLAS_CUENTA[f.regla]?.nombre ?? f.regla;
|
|
42774
|
+
if (f.regla === "epicrisis_faltante") {
|
|
42775
|
+
const p = f.responsable?.tipo === "profesional" && f.responsable.id ? f.responsable : null;
|
|
42776
|
+
const internacion = texto2(d.internacion_id) ? `Internaci\xF3n ${texto2(d.internacion_id)}` : bono;
|
|
42777
|
+
out.push({
|
|
42778
|
+
...base,
|
|
42779
|
+
que: `${internacion}: falta la epicrisis`,
|
|
42780
|
+
responsable: p ? { clave: `profesional:${p.id}`, nombre: p.nombre ?? `Profesional ${p.id}`, reserva: "rol:internacion" } : rol("internacion")
|
|
42781
|
+
});
|
|
42782
|
+
} else if (f.regla === "prestacion_sin_valor") {
|
|
42783
|
+
out.push({ ...base, que: `${bono}: ${nombreRegla.toLowerCase()}`, responsable: rol("aranceles") });
|
|
42784
|
+
} else {
|
|
42785
|
+
out.push({ ...base, que: `${bono}: ${nombreRegla.toLowerCase()}`, responsable: rol("admision") });
|
|
42786
|
+
}
|
|
42787
|
+
}
|
|
42788
|
+
return out;
|
|
42789
|
+
}
|
|
42790
|
+
if (caso.workflow === "facturacion_pendiente") {
|
|
42791
|
+
const vence = texto2(d.vence_el);
|
|
42792
|
+
return [{ ...base, que: `${bono}: sin presentar${vence ? `, vence el ${vence.slice(8, 10)}/${vence.slice(5, 7)}` : ""}`, responsable: rol("facturacion") }];
|
|
42793
|
+
}
|
|
42794
|
+
if (caso.workflow === "credencial_profesional") {
|
|
42795
|
+
const id = texto2(d.profesional_id);
|
|
42796
|
+
const nombre = texto2(d.profesional) ?? (id ? `Profesional ${id}` : "Profesional");
|
|
42797
|
+
const obs = texto2(d.observacion);
|
|
42798
|
+
const que = `${nombre}: ${(obs && OBSERVACIONES_MATRICULA[obs]) ?? "la matr\xEDcula tiene una observaci\xF3n del registro nacional"}`;
|
|
42799
|
+
return [{ ...base, que, responsable: id ? { clave: `profesional:${id}`, nombre, reserva: "rol:personal" } : rol("personal") }];
|
|
42800
|
+
}
|
|
42801
|
+
return [];
|
|
42802
|
+
}
|
|
42803
|
+
var CLAVE_RE = /^(profesional:[A-Za-z0-9_-]{1,40}|rol:(admision|aranceles|facturacion|internacion|personal))$/;
|
|
42804
|
+
var MAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
42805
|
+
var COLS_CONTACTO = "id, clave, nombre, mail, whatsapp, activo, updated_at";
|
|
42806
|
+
function mapContacto(r) {
|
|
42807
|
+
return { id: String(r.id), clave: String(r.clave), nombre: String(r.nombre), mail: texto2(r.mail), whatsapp: texto2(r.whatsapp), activo: r.activo !== false, actualizadoEn: iso3(r.updated_at) };
|
|
42808
|
+
}
|
|
42809
|
+
async function listarContactos(ctx, companyId) {
|
|
42810
|
+
const rows = await ctx.db.query(`SELECT ${COLS_CONTACTO} FROM ${t3(ctx, "contacto")} WHERE company_id = $1 ORDER BY nombre ASC`, [companyId]);
|
|
42811
|
+
return rows.map(mapContacto);
|
|
42812
|
+
}
|
|
42813
|
+
function leerWhatsapp(v) {
|
|
42814
|
+
const s = typeof v === "string" ? v.replace(/[\s()+.-]/g, "") : "";
|
|
42815
|
+
if (!s) return null;
|
|
42816
|
+
if (!/^\d{8,15}$/.test(s)) throw new OpsError("validacion", "whatsapp: tiene que ser un n\xFAmero de 8 a 15 d\xEDgitos (con c\xF3digo de pa\xEDs, sin espacios)");
|
|
42817
|
+
return s;
|
|
42818
|
+
}
|
|
42819
|
+
async function guardarContacto(ctx, input2) {
|
|
42820
|
+
if (input2.actor.tipo !== "humano") throw new OpsError("actor_no_permitido", "los contactos los carga una persona de la instituci\xF3n desde el portal");
|
|
42821
|
+
const clave = texto2(input2.clave) ?? "";
|
|
42822
|
+
if (!CLAVE_RE.test(clave)) throw new OpsError("validacion", "clave: tiene que ser profesional:<id> o rol:<admision|aranceles|facturacion|internacion|personal>");
|
|
42823
|
+
const mail = texto2(input2.mail)?.toLowerCase() ?? null;
|
|
42824
|
+
if (mail && !MAIL_RE.test(mail)) throw new OpsError("validacion", "mail: no tiene formato de correo");
|
|
42825
|
+
const whatsapp = leerWhatsapp(input2.whatsapp);
|
|
42826
|
+
const activo = input2.activo !== false;
|
|
42827
|
+
if (activo && !mail && !whatsapp) throw new OpsError("validacion", "hace falta un mail o un WhatsApp para poder avisar");
|
|
42828
|
+
const existente = (await ctx.db.query(`SELECT ${COLS_CONTACTO} FROM ${t3(ctx, "contacto")} WHERE company_id = $1 AND clave = $2 LIMIT 1`, [input2.companyId, clave]))[0];
|
|
42829
|
+
const nombre = texto2(input2.nombre)?.slice(0, 120) ?? (existente ? String(existente.nombre) : clave.startsWith("rol:") ? ROLES[clave.slice(4)] ?? clave : null);
|
|
42830
|
+
if (!nombre) throw new OpsError("validacion", "nombre: es obligatorio para un profesional nuevo");
|
|
42831
|
+
if (existente) {
|
|
42832
|
+
await ctx.db.execute(
|
|
42833
|
+
`UPDATE ${t3(ctx, "contacto")} SET nombre = $3, mail = $4, whatsapp = $5, activo = $6, actor_tipo = $7, actor_id = $8, updated_at = now() WHERE company_id = $1 AND clave = $2`,
|
|
42834
|
+
[input2.companyId, clave, nombre, mail, whatsapp, activo, input2.actor.tipo, input2.actor.id]
|
|
42835
|
+
);
|
|
42836
|
+
return { id: String(existente.id), clave, nombre, mail, whatsapp, activo, actualizadoEn: (/* @__PURE__ */ new Date()).toISOString() };
|
|
42837
|
+
}
|
|
42838
|
+
const id = randomUUID3();
|
|
42839
|
+
await ctx.db.execute(
|
|
42840
|
+
`INSERT INTO ${t3(ctx, "contacto")} (id, company_id, clave, nombre, mail, whatsapp, activo, actor_tipo, actor_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
|
42841
|
+
[id, input2.companyId, clave, nombre, mail, whatsapp, activo, input2.actor.tipo, input2.actor.id]
|
|
42842
|
+
);
|
|
42843
|
+
return { id, clave, nombre, mail, whatsapp, activo, actualizadoEn: (/* @__PURE__ */ new Date()).toISOString() };
|
|
42844
|
+
}
|
|
42845
|
+
var COLS_AVISO = "id, clave, nombre, canal, destino, dia, casos, asunto, texto, estado, intentos_envio, error, proveedor_id, enviado_en, created_at";
|
|
42846
|
+
function mapAviso(r) {
|
|
42847
|
+
return {
|
|
42848
|
+
id: String(r.id),
|
|
42849
|
+
clave: String(r.clave),
|
|
42850
|
+
nombre: String(r.nombre),
|
|
42851
|
+
canal: String(r.canal) === "whatsapp" ? "whatsapp" : "mail",
|
|
42852
|
+
destino: String(r.destino),
|
|
42853
|
+
dia: dia(r.dia),
|
|
42854
|
+
casos: lista(r.casos).map((c) => ({ casoId: String(c.casoId), workflow: String(c.workflow), que: String(c.que), intento: Number(c.intento) || 1 })),
|
|
42855
|
+
asunto: String(r.asunto),
|
|
42856
|
+
texto: String(r.texto),
|
|
42857
|
+
estado: ["pendiente", "enviado", "fallido", "cancelado"].includes(String(r.estado)) ? String(r.estado) : "pendiente",
|
|
42858
|
+
intentosEnvio: Number(r.intentos_envio) || 0,
|
|
42859
|
+
error: texto2(r.error),
|
|
42860
|
+
proveedorId: texto2(r.proveedor_id),
|
|
42861
|
+
enviadoEn: iso3(r.enviado_en),
|
|
42862
|
+
creadoEn: iso3(r.created_at)
|
|
42863
|
+
};
|
|
42864
|
+
}
|
|
42865
|
+
async function listarAvisos(ctx, companyId, opciones = {}) {
|
|
42866
|
+
const desde = opciones.desde ?? sumarDias(ahoraEnHospital().hoy, -DIAS_VENTANA);
|
|
42867
|
+
const rows = await ctx.db.query(`SELECT ${COLS_AVISO} FROM ${t3(ctx, "aviso")} WHERE company_id = $1 AND dia >= $2 ORDER BY created_at ASC`, [companyId, desde]);
|
|
42868
|
+
return rows.map(mapAviso);
|
|
42869
|
+
}
|
|
42870
|
+
async function casosQueEsperan(ctx, companyId, workflows) {
|
|
42871
|
+
const out = [];
|
|
42872
|
+
for (const [workflow, estados] of Object.entries(ESPERAN_PERSONA)) {
|
|
42873
|
+
if (workflows && workflows.length > 0 && !workflows.includes(workflow)) continue;
|
|
42874
|
+
out.push(...await listarCasos(ctx, { companyId, workflow, estados, limit: 100 }));
|
|
42875
|
+
}
|
|
42876
|
+
return out;
|
|
42877
|
+
}
|
|
42878
|
+
async function responsablesPendientes(ctx, companyId, workflows) {
|
|
42879
|
+
const contactos = await listarContactos(ctx, companyId);
|
|
42880
|
+
const con = new Map(contactos.filter((c) => c.activo && (c.mail || c.whatsapp)).map((c) => [c.clave, c]));
|
|
42881
|
+
const acumulado = /* @__PURE__ */ new Map();
|
|
42882
|
+
for (const caso of await casosQueEsperan(ctx, companyId, workflows)) {
|
|
42883
|
+
for (const p of pendientesDe(caso)) {
|
|
42884
|
+
const r = acumulado.get(p.responsable.clave) ?? { clave: p.responsable.clave, nombre: p.responsable.nombre, pendientes: 0, tieneContacto: con.has(p.responsable.clave), reserva: p.responsable.reserva };
|
|
42885
|
+
r.pendientes += 1;
|
|
42886
|
+
acumulado.set(r.clave, r);
|
|
42887
|
+
}
|
|
42888
|
+
}
|
|
42889
|
+
const responsables = [...acumulado.values()].sort((a, b) => Number(a.tieneContacto) - Number(b.tieneContacto) || b.pendientes - a.pendientes || a.nombre.localeCompare(b.nombre));
|
|
42890
|
+
return { contactos, responsables };
|
|
42891
|
+
}
|
|
42892
|
+
function esDiaHabil(hoy) {
|
|
42893
|
+
const d = (/* @__PURE__ */ new Date(`${hoy}T12:00:00Z`)).getUTCDay();
|
|
42894
|
+
return d >= 1 && d <= 5;
|
|
42895
|
+
}
|
|
42896
|
+
function enHorarioDeAvisos(hoy, hora) {
|
|
42897
|
+
return esDiaHabil(hoy) && hora >= HORARIO_AVISOS.desde && hora < HORARIO_AVISOS.hasta;
|
|
42898
|
+
}
|
|
42899
|
+
function redactarAviso(nombre, institucion, pendientes) {
|
|
42900
|
+
const n = pendientes.length;
|
|
42901
|
+
const repetidos = pendientes.filter((p) => p.intento > 1).length;
|
|
42902
|
+
const asunto = `${n === 1 ? "1 pendiente" : `${n} pendientes`} para resolver en el HIS \xB7 ${institucion}`;
|
|
42903
|
+
const lineas = pendientes.map((p) => `- ${p.que}${p.intento > 1 ? " (segundo aviso)" : ""}`);
|
|
42904
|
+
const texto4 = [
|
|
42905
|
+
`Hola ${nombre}:`,
|
|
42906
|
+
"",
|
|
42907
|
+
`Desde ${institucion}, Autopilot Opera te avisa que ${n === 1 ? "ten\xE9s 1 pendiente" : `ten\xE9s ${n} pendientes`} para resolver en el HIS:`,
|
|
42908
|
+
"",
|
|
42909
|
+
...lineas,
|
|
42910
|
+
"",
|
|
42911
|
+
repetidos > 0 ? "Los marcados como segundo aviso ya se avisaron antes; si no se resuelven, pasan a la direcci\xF3n." : "Cuando lo cargues en el HIS, el puesto lo comprueba solo: no hace falta responder este mensaje.",
|
|
42912
|
+
"",
|
|
42913
|
+
"Autopilot Opera \xB7 avisos autom\xE1ticos de la administraci\xF3n"
|
|
42914
|
+
].join("\n");
|
|
42915
|
+
return { asunto, texto: texto4 };
|
|
42916
|
+
}
|
|
42917
|
+
async function generarAvisos(ctx, input2) {
|
|
42918
|
+
const { hoy, hora } = ahoraEnHospital(input2.ahora);
|
|
42919
|
+
const canales = input2.canales.filter((c) => CANALES_AVISO.includes(c));
|
|
42920
|
+
const todos = await listarAvisos(ctx, input2.companyId);
|
|
42921
|
+
const pendientesHoy = () => todos.filter((a) => a.estado === "pendiente");
|
|
42922
|
+
if (!enHorarioDeAvisos(hoy, hora) || canales.length === 0) return { avisos: pendientesHoy(), generados: 0, sinContacto: 0, fueraDeHorario: true };
|
|
42923
|
+
const contactos = await listarContactos(ctx, input2.companyId);
|
|
42924
|
+
const con = new Map(contactos.filter((c) => c.activo && (c.mail || c.whatsapp)).map((c) => [c.clave, c]));
|
|
42925
|
+
const historial = /* @__PURE__ */ new Map();
|
|
42926
|
+
for (const a of todos) {
|
|
42927
|
+
if (a.estado !== "enviado") continue;
|
|
42928
|
+
for (const c of a.casos) {
|
|
42929
|
+
const k = `${c.casoId}|${a.clave}`;
|
|
42930
|
+
const h = historial.get(k) ?? { veces: 0, ultimo: "" };
|
|
42931
|
+
h.veces += 1;
|
|
42932
|
+
if (a.dia > h.ultimo) h.ultimo = a.dia;
|
|
42933
|
+
historial.set(k, h);
|
|
42934
|
+
}
|
|
42935
|
+
}
|
|
42936
|
+
const yaHoy = new Set(todos.filter((a) => a.dia === hoy && a.estado !== "cancelado").map((a) => a.clave));
|
|
42937
|
+
const porDestinatario = /* @__PURE__ */ new Map();
|
|
42938
|
+
let sinContacto = 0;
|
|
42939
|
+
for (const caso of await casosQueEsperan(ctx, input2.companyId)) {
|
|
42940
|
+
for (const p of pendientesDe(caso)) {
|
|
42941
|
+
const contacto = con.get(p.responsable.clave) ?? (p.responsable.reserva ? con.get(p.responsable.reserva) : void 0);
|
|
42942
|
+
if (!contacto) {
|
|
42943
|
+
sinContacto += 1;
|
|
42944
|
+
continue;
|
|
42945
|
+
}
|
|
42946
|
+
const h = historial.get(`${caso.id}|${contacto.clave}`) ?? { veces: 0, ultimo: "" };
|
|
42947
|
+
if (h.veces >= MAX_AVISOS) continue;
|
|
42948
|
+
if (h.veces > 0 && sumarDias(h.ultimo, DIAS_ENTRE_AVISOS) > hoy) continue;
|
|
42949
|
+
if (yaHoy.has(contacto.clave)) continue;
|
|
42950
|
+
const g = porDestinatario.get(contacto.clave) ?? { contacto, pendientes: [] };
|
|
42951
|
+
g.pendientes.push({ casoId: caso.id, workflow: caso.workflow, que: p.que, intento: h.veces + 1, titulo: caso.titulo });
|
|
42952
|
+
porDestinatario.set(contacto.clave, g);
|
|
42953
|
+
}
|
|
42954
|
+
}
|
|
42955
|
+
let generados = 0;
|
|
42956
|
+
for (const g of porDestinatario.values()) {
|
|
42957
|
+
const canal = g.contacto.mail && canales.includes("mail") ? "mail" : g.contacto.whatsapp && canales.includes("whatsapp") ? "whatsapp" : null;
|
|
42958
|
+
if (!canal) continue;
|
|
42959
|
+
const destino = canal === "mail" ? String(g.contacto.mail) : String(g.contacto.whatsapp);
|
|
42960
|
+
const { asunto, texto: cuerpo } = redactarAviso(g.contacto.nombre, input2.institucion, g.pendientes);
|
|
42961
|
+
const casos = g.pendientes.map(({ casoId, workflow, que, intento }) => ({ casoId, workflow, que, intento }));
|
|
42962
|
+
const id = randomUUID3();
|
|
42963
|
+
await ctx.db.execute(
|
|
42964
|
+
`INSERT INTO ${t3(ctx, "aviso")} (id, company_id, clave, nombre, canal, destino, dia, casos, asunto, texto) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9, $10)`,
|
|
42965
|
+
[id, input2.companyId, g.contacto.clave, g.contacto.nombre, canal, destino, hoy, JSON.stringify(casos), asunto, cuerpo]
|
|
42966
|
+
);
|
|
42967
|
+
todos.push({ id, clave: g.contacto.clave, nombre: g.contacto.nombre, canal, destino, dia: hoy, casos, asunto, texto: cuerpo, estado: "pendiente", intentosEnvio: 0, error: null, proveedorId: null, enviadoEn: null, creadoEn: (/* @__PURE__ */ new Date()).toISOString() });
|
|
42968
|
+
generados += 1;
|
|
42969
|
+
}
|
|
42970
|
+
return { avisos: pendientesHoy(), generados, sinContacto, fueraDeHorario: false };
|
|
42971
|
+
}
|
|
42972
|
+
async function informarResultadoAviso(ctx, input2) {
|
|
42973
|
+
const fila = (await ctx.db.query(`SELECT ${COLS_AVISO} FROM ${t3(ctx, "aviso")} WHERE company_id = $1 AND id = $2 LIMIT 1`, [input2.companyId, input2.avisoId]))[0];
|
|
42974
|
+
if (!fila) throw new OpsError("aviso_inexistente", `no existe el aviso ${input2.avisoId} en esta instituci\xF3n`);
|
|
42975
|
+
const aviso = mapAviso(fila);
|
|
42976
|
+
if (aviso.estado !== "pendiente") throw new OpsError("aviso_cerrado", `el aviso ya est\xE1 ${aviso.estado}`);
|
|
42977
|
+
const actor = { tipo: "sistema", id: input2.actor.id ?? "portal" };
|
|
42978
|
+
if (input2.resultado === "enviado") {
|
|
42979
|
+
await ctx.db.execute(
|
|
42980
|
+
`UPDATE ${t3(ctx, "aviso")} SET estado = $3, proveedor_id = $4, enviado_en = now(), intentos_envio = intentos_envio + 1, error = NULL, updated_at = now() WHERE company_id = $1 AND id = $2`,
|
|
42981
|
+
[input2.companyId, aviso.id, "enviado", texto2(input2.proveedorId)]
|
|
42982
|
+
);
|
|
42983
|
+
for (const c of aviso.casos) {
|
|
42984
|
+
await registrarAccion(ctx, {
|
|
42985
|
+
companyId: input2.companyId,
|
|
42986
|
+
casoId: c.casoId,
|
|
42987
|
+
tipo: "aviso",
|
|
42988
|
+
conector: aviso.canal,
|
|
42989
|
+
objetivo: `Aviso a ${aviso.nombre} (${aviso.canal === "mail" ? "correo" : "WhatsApp"})`,
|
|
42990
|
+
resultado: "ok",
|
|
42991
|
+
evidencia: { aviso_id: aviso.id, intento: c.intento, destinatario: aviso.clave, pendientes_en_el_aviso: aviso.casos.length, que: c.que },
|
|
42992
|
+
actor
|
|
42993
|
+
});
|
|
42994
|
+
}
|
|
42995
|
+
return { ...aviso, estado: "enviado", proveedorId: texto2(input2.proveedorId), enviadoEn: (/* @__PURE__ */ new Date()).toISOString(), intentosEnvio: aviso.intentosEnvio + 1, error: null };
|
|
42996
|
+
}
|
|
42997
|
+
const intentos = aviso.intentosEnvio + 1;
|
|
42998
|
+
const estado = intentos >= MAX_INTENTOS_ENVIO ? "fallido" : "pendiente";
|
|
42999
|
+
const error62 = texto2(input2.error)?.slice(0, 300) ?? "no se pudo enviar";
|
|
43000
|
+
await ctx.db.execute(
|
|
43001
|
+
`UPDATE ${t3(ctx, "aviso")} SET estado = $3, error = $4, intentos_envio = $5, updated_at = now() WHERE company_id = $1 AND id = $2`,
|
|
43002
|
+
[input2.companyId, aviso.id, estado, error62, intentos]
|
|
43003
|
+
);
|
|
43004
|
+
if (estado === "fallido") {
|
|
43005
|
+
for (const c of aviso.casos) {
|
|
43006
|
+
await registrarAccion(ctx, {
|
|
43007
|
+
companyId: input2.companyId,
|
|
43008
|
+
casoId: c.casoId,
|
|
43009
|
+
tipo: "aviso",
|
|
43010
|
+
conector: aviso.canal,
|
|
43011
|
+
objetivo: `Aviso a ${aviso.nombre} (${aviso.canal === "mail" ? "correo" : "WhatsApp"})`,
|
|
43012
|
+
resultado: "error",
|
|
43013
|
+
evidencia: { aviso_id: aviso.id, intento: c.intento, destinatario: aviso.clave, error: error62 },
|
|
43014
|
+
detalle: `No se pudo enviar despu\xE9s de ${intentos} intentos: ${error62}`,
|
|
43015
|
+
actor
|
|
43016
|
+
});
|
|
43017
|
+
}
|
|
43018
|
+
}
|
|
43019
|
+
return { ...aviso, estado, error: error62, intentosEnvio: intentos };
|
|
43020
|
+
}
|
|
43021
|
+
function hoyEnLaInstitucion() {
|
|
43022
|
+
return ahoraEnHospital().hoy;
|
|
43023
|
+
}
|
|
43024
|
+
|
|
42657
43025
|
// src/metricas.ts
|
|
42658
43026
|
var CATALOGO_VERSION = "2026-10-15.3";
|
|
42659
43027
|
var METRICAS = [
|
|
@@ -42681,14 +43049,14 @@ function metricaPorId(id) {
|
|
|
42681
43049
|
}
|
|
42682
43050
|
|
|
42683
43051
|
// src/tableros.ts
|
|
42684
|
-
import { randomUUID as
|
|
43052
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
42685
43053
|
var TIPOS = ["indicador", "barras", "linea", "tabla", "texto"];
|
|
42686
43054
|
var TONOS = ["neutro", "ambar", "senal", "corte"];
|
|
42687
43055
|
var MAX_WIDGETS = 24;
|
|
42688
|
-
function
|
|
43056
|
+
function t4(ctx, table) {
|
|
42689
43057
|
return `${ctx.db.namespace}.${table}`;
|
|
42690
43058
|
}
|
|
42691
|
-
function
|
|
43059
|
+
function iso4(v) {
|
|
42692
43060
|
if (v instanceof Date) return v.toISOString();
|
|
42693
43061
|
if (typeof v === "string") return v;
|
|
42694
43062
|
return "";
|
|
@@ -42705,7 +43073,7 @@ function arr(v) {
|
|
|
42705
43073
|
}
|
|
42706
43074
|
return [];
|
|
42707
43075
|
}
|
|
42708
|
-
function
|
|
43076
|
+
function texto3(v, max, campo, problemas, obligatorio = false) {
|
|
42709
43077
|
if (v === void 0 || v === null || v === "") {
|
|
42710
43078
|
if (obligatorio) problemas.push(`${campo} es obligatorio`);
|
|
42711
43079
|
return void 0;
|
|
@@ -42749,11 +43117,11 @@ function leerCalculo(v, ref, problemas) {
|
|
|
42749
43117
|
return { origen: "exploratorio" };
|
|
42750
43118
|
}
|
|
42751
43119
|
const o = v;
|
|
42752
|
-
const
|
|
42753
|
-
if (
|
|
43120
|
+
const lista2 = arr(o.metricas);
|
|
43121
|
+
if (lista2.length === 0) problemas.push(`${ref}.calculo.metricas est\xE1 vac\xEDo`);
|
|
42754
43122
|
let propuesta = false;
|
|
42755
43123
|
const metricas = [];
|
|
42756
|
-
|
|
43124
|
+
lista2.slice(0, 12).forEach((m, j) => {
|
|
42757
43125
|
const mo = m && typeof m === "object" ? m : {};
|
|
42758
43126
|
const id = typeof mo.id === "string" ? mo.id : "";
|
|
42759
43127
|
const version2 = Number(mo.version);
|
|
@@ -42801,11 +43169,11 @@ function controlarTabla(ref, columnas, filas, problemas) {
|
|
|
42801
43169
|
}
|
|
42802
43170
|
function validarWidgets(entrada) {
|
|
42803
43171
|
const problemas = [];
|
|
42804
|
-
const
|
|
42805
|
-
if (!Array.isArray(entrada) ||
|
|
42806
|
-
if (
|
|
43172
|
+
const lista2 = Array.isArray(entrada) ? entrada : [];
|
|
43173
|
+
if (!Array.isArray(entrada) || lista2.length === 0) problemas.push("widgets debe ser una lista con al menos un elemento");
|
|
43174
|
+
if (lista2.length > MAX_WIDGETS) problemas.push(`widgets admite hasta ${MAX_WIDGETS} elementos`);
|
|
42807
43175
|
const salida = [];
|
|
42808
|
-
|
|
43176
|
+
lista2.slice(0, MAX_WIDGETS).forEach((w, i) => {
|
|
42809
43177
|
const ref = `widgets[${i}]`;
|
|
42810
43178
|
const o = w && typeof w === "object" && !Array.isArray(w) ? w : {};
|
|
42811
43179
|
const tipo = o.tipo;
|
|
@@ -42816,24 +43184,24 @@ function validarWidgets(entrada) {
|
|
|
42816
43184
|
const a = ancho(o.ancho);
|
|
42817
43185
|
switch (tipo) {
|
|
42818
43186
|
case "indicador": {
|
|
42819
|
-
const etiqueta =
|
|
42820
|
-
const valor = typeof o.valor === "number" && Number.isFinite(o.valor) ? o.valor :
|
|
43187
|
+
const etiqueta = texto3(o.etiqueta, 80, `${ref}.etiqueta`, problemas, true) ?? "";
|
|
43188
|
+
const valor = typeof o.valor === "number" && Number.isFinite(o.valor) ? o.valor : texto3(o.valor, 40, `${ref}.valor`, problemas, true) ?? "";
|
|
42821
43189
|
const tono = typeof o.tono === "string" && TONOS.includes(o.tono) ? o.tono : void 0;
|
|
42822
43190
|
if (o.tono !== void 0 && !tono) problemas.push(`${ref}.tono debe ser uno de ${TONOS.join(", ")}`);
|
|
42823
|
-
const unidad =
|
|
43191
|
+
const unidad = texto3(o.unidad, 16, `${ref}.unidad`, problemas);
|
|
42824
43192
|
if (unidad === "%" && !ES_VARIACION.test(etiqueta)) controlarPorcentaje(valor, `${ref}.valor`, problemas);
|
|
42825
43193
|
const { calculo, origen } = leerCalculo(o.calculo, ref, problemas);
|
|
42826
|
-
salida.push({ tipo, etiqueta, valor, unidad, contexto:
|
|
43194
|
+
salida.push({ tipo, etiqueta, valor, unidad, contexto: texto3(o.contexto, 160, `${ref}.contexto`, problemas), tono, ancho: a, ...calculo ? { calculo } : {}, origen });
|
|
42827
43195
|
break;
|
|
42828
43196
|
}
|
|
42829
43197
|
case "barras": {
|
|
42830
|
-
const titulo =
|
|
43198
|
+
const titulo = texto3(o.titulo, 120, `${ref}.titulo`, problemas, true) ?? "";
|
|
42831
43199
|
const series = arr(o.series);
|
|
42832
43200
|
if (series.length === 0 || series.length > 40) problemas.push(`${ref}.series debe tener entre 1 y 40 barras`);
|
|
42833
|
-
const unidad =
|
|
43201
|
+
const unidad = texto3(o.unidad, 16, `${ref}.unidad`, problemas);
|
|
42834
43202
|
const barras = series.slice(0, 40).map((s, j) => {
|
|
42835
43203
|
const so = s && typeof s === "object" ? s : {};
|
|
42836
|
-
return { etiqueta:
|
|
43204
|
+
return { etiqueta: texto3(so.etiqueta, 60, `${ref}.series[${j}].etiqueta`, problemas, true) ?? "", valor: numero2(so.valor, `${ref}.series[${j}].valor`, problemas) };
|
|
42837
43205
|
});
|
|
42838
43206
|
if (unidad === "%" && !ES_VARIACION.test(titulo)) barras.forEach((b, j) => controlarPorcentaje(b.valor, `${ref}.series[${j}].valor`, problemas));
|
|
42839
43207
|
const { calculo, origen } = leerCalculo(o.calculo, ref, problemas);
|
|
@@ -42841,13 +43209,13 @@ function validarWidgets(entrada) {
|
|
|
42841
43209
|
break;
|
|
42842
43210
|
}
|
|
42843
43211
|
case "linea": {
|
|
42844
|
-
const titulo =
|
|
43212
|
+
const titulo = texto3(o.titulo, 120, `${ref}.titulo`, problemas, true) ?? "";
|
|
42845
43213
|
const puntos = arr(o.puntos);
|
|
42846
43214
|
if (puntos.length === 0 || puntos.length > 400) problemas.push(`${ref}.puntos debe tener entre 1 y 400 puntos`);
|
|
42847
|
-
const unidad =
|
|
43215
|
+
const unidad = texto3(o.unidad, 16, `${ref}.unidad`, problemas);
|
|
42848
43216
|
const serie2 = puntos.slice(0, 400).map((p, j) => {
|
|
42849
43217
|
const po = p && typeof p === "object" ? p : {};
|
|
42850
|
-
return { x:
|
|
43218
|
+
return { x: texto3(po.x, 40, `${ref}.puntos[${j}].x`, problemas, true) ?? "", y: numero2(po.y, `${ref}.puntos[${j}].y`, problemas) };
|
|
42851
43219
|
});
|
|
42852
43220
|
if (unidad === "%" && !ES_VARIACION.test(titulo)) serie2.forEach((p, j) => controlarPorcentaje(p.y, `${ref}.puntos[${j}].y`, problemas));
|
|
42853
43221
|
const { calculo, origen } = leerCalculo(o.calculo, ref, problemas);
|
|
@@ -42855,12 +43223,12 @@ function validarWidgets(entrada) {
|
|
|
42855
43223
|
break;
|
|
42856
43224
|
}
|
|
42857
43225
|
case "tabla": {
|
|
42858
|
-
const titulo =
|
|
43226
|
+
const titulo = texto3(o.titulo, 120, `${ref}.titulo`, problemas, true) ?? "";
|
|
42859
43227
|
const columnas = arr(o.columnas);
|
|
42860
43228
|
if (columnas.length === 0 || columnas.length > 12) problemas.push(`${ref}.columnas debe tener entre 1 y 12 columnas`);
|
|
42861
43229
|
const filas = arr(o.filas);
|
|
42862
43230
|
if (filas.length > 200) problemas.push(`${ref}.filas admite hasta 200 filas`);
|
|
42863
|
-
const cols = columnas.slice(0, 12).map((c, j) =>
|
|
43231
|
+
const cols = columnas.slice(0, 12).map((c, j) => texto3(c, 60, `${ref}.columnas[${j}]`, problemas, true) ?? "");
|
|
42864
43232
|
const celdasTabla = filas.slice(0, 200).map((f, j) => {
|
|
42865
43233
|
const celdas = arr(f);
|
|
42866
43234
|
if (celdas.length !== cols.length) problemas.push(`${ref}.filas[${j}] tiene ${celdas.length} celdas y la tabla ${cols.length} columnas`);
|
|
@@ -42876,7 +43244,7 @@ function validarWidgets(entrada) {
|
|
|
42876
43244
|
break;
|
|
42877
43245
|
}
|
|
42878
43246
|
case "texto": {
|
|
42879
|
-
salida.push({ tipo, titulo:
|
|
43247
|
+
salida.push({ tipo, titulo: texto3(o.titulo, 120, `${ref}.titulo`, problemas), texto: texto3(o.texto, 4e3, `${ref}.texto`, problemas, true) ?? "", ancho: a });
|
|
42880
43248
|
break;
|
|
42881
43249
|
}
|
|
42882
43250
|
}
|
|
@@ -42900,23 +43268,23 @@ function mapTablero(r) {
|
|
|
42900
43268
|
publicadoPorTipo: String(r.publicado_por_tipo),
|
|
42901
43269
|
publicadoPorId: r.publicado_por_id ?? null,
|
|
42902
43270
|
versiones: Number(r.versiones ?? 1),
|
|
42903
|
-
createdAt:
|
|
42904
|
-
updatedAt:
|
|
43271
|
+
createdAt: iso4(r.created_at),
|
|
43272
|
+
updatedAt: iso4(r.updated_at)
|
|
42905
43273
|
};
|
|
42906
43274
|
}
|
|
42907
43275
|
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";
|
|
42908
|
-
var
|
|
43276
|
+
var CLAVE_RE2 = /^[a-z0-9][a-z0-9-]{1,60}$/;
|
|
42909
43277
|
async function listarTableros(ctx, companyId) {
|
|
42910
|
-
const rows = await ctx.db.query(`SELECT ${COLS2} FROM ${
|
|
43278
|
+
const rows = await ctx.db.query(`SELECT ${COLS2} FROM ${t4(ctx, "tablero")} WHERE company_id = $1 ORDER BY updated_at DESC`, [companyId]);
|
|
42911
43279
|
return rows.map(mapTablero);
|
|
42912
43280
|
}
|
|
42913
43281
|
async function leerTablero(ctx, companyId, clave) {
|
|
42914
|
-
const rows = await ctx.db.query(`SELECT ${COLS2} FROM ${
|
|
43282
|
+
const rows = await ctx.db.query(`SELECT ${COLS2} FROM ${t4(ctx, "tablero")} WHERE company_id = $1 AND clave = $2 LIMIT 1`, [companyId, clave]);
|
|
42915
43283
|
return rows[0] ? mapTablero(rows[0]) : null;
|
|
42916
43284
|
}
|
|
42917
43285
|
async function publicarTablero(ctx, input2) {
|
|
42918
43286
|
const clave = typeof input2.clave === "string" ? input2.clave.trim().toLowerCase() : "";
|
|
42919
|
-
if (!
|
|
43287
|
+
if (!CLAVE_RE2.test(clave)) throw new OpsError("clave_invalida", "clave debe ser un identificador estable en min\xFAsculas (letras, n\xFAmeros y guiones, 2 a 61 caracteres), p. ej. turnos-ausentismo");
|
|
42920
43288
|
const titulo = typeof input2.titulo === "string" ? input2.titulo.trim().slice(0, 140) : "";
|
|
42921
43289
|
if (!titulo) throw new OpsError("titulo_requerido", "titulo es obligatorio");
|
|
42922
43290
|
const widgets = validarWidgets(input2.widgets);
|
|
@@ -42926,7 +43294,7 @@ async function publicarTablero(ctx, input2) {
|
|
|
42926
43294
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
42927
43295
|
if (previo) {
|
|
42928
43296
|
await ctx.db.execute(
|
|
42929
|
-
`UPDATE ${
|
|
43297
|
+
`UPDATE ${t4(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`,
|
|
42930
43298
|
[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]
|
|
42931
43299
|
);
|
|
42932
43300
|
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 } });
|
|
@@ -42935,9 +43303,9 @@ async function publicarTablero(ctx, input2) {
|
|
|
42935
43303
|
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 }
|
|
42936
43304
|
};
|
|
42937
43305
|
}
|
|
42938
|
-
const id =
|
|
43306
|
+
const id = randomUUID4();
|
|
42939
43307
|
await ctx.db.execute(
|
|
42940
|
-
`INSERT INTO ${
|
|
43308
|
+
`INSERT INTO ${t4(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)`,
|
|
42941
43309
|
[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]
|
|
42942
43310
|
);
|
|
42943
43311
|
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 } });
|
|
@@ -42968,7 +43336,7 @@ function companyFrom(params, fallback) {
|
|
|
42968
43336
|
return companyId;
|
|
42969
43337
|
}
|
|
42970
43338
|
function toolSchema(name) {
|
|
42971
|
-
const decl = manifest_default.tools?.find((
|
|
43339
|
+
const decl = manifest_default.tools?.find((t5) => t5.name === name);
|
|
42972
43340
|
return {
|
|
42973
43341
|
displayName: decl?.displayName ?? name,
|
|
42974
43342
|
description: decl?.description ?? "",
|
|
@@ -43129,7 +43497,7 @@ function registrarDatosYAcciones(ctx) {
|
|
|
43129
43497
|
finalesOk: w.finalesOk,
|
|
43130
43498
|
finalesNoOk: w.finalesNoOk,
|
|
43131
43499
|
minutosAbsorbidos: w.minutosAbsorbidos,
|
|
43132
|
-
transiciones: w.transiciones.map((
|
|
43500
|
+
transiciones: w.transiciones.map((t5) => ({ via: t5.via, de: t5.de, a: typeof t5.a === "string" ? t5.a : "(seg\xFAn datos)", actor: t5.actor, nivelRequerido: t5.nivelRequerido ?? null, descripcion: t5.descripcion }))
|
|
43133
43501
|
})));
|
|
43134
43502
|
ctx.data.register(DATA_KEYS.tablero, async (params) => tablero(ctx, companyFrom(params), numOrNull(params.dias)));
|
|
43135
43503
|
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) }));
|
|
@@ -43217,7 +43585,7 @@ async function manejarApi(ctx, input2) {
|
|
|
43217
43585
|
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 }));
|
|
43218
43586
|
}
|
|
43219
43587
|
case "version":
|
|
43220
|
-
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) });
|
|
43588
|
+
return respuesta(200, { version: manifest_default.version, capacidades: ["carga-manual", "recupero-turnos", "ofrecer-primero", "cuenta-completa", "metricas-certificadas", "facturacion-pendiente", "credenciales", "demanda-pendiente", "reglas", "avisos"], workflows: listarWorkflows().map((w) => w.clave) });
|
|
43221
43589
|
case "politicas":
|
|
43222
43590
|
return respuesta(200, { politicas: await listarPoliticas(ctx, companyId) });
|
|
43223
43591
|
case "serie":
|
|
@@ -43253,6 +43621,21 @@ async function manejarApi(ctx, input2) {
|
|
|
43253
43621
|
}
|
|
43254
43622
|
case "contacto":
|
|
43255
43623
|
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 }));
|
|
43624
|
+
case "contactos":
|
|
43625
|
+
return respuesta(200, await responsablesPendientes(ctx, companyId, (qs("workflows") ?? "").split(",").map((w) => w.trim()).filter(Boolean)));
|
|
43626
|
+
case "guardar-contacto": {
|
|
43627
|
+
if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "los contactos los carga una persona de la instituci\xF3n" });
|
|
43628
|
+
return respuesta(200, { contacto: await guardarContacto(ctx, { companyId, clave: str3(body.clave) ?? "", nombre: str3(body.nombre), mail: str3(body.mail), whatsapp: str3(body.whatsapp), activo: body.activo !== false, actor: { tipo: "humano", id: decididoPor ?? actor.id } }) });
|
|
43629
|
+
}
|
|
43630
|
+
case "avisos-pendientes":
|
|
43631
|
+
return respuesta(200, await generarAvisos(ctx, { companyId, institucion: qs("institucion") ?? "la instituci\xF3n", canales: (qs("canales") ?? "").split(",").map((c) => c.trim()).filter(Boolean) }));
|
|
43632
|
+
case "avisos":
|
|
43633
|
+
return respuesta(200, { avisos: await listarAvisos(ctx, companyId, { desde: qs("desde") }), hoy: hoyEnLaInstitucion() });
|
|
43634
|
+
case "resultado-aviso": {
|
|
43635
|
+
const resultado = body.resultado === "enviado" ? "enviado" : body.resultado === "fallido" ? "fallido" : null;
|
|
43636
|
+
if (!resultado) return respuesta(400, { error: "resultado_invalido", detalle: "resultado debe ser enviado o fallido" });
|
|
43637
|
+
return respuesta(200, { aviso: await informarResultadoAviso(ctx, { companyId, avisoId: input2.params.avisoId ?? "", resultado, proveedorId: str3(body.proveedorId), error: str3(body.error), actor }) });
|
|
43638
|
+
}
|
|
43256
43639
|
case "cambiar-politica": {
|
|
43257
43640
|
const workflow = str3(body.workflow);
|
|
43258
43641
|
if (!workflow) return respuesta(400, { error: "workflow_requerido" });
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
-- Avisos a responsables: cada faltante que encuentra un puesto le llega por mensaje a quien lo
|
|
2
|
+
-- puede resolver (médico, admisión, facturación), con contactos que carga la institución en Opera.
|
|
3
|
+
-- Ver docs/AVISOS-A-RESPONSABLES.md.
|
|
4
|
+
|
|
5
|
+
-- Contactos por institución: una persona (profesional:<id del HIS>) o un rol (rol:admision).
|
|
6
|
+
CREATE TABLE plugin_autopilot_ops_1a68482ede.contacto (
|
|
7
|
+
id uuid PRIMARY KEY,
|
|
8
|
+
company_id uuid NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE,
|
|
9
|
+
clave text NOT NULL,
|
|
10
|
+
nombre text NOT NULL,
|
|
11
|
+
mail text,
|
|
12
|
+
whatsapp text,
|
|
13
|
+
activo boolean NOT NULL DEFAULT true,
|
|
14
|
+
actor_tipo text NOT NULL,
|
|
15
|
+
actor_id text,
|
|
16
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
17
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
18
|
+
UNIQUE (company_id, clave)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
-- Un aviso: un mensaje a un destinatario con todos sus pendientes del día. Lo genera el plugin,
|
|
22
|
+
-- lo envía el portal y el plugin registra el resultado en cada caso incluido.
|
|
23
|
+
CREATE TABLE plugin_autopilot_ops_1a68482ede.aviso (
|
|
24
|
+
id uuid PRIMARY KEY,
|
|
25
|
+
company_id uuid NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE,
|
|
26
|
+
clave text NOT NULL,
|
|
27
|
+
nombre text NOT NULL,
|
|
28
|
+
canal text NOT NULL,
|
|
29
|
+
destino text NOT NULL,
|
|
30
|
+
dia date NOT NULL,
|
|
31
|
+
casos jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
32
|
+
asunto text NOT NULL,
|
|
33
|
+
texto text NOT NULL,
|
|
34
|
+
estado text NOT NULL DEFAULT 'pendiente',
|
|
35
|
+
intentos_envio integer NOT NULL DEFAULT 0,
|
|
36
|
+
error text,
|
|
37
|
+
proveedor_id text,
|
|
38
|
+
enviado_en timestamptz,
|
|
39
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
40
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
CREATE INDEX aviso_company_dia_idx
|
|
44
|
+
ON plugin_autopilot_ops_1a68482ede.aviso (company_id, dia, estado);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paperclip-plugin-autopilot-ops",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Autopilot Care · operación autónoma gobernada para instituciones de salud: workflows determinísticos, validaciones, registro de negocio, políticas (observa/propone/ejecuta/corte) y tablero del director, como plugin de Paperclip.",
|
|
6
6
|
"files": [
|