paperclip-plugin-autopilot-ops 0.1.8 → 0.1.9
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 +10 -2
- package/dist/ui/index.js +10 -2
- package/dist/worker.js +141 -32
- 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.9";
|
|
4
4
|
var DB_NAMESPACE_SLUG = "autopilot_ops";
|
|
5
5
|
var PAGE_ROUTE = "autopilot";
|
|
6
6
|
var TOOL_NAMES = {
|
|
@@ -149,7 +149,7 @@ var manifest = {
|
|
|
149
149
|
{
|
|
150
150
|
name: TOOL_NAMES.publicarTablero,
|
|
151
151
|
displayName: "Publicar tablero",
|
|
152
|
-
description: "Publica o actualiza un tablero de gesti\xF3n para la direcci\xF3n del hospital, armado sobre datos reales de Alephoo (turnos, facturaci\xF3n, internaci\xF3n, producci\xF3n). `clave` identifica el tablero de forma estable (min\xFAsculas y guiones; volver a publicar con la misma clave lo actualiza en el lugar). `widgets` es la lista de piezas que se muestran, en orden: {tipo:'indicador', etiqueta, valor, unidad?, contexto?, tono?: neutro|ambar|senal|corte} \xB7 {tipo:'barras', titulo, unidad?, series:[{etiqueta, valor}]} \xB7 {tipo:'linea', titulo, unidad?, puntos:[{x, y}]} \xB7 {tipo:'tabla', titulo, columnas:[\u2026], filas:[[\u2026]]} \xB7 {tipo:'texto', titulo?, texto}. Cada widget admite `ancho` 1, 2 o 3 (columnas de 3). `fuentes` lista qu\xE9 se consult\xF3 (p. ej. 'Alephoo \xB7
|
|
152
|
+
description: "Publica o actualiza un tablero de gesti\xF3n para la direcci\xF3n del hospital, armado sobre datos reales de Alephoo (turnos, facturaci\xF3n, internaci\xF3n, producci\xF3n). `clave` identifica el tablero de forma estable (min\xFAsculas y guiones; volver a publicar con la misma clave lo actualiza en el lugar). `widgets` es la lista de piezas que se muestran, en orden: {tipo:'indicador', etiqueta, valor, unidad?, contexto?, tono?: neutro|ambar|senal|corte} \xB7 {tipo:'barras', titulo, unidad?, series:[{etiqueta, valor}]} \xB7 {tipo:'linea', titulo, unidad?, puntos:[{x, y}]} \xB7 {tipo:'tabla', titulo, columnas:[\u2026], filas:[[\u2026]]} \xB7 {tipo:'texto', titulo?, texto}. Cada widget admite `ancho` 1, 2 o 3 (columnas de 3). **N\xFAmeros oficiales:** cada indicador, barras, l\xEDnea o tabla calculado con calcular.mjs lleva `calculo` = el bloque `calculo` que devuelve calcular.mjs, tal cual (cat\xE1logo, m\xE9tricas con versi\xF3n, par\xE1metros, huella, consulta); el plugin lo valida contra el cat\xE1logo de m\xE9tricas y marca el widget como certificado, propuesta (m\xE9trica sin aprobar) o exploratorio (sin `calculo`). Controles que frenan la publicaci\xF3n: una tabla con fila \xABTOTAL\xBB tiene que sumar ese total en cada columna num\xE9rica (las filas \xABSubtotal \u2026\xBB no suman); un desglose con `calculo` y `por` lleva la fila TOTAL; un porcentaje (`unidad` '%' o columna con '%') va de 0 a 100 salvo que el t\xEDtulo diga variaci\xF3n, diferencia o cumplimiento de meta. `fuentes` lista qu\xE9 se consult\xF3 (p. ej. 'Alephoo \xB7 base \xB7 semantica_turnos \xB7 turnos 01/09\u201330/09'). Si el pedido vino de una tarea, pasar `issueId`.",
|
|
153
153
|
parametersSchema: {
|
|
154
154
|
type: "object",
|
|
155
155
|
properties: {
|
|
@@ -277,6 +277,14 @@ var manifest = {
|
|
|
277
277
|
capability: "api.routes.register",
|
|
278
278
|
companyResolution: { from: "body", key: "companyId" }
|
|
279
279
|
},
|
|
280
|
+
{
|
|
281
|
+
routeKey: "metricas",
|
|
282
|
+
method: "GET",
|
|
283
|
+
path: "/metricas",
|
|
284
|
+
auth: "board",
|
|
285
|
+
capability: "api.routes.register",
|
|
286
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
287
|
+
},
|
|
280
288
|
{
|
|
281
289
|
routeKey: "tableros",
|
|
282
290
|
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.
|
|
11
|
+
var PLUGIN_VERSION = "0.1.9";
|
|
12
12
|
var DB_NAMESPACE_SLUG = "autopilot_ops";
|
|
13
13
|
var PAGE_ROUTE = "autopilot";
|
|
14
14
|
var TOOL_NAMES = {
|
|
@@ -157,7 +157,7 @@ var manifest = {
|
|
|
157
157
|
{
|
|
158
158
|
name: TOOL_NAMES.publicarTablero,
|
|
159
159
|
displayName: "Publicar tablero",
|
|
160
|
-
description: "Publica o actualiza un tablero de gesti\xF3n para la direcci\xF3n del hospital, armado sobre datos reales de Alephoo (turnos, facturaci\xF3n, internaci\xF3n, producci\xF3n). `clave` identifica el tablero de forma estable (min\xFAsculas y guiones; volver a publicar con la misma clave lo actualiza en el lugar). `widgets` es la lista de piezas que se muestran, en orden: {tipo:'indicador', etiqueta, valor, unidad?, contexto?, tono?: neutro|ambar|senal|corte} \xB7 {tipo:'barras', titulo, unidad?, series:[{etiqueta, valor}]} \xB7 {tipo:'linea', titulo, unidad?, puntos:[{x, y}]} \xB7 {tipo:'tabla', titulo, columnas:[\u2026], filas:[[\u2026]]} \xB7 {tipo:'texto', titulo?, texto}. Cada widget admite `ancho` 1, 2 o 3 (columnas de 3). `fuentes` lista qu\xE9 se consult\xF3 (p. ej. 'Alephoo \xB7
|
|
160
|
+
description: "Publica o actualiza un tablero de gesti\xF3n para la direcci\xF3n del hospital, armado sobre datos reales de Alephoo (turnos, facturaci\xF3n, internaci\xF3n, producci\xF3n). `clave` identifica el tablero de forma estable (min\xFAsculas y guiones; volver a publicar con la misma clave lo actualiza en el lugar). `widgets` es la lista de piezas que se muestran, en orden: {tipo:'indicador', etiqueta, valor, unidad?, contexto?, tono?: neutro|ambar|senal|corte} \xB7 {tipo:'barras', titulo, unidad?, series:[{etiqueta, valor}]} \xB7 {tipo:'linea', titulo, unidad?, puntos:[{x, y}]} \xB7 {tipo:'tabla', titulo, columnas:[\u2026], filas:[[\u2026]]} \xB7 {tipo:'texto', titulo?, texto}. Cada widget admite `ancho` 1, 2 o 3 (columnas de 3). **N\xFAmeros oficiales:** cada indicador, barras, l\xEDnea o tabla calculado con calcular.mjs lleva `calculo` = el bloque `calculo` que devuelve calcular.mjs, tal cual (cat\xE1logo, m\xE9tricas con versi\xF3n, par\xE1metros, huella, consulta); el plugin lo valida contra el cat\xE1logo de m\xE9tricas y marca el widget como certificado, propuesta (m\xE9trica sin aprobar) o exploratorio (sin `calculo`). Controles que frenan la publicaci\xF3n: una tabla con fila \xABTOTAL\xBB tiene que sumar ese total en cada columna num\xE9rica (las filas \xABSubtotal \u2026\xBB no suman); un desglose con `calculo` y `por` lleva la fila TOTAL; un porcentaje (`unidad` '%' o columna con '%') va de 0 a 100 salvo que el t\xEDtulo diga variaci\xF3n, diferencia o cumplimiento de meta. `fuentes` lista qu\xE9 se consult\xF3 (p. ej. 'Alephoo \xB7 base \xB7 semantica_turnos \xB7 turnos 01/09\u201330/09'). Si el pedido vino de una tarea, pasar `issueId`.",
|
|
161
161
|
parametersSchema: {
|
|
162
162
|
type: "object",
|
|
163
163
|
properties: {
|
|
@@ -285,6 +285,14 @@ var manifest = {
|
|
|
285
285
|
capability: "api.routes.register",
|
|
286
286
|
companyResolution: { from: "body", key: "companyId" }
|
|
287
287
|
},
|
|
288
|
+
{
|
|
289
|
+
routeKey: "metricas",
|
|
290
|
+
method: "GET",
|
|
291
|
+
path: "/metricas",
|
|
292
|
+
auth: "board",
|
|
293
|
+
capability: "api.routes.register",
|
|
294
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
295
|
+
},
|
|
288
296
|
{
|
|
289
297
|
routeKey: "tableros",
|
|
290
298
|
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.
|
|
39935
|
+
var PLUGIN_VERSION = "0.1.9";
|
|
39936
39936
|
var DB_NAMESPACE_SLUG = "autopilot_ops";
|
|
39937
39937
|
var PAGE_ROUTE = "autopilot";
|
|
39938
39938
|
var TOOL_NAMES = {
|
|
@@ -40081,7 +40081,7 @@ var manifest = {
|
|
|
40081
40081
|
{
|
|
40082
40082
|
name: TOOL_NAMES.publicarTablero,
|
|
40083
40083
|
displayName: "Publicar tablero",
|
|
40084
|
-
description: "Publica o actualiza un tablero de gesti\xF3n para la direcci\xF3n del hospital, armado sobre datos reales de Alephoo (turnos, facturaci\xF3n, internaci\xF3n, producci\xF3n). `clave` identifica el tablero de forma estable (min\xFAsculas y guiones; volver a publicar con la misma clave lo actualiza en el lugar). `widgets` es la lista de piezas que se muestran, en orden: {tipo:'indicador', etiqueta, valor, unidad?, contexto?, tono?: neutro|ambar|senal|corte} \xB7 {tipo:'barras', titulo, unidad?, series:[{etiqueta, valor}]} \xB7 {tipo:'linea', titulo, unidad?, puntos:[{x, y}]} \xB7 {tipo:'tabla', titulo, columnas:[\u2026], filas:[[\u2026]]} \xB7 {tipo:'texto', titulo?, texto}. Cada widget admite `ancho` 1, 2 o 3 (columnas de 3). `fuentes` lista qu\xE9 se consult\xF3 (p. ej. 'Alephoo \xB7
|
|
40084
|
+
description: "Publica o actualiza un tablero de gesti\xF3n para la direcci\xF3n del hospital, armado sobre datos reales de Alephoo (turnos, facturaci\xF3n, internaci\xF3n, producci\xF3n). `clave` identifica el tablero de forma estable (min\xFAsculas y guiones; volver a publicar con la misma clave lo actualiza en el lugar). `widgets` es la lista de piezas que se muestran, en orden: {tipo:'indicador', etiqueta, valor, unidad?, contexto?, tono?: neutro|ambar|senal|corte} \xB7 {tipo:'barras', titulo, unidad?, series:[{etiqueta, valor}]} \xB7 {tipo:'linea', titulo, unidad?, puntos:[{x, y}]} \xB7 {tipo:'tabla', titulo, columnas:[\u2026], filas:[[\u2026]]} \xB7 {tipo:'texto', titulo?, texto}. Cada widget admite `ancho` 1, 2 o 3 (columnas de 3). **N\xFAmeros oficiales:** cada indicador, barras, l\xEDnea o tabla calculado con calcular.mjs lleva `calculo` = el bloque `calculo` que devuelve calcular.mjs, tal cual (cat\xE1logo, m\xE9tricas con versi\xF3n, par\xE1metros, huella, consulta); el plugin lo valida contra el cat\xE1logo de m\xE9tricas y marca el widget como certificado, propuesta (m\xE9trica sin aprobar) o exploratorio (sin `calculo`). Controles que frenan la publicaci\xF3n: una tabla con fila \xABTOTAL\xBB tiene que sumar ese total en cada columna num\xE9rica (las filas \xABSubtotal \u2026\xBB no suman); un desglose con `calculo` y `por` lleva la fila TOTAL; un porcentaje (`unidad` '%' o columna con '%') va de 0 a 100 salvo que el t\xEDtulo diga variaci\xF3n, diferencia o cumplimiento de meta. `fuentes` lista qu\xE9 se consult\xF3 (p. ej. 'Alephoo \xB7 base \xB7 semantica_turnos \xB7 turnos 01/09\u201330/09'). Si el pedido vino de una tarea, pasar `issueId`.",
|
|
40085
40085
|
parametersSchema: {
|
|
40086
40086
|
type: "object",
|
|
40087
40087
|
properties: {
|
|
@@ -40209,6 +40209,14 @@ var manifest = {
|
|
|
40209
40209
|
capability: "api.routes.register",
|
|
40210
40210
|
companyResolution: { from: "body", key: "companyId" }
|
|
40211
40211
|
},
|
|
40212
|
+
{
|
|
40213
|
+
routeKey: "metricas",
|
|
40214
|
+
method: "GET",
|
|
40215
|
+
path: "/metricas",
|
|
40216
|
+
auth: "board",
|
|
40217
|
+
capability: "api.routes.register",
|
|
40218
|
+
companyResolution: { from: "query", key: "companyId" }
|
|
40219
|
+
},
|
|
40212
40220
|
{
|
|
40213
40221
|
routeKey: "tableros",
|
|
40214
40222
|
method: "GET",
|
|
@@ -41669,6 +41677,32 @@ function errorParaAgente(err) {
|
|
|
41669
41677
|
return { error: `error_interno: ${message2}`, data: { codigo: "error_interno" } };
|
|
41670
41678
|
}
|
|
41671
41679
|
|
|
41680
|
+
// src/metricas.ts
|
|
41681
|
+
var CATALOGO_VERSION = "2026-10-15.1";
|
|
41682
|
+
var METRICAS = [
|
|
41683
|
+
{ id: "turnos_dados", version: 1, estado: "validada", nombre: "Turnos dados", unidad: "turnos", definicion: "Turnos agendados en el per\xEDodo que no fueron cancelados ni bloqueados (incluye atendidos, ausentes y los que siguen pendientes)." },
|
|
41684
|
+
{ id: "turnos_atendidos", version: 1, estado: "validada", nombre: "Turnos atendidos", unidad: "turnos", definicion: "Turnos en los que hubo atenci\xF3n: en atenci\xF3n, atendido o revisado por el profesional." },
|
|
41685
|
+
{ id: "ausencias", version: 1, estado: "validada", nombre: "Ausencias", unidad: "turnos", definicion: "Turnos marcados como \xABno asisti\xF3\xBB." },
|
|
41686
|
+
{ id: "ausentismo", version: 1, estado: "validada", nombre: "Ausentismo", unidad: "%", definicion: "Ausencias sobre turnos dados. Las cancelaciones no cuentan como ausencia. Los turnos que nadie cerr\xF3 no figuran como ausencia, as\xED que con muchos turnos sin cerrar el ausentismo real es mayor." },
|
|
41687
|
+
{ id: "turnos_sin_cerrar", version: 1, estado: "propuesta", nombre: "Turnos sin cerrar", unidad: "turnos", definicion: "Turnos con fecha ya pasada que siguen en \xABpendiente\xBB o \xABanunciado\xBB: nadie los cerr\xF3 como atendidos ni como ausentes." },
|
|
41688
|
+
{ id: "cancelaciones", version: 1, estado: "validada", nombre: "Cancelaciones", unidad: "turnos", definicion: "Turnos cancelados del per\xEDodo." },
|
|
41689
|
+
{ id: "cancelaciones_mismo_dia", version: 1, estado: "validada", nombre: "Cancelaciones del mismo d\xEDa", unidad: "turnos", definicion: "Turnos cancelados el mismo d\xEDa del turno (el cupo ya casi no se puede reutilizar). Es una aproximaci\xF3n: Alephoo no guarda la fecha de cancelaci\xF3n y se usa la \xFAltima modificaci\xF3n del turno." },
|
|
41690
|
+
{ id: "tasa_cancelacion", version: 1, estado: "validada", nombre: "Tasa de cancelaci\xF3n", unidad: "%", definicion: "Cancelaciones sobre lo agendado (turnos dados m\xE1s cancelados)." },
|
|
41691
|
+
{ id: "turnos_agendados", version: 1, estado: "validada", nombre: "Turnos agendados", unidad: "turnos", definicion: "Turnos dados m\xE1s cancelados: todo lo que se agend\xF3 en el per\xEDodo, sin los cupos bloqueados." },
|
|
41692
|
+
{ id: "facturado", version: 1, estado: "validada", nombre: "Facturado", unidad: "$", definicion: "Importe de las facturas emitidas en el per\xEDodo, sin las anuladas, con IVA, por fecha de emisi\xF3n (la de AFIP en las electr\xF3nicas)." },
|
|
41693
|
+
{ id: "facturas_emitidas", version: 1, estado: "validada", nombre: "Facturas emitidas", unidad: "facturas", definicion: "Cantidad de facturas emitidas en el per\xEDodo, sin las anuladas." },
|
|
41694
|
+
{ id: "notas_credito", version: 1, estado: "validada", nombre: "Notas de cr\xE9dito", unidad: "$", definicion: "Notas de cr\xE9dito no anuladas sobre las facturas emitidas en el per\xEDodo (se asignan al mes de la factura). Incluyen d\xE9bitos de financiadores, anulaciones parciales y ajustes propios: no son lo mismo que \xABd\xE9bitos\xBB." },
|
|
41695
|
+
{ id: "notas_debito", version: 1, estado: "validada", nombre: "Notas de d\xE9bito", unidad: "$", definicion: "Notas de d\xE9bito no anuladas sobre las facturas emitidas en el per\xEDodo." },
|
|
41696
|
+
{ id: "cobrado_de_lo_facturado", version: 1, estado: "validada", nombre: "Cobrado de lo facturado", unidad: "$", definicion: "Lo cobrado (recibos no anulados aplicados) sobre las facturas emitidas en el per\xEDodo, cualquiera sea la fecha del cobro." },
|
|
41697
|
+
{ id: "deuda_pendiente", version: 1, estado: "validada", nombre: "Deuda pendiente", unidad: "$", definicion: "Saldo sin cobrar de las facturas emitidas en el per\xEDodo que siguen pendientes (facturado menos notas de cr\xE9dito, m\xE1s notas de d\xE9bito, menos lo cobrado)." },
|
|
41698
|
+
{ id: "bonos_pendientes_de_facturar", version: 1, estado: "validada", nombre: "Bonos pendientes de facturar", unidad: "bonos", definicion: "Prestaciones (bonos) del per\xEDodo todav\xEDa no confirmadas o confirmadas y sin prefacturar ni facturar." },
|
|
41699
|
+
{ id: "monto_pendiente_de_facturar", version: 1, estado: "validada", nombre: "Monto pendiente de facturar", unidad: "$", definicion: "Importe de los bonos pendientes de facturar del per\xEDodo, por fecha de la prestaci\xF3n." },
|
|
41700
|
+
{ id: "debitos_financiador", version: 1, estado: "a_definir", nombre: "D\xE9bitos de financiadores", unidad: "$", definicion: "Sin definir: el hospital tiene que decidir si cuenta todas las notas de cr\xE9dito o solo las que responden a un d\xE9bito del financiador. Hasta entonces no se publica como n\xFAmero oficial." }
|
|
41701
|
+
];
|
|
41702
|
+
function metricaPorId(id) {
|
|
41703
|
+
return METRICAS.find((m) => m.id === id) ?? null;
|
|
41704
|
+
}
|
|
41705
|
+
|
|
41672
41706
|
// src/tableros.ts
|
|
41673
41707
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
41674
41708
|
var TIPOS = ["indicador", "barras", "linea", "tabla", "texto"];
|
|
@@ -41715,6 +41749,79 @@ function numero2(v, campo, problemas) {
|
|
|
41715
41749
|
function ancho(v) {
|
|
41716
41750
|
return v === 1 || v === 2 || v === 3 ? v : void 0;
|
|
41717
41751
|
}
|
|
41752
|
+
function aNumero(v) {
|
|
41753
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
41754
|
+
if (typeof v !== "string") return null;
|
|
41755
|
+
let s = v.trim().replace(/[$\s%]/g, "");
|
|
41756
|
+
if (s === "") return null;
|
|
41757
|
+
if (s.includes(",")) s = s.replace(/\./g, "").replace(",", ".");
|
|
41758
|
+
else if (/^-?\d{1,3}(\.\d{3})+$/.test(s)) s = s.replace(/\./g, "");
|
|
41759
|
+
const n = Number(s);
|
|
41760
|
+
return Number.isFinite(n) ? n : null;
|
|
41761
|
+
}
|
|
41762
|
+
var ES_VARIACION = /varia|cambio|crec|difer|dif\.|Δ|meta|cumpl|\bvs\b|versus/i;
|
|
41763
|
+
function controlarPorcentaje(valor, donde, problemas) {
|
|
41764
|
+
const n = aNumero(valor);
|
|
41765
|
+
if (n !== null && (n < 0 || n > 100)) problemas.push(`${donde} es un porcentaje y vale ${n}: tiene que estar entre 0 y 100 (si es una variaci\xF3n o un cumplimiento sobre meta, decirlo en el t\xEDtulo)`);
|
|
41766
|
+
}
|
|
41767
|
+
var MAX_CONSULTA = 8e3;
|
|
41768
|
+
function leerCalculo(v, ref, problemas) {
|
|
41769
|
+
if (v === void 0 || v === null) return { origen: "exploratorio" };
|
|
41770
|
+
if (typeof v !== "object" || Array.isArray(v)) {
|
|
41771
|
+
problemas.push(`${ref}.calculo tiene que ser el bloque \xABcalculo\xBB que devuelve calcular.mjs`);
|
|
41772
|
+
return { origen: "exploratorio" };
|
|
41773
|
+
}
|
|
41774
|
+
const o = v;
|
|
41775
|
+
const lista = arr(o.metricas);
|
|
41776
|
+
if (lista.length === 0) problemas.push(`${ref}.calculo.metricas est\xE1 vac\xEDo`);
|
|
41777
|
+
let propuesta = false;
|
|
41778
|
+
const metricas = [];
|
|
41779
|
+
lista.slice(0, 12).forEach((m, j) => {
|
|
41780
|
+
const mo = m && typeof m === "object" ? m : {};
|
|
41781
|
+
const id = typeof mo.id === "string" ? mo.id : "";
|
|
41782
|
+
const version2 = Number(mo.version);
|
|
41783
|
+
const def2 = metricaPorId(id);
|
|
41784
|
+
if (!def2) {
|
|
41785
|
+
problemas.push(`${ref}.calculo.metricas[${j}]: \xAB${id}\xBB no est\xE1 en el cat\xE1logo de m\xE9tricas; un n\xFAmero fuera del cat\xE1logo se publica sin calculo (queda como exploratorio)`);
|
|
41786
|
+
return;
|
|
41787
|
+
}
|
|
41788
|
+
if (def2.version !== version2) problemas.push(`${ref}.calculo.metricas[${j}]: \xAB${id}\xBB versi\xF3n ${Number.isFinite(version2) ? version2 : "?"} no es la vigente (${def2.version}): recalcular con calcular.mjs`);
|
|
41789
|
+
if (def2.estado === "a_definir") problemas.push(`${ref}.calculo.metricas[${j}]: \xAB${id}\xBB todav\xEDa no tiene definici\xF3n aprobada y no se publica como n\xFAmero oficial`);
|
|
41790
|
+
if (def2.estado === "propuesta") propuesta = true;
|
|
41791
|
+
metricas.push({ id, version: def2.version });
|
|
41792
|
+
});
|
|
41793
|
+
const huella = typeof o.huella === "string" && /^[0-9a-f]{16}$/.test(o.huella) ? o.huella : "";
|
|
41794
|
+
if (!huella) problemas.push(`${ref}.calculo.huella falta o no tiene la forma de calcular.mjs (16 caracteres hexadecimales)`);
|
|
41795
|
+
const parametros = o.parametros && typeof o.parametros === "object" && !Array.isArray(o.parametros) ? o.parametros : {};
|
|
41796
|
+
if (JSON.stringify(parametros).length > 2e3) problemas.push(`${ref}.calculo.parametros es demasiado largo`);
|
|
41797
|
+
const consulta = typeof o.consulta === "string" && o.consulta.trim() ? o.consulta.slice(0, MAX_CONSULTA) : void 0;
|
|
41798
|
+
const catalogo = typeof o.catalogo === "string" ? o.catalogo.slice(0, 40) : "";
|
|
41799
|
+
return { calculo: { catalogo, metricas, parametros, huella, ...consulta ? { consulta } : {} }, origen: propuesta ? "propuesta" : "certificado" };
|
|
41800
|
+
}
|
|
41801
|
+
var esTotal = (c) => typeof c === "string" && /^total\b/i.test(c.trim());
|
|
41802
|
+
var esSubtotal = (c) => typeof c === "string" && /^subtotal\b/i.test(c.trim());
|
|
41803
|
+
function controlarTabla(ref, columnas, filas, problemas) {
|
|
41804
|
+
columnas.forEach((col, j) => {
|
|
41805
|
+
if (!col.includes("%") || ES_VARIACION.test(col)) return;
|
|
41806
|
+
filas.forEach((f, i) => controlarPorcentaje(f[j], `${ref}.filas[${i}] \xAB${col}\xBB`, problemas));
|
|
41807
|
+
});
|
|
41808
|
+
const totales = filas.filter((f) => esTotal(f[0]));
|
|
41809
|
+
if (totales.length !== 1) return;
|
|
41810
|
+
const total = totales[0];
|
|
41811
|
+
const detalle = filas.filter((f) => !esTotal(f[0]) && !esSubtotal(f[0]));
|
|
41812
|
+
if (detalle.length === 0) return;
|
|
41813
|
+
for (let j = 1; j < columnas.length; j += 1) {
|
|
41814
|
+
if (columnas[j].includes("%")) continue;
|
|
41815
|
+
const esperado = aNumero(total[j]);
|
|
41816
|
+
if (esperado === null) continue;
|
|
41817
|
+
const valores = detalle.map((f) => f[j]);
|
|
41818
|
+
if (valores.some((x) => x !== null && x !== "" && aNumero(x) === null)) continue;
|
|
41819
|
+
const suma = valores.reduce((acc, x) => acc + (aNumero(x) ?? 0), 0);
|
|
41820
|
+
if (Math.abs(suma - esperado) > Math.max(0.011, Math.abs(esperado) * 1e-6)) {
|
|
41821
|
+
problemas.push(`${ref}: la columna \xAB${columnas[j]}\xBB suma ${Math.round(suma * 100) / 100} y la fila TOTAL dice ${esperado}`);
|
|
41822
|
+
}
|
|
41823
|
+
}
|
|
41824
|
+
}
|
|
41718
41825
|
function validarWidgets(entrada) {
|
|
41719
41826
|
const problemas = [];
|
|
41720
41827
|
const lista = Array.isArray(entrada) ? entrada : [];
|
|
@@ -41736,39 +41843,38 @@ function validarWidgets(entrada) {
|
|
|
41736
41843
|
const valor = typeof o.valor === "number" && Number.isFinite(o.valor) ? o.valor : texto2(o.valor, 40, `${ref}.valor`, problemas, true) ?? "";
|
|
41737
41844
|
const tono = typeof o.tono === "string" && TONOS.includes(o.tono) ? o.tono : void 0;
|
|
41738
41845
|
if (o.tono !== void 0 && !tono) problemas.push(`${ref}.tono debe ser uno de ${TONOS.join(", ")}`);
|
|
41739
|
-
|
|
41846
|
+
const unidad = texto2(o.unidad, 16, `${ref}.unidad`, problemas);
|
|
41847
|
+
if (unidad === "%" && !ES_VARIACION.test(etiqueta)) controlarPorcentaje(valor, `${ref}.valor`, problemas);
|
|
41848
|
+
const { calculo, origen } = leerCalculo(o.calculo, ref, problemas);
|
|
41849
|
+
salida.push({ tipo, etiqueta, valor, unidad, contexto: texto2(o.contexto, 160, `${ref}.contexto`, problemas), tono, ancho: a, ...calculo ? { calculo } : {}, origen });
|
|
41740
41850
|
break;
|
|
41741
41851
|
}
|
|
41742
41852
|
case "barras": {
|
|
41743
41853
|
const titulo = texto2(o.titulo, 120, `${ref}.titulo`, problemas, true) ?? "";
|
|
41744
41854
|
const series = arr(o.series);
|
|
41745
41855
|
if (series.length === 0 || series.length > 40) problemas.push(`${ref}.series debe tener entre 1 y 40 barras`);
|
|
41746
|
-
|
|
41747
|
-
|
|
41748
|
-
|
|
41749
|
-
|
|
41750
|
-
series: series.slice(0, 40).map((s, j) => {
|
|
41751
|
-
const so = s && typeof s === "object" ? s : {};
|
|
41752
|
-
return { etiqueta: texto2(so.etiqueta, 60, `${ref}.series[${j}].etiqueta`, problemas, true) ?? "", valor: numero2(so.valor, `${ref}.series[${j}].valor`, problemas) };
|
|
41753
|
-
}),
|
|
41754
|
-
ancho: a
|
|
41856
|
+
const unidad = texto2(o.unidad, 16, `${ref}.unidad`, problemas);
|
|
41857
|
+
const barras = series.slice(0, 40).map((s, j) => {
|
|
41858
|
+
const so = s && typeof s === "object" ? s : {};
|
|
41859
|
+
return { etiqueta: texto2(so.etiqueta, 60, `${ref}.series[${j}].etiqueta`, problemas, true) ?? "", valor: numero2(so.valor, `${ref}.series[${j}].valor`, problemas) };
|
|
41755
41860
|
});
|
|
41861
|
+
if (unidad === "%" && !ES_VARIACION.test(titulo)) barras.forEach((b, j) => controlarPorcentaje(b.valor, `${ref}.series[${j}].valor`, problemas));
|
|
41862
|
+
const { calculo, origen } = leerCalculo(o.calculo, ref, problemas);
|
|
41863
|
+
salida.push({ tipo, titulo, unidad, series: barras, ancho: a, ...calculo ? { calculo } : {}, origen });
|
|
41756
41864
|
break;
|
|
41757
41865
|
}
|
|
41758
41866
|
case "linea": {
|
|
41759
41867
|
const titulo = texto2(o.titulo, 120, `${ref}.titulo`, problemas, true) ?? "";
|
|
41760
41868
|
const puntos = arr(o.puntos);
|
|
41761
41869
|
if (puntos.length === 0 || puntos.length > 400) problemas.push(`${ref}.puntos debe tener entre 1 y 400 puntos`);
|
|
41762
|
-
|
|
41763
|
-
|
|
41764
|
-
|
|
41765
|
-
|
|
41766
|
-
puntos: puntos.slice(0, 400).map((p, j) => {
|
|
41767
|
-
const po = p && typeof p === "object" ? p : {};
|
|
41768
|
-
return { x: texto2(po.x, 40, `${ref}.puntos[${j}].x`, problemas, true) ?? "", y: numero2(po.y, `${ref}.puntos[${j}].y`, problemas) };
|
|
41769
|
-
}),
|
|
41770
|
-
ancho: a
|
|
41870
|
+
const unidad = texto2(o.unidad, 16, `${ref}.unidad`, problemas);
|
|
41871
|
+
const serie2 = puntos.slice(0, 400).map((p, j) => {
|
|
41872
|
+
const po = p && typeof p === "object" ? p : {};
|
|
41873
|
+
return { x: texto2(po.x, 40, `${ref}.puntos[${j}].x`, problemas, true) ?? "", y: numero2(po.y, `${ref}.puntos[${j}].y`, problemas) };
|
|
41771
41874
|
});
|
|
41875
|
+
if (unidad === "%" && !ES_VARIACION.test(titulo)) serie2.forEach((p, j) => controlarPorcentaje(p.y, `${ref}.puntos[${j}].y`, problemas));
|
|
41876
|
+
const { calculo, origen } = leerCalculo(o.calculo, ref, problemas);
|
|
41877
|
+
salida.push({ tipo, titulo, unidad, puntos: serie2, ancho: a, ...calculo ? { calculo } : {}, origen });
|
|
41772
41878
|
break;
|
|
41773
41879
|
}
|
|
41774
41880
|
case "tabla": {
|
|
@@ -41778,17 +41884,18 @@ function validarWidgets(entrada) {
|
|
|
41778
41884
|
const filas = arr(o.filas);
|
|
41779
41885
|
if (filas.length > 200) problemas.push(`${ref}.filas admite hasta 200 filas`);
|
|
41780
41886
|
const cols = columnas.slice(0, 12).map((c, j) => texto2(c, 60, `${ref}.columnas[${j}]`, problemas, true) ?? "");
|
|
41781
|
-
|
|
41782
|
-
|
|
41783
|
-
|
|
41784
|
-
|
|
41785
|
-
filas: filas.slice(0, 200).map((f, j) => {
|
|
41786
|
-
const celdas = arr(f);
|
|
41787
|
-
if (celdas.length !== cols.length) problemas.push(`${ref}.filas[${j}] tiene ${celdas.length} celdas y la tabla ${cols.length} columnas`);
|
|
41788
|
-
return celdas.slice(0, cols.length).map((c) => typeof c === "number" && Number.isFinite(c) ? c : c === null || c === void 0 ? null : String(c).slice(0, 200));
|
|
41789
|
-
}),
|
|
41790
|
-
ancho: a
|
|
41887
|
+
const celdasTabla = filas.slice(0, 200).map((f, j) => {
|
|
41888
|
+
const celdas = arr(f);
|
|
41889
|
+
if (celdas.length !== cols.length) problemas.push(`${ref}.filas[${j}] tiene ${celdas.length} celdas y la tabla ${cols.length} columnas`);
|
|
41890
|
+
return celdas.slice(0, cols.length).map((c) => typeof c === "number" && Number.isFinite(c) ? c : c === null || c === void 0 ? null : String(c).slice(0, 200));
|
|
41791
41891
|
});
|
|
41892
|
+
controlarTabla(ref, cols, celdasTabla, problemas);
|
|
41893
|
+
const { calculo, origen } = leerCalculo(o.calculo, ref, problemas);
|
|
41894
|
+
const por = calculo && Array.isArray(calculo.parametros.por) ? calculo.parametros.por : [];
|
|
41895
|
+
if (calculo && por.length > 0 && !celdasTabla.some((f) => esTotal(f[0]))) {
|
|
41896
|
+
problemas.push(`${ref}: una tabla con desglose de m\xE9tricas del cat\xE1logo lleva la fila TOTAL que devuelve calcular.mjs (primera celda \xABTOTAL\xBB)`);
|
|
41897
|
+
}
|
|
41898
|
+
salida.push({ tipo, titulo, columnas: cols, filas: celdasTabla, ancho: a, ...calculo ? { calculo } : {}, origen });
|
|
41792
41899
|
break;
|
|
41793
41900
|
}
|
|
41794
41901
|
case "texto": {
|
|
@@ -42120,13 +42227,15 @@ async function manejarApi(ctx, input2) {
|
|
|
42120
42227
|
return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "no_corresponde", motivo: motivoConPersona(str2(body.motivo)), datos: conPersona(obj(body.datos)), idempotencyKey: str2(body.idempotencyKey), actor }));
|
|
42121
42228
|
}
|
|
42122
42229
|
case "version":
|
|
42123
|
-
return respuesta(200, { version: manifest_default.version, capacidades: ["carga-manual", "recupero-turnos", "ofrecer-primero", "cuenta-completa"], workflows: listarWorkflows().map((w) => w.clave) });
|
|
42230
|
+
return respuesta(200, { version: manifest_default.version, capacidades: ["carga-manual", "recupero-turnos", "ofrecer-primero", "cuenta-completa", "metricas-certificadas"], workflows: listarWorkflows().map((w) => w.clave) });
|
|
42124
42231
|
case "politicas":
|
|
42125
42232
|
return respuesta(200, { politicas: await listarPoliticas(ctx, companyId) });
|
|
42126
42233
|
case "serie":
|
|
42127
42234
|
return respuesta(200, await serie(ctx, companyId, numOrNull(qs("dias"))));
|
|
42128
42235
|
case "historial-politicas":
|
|
42129
42236
|
return respuesta(200, { cambios: await historialPoliticas(ctx, companyId, numOrNull(qs("limit"))) });
|
|
42237
|
+
case "metricas":
|
|
42238
|
+
return respuesta(200, { version: CATALOGO_VERSION, metricas: METRICAS });
|
|
42130
42239
|
case "tableros":
|
|
42131
42240
|
return respuesta(200, { tableros: await listarTableros(ctx, companyId) });
|
|
42132
42241
|
case "tablero-publicado": {
|
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.9",
|
|
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": [
|