paperclip-plugin-autopilot-ops 0.1.47 → 0.1.49

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/manifest.ts
2
2
  var PLUGIN_ID = "alephoo.autopilot-ops";
3
- var PLUGIN_VERSION = "0.1.47";
3
+ var PLUGIN_VERSION = "0.1.49";
4
4
  var DB_NAMESPACE_SLUG = "autopilot_ops";
5
5
  var PAGE_ROUTE = "autopilot";
6
6
  var TOOL_NAMES = {
@@ -345,6 +345,14 @@ var manifest = {
345
345
  capability: "api.routes.register",
346
346
  companyResolution: { from: "body", key: "companyId" }
347
347
  },
348
+ {
349
+ routeKey: "suspender-agenda",
350
+ method: "POST",
351
+ path: "/casos/:casoId/suspender-agenda",
352
+ auth: "board",
353
+ capability: "api.routes.register",
354
+ companyResolution: { from: "body", key: "companyId" }
355
+ },
348
356
  {
349
357
  routeKey: "version",
350
358
  method: "GET",
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.47";
11
+ var PLUGIN_VERSION = "0.1.49";
12
12
  var DB_NAMESPACE_SLUG = "autopilot_ops";
13
13
  var PAGE_ROUTE = "autopilot";
14
14
  var TOOL_NAMES = {
@@ -353,6 +353,14 @@ var manifest = {
353
353
  capability: "api.routes.register",
354
354
  companyResolution: { from: "body", key: "companyId" }
355
355
  },
356
+ {
357
+ routeKey: "suspender-agenda",
358
+ method: "POST",
359
+ path: "/casos/:casoId/suspender-agenda",
360
+ auth: "board",
361
+ capability: "api.routes.register",
362
+ companyResolution: { from: "body", key: "companyId" }
363
+ },
356
364
  {
357
365
  routeKey: "version",
358
366
  method: "GET",
package/dist/worker.js CHANGED
@@ -39932,7 +39932,7 @@ function startWorkerRpcHost(options) {
39932
39932
 
39933
39933
  // src/manifest.ts
39934
39934
  var PLUGIN_ID = "alephoo.autopilot-ops";
39935
- var PLUGIN_VERSION = "0.1.47";
39935
+ var PLUGIN_VERSION = "0.1.49";
39936
39936
  var DB_NAMESPACE_SLUG = "autopilot_ops";
39937
39937
  var PAGE_ROUTE = "autopilot";
39938
39938
  var TOOL_NAMES = {
@@ -40277,6 +40277,14 @@ var manifest = {
40277
40277
  capability: "api.routes.register",
40278
40278
  companyResolution: { from: "body", key: "companyId" }
40279
40279
  },
40280
+ {
40281
+ routeKey: "suspender-agenda",
40282
+ method: "POST",
40283
+ path: "/casos/:casoId/suspender-agenda",
40284
+ auth: "board",
40285
+ capability: "api.routes.register",
40286
+ companyResolution: { from: "body", key: "companyId" }
40287
+ },
40280
40288
  {
40281
40289
  routeKey: "version",
40282
40290
  method: "GET",
@@ -42494,15 +42502,16 @@ function observacionDe(r) {
42494
42502
  if (r.situacion !== "habilitado") return "inhabilitada";
42495
42503
  return null;
42496
42504
  }
42505
+ var GRAVES_MATRICULA = ["inhabilitada", "sancion", "otra_persona"];
42497
42506
  var credencialProfesional = {
42498
42507
  clave: "credencial_profesional",
42499
- nombre: "Credenciales del profesional",
42508
+ nombre: "Control de matr\xEDculas",
42500
42509
  puesto: "Personal y profesionales",
42501
- descripcion: "Lee en el HIS la matr\xEDcula de cada profesional con agenda y la verifica contra el registro nacional de profesionales de la salud (REFEPS). Si no figura, est\xE1 a nombre de otra persona, est\xE1 inhabilitada o falta cargarla, avisa al equipo con d\xF3nde se corrige. El puesto no escribe en el sistema.",
42510
+ descripcion: "Lee en el HIS la matr\xEDcula de cada profesional con agenda y la verifica contra el registro nacional de profesionales de la salud (REFEPS). Si no figura, est\xE1 a nombre de otra persona, est\xE1 inhabilitada o falta cargarla, avisa al equipo con d\xF3nde se corrige. El agente no escribe en el sistema.",
42502
42511
  inicial: "detectado",
42503
- estados: ["detectado", "vigente", "observada", "corregida", "no_corresponde", "descartado"],
42512
+ estados: ["detectado", "vigente", "observada", "corregida", "no_corresponde", "agenda_suspendida", "descartado"],
42504
42513
  finalesOk: ["vigente"],
42505
- finalesNoOk: ["no_corresponde", "descartado"],
42514
+ finalesNoOk: ["no_corresponde", "agenda_suspendida", "descartado"],
42506
42515
  minutosAbsorbidos: 5,
42507
42516
  transiciones: [
42508
42517
  {
@@ -42534,7 +42543,7 @@ var credencialProfesional = {
42534
42543
  a: "corregida",
42535
42544
  actor: ["humano"],
42536
42545
  cargaManual: true,
42537
- descripcion: "Una persona de la instituci\xF3n corrigi\xF3 la ficha del profesional en el HIS. El puesto vuelve a verificar.",
42546
+ descripcion: "Una persona de la instituci\xF3n corrigi\xF3 la ficha del profesional en el HIS. El agente vuelve a consultar el registro.",
42538
42547
  validar: (_caso, datos) => [
42539
42548
  texto4(datos.accion_id) ? ok("accion_registrada") : falla("accion_registrada", "corregir requiere accion_id de la carga registrada")
42540
42549
  ]
@@ -42547,6 +42556,17 @@ var credencialProfesional = {
42547
42556
  descripcion: "El equipo indica que la observaci\xF3n no aplica (por ejemplo, personal no matriculado o matr\xEDcula provincial confirmada con el colegio). Queda registrado con el motivo.",
42548
42557
  validar: (_caso, datos) => [texto4(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "no_corresponde requiere motivo")]
42549
42558
  },
42559
+ {
42560
+ via: "suspender_agenda",
42561
+ de: ["observada"],
42562
+ a: "agenda_suspendida",
42563
+ actor: ["humano"],
42564
+ descripcion: "La matr\xEDcula est\xE1 de verdad inhabilitada, con sanci\xF3n o a nombre de otra persona: la instituci\xF3n suspendi\xF3 la agenda del profesional hasta que lo regularice. Queda registrado qui\xE9n lo decidi\xF3 y por qu\xE9.",
42565
+ validar: (caso, datos) => [
42566
+ GRAVES_MATRICULA.includes(texto4(caso.datos.observacion) ?? "") ? ok("observacion_grave") : falla("observacion_grave", "suspender la agenda es para una matr\xEDcula inhabilitada, con sanci\xF3n o a nombre de otra persona"),
42567
+ texto4(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "suspender_agenda requiere motivo")
42568
+ ]
42569
+ },
42550
42570
  {
42551
42571
  via: "descartar",
42552
42572
  de: ["detectado", "observada", "corregida"],
@@ -44956,6 +44976,11 @@ function opcionesCostos(op) {
44956
44976
  function opcionesRentabilidad(op) {
44957
44977
  return { umbral: enteroEntre(op.umbral_margen, 0, 90) ?? UMBRAL_MARGEN_POR_DEFECTO, mesesPrecio: enteroEntre(op.meses_precio, 1, 36) ?? MESES_PRECIO_POR_DEFECTO };
44958
44978
  }
44979
+ function pareceNoProfesional(nombre) {
44980
+ const n = nombre.trim();
44981
+ const [apellido = "", nombres = ""] = n.split(",").map((x) => x.trim().toLowerCase());
44982
+ return /^[^a-záéíóúñü]/i.test(n) || /\d/.test(n) || apellido !== "" && apellido === nombres || /\b(admin|administrador|prueba|test|sistema|usuario)\b/i.test(n);
44983
+ }
44959
44984
  async function evaluarRentabilidades(ctx, companyId, lecturas) {
44960
44985
  if (!Array.isArray(lecturas)) throw new OpsError("validacion", "lecturas: tiene que ser una lista");
44961
44986
  if (lecturas.length > 2e3) throw new OpsError("validacion", "lecturas: como m\xE1ximo 2000 por llamada");
@@ -45459,12 +45484,14 @@ async function transicionar(ctx, input2) {
45459
45484
  const lecturaId = str2(datos.lectura_id);
45460
45485
  if (lecturaId) {
45461
45486
  const rows = await ctx.db.query(
45462
- `SELECT id, caso_id, tipo, resultado, created_at FROM ${t3(ctx, "accion")} WHERE company_id = $1 AND id = $2 LIMIT 1`,
45487
+ `SELECT id, caso_id, tipo, resultado, evidencia, created_at FROM ${t3(ctx, "accion")} WHERE company_id = $1 AND id = $2 LIMIT 1`,
45463
45488
  [input2.companyId, lecturaId]
45464
45489
  );
45465
45490
  const lectura = rows[0];
45491
+ const evidencia = lectura && lectura.evidencia && typeof lectura.evidencia === "object" ? lectura.evidencia : null;
45492
+ const distinta = lectura && caso.workflow === "credencial_profesional" && resultado.via === "verificar" && evidencia ? evidencia.profesional_id !== void 0 && String(evidencia.profesional_id) !== String(datos.profesional_id ?? caso.datos.profesional_id ?? "") ? `la lectura ${lecturaId} es de otro profesional (${String(evidencia.profesional_id)})` : evidencia.matricula !== void 0 && datos.matricula !== void 0 && datos.matricula !== null && String(evidencia.matricula) !== String(datos.matricula) ? `la lectura ${lecturaId} es de la matr\xEDcula ${String(evidencia.matricula)} y se informa la ${String(datos.matricula)}` : null : null;
45466
45493
  let vieja = false;
45467
- if (lectura && (resultado.via === "comprobar" || (caso.workflow === "circular_aranceles" || caso.workflow === "ajuste_convenio") && resultado.via === "verificar")) {
45494
+ if (lectura && (resultado.via === "comprobar" || (caso.workflow === "circular_aranceles" || caso.workflow === "ajuste_convenio") && resultado.via === "verificar" || caso.workflow === "credencial_profesional" && resultado.via === "verificar" && caso.estado === "corregida")) {
45468
45495
  const ultima = await ctx.db.query(
45469
45496
  `SELECT max(created_at) AS ultima FROM ${t3(ctx, "transicion")} WHERE company_id = $1 AND caso_id = $2`,
45470
45497
  [input2.companyId, caso.id]
@@ -45472,7 +45499,7 @@ async function transicionar(ctx, input2) {
45472
45499
  const desde = ultima[0]?.ultima ? Date.parse(iso2(ultima[0].ultima)) : NaN;
45473
45500
  vieja = Number.isFinite(desde) && Date.parse(iso2(lectura.created_at)) <= desde;
45474
45501
  }
45475
- 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;
45502
+ 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 y registrar esa lectura` : distinta;
45476
45503
  if (problema) {
45477
45504
  throw new OpsError("validacion", `lectura_registrada: ${problema}`, { validaciones: [{ regla: "lectura_registrada", ok: false, detalle: problema }], estado: caso.estado });
45478
45505
  }
@@ -45799,6 +45826,18 @@ async function transicionar(ctx, input2) {
45799
45826
  datos.observacion = observacionDe(r);
45800
45827
  datos.verificada_el = contexto.hoy ?? null;
45801
45828
  }
45829
+ if (caso.estado === "corregida") {
45830
+ const antes = [caso.datos.matricula_nacional, caso.datos.matricula_provincial].map((v) => String(v ?? "").replace(/\D/g, ""));
45831
+ const ahora = [datos.matricula_nacional ?? caso.datos.matricula_nacional, datos.matricula_provincial ?? caso.datos.matricula_provincial].map((v) => String(v ?? "").replace(/\D/g, ""));
45832
+ datos.ficha_sin_cambios = datos.observacion !== null && antes.join("|") === ahora.join("|");
45833
+ } else delete datos.ficha_sin_cambios;
45834
+ datos.posible_no_profesional = pareceNoProfesional(String(datos.profesional ?? caso.datos.profesional ?? caso.titulo.split(" \xB7 ")[0] ?? ""));
45835
+ const ag = datos.agenda && typeof datos.agenda === "object" ? datos.agenda : null;
45836
+ if (ag) {
45837
+ const proximo = typeof ag.proximo_turno === "string" && /^\d{4}-\d{2}-\d{2}( \d{2}:\d{2})?$/.test(ag.proximo_turno) ? ag.proximo_turno : null;
45838
+ const n = Number(ag.turnos_7_dias);
45839
+ datos.agenda = { proximo_turno: proximo, turnos_7_dias: Number.isInteger(n) && n >= 0 && n < 1e4 ? n : 0, leida_el: contexto.hoy ?? null };
45840
+ } else delete datos.agenda;
45802
45841
  }
45803
45842
  if (caso.workflow === "facturacion_pendiente" && resultado.via === "observar") {
45804
45843
  const fecha5 = String(datos.fecha_prestacion ?? caso.datos.fecha_prestacion ?? "");
@@ -46581,7 +46620,8 @@ var ROLES = {
46581
46620
  facturacion: "Facturaci\xF3n",
46582
46621
  internacion: "Internaci\xF3n",
46583
46622
  honorarios: "Honorarios",
46584
- personal: "Administraci\xF3n de personal"
46623
+ personal: "Administraci\xF3n de personal",
46624
+ direccion_medica: "Direcci\xF3n m\xE9dica"
46585
46625
  };
46586
46626
  var ESPERAN_PERSONA = {
46587
46627
  cuenta_completa: ["para_completar", "con_faltantes"],
@@ -46681,11 +46721,12 @@ function pendientesDe(caso, hoy) {
46681
46721
  const nombre = texto8(d.profesional) ?? (id3 ? `Profesional ${id3}` : "Profesional");
46682
46722
  const obs = texto8(d.observacion);
46683
46723
  const que = `${nombre}: ${(obs && OBSERVACIONES_MATRICULA[obs]) ?? "la matr\xEDcula tiene una observaci\xF3n del registro nacional"}`;
46684
- return [{ ...base, que, responsable: id3 ? { clave: `profesional:${id3}`, nombre, reserva: "rol:personal" } : rol("personal") }];
46724
+ const grave = obs === "inhabilitada" || obs === "sancion" || obs === "otra_persona";
46725
+ return [{ ...base, que, responsable: rol("personal") }, ...grave ? [{ ...base, que, responsable: rol("direccion_medica") }] : []];
46685
46726
  }
46686
46727
  return [];
46687
46728
  }
46688
- var CLAVE_RE = /^(profesional:[A-Za-z0-9_-]{1,40}|financiador:\d{1,12}|rol:(admision|aranceles|facturacion|internacion|personal|honorarios))$/;
46729
+ var CLAVE_RE = /^(profesional:[A-Za-z0-9_-]{1,40}|financiador:\d{1,12}|rol:(admision|aranceles|facturacion|internacion|personal|honorarios|direccion_medica))$/;
46689
46730
  var MAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
46690
46731
  var COLS_CONTACTO = "id, clave, nombre, mail, whatsapp, activo, updated_at";
46691
46732
  function mapContacto(r) {
@@ -47672,6 +47713,11 @@ async function manejarApi(ctx, input2) {
47672
47713
  return respuesta(200, await registrarCargaManual(ctx, { companyId, casoId: input2.params.casoId ?? "", valorCargado: body.valorCargado, archivoHash: str3(body.archivoHash), canal: str3(body.canal), destinatario: str3(body.destinatario), comentario: str3(body.comentario), persona: decididoPor, idempotencyKey: str3(body.idempotencyKey), actor }));
47673
47714
  case "respuesta-paciente":
47674
47715
  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 }));
47716
+ case "suspender-agenda": {
47717
+ if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "suspender la agenda lo decide una persona de la instituci\xF3n" });
47718
+ if (!str3(body.motivo)) return respuesta(422, { error: "validacion", motivo: "motivo_presente: suspender_agenda requiere motivo" });
47719
+ return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "suspender_agenda", motivo: motivoConPersona(str3(body.motivo)), datos: conPersona(obj(body.datos)), idempotencyKey: str3(body.idempotencyKey), actor }));
47720
+ }
47675
47721
  case "no-corresponde": {
47676
47722
  if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "no_corresponde lo decide una persona de la instituci\xF3n" });
47677
47723
  if (!str3(body.motivo)) return respuesta(422, { error: "validacion", motivo: "motivo_presente: no_corresponde requiere motivo" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperclip-plugin-autopilot-ops",
3
- "version": "0.1.47",
3
+ "version": "0.1.49",
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": [