paperclip-plugin-autopilot-ops 0.1.60 → 0.1.62
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 +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/worker.js +76 -22
- package/package.json +1 -1
package/dist/manifest.js
CHANGED
package/dist/ui/index.js
CHANGED
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.62";
|
|
39936
39936
|
var DB_NAMESPACE_SLUG = "autopilot_ops";
|
|
39937
39937
|
var PAGE_ROUTE = "autopilot";
|
|
39938
39938
|
var TOOL_NAMES = {
|
|
@@ -43710,7 +43710,7 @@ var rentabilidadPractica = {
|
|
|
43710
43710
|
clave: "rentabilidad_practica",
|
|
43711
43711
|
nombre: "Rentabilidad por pr\xE1ctica",
|
|
43712
43712
|
puesto: "Costos y rentabilidad",
|
|
43713
|
-
descripcion: "Calcula, para cada pr\xE1ctica y cada financiador, lo que deja: lo que paga el convenio menos el honorario del profesional (con la regla del HIS) y los insumos. Marca las que dan p\xE9rdida o quedan debajo del umbral de la instituci\xF3n, y las que no se pueden calcular porque faltan datos en el HIS.
|
|
43713
|
+
descripcion: "Calcula, para cada pr\xE1ctica y cada financiador, lo que deja: lo que paga el convenio menos el honorario del profesional (con la regla del HIS) y los insumos. Marca las que dan p\xE9rdida o quedan debajo del umbral de la instituci\xF3n, y las que no se pueden calcular porque faltan datos en el HIS. La direcci\xF3n decide seg\xFAn la causa: revisar el honorario de un profesional, subir el precio particular, pedir aumento al financiador, revisar insumos o costos indirectos, o aceptarla con motivo y plazo. El puesto no escribe en el sistema.",
|
|
43714
43714
|
inicial: "detectado",
|
|
43715
43715
|
estados: ["detectado", "pendiente_aprobacion", "datos_incompletos", "completado", "en_renegociacion", "corregida", "datos_en_revision", "resuelta", "aceptada", "rentable", "descartado"],
|
|
43716
43716
|
finalesOk: ["resuelta", "rentable", "aceptada"],
|
|
@@ -44451,6 +44451,8 @@ function dia3(v) {
|
|
|
44451
44451
|
return s && /^\d{4}-\d{2}-\d{2}/.test(s) ? s.slice(0, 10) : null;
|
|
44452
44452
|
}
|
|
44453
44453
|
var LIQUIDACIONES_MINIMAS = 3;
|
|
44454
|
+
var INSUMOS_CAUSA_PCT = 10;
|
|
44455
|
+
var PLANES_SIN_VALOR_PCT = 10;
|
|
44454
44456
|
function honorarioDeRegla(regla, valor) {
|
|
44455
44457
|
const pct = (n2) => `${n2.toLocaleString("es-AR", { maximumFractionDigits: 2 })} %`;
|
|
44456
44458
|
const importe = regla.importe ?? null;
|
|
@@ -44534,7 +44536,8 @@ function leerLecturaRentabilidad(v) {
|
|
|
44534
44536
|
volumen: volumenPeriodo !== null && dias !== null && dias >= 1 ? Math.max(0, Math.round(volumenPeriodo)) : volumen !== null && volumen > 0 ? Math.round(volumen) : 0,
|
|
44535
44537
|
dias: volumenPeriodo !== null && dias !== null && dias >= 1 ? Math.round(dias) : 90,
|
|
44536
44538
|
minutos: minutos !== null && minutos > 0 && minutos <= 1440 ? Math.round(minutos) : null,
|
|
44537
|
-
tipo: texto5(r.tipo)?.slice(0, 80) ?? null
|
|
44539
|
+
tipo: texto5(r.tipo)?.slice(0, 80) ?? null,
|
|
44540
|
+
...num6(r.volumen_sin_valor) !== null && num6(r.volumen_sin_valor) > 0 ? { volumen_sin_valor: Math.round(num6(r.volumen_sin_valor)) } : {}
|
|
44538
44541
|
}
|
|
44539
44542
|
};
|
|
44540
44543
|
}
|
|
@@ -44596,6 +44599,7 @@ function evaluarRentabilidad(input2) {
|
|
|
44596
44599
|
const insumosARevisar = [];
|
|
44597
44600
|
const valor = redondear5(l.componentes.reduce((a, c) => a + c.monto, 0));
|
|
44598
44601
|
if (valor <= 0) faltas.push(esParticular(l.financiador) ? "particular_sin_precio" : "sin_valor_convenio");
|
|
44602
|
+
else if ((l.volumen_sin_valor ?? 0) > 0 && l.volumen > 0 && l.volumen_sin_valor / l.volumen > PLANES_SIN_VALOR_PCT / 100) faltas.push("planes_sin_valor");
|
|
44599
44603
|
let insumos = 0;
|
|
44600
44604
|
for (const i of l.insumos) {
|
|
44601
44605
|
if (i.precio === null || i.precio <= 0) {
|
|
@@ -44623,10 +44627,20 @@ function evaluarRentabilidad(input2) {
|
|
|
44623
44627
|
const deRegla = h.regla ? honorarioDeRegla(h.regla, valor) : null;
|
|
44624
44628
|
let hon;
|
|
44625
44629
|
const pocasLiquidaciones = Boolean(h.liquidado && deRegla && typeof h.liquidado.muestra === "number" && h.liquidado.muestra < LIQUIDACIONES_MINIMAS);
|
|
44626
|
-
|
|
44630
|
+
const reglaProporcional = deRegla?.fuente === "porcentaje";
|
|
44631
|
+
if (h.liquidado && reglaProporcional && h.liquidado.cantidad > 0) {
|
|
44632
|
+
const liq = h.liquidado.honorario / h.liquidado.cantidad;
|
|
44633
|
+
if (Math.abs(deRegla.honorario - liq) > Math.max(1, liq * 0.15) && !avisos.includes("regla_distinta_de_lo_liquidado")) avisos.push("regla_distinta_de_lo_liquidado");
|
|
44634
|
+
}
|
|
44635
|
+
if (h.liquidado && !pocasLiquidaciones && !reglaProporcional && h.liquidado.honorario > 0) {
|
|
44627
44636
|
hon = redondear5(h.liquidado.honorario / h.liquidado.cantidad);
|
|
44628
44637
|
if (deRegla && Math.abs(deRegla.honorario - hon) > Math.max(1, hon * 0.01) && !avisos.includes("regla_distinta_de_lo_liquidado")) avisos.push("regla_distinta_de_lo_liquidado");
|
|
44629
44638
|
detalle.push({ profesional_id: h.profesional_id, profesional: nombre, honorario: hon, fuente: "liquidado", detalle: `liquidado por el HIS: ${pesos4(hon)} por pr\xE1ctica${deRegla ? ` (la regla da ${pesos4(deRegla.honorario)}: ${deRegla.detalle})` : ""}` });
|
|
44639
|
+
} else if (h.liquidado && h.liquidado.honorario <= 0 && !deRegla) {
|
|
44640
|
+
falta("honorario_a_confirmar");
|
|
44641
|
+
debajo.push(`${nombre} (liquidado en $ 0: a confirmar)`);
|
|
44642
|
+
detalle.push({ profesional_id: h.profesional_id, profesional: nombre, honorario: 0, fuente: "liquidado", detalle: "el HIS liquid\xF3 $ 0: hay que confirmar si est\xE1 bien" });
|
|
44643
|
+
continue;
|
|
44630
44644
|
} else if (!h.regla) {
|
|
44631
44645
|
falta("sin_regla_honorario");
|
|
44632
44646
|
debajo.push(`${nombre} (sin regla)`);
|
|
@@ -44646,7 +44660,9 @@ function evaluarRentabilidad(input2) {
|
|
|
44646
44660
|
hon = deRegla.honorario;
|
|
44647
44661
|
detalle.push({ profesional_id: h.profesional_id, profesional: nombre, honorario: hon, fuente: deRegla.fuente, detalle: `regla ${h.regla.id}: ${deRegla.detalle}` });
|
|
44648
44662
|
}
|
|
44649
|
-
|
|
44663
|
+
const fuente = detalle[detalle.length - 1]?.fuente;
|
|
44664
|
+
const pct = fuente === "porcentaje" && valor > 0 ? hon / valor : 0;
|
|
44665
|
+
conHonorario.push({ profesional_id: h.profesional_id, profesional: nombre, honorario: hon, volumen: h.volumen ?? null, pct });
|
|
44650
44666
|
}
|
|
44651
44667
|
const honorarios = conHonorario.map((h) => h.honorario);
|
|
44652
44668
|
const honorarioMin = honorarios.length ? Math.min(...honorarios) : null;
|
|
@@ -44668,16 +44684,21 @@ function evaluarRentabilidad(input2) {
|
|
|
44668
44684
|
const pctDe = (m) => m !== null && valor > 0 ? Math.round(m / valor * 1e3) / 10 : null;
|
|
44669
44685
|
const margenPct = pctDe(medido);
|
|
44670
44686
|
const porMes = (m, vol) => m !== null ? redondear5(m * vol * 30 / l.dias) : null;
|
|
44671
|
-
const
|
|
44687
|
+
const impactoPromedio = porMes(medido, l.volumen);
|
|
44672
44688
|
const impactoPeor = margenPeor !== null ? porMes(margenPeor - indUsado, l.volumen) : null;
|
|
44673
44689
|
for (const h of conHonorario) {
|
|
44674
44690
|
const m = valor - descuento - insumos - indUsado - h.honorario;
|
|
44675
44691
|
if (valor > 0 && m / valor * 100 < umbral) debajo.push(h.profesional);
|
|
44676
44692
|
}
|
|
44693
|
+
const minimoPorPractica = valor * umbral / 100;
|
|
44694
|
+
const repartoConVolumen = segunQuien && valor > 0 ? conHonorario.map((h) => ({ h, m: valor - descuento - insumos - indUsado - h.honorario })) : [];
|
|
44695
|
+
const pierdenProf = repartoConVolumen.filter((x) => x.m < 0);
|
|
44696
|
+
const debajoProf = repartoConVolumen.filter((x) => x.m < minimoPorPractica);
|
|
44697
|
+
const perdidaProfesionales = pierdenProf.length ? redondear5(pierdenProf.reduce((a, x) => a + x.m * (x.h.volumen ?? 0) * 30 / l.dias, 0)) : null;
|
|
44677
44698
|
let clase2;
|
|
44678
44699
|
if (faltas.length > 0 || margenPct === null) clase2 = "datos_incompletos";
|
|
44679
|
-
else if (medido < 0) clase2 = "perdida";
|
|
44680
|
-
else if (margenPct < umbral) clase2 = "margen_bajo";
|
|
44700
|
+
else if (medido < 0 || pierdenProf.length > 0) clase2 = "perdida";
|
|
44701
|
+
else if (margenPct < umbral || debajoProf.length > 0) clase2 = "margen_bajo";
|
|
44681
44702
|
else if (l.insumos.length === 0 && !input2.sinInsumos) {
|
|
44682
44703
|
clase2 = "datos_incompletos";
|
|
44683
44704
|
faltas.push("insumos_sin_cargar");
|
|
@@ -44687,6 +44708,7 @@ function evaluarRentabilidad(input2) {
|
|
|
44687
44708
|
if (i >= 0) avisos.splice(i, 1);
|
|
44688
44709
|
}
|
|
44689
44710
|
if (valor > 0 && (insumos / valor * 100 > INSUMOS_A_VERIFICAR_PCT || honorarioMax !== null && honorarioMax > valor)) avisos.push("dato_a_verificar");
|
|
44711
|
+
const impacto = impactoPromedio !== null && perdidaProfesionales !== null ? Math.min(impactoPromedio, perdidaProfesionales) : impactoPromedio;
|
|
44690
44712
|
const reparto = valor > 0 ? conHonorario.map((h) => {
|
|
44691
44713
|
const m = redondear5(valor - descuento - insumos - indUsado - h.honorario);
|
|
44692
44714
|
return { profesional_id: h.profesional_id, profesional: h.profesional, honorario: h.honorario, margen: m, volumen: h.volumen, por_mes: h.volumen !== null ? porMes(m, h.volumen) : null };
|
|
@@ -44698,15 +44720,21 @@ function evaluarRentabilidad(input2) {
|
|
|
44698
44720
|
const sinInsumos = valor - descuento - honorarioBase - indUsado;
|
|
44699
44721
|
const llegan = reparto.filter((r) => r.margen >= minimo);
|
|
44700
44722
|
const noLlegan = reparto.filter((r) => r.margen < minimo);
|
|
44701
|
-
if (
|
|
44702
|
-
else if (llegan.length > 0 && noLlegan.length > 0) {
|
|
44723
|
+
if (llegan.length > 0 && noLlegan.length > 0) {
|
|
44703
44724
|
causa = "honorario_profesional";
|
|
44704
44725
|
causaProfesionales = noLlegan.map((r) => ({ profesional_id: r.profesional_id, profesional: r.profesional, honorario: r.honorario, margen: r.margen }));
|
|
44705
|
-
} else if (
|
|
44726
|
+
} else if (insumos > 0 && sinInsumos >= minimo && insumos / valor * 100 >= INSUMOS_CAUSA_PCT) causa = "insumos";
|
|
44727
|
+
else if (medidoCon === "total" && margen !== null && margen >= minimo) causa = "indirectos";
|
|
44706
44728
|
else causa = esParticular(l.financiador) ? "precio_particular" : "valor_convenio";
|
|
44707
44729
|
}
|
|
44708
|
-
const
|
|
44709
|
-
const
|
|
44730
|
+
const honorarioPct = !conHonorario.length ? 0 : segunQuien ? conHonorario.reduce((a, h) => a + h.pct * h.volumen, 0) / totalVol : conHonorario.reduce((m, h) => h.honorario === honorarioMax ? Math.max(m, h.pct) : m, 0);
|
|
44731
|
+
const indirectosPct = medidoCon === "total" && valor > 0 ? ind.detalle.filter((d) => (input2.indirectos ?? []).some((c) => c.id === d.id && c.reparto === "porcentaje")).reduce((a, d) => a + d.monto, 0) / valor : 0;
|
|
44732
|
+
const peorDebajo = debajoProf.length ? debajoProf.reduce((a, x) => x.h.honorario > a.h.honorario ? x : a).h : null;
|
|
44733
|
+
const [honObj, pctObj] = peorDebajo ? [peorDebajo.honorario, peorDebajo.pct] : [honorarioBase, honorarioPct];
|
|
44734
|
+
const fijos = honObj !== null ? honObj - pctObj * valor + insumos + (indUsado - indirectosPct * valor) : null;
|
|
44735
|
+
const libre = 1 - (umbral + descuentoPct) / 100 - pctObj - indirectosPct;
|
|
44736
|
+
const objetivoCalculado = fijos !== null && libre > 0.05 ? Math.ceil(fijos / libre) : null;
|
|
44737
|
+
const valorObjetivo = objetivoCalculado !== null && objetivoCalculado > valor ? objetivoCalculado : null;
|
|
44710
44738
|
const honorarioObjetivo = valor > 0 ? Math.max(0, Math.floor(valor * (1 - umbral / 100) - descuento - insumos - indUsado)) : null;
|
|
44711
44739
|
const insumosObjetivo = valor > 0 && honorarioBase !== null ? Math.max(0, Math.floor(valor * (1 - umbral / 100) - descuento - honorarioBase - indUsado)) : null;
|
|
44712
44740
|
const e = {
|
|
@@ -44727,6 +44755,9 @@ function evaluarRentabilidad(input2) {
|
|
|
44727
44755
|
margen_pct: margenPct,
|
|
44728
44756
|
umbral,
|
|
44729
44757
|
impacto_mensual: impacto,
|
|
44758
|
+
honorario_pct: Math.round(honorarioPct * 1e4) / 1e4,
|
|
44759
|
+
indirectos_pct: Math.round(indirectosPct * 1e4) / 1e4,
|
|
44760
|
+
perdida_profesionales: perdidaProfesionales,
|
|
44730
44761
|
impacto_peor: impactoPeor,
|
|
44731
44762
|
volumen: l.volumen,
|
|
44732
44763
|
dias: l.dias,
|
|
@@ -44758,6 +44789,7 @@ var NOMBRE_FALTA = {
|
|
|
44758
44789
|
sin_regla_honorario: "hay profesionales sin regla de honorarios",
|
|
44759
44790
|
sin_profesional: "se factur\xF3 sin un profesional (si es un medicamento o un descartable, no corresponde medirla como pr\xE1ctica)",
|
|
44760
44791
|
regla_sin_valor: "hay reglas de honorarios sin valor",
|
|
44792
|
+
planes_sin_valor: "m\xE1s de un 10 % se factur\xF3 con planes cuyo convenio no tiene valor para esta pr\xE1ctica: lo que paga el financiador no se puede afirmar",
|
|
44761
44793
|
insumos_sin_cargar: "no tiene los insumos cargados (sin ellos no se puede afirmar que deje margen; si no usa insumos, marcalo as\xED)"
|
|
44762
44794
|
};
|
|
44763
44795
|
var porcentaje = (n2) => `${n2.toLocaleString("es-AR", { maximumFractionDigits: 1 })} %`;
|
|
@@ -44790,6 +44822,8 @@ function oracionRentabilidad(l, e) {
|
|
|
44790
44822
|
const porque = porqueRentabilidad(l, e);
|
|
44791
44823
|
const total = e.medido_con === "total" ? " despu\xE9s de los costos indirectos" : "";
|
|
44792
44824
|
if (e.clase === "perdida") return `${practica} da p\xE9rdida${total}.${porque ? ` ${porque}` : ""}${mensual}`;
|
|
44825
|
+
const prof = e.causa === "honorario_profesional" ? e.causa_profesionales[0] : void 0;
|
|
44826
|
+
if (e.clase === "margen_bajo" && prof && e.valor > 0) return `${practica}: con ${prof.profesional} deja un margen de ${porcentaje(Math.round(prof.margen / e.valor * 1e3) / 10)}${total}, debajo del ${porcentaje(e.umbral)} de la instituci\xF3n.${porque ? ` ${porque}` : ""}${mensual}`;
|
|
44793
44827
|
if (e.clase === "margen_bajo") return `${practica} deja un margen de ${porcentaje(e.margen_pct)}${total}, debajo del ${porcentaje(e.umbral)} de la instituci\xF3n.${porque ? ` ${porque}` : ""}${mensual}`;
|
|
44794
44828
|
return `${practica} deja un margen de ${porcentaje(e.margen_pct)}${total}.`;
|
|
44795
44829
|
}
|
|
@@ -45208,6 +45242,7 @@ ON CONFLICT (company_id) DO UPDATE SET bases = EXCLUDED.bases, practicas = EXCLU
|
|
|
45208
45242
|
return bases;
|
|
45209
45243
|
}
|
|
45210
45244
|
async function guardarMediciones(ctx, companyId, items, quitar = []) {
|
|
45245
|
+
await ctx.db.execute(`DELETE FROM ${t3(ctx, "rentabilidad_medicion")} WHERE company_id = $1 AND medido_el < now() - interval '45 days'`, [companyId]);
|
|
45211
45246
|
for (const referencia of quitar) await ctx.db.execute(`DELETE FROM ${t3(ctx, "rentabilidad_medicion")} WHERE company_id = $1 AND referencia = $2`, [companyId, referencia]);
|
|
45212
45247
|
for (const { lectura: l, evaluacion } of items) {
|
|
45213
45248
|
await ctx.db.execute(
|
|
@@ -45233,7 +45268,7 @@ async function listarMediciones(ctx, companyId) {
|
|
|
45233
45268
|
}
|
|
45234
45269
|
async function ultimosPedidos(ctx, companyId) {
|
|
45235
45270
|
const filas = await ctx.db.query(
|
|
45236
|
-
`SELECT DISTINCT ON (referencia) referencia, estado, cerrado_at, datos->>'aceptada_hasta' AS aceptada_hasta, datos->>'perdida_aceptada' AS perdida_aceptada, datos->>'calculo_aceptado' AS calculo_aceptado
|
|
45271
|
+
`SELECT DISTINCT ON (referencia) referencia, estado, cerrado_at, datos->>'aceptada_hasta' AS aceptada_hasta, datos->>'perdida_aceptada' AS perdida_aceptada, datos->>'calculo_aceptado' AS calculo_aceptado, datos->>'peor_aceptada' AS peor_aceptada, datos->>'clase_aceptada' AS clase_aceptada
|
|
45237
45272
|
FROM ${t3(ctx, "caso")} WHERE company_id = $1 AND workflow = 'rentabilidad_practica' AND referencia IS NOT NULL
|
|
45238
45273
|
ORDER BY referencia, created_at DESC`,
|
|
45239
45274
|
[companyId]
|
|
@@ -45241,11 +45276,11 @@ ORDER BY referencia, created_at DESC`,
|
|
|
45241
45276
|
const out = /* @__PURE__ */ new Map();
|
|
45242
45277
|
for (const r of filas) {
|
|
45243
45278
|
const perdida = r.perdida_aceptada === null || r.perdida_aceptada === void 0 ? null : Number(r.perdida_aceptada);
|
|
45244
|
-
out.set(String(r.referencia), { estado: String(r.estado), abierto: !r.cerrado_at, aceptada_hasta: r.aceptada_hasta ?? null, perdida_aceptada: Number.isFinite(perdida) ? perdida : null, calculo_aceptado: r.calculo_aceptado ?? null });
|
|
45279
|
+
out.set(String(r.referencia), { estado: String(r.estado), abierto: !r.cerrado_at, aceptada_hasta: r.aceptada_hasta ?? null, perdida_aceptada: Number.isFinite(perdida) ? perdida : null, calculo_aceptado: r.calculo_aceptado ?? null, peor_aceptada: r.peor_aceptada === null || r.peor_aceptada === void 0 ? null : Number(r.peor_aceptada), clase_aceptada: r.clase_aceptada ?? null });
|
|
45245
45280
|
}
|
|
45246
45281
|
return out;
|
|
45247
45282
|
}
|
|
45248
|
-
function decidirApertura(e, ultimo, hoy) {
|
|
45283
|
+
function decidirApertura(e, ultimo, hoy, piso = 0) {
|
|
45249
45284
|
if (e.no_es_practica) return { abrir: false, motivo: "es un medicamento o un descartable facturado suelto: no se mide como pr\xE1ctica" };
|
|
45250
45285
|
const mala = e.clase === "perdida" || e.clase === "margen_bajo";
|
|
45251
45286
|
if (!ultimo) return { abrir: true, motivo: "no tiene pedido" };
|
|
@@ -45255,8 +45290,11 @@ function decidirApertura(e, ultimo, hoy) {
|
|
|
45255
45290
|
if (ultimo.aceptada_hasta && hoy >= ultimo.aceptada_hasta) return { abrir: true, motivo: `venci\xF3 la aceptaci\xF3n (${ultimo.aceptada_hasta})` };
|
|
45256
45291
|
const antes = ultimo.perdida_aceptada;
|
|
45257
45292
|
const ahora = e.impacto_mensual;
|
|
45293
|
+
if (ultimo.clase_aceptada === "margen_bajo" && e.clase === "perdida") return { abrir: true, motivo: "aceptada con margen bajo, ahora da p\xE9rdida" };
|
|
45294
|
+
if (antes !== null && antes >= 0 && ahora !== null && ahora < 0) return { abrir: true, motivo: "aceptada cuando dejaba margen, ahora da p\xE9rdida" };
|
|
45258
45295
|
const mismoCalculo = !ultimo.calculo_aceptado || !e.calculo || ultimo.calculo_aceptado === e.calculo;
|
|
45259
|
-
|
|
45296
|
+
const [a0, a1] = mismoCalculo ? [antes, ahora] : [ultimo.peor_aceptada ?? null, e.impacto_peor ?? null];
|
|
45297
|
+
if (a0 !== null && a1 !== null && (a1 < 0 || e.clase === "margen_bajo") && a0 - a1 > Math.max(Math.abs(a0) * CRECIMIENTO_QUE_REABRE, piso)) return { abrir: true, motivo: a1 < 0 ? "la p\xE9rdida por mes creci\xF3 m\xE1s de un 20 % desde que la acept\xF3 la direcci\xF3n" : "lo que deja por mes cay\xF3 m\xE1s de un 20 % desde que la acept\xF3 la direcci\xF3n" };
|
|
45260
45298
|
if (!ultimo.aceptada_hasta && antes === null) return { abrir: false, motivo: "la direcci\xF3n la acept\xF3 as\xED" };
|
|
45261
45299
|
return { abrir: false, motivo: `aceptada por la direcci\xF3n${ultimo.aceptada_hasta ? ` hasta el ${ultimo.aceptada_hasta}` : ""}` };
|
|
45262
45300
|
}
|
|
@@ -45698,6 +45736,17 @@ function pareceNoProfesional(nombre) {
|
|
|
45698
45736
|
const [apellido = "", nombres = ""] = n2.split(",").map((x) => x.trim().toLowerCase());
|
|
45699
45737
|
return /^[^a-záéíóúñü]/i.test(n2) || /\d/.test(n2) || apellido !== "" && apellido === nombres || /\b(admin|administrador|prueba|test|sistema|usuario)\b/i.test(n2);
|
|
45700
45738
|
}
|
|
45739
|
+
function imputarMinutos(lecturas) {
|
|
45740
|
+
const mediana2 = (xs) => {
|
|
45741
|
+
const o = [...xs].sort((a, b) => a - b);
|
|
45742
|
+
return o.length ? o[Math.floor(o.length / 2)] : null;
|
|
45743
|
+
};
|
|
45744
|
+
const conDuracion = lecturas.filter((l) => l.minutos !== null);
|
|
45745
|
+
const general = mediana2(conDuracion.map((l) => l.minutos));
|
|
45746
|
+
const porTipo = /* @__PURE__ */ new Map();
|
|
45747
|
+
for (const l of conDuracion) porTipo.set(l.tipo ?? "", [...porTipo.get(l.tipo ?? "") ?? [], l.minutos]);
|
|
45748
|
+
for (const l of lecturas) if (l.minutos === null) l.minutos = mediana2(porTipo.get(l.tipo ?? "") ?? []) ?? general;
|
|
45749
|
+
}
|
|
45701
45750
|
function resultadoPedido(d, e) {
|
|
45702
45751
|
const accion = str2(d.accion) ?? "pedir_aumento";
|
|
45703
45752
|
const pedido = typeof d.precio_propuesto === "number" ? d.precio_propuesto : null;
|
|
@@ -45705,8 +45754,9 @@ function resultadoPedido(d, e) {
|
|
|
45705
45754
|
if (accion === "revisar_honorario") {
|
|
45706
45755
|
const previo = d.evaluacion && typeof d.evaluacion === "object" ? d.evaluacion : {};
|
|
45707
45756
|
const objetivo = typeof previo.honorario_objetivo === "number" ? previo.honorario_objetivo : null;
|
|
45708
|
-
const
|
|
45709
|
-
const
|
|
45757
|
+
const profs = Array.isArray(previo.causa_profesionales) ? previo.causa_profesionales : [];
|
|
45758
|
+
const honorarios = profs.map((p) => e.reparto.find((r) => r.profesional_id === p.profesional_id)?.honorario ?? null);
|
|
45759
|
+
const ahora = honorarios.length && honorarios.every((h) => h !== null) ? Math.max(...honorarios) : null;
|
|
45710
45760
|
return { accion, pedido: objetivo, ahora, cumple: objetivo !== null && ahora !== null ? ahora <= objetivo + 0.5 : null };
|
|
45711
45761
|
}
|
|
45712
45762
|
return { accion, pedido: null, ahora: null, cumple: e.clase === "rentable" ? true : null };
|
|
@@ -45723,8 +45773,10 @@ async function evaluarRentabilidades(ctx, companyId, lecturas) {
|
|
|
45723
45773
|
const leidas = lecturas.map((x) => leerLecturaRentabilidad(x));
|
|
45724
45774
|
const validas = leidas.flatMap((l) => "error" in l ? [] : [l.lectura]);
|
|
45725
45775
|
for (const l of validas) conInsumosDeOpera(l, insumos);
|
|
45726
|
-
|
|
45776
|
+
imputarMinutos(validas);
|
|
45727
45777
|
const anteriores = new Map((await listarMediciones(ctx, companyId)).map((m) => [m.referencia, m]));
|
|
45778
|
+
const completa = validas.length > 0 && validas.length >= anteriores.size * 0.8;
|
|
45779
|
+
const bases = completa ? await guardarBases(ctx, companyId, validas, opciones.indirectos) : await leerBases(ctx, companyId);
|
|
45728
45780
|
const pedidos = await ultimosPedidos(ctx, companyId);
|
|
45729
45781
|
const medidas = [];
|
|
45730
45782
|
const resultados = [];
|
|
@@ -45747,7 +45799,7 @@ async function evaluarRentabilidades(ctx, companyId, lecturas) {
|
|
|
45747
45799
|
despues: { valor: evaluacion.valor, honorarios: evaluacion.honorarios.map((h) => ({ profesional_id: h.profesional_id, profesional: h.profesional, honorario: h.honorario })) }
|
|
45748
45800
|
});
|
|
45749
45801
|
if (!evaluacion.no_es_practica) medidas.push({ lectura: l.lectura, evaluacion });
|
|
45750
|
-
const apertura = decidirApertura(evaluacion, pedidos.get(referencia) ?? null, hoy);
|
|
45802
|
+
const apertura = decidirApertura(evaluacion, pedidos.get(referencia) ?? null, hoy, opciones.perdidaMenor);
|
|
45751
45803
|
resultados.push({ clave: `${l.lectura.prestacion_id}:${l.lectura.financiador_id}`, referencia, codigo: l.lectura.codigo, practica: l.lectura.nombre, financiador: l.lectura.financiador, evaluacion, oracion: oracionRentabilidad(l.lectura, evaluacion), abrir: apertura.abrir, motivo_abrir: apertura.motivo });
|
|
45752
45804
|
}
|
|
45753
45805
|
await guardarMediciones(ctx, companyId, medidas, noSonPracticas);
|
|
@@ -46487,7 +46539,7 @@ async function transicionar(ctx, input2) {
|
|
|
46487
46539
|
delete datos.lectura;
|
|
46488
46540
|
}
|
|
46489
46541
|
if (caso.workflow === "rentabilidad_practica") {
|
|
46490
|
-
for (const k of ["evaluacion", "oracion", "practica", "financiador", "codigo", "componentes", "insumos", "honorarios", "volumen_90d", "comprobado_el", "tipo", "minutos", "aceptada_hasta", "perdida_aceptada", "calculo_aceptado", "hecho_el", "pedido_enviado_el", "resultado_pedido"]) delete datos[k];
|
|
46542
|
+
for (const k of ["evaluacion", "oracion", "practica", "financiador", "codigo", "componentes", "insumos", "honorarios", "volumen_90d", "comprobado_el", "tipo", "minutos", "aceptada_hasta", "perdida_aceptada", "calculo_aceptado", "peor_aceptada", "clase_aceptada", "hecho_el", "pedido_enviado_el", "resultado_pedido"]) delete datos[k];
|
|
46491
46543
|
if (resultado.via === "no_se_pudo") {
|
|
46492
46544
|
datos.no_se_pudo = { motivo: str2(datos.motivo) ?? "", accion: str2(caso.datos.accion) ?? null, el: contexto.hoy ?? ahoraEnHospital().hoy };
|
|
46493
46545
|
datos.accion = null;
|
|
@@ -46504,6 +46556,8 @@ async function transicionar(ctx, input2) {
|
|
|
46504
46556
|
datos.aceptada_hasta = sumarMeses(contexto.hoy ?? ahoraEnHospital().hoy, meses);
|
|
46505
46557
|
datos.perdida_aceptada = typeof ev.impacto_mensual === "number" ? ev.impacto_mensual : null;
|
|
46506
46558
|
datos.calculo_aceptado = typeof ev.calculo === "string" ? ev.calculo : null;
|
|
46559
|
+
datos.peor_aceptada = typeof ev.impacto_peor === "number" ? ev.impacto_peor : null;
|
|
46560
|
+
datos.clase_aceptada = typeof ev.clase === "string" ? ev.clase : null;
|
|
46507
46561
|
}
|
|
46508
46562
|
const r = contexto.rentabilidad;
|
|
46509
46563
|
const l = leerLecturaRentabilidad(datos.lectura);
|
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.62",
|
|
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": [
|