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 CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/manifest.ts
2
2
  var PLUGIN_ID = "alephoo.autopilot-ops";
3
- var PLUGIN_VERSION = "0.1.60";
3
+ var PLUGIN_VERSION = "0.1.62";
4
4
  var DB_NAMESPACE_SLUG = "autopilot_ops";
5
5
  var PAGE_ROUTE = "autopilot";
6
6
  var TOOL_NAMES = {
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.60";
11
+ var PLUGIN_VERSION = "0.1.62";
12
12
  var DB_NAMESPACE_SLUG = "autopilot_ops";
13
13
  var PAGE_ROUTE = "autopilot";
14
14
  var TOOL_NAMES = {
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.60";
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. Decide la direcci\xF3n: renegociar con el financiador o aceptarla. El puesto no escribe en el sistema.",
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
- if (h.liquidado && !pocasLiquidaciones) {
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
- conHonorario.push({ profesional_id: h.profesional_id, profesional: nombre, honorario: hon, volumen: h.volumen ?? null });
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 impacto = porMes(medido, l.volumen);
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 (insumos > 0 && sinInsumos >= minimo) causa = "insumos";
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 (medidoCon === "total" && margen !== null && margen >= minimo) causa = "indirectos";
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 costos = honorarioBase !== null ? honorarioBase + insumos + indUsado : null;
44709
- const valorObjetivo = costos !== null && umbral + descuentoPct < 100 ? Math.ceil(costos / (1 - (umbral + descuentoPct) / 100)) : null;
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
- if (mismoCalculo && antes !== null && antes < 0 && ahora !== null && ahora < antes * (1 + CRECIMIENTO_QUE_REABRE)) return { abrir: true, motivo: "la p\xE9rdida por mes creci\xF3 m\xE1s de un 20 % desde que la acept\xF3 la direcci\xF3n" };
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 prof = Array.isArray(previo.causa_profesionales) ? previo.causa_profesionales[0] : void 0;
45709
- const ahora = prof ? e.reparto.find((r) => r.profesional_id === prof.profesional_id)?.honorario ?? null : null;
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
- const bases = validas.length ? await guardarBases(ctx, companyId, validas, opciones.indirectos) : await leerBases(ctx, companyId);
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.60",
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": [