paperclip-plugin-autopilot-ops 0.1.57 → 0.1.58

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.57";
3
+ var PLUGIN_VERSION = "0.1.58";
4
4
  var DB_NAMESPACE_SLUG = "autopilot_ops";
5
5
  var PAGE_ROUTE = "autopilot";
6
6
  var TOOL_NAMES = {
@@ -577,6 +577,14 @@ var manifest = {
577
577
  capability: "api.routes.register",
578
578
  companyResolution: { from: "body", key: "companyId" }
579
579
  },
580
+ {
581
+ routeKey: "no-se-pudo",
582
+ method: "POST",
583
+ path: "/casos/:casoId/no-se-pudo",
584
+ auth: "board",
585
+ capability: "api.routes.register",
586
+ companyResolution: { from: "body", key: "companyId" }
587
+ },
580
588
  {
581
589
  routeKey: "contactos",
582
590
  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.57";
11
+ var PLUGIN_VERSION = "0.1.58";
12
12
  var DB_NAMESPACE_SLUG = "autopilot_ops";
13
13
  var PAGE_ROUTE = "autopilot";
14
14
  var TOOL_NAMES = {
@@ -585,6 +585,14 @@ var manifest = {
585
585
  capability: "api.routes.register",
586
586
  companyResolution: { from: "body", key: "companyId" }
587
587
  },
588
+ {
589
+ routeKey: "no-se-pudo",
590
+ method: "POST",
591
+ path: "/casos/:casoId/no-se-pudo",
592
+ auth: "board",
593
+ capability: "api.routes.register",
594
+ companyResolution: { from: "body", key: "companyId" }
595
+ },
588
596
  {
589
597
  routeKey: "contactos",
590
598
  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.57";
39935
+ var PLUGIN_VERSION = "0.1.58";
39936
39936
  var DB_NAMESPACE_SLUG = "autopilot_ops";
39937
39937
  var PAGE_ROUTE = "autopilot";
39938
39938
  var TOOL_NAMES = {
@@ -40509,6 +40509,14 @@ var manifest = {
40509
40509
  capability: "api.routes.register",
40510
40510
  companyResolution: { from: "body", key: "companyId" }
40511
40511
  },
40512
+ {
40513
+ routeKey: "no-se-pudo",
40514
+ method: "POST",
40515
+ path: "/casos/:casoId/no-se-pudo",
40516
+ auth: "board",
40517
+ capability: "api.routes.register",
40518
+ companyResolution: { from: "body", key: "companyId" }
40519
+ },
40512
40520
  {
40513
40521
  routeKey: "contactos",
40514
40522
  method: "GET",
@@ -41275,9 +41283,9 @@ function textoIndices(detalle, desde, hasta) {
41275
41283
  const mes = (p) => (/* @__PURE__ */ new Date(`${p}-15T12:00:00Z`)).toLocaleDateString("es-AR", { month: "long", year: "numeric", timeZone: "UTC" });
41276
41284
  const pct = (n2) => `${n2.toLocaleString("es-AR", { maximumFractionDigits: 2 })} %`;
41277
41285
  const partes = detalle.map((d) => `${d.nombre} ${pct(d.variacion)}`);
41278
- const pesos4 = detalle.length > 1 ? ` (ponderados ${detalle.map((d) => `${d.peso} %`).join(" y ")})` : "";
41286
+ const pesos5 = detalle.length > 1 ? ` (ponderados ${detalle.map((d) => `${d.peso} %`).join(" y ")})` : "";
41279
41287
  const periodo = desde && hasta ? `, de ${mes(desde)} a ${mes(hasta)}` : "";
41280
- return `${partes.join(" y ")}${pesos4}${periodo}`;
41288
+ return `${partes.join(" y ")}${pesos5}${periodo}`;
41281
41289
  }
41282
41290
  var NOTA_POR_DEFECTO = {
41283
41291
  asunto: "Actualizaci\xF3n de aranceles \xB7 {convenio} \xB7 {institucion}",
@@ -43755,6 +43763,14 @@ var rentabilidadPractica = {
43755
43763
  MESES_ACEPTAR.includes(Number(datos.revisar_meses)) ? ok("plazo_valido") : falla("plazo_valido", `revisar_meses tiene que ser ${MESES_ACEPTAR.join(", ")}: en cu\xE1ntos meses se vuelve a mirar`)
43756
43764
  ]
43757
43765
  },
43766
+ {
43767
+ via: "no_se_pudo",
43768
+ de: ["en_renegociacion"],
43769
+ a: "pendiente_aprobacion",
43770
+ actor: ["humano"],
43771
+ descripcion: "El equipo no pudo hacer lo que pidi\xF3 la direcci\xF3n (el HIS no lo deja, el profesional no acepta, el financiador no recibe pedidos): vuelve a la direcci\xF3n con el motivo para que decida otra cosa.",
43772
+ validar: (_caso, datos) => [texto4(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "\xABno se pudo\xBB requiere el motivo: qu\xE9 lo impidi\xF3")]
43773
+ },
43758
43774
  {
43759
43775
  via: "cuestionar",
43760
43776
  de: ["pendiente_aprobacion"],
@@ -44418,6 +44434,7 @@ async function informarPruebaRegla(ctx, input2) {
44418
44434
  }
44419
44435
 
44420
44436
  // src/rentabilidad.ts
44437
+ var pesos4 = (n2) => `${n2 < 0 ? "\u2212" : ""}$ ${Math.round(Math.abs(n2)).toLocaleString("es-AR")}`;
44421
44438
  var UMBRAL_MARGEN_POR_DEFECTO = 15;
44422
44439
  var MESES_PRECIO_POR_DEFECTO = 6;
44423
44440
  var redondear5 = (n2) => Math.round(n2 * 100) / 100;
@@ -44433,19 +44450,26 @@ function dia3(v) {
44433
44450
  const s = texto5(v);
44434
44451
  return s && /^\d{4}-\d{2}-\d{2}/.test(s) ? s.slice(0, 10) : null;
44435
44452
  }
44453
+ var LIQUIDACIONES_MINIMAS = 3;
44436
44454
  function honorarioDeRegla(regla, valor) {
44437
44455
  const pct = (n2) => `${n2.toLocaleString("es-AR", { maximumFractionDigits: 2 })} %`;
44438
44456
  const importe = regla.importe ?? null;
44439
44457
  if (importe !== null && importe > 0) {
44440
- if (regla.prof !== null) return { honorario: redondear5(importe * regla.prof / 100), fuente: "importe_porcentaje", detalle: `honorario ${pesos2(importe)} \xD7 ${pct(regla.prof)} del profesional` };
44441
- if (regla.inst !== null) return { honorario: redondear5(importe * (1 - regla.inst / 100)), fuente: "importe", detalle: `honorario ${pesos2(importe)} menos ${pct(regla.inst)} de la instituci\xF3n` };
44442
- return { honorario: importe, fuente: "importe", detalle: `honorario fijo ${pesos2(importe)}` };
44458
+ if (regla.prof !== null) return { honorario: redondear5(importe * regla.prof / 100), fuente: "importe_porcentaje", detalle: `honorario ${pesos4(importe)} \xD7 ${pct(regla.prof)} del profesional` };
44459
+ if (regla.inst !== null) return { honorario: redondear5(importe * (1 - regla.inst / 100)), fuente: "importe", detalle: `honorario ${pesos4(importe)} menos ${pct(regla.inst)} de la instituci\xF3n` };
44460
+ return { honorario: importe, fuente: "importe", detalle: `honorario fijo ${pesos4(importe)}` };
44443
44461
  }
44444
- if (regla.prof !== null) return { honorario: regla.prof, fuente: "monto_fijo", detalle: `\xABprof\xBB ${regla.prof.toLocaleString("es-AR", { maximumFractionDigits: 2 })} sin honorario base: el HIS liquida ${pesos2(regla.prof)} fijos` };
44462
+ if (regla.prof !== null) return { honorario: regla.prof, fuente: "monto_fijo", detalle: `\xABprof\xBB ${regla.prof.toLocaleString("es-AR", { maximumFractionDigits: 2 })} sin honorario base: el HIS liquida ${pesos4(regla.prof)} fijos` };
44445
44463
  if (regla.inst !== null) return { honorario: redondear5(valor * regla.inst / 100), fuente: "porcentaje", detalle: `${pct(regla.inst)} de lo que paga el convenio` };
44446
44464
  return null;
44447
44465
  }
44448
44466
  var HONORARIO_MINIMO_PCT = 1;
44467
+ var HONORARIO_FIJO_MINIMO_PCT = 10;
44468
+ var INSUMOS_A_VERIFICAR_PCT = 50;
44469
+ function noEsPractica(l) {
44470
+ if (l.honorarios.length > 0) return false;
44471
+ return /medicament|descartable|insumo|material|farmac/i.test(l.tipo ?? "") || /\b(comprimid|ampolla|frasco|sachet|cateter|catéter|guantes?|jeringa|gasas?|capsula|cápsula|mg\b)/i.test(l.nombre);
44472
+ }
44449
44473
  function leerLecturaRentabilidad(v) {
44450
44474
  if (!v || typeof v !== "object" || Array.isArray(v)) return { error: "lectura: tiene que ser un objeto" };
44451
44475
  const r = v;
@@ -44484,7 +44508,8 @@ function leerLecturaRentabilidad(v) {
44484
44508
  const h = num6(lq.honorario);
44485
44509
  const c2 = num6(lq.cantidad);
44486
44510
  if (h === null || h < 0 || c2 === null || c2 <= 0) return { error: `lectura.honorarios[${i}].liquidado: tiene que ser {honorario \u2265 0, cantidad > 0} o null` };
44487
- liquidado = { honorario: redondear5(h), cantidad: c2 };
44511
+ const muestra = num6(lq.muestra);
44512
+ liquidado = { honorario: redondear5(h), cantidad: c2, ...muestra !== null && muestra >= 0 ? { muestra: Math.round(muestra) } : {} };
44488
44513
  }
44489
44514
  const vol = num6(x.volumen);
44490
44515
  honorarios.push({ profesional_id: pid, profesional: texto5(x.profesional), regla: leerRegla(x.regla), ...liquidado ? { liquidado } : {}, volumen: vol !== null && vol >= 0 ? Math.round(vol) : null });
@@ -44537,7 +44562,10 @@ function indirectosDe(l, valor, costos, bases) {
44537
44562
  continue;
44538
44563
  }
44539
44564
  const base = bases[c.id] ?? 0;
44540
- if (base <= 0) continue;
44565
+ if (base <= 0) {
44566
+ if (!avisos.includes("indirecto_sin_base")) avisos.push("indirecto_sin_base");
44567
+ continue;
44568
+ }
44541
44569
  detalle.push({ id: c.id, nombre: c.nombre, monto: redondear5(c.mensual * uso / base) });
44542
44570
  }
44543
44571
  return { total: redondear5(detalle.reduce((a, d) => a + d.monto, 0)), detalle, avisos };
@@ -44594,10 +44622,11 @@ function evaluarRentabilidad(input2) {
44594
44622
  const nombre = h.profesional ?? h.profesional_id;
44595
44623
  const deRegla = h.regla ? honorarioDeRegla(h.regla, valor) : null;
44596
44624
  let hon;
44597
- if (h.liquidado) {
44625
+ const pocasLiquidaciones = Boolean(h.liquidado && deRegla && typeof h.liquidado.muestra === "number" && h.liquidado.muestra < LIQUIDACIONES_MINIMAS);
44626
+ if (h.liquidado && !pocasLiquidaciones) {
44598
44627
  hon = redondear5(h.liquidado.honorario / h.liquidado.cantidad);
44599
44628
  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");
44600
- detalle.push({ profesional_id: h.profesional_id, profesional: nombre, honorario: hon, fuente: "liquidado", detalle: `liquidado por el HIS: ${pesos2(hon)} por pr\xE1ctica${deRegla ? ` (la regla da ${pesos2(deRegla.honorario)}: ${deRegla.detalle})` : ""}` });
44629
+ 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})` : ""}` });
44601
44630
  } else if (!h.regla) {
44602
44631
  falta("sin_regla_honorario");
44603
44632
  debajo.push(`${nombre} (sin regla)`);
@@ -44608,10 +44637,10 @@ function evaluarRentabilidad(input2) {
44608
44637
  debajo.push(`${nombre} (regla sin valor)`);
44609
44638
  detalle.push({ profesional_id: h.profesional_id, profesional: nombre, honorario: null, fuente: null, detalle: `la regla ${h.regla.id} del HIS no tiene honorario, \xABprof\xBB ni \xABinst\xBB` });
44610
44639
  continue;
44611
- } else if (valor > 0 && deRegla.honorario < valor * HONORARIO_MINIMO_PCT / 100) {
44640
+ } else if (valor > 0 && (deRegla.honorario < valor * HONORARIO_MINIMO_PCT / 100 || deRegla.fuente === "monto_fijo" && deRegla.honorario < valor * HONORARIO_FIJO_MINIMO_PCT / 100)) {
44612
44641
  falta("honorario_a_confirmar");
44613
- debajo.push(`${nombre} (honorario de ${pesos2(deRegla.honorario)}: a confirmar)`);
44614
- detalle.push({ profesional_id: h.profesional_id, profesional: nombre, honorario: deRegla.honorario, fuente: deRegla.fuente, detalle: `regla ${h.regla.id}: ${deRegla.detalle}. Es menos del ${HONORARIO_MINIMO_PCT} % de lo que paga el convenio: hay que confirmar si est\xE1 bien cargada` });
44642
+ debajo.push(`${nombre} (honorario de ${pesos4(deRegla.honorario)}: a confirmar)`);
44643
+ detalle.push({ profesional_id: h.profesional_id, profesional: nombre, honorario: deRegla.honorario, fuente: deRegla.fuente, detalle: `regla ${h.regla.id}: ${deRegla.detalle}. Es menos del ${deRegla.fuente === "monto_fijo" ? HONORARIO_FIJO_MINIMO_PCT : HONORARIO_MINIMO_PCT} % de lo que se cobra: hay que confirmar si est\xE1 bien cargada (\xBFun porcentaje cargado como pesos?)` });
44615
44644
  continue;
44616
44645
  } else {
44617
44646
  hon = deRegla.honorario;
@@ -44625,13 +44654,15 @@ function evaluarRentabilidad(input2) {
44625
44654
  const totalVol = conHonorario.reduce((a, h) => a + (h.volumen ?? 0), 0);
44626
44655
  const segunQuien = conHonorario.length > 0 && conHonorario.every((h) => h.volumen !== null && h.volumen > 0) && totalVol > 0;
44627
44656
  const honorarioBase = !honorarios.length ? null : segunQuien ? redondear5(conHonorario.reduce((a, h) => a + h.honorario * h.volumen, 0) / totalVol) : honorarioMax;
44657
+ const descuentoPct = input2.descuento !== void 0 && input2.descuento > 0 && input2.descuento < 100 ? input2.descuento : 0;
44658
+ const descuento = redondear5(valor * descuentoPct / 100);
44628
44659
  const ind = indirectosDe(l, valor, input2.indirectos ?? [], input2.bases ?? null);
44629
44660
  for (const a of ind.avisos) if (!avisos.includes(a)) avisos.push(a);
44630
44661
  const medidoCon = input2.medirCon === "total" && input2.bases ? "total" : "directo";
44631
44662
  const indUsado = medidoCon === "total" ? ind.total : 0;
44632
- const margen = honorarioBase !== null && valor > 0 ? redondear5(valor - insumos - honorarioBase) : null;
44633
- const margenPeor = honorarioMax !== null && valor > 0 ? redondear5(valor - insumos - honorarioMax) : null;
44634
- const margenMejor = honorarioMin !== null && valor > 0 ? redondear5(valor - insumos - honorarioMin) : null;
44663
+ const margen = honorarioBase !== null && valor > 0 ? redondear5(valor - descuento - insumos - honorarioBase) : null;
44664
+ const margenPeor = honorarioMax !== null && valor > 0 ? redondear5(valor - descuento - insumos - honorarioMax) : null;
44665
+ const margenMejor = honorarioMin !== null && valor > 0 ? redondear5(valor - descuento - insumos - honorarioMin) : null;
44635
44666
  const margenTotal = margen !== null ? redondear5(margen - ind.total) : null;
44636
44667
  const medido = margen !== null ? redondear5(margen - indUsado) : null;
44637
44668
  const pctDe = (m) => m !== null && valor > 0 ? Math.round(m / valor * 1e3) / 10 : null;
@@ -44640,23 +44671,31 @@ function evaluarRentabilidad(input2) {
44640
44671
  const impacto = porMes(medido, l.volumen);
44641
44672
  const impactoPeor = margenPeor !== null ? porMes(margenPeor - indUsado, l.volumen) : null;
44642
44673
  for (const h of conHonorario) {
44643
- const m = valor - insumos - indUsado - h.honorario;
44674
+ const m = valor - descuento - insumos - indUsado - h.honorario;
44644
44675
  if (valor > 0 && m / valor * 100 < umbral) debajo.push(h.profesional);
44645
44676
  }
44646
44677
  let clase2;
44647
44678
  if (faltas.length > 0 || margenPct === null) clase2 = "datos_incompletos";
44648
44679
  else if (medido < 0) clase2 = "perdida";
44649
44680
  else if (margenPct < umbral) clase2 = "margen_bajo";
44650
- else clase2 = "rentable";
44681
+ else if (l.insumos.length === 0 && !input2.sinInsumos) {
44682
+ clase2 = "datos_incompletos";
44683
+ faltas.push("insumos_sin_cargar");
44684
+ } else clase2 = "rentable";
44685
+ if (input2.sinInsumos) {
44686
+ const i = avisos.indexOf("sin_insumos");
44687
+ if (i >= 0) avisos.splice(i, 1);
44688
+ }
44689
+ if (valor > 0 && (insumos / valor * 100 > INSUMOS_A_VERIFICAR_PCT || honorarioMax !== null && honorarioMax > valor)) avisos.push("dato_a_verificar");
44651
44690
  const reparto = valor > 0 ? conHonorario.map((h) => {
44652
- const m = redondear5(valor - insumos - indUsado - h.honorario);
44691
+ const m = redondear5(valor - descuento - insumos - indUsado - h.honorario);
44653
44692
  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 };
44654
44693
  }) : [];
44655
44694
  let causa = null;
44656
44695
  let causaProfesionales = [];
44657
44696
  const minimo = valor * umbral / 100;
44658
44697
  if ((clase2 === "perdida" || clase2 === "margen_bajo") && honorarioBase !== null) {
44659
- const sinInsumos = valor - honorarioBase - indUsado;
44698
+ const sinInsumos = valor - descuento - honorarioBase - indUsado;
44660
44699
  const llegan = reparto.filter((r) => r.margen >= minimo);
44661
44700
  const noLlegan = reparto.filter((r) => r.margen < minimo);
44662
44701
  if (insumos > 0 && sinInsumos >= minimo) causa = "insumos";
@@ -44667,8 +44706,9 @@ function evaluarRentabilidad(input2) {
44667
44706
  else causa = esParticular(l.financiador) ? "precio_particular" : "valor_convenio";
44668
44707
  }
44669
44708
  const costos = honorarioBase !== null ? honorarioBase + insumos + indUsado : null;
44670
- const valorObjetivo = costos !== null && umbral < 100 ? Math.ceil(costos / (1 - umbral / 100)) : null;
44671
- const honorarioObjetivo = valor > 0 ? Math.max(0, Math.floor(valor * (1 - umbral / 100) - insumos - indUsado)) : null;
44709
+ const valorObjetivo = costos !== null && umbral + descuentoPct < 100 ? Math.ceil(costos / (1 - (umbral + descuentoPct) / 100)) : null;
44710
+ const honorarioObjetivo = valor > 0 ? Math.max(0, Math.floor(valor * (1 - umbral / 100) - descuento - insumos - indUsado)) : null;
44711
+ const insumosObjetivo = valor > 0 && honorarioBase !== null ? Math.max(0, Math.floor(valor * (1 - umbral / 100) - descuento - honorarioBase - indUsado)) : null;
44672
44712
  const e = {
44673
44713
  clase: clase2,
44674
44714
  valor,
@@ -44696,6 +44736,10 @@ function evaluarRentabilidad(input2) {
44696
44736
  causa_profesionales: causaProfesionales,
44697
44737
  valor_objetivo: clase2 === "perdida" || clase2 === "margen_bajo" ? valorObjetivo : null,
44698
44738
  honorario_objetivo: causa === "honorario_profesional" ? honorarioObjetivo : null,
44739
+ insumos_objetivo: causa === "insumos" ? insumosObjetivo : null,
44740
+ descuento,
44741
+ descuento_pct: descuentoPct,
44742
+ no_es_practica: noEsPractica(l),
44699
44743
  perdida_menor: impacto !== null && impacto < 0 && Math.abs(impacto) < perdidaMenor,
44700
44744
  porque: null,
44701
44745
  faltas,
@@ -44713,7 +44757,8 @@ var NOMBRE_FALTA = {
44713
44757
  insumo_sin_precio: "hay insumos sin precio",
44714
44758
  sin_regla_honorario: "hay profesionales sin regla de honorarios",
44715
44759
  sin_profesional: "se factur\xF3 sin un profesional (si es un medicamento o un descartable, no corresponde medirla como pr\xE1ctica)",
44716
- regla_sin_valor: "hay reglas de honorarios sin valor"
44760
+ regla_sin_valor: "hay reglas de honorarios sin valor",
44761
+ insumos_sin_cargar: "no tiene los insumos cargados (sin ellos no se puede afirmar que deje margen; si no usa insumos, marcalo as\xED)"
44717
44762
  };
44718
44763
  var porcentaje = (n2) => `${n2.toLocaleString("es-AR", { maximumFractionDigits: 1 })} %`;
44719
44764
  function porqueRentabilidad(l, e) {
@@ -44723,16 +44768,16 @@ function porqueRentabilidad(l, e) {
44723
44768
  const otro = [...e.reparto].sort((a, b) => b.margen - a.margen)[0];
44724
44769
  if (!p) return null;
44725
44770
  const quienes = e.causa_profesionales.length > 1 ? `${p.profesional} y ${e.causa_profesionales.length - 1} m\xE1s` : p.profesional;
44726
- return `Con ${quienes} (honorario ${pesos2(p.honorario)}) no llega al m\xEDnimo${otro && otro.profesional_id !== p.profesional_id ? `; con ${otro.profesional} (${pesos2(otro.honorario)}) la misma pr\xE1ctica deja ${pesos2(otro.margen)}` : ""}.`;
44771
+ return `Con ${quienes} (honorario ${pesos4(p.honorario)}) no llega al m\xEDnimo${otro && otro.profesional_id !== p.profesional_id ? `; con ${otro.profesional} (${pesos4(otro.honorario)}) la misma pr\xE1ctica deja ${pesos4(otro.margen)}` : ""}.`;
44727
44772
  }
44728
44773
  case "insumos":
44729
- return `Los insumos (${pesos2(e.insumos)}) son el ${porcentaje(Math.round(e.insumos / e.valor * 1e3) / 10)} de lo que se cobra: sin ellos dejar\xEDa margen.`;
44774
+ return `Los insumos (${pesos4(e.insumos)}) son el ${porcentaje(Math.round(e.insumos / e.valor * 1e3) / 10)} de lo que se cobra: sin ellos dejar\xEDa margen${e.insumos_objetivo !== null ? `; para el ${porcentaje(e.umbral)} tendr\xEDan que costar hasta ${pesos4(e.insumos_objetivo)}` : ""}.`;
44730
44775
  case "indirectos":
44731
- return `Deja margen antes de los costos indirectos; los ${pesos2(e.indirectos)} que le tocan por pr\xE1ctica la llevan debajo del m\xEDnimo.`;
44776
+ return `Deja margen antes de los costos indirectos; los ${pesos4(e.indirectos)} que le tocan por pr\xE1ctica la llevan debajo del m\xEDnimo.`;
44732
44777
  case "precio_particular":
44733
- return `El precio particular (${pesos2(e.valor)}) no cubre el honorario y los insumos${e.valor_objetivo !== null ? `: para el ${porcentaje(e.umbral)} hace falta cobrar ${pesos2(e.valor_objetivo)}` : ""}.`;
44778
+ return `El precio particular (${pesos4(e.valor)}) no cubre el honorario y los insumos${e.valor_objetivo !== null ? `: para el ${porcentaje(e.umbral)} hace falta cobrar ${pesos4(e.valor_objetivo)}` : ""}.`;
44734
44779
  case "valor_convenio":
44735
- return `Lo que paga ${l.financiador} (${pesos2(e.valor)}) no alcanza con ning\xFAn profesional${e.valor_objetivo !== null ? `: para el ${porcentaje(e.umbral)} tendr\xEDa que pagar ${pesos2(e.valor_objetivo)}` : ""}.`;
44780
+ return `Lo que paga ${l.financiador} (${pesos4(e.valor)}) no alcanza con ning\xFAn profesional${e.valor_objetivo !== null ? `: para el ${porcentaje(e.umbral)} tendr\xEDa que pagar ${pesos4(e.valor_objetivo)}` : ""}.`;
44736
44781
  default:
44737
44782
  return null;
44738
44783
  }
@@ -44741,7 +44786,7 @@ function oracionRentabilidad(l, e) {
44741
44786
  const practica = `${l.nombre} (${l.codigo}) con ${l.financiador}`;
44742
44787
  if (e.clase === "datos_incompletos") return `${practica}: no se puede calcular la rentabilidad porque ${e.faltas.map((f) => NOMBRE_FALTA[f]).join(" y ")}.`;
44743
44788
  const como = e.calculo === "segun_quien" ? "seg\xFAn qui\xE9n la hizo" : "peor caso: como si todas las hiciera el profesional que m\xE1s cobra";
44744
- const mensual = e.impacto_mensual !== null && e.impacto_mensual !== 0 ? ` Con el volumen de los \xFAltimos ${e.dias ?? 90} d\xEDas, ${e.impacto_mensual < 0 ? "se pierden" : "deja"} unos ${pesos2(Math.abs(e.impacto_mensual))} por mes (${como}).` : "";
44789
+ const mensual = e.impacto_mensual !== null && e.impacto_mensual !== 0 ? ` Con el volumen de los \xFAltimos ${e.dias ?? 90} d\xEDas, ${e.impacto_mensual < 0 ? "se pierden" : "deja"} unos ${pesos4(Math.abs(e.impacto_mensual))} por mes (${como}).` : "";
44745
44790
  const porque = porqueRentabilidad(l, e);
44746
44791
  const total = e.medido_con === "total" ? " despu\xE9s de los costos indirectos" : "";
44747
44792
  if (e.clase === "perdida") return `${practica} da p\xE9rdida${total}.${porque ? ` ${porque}` : ""}${mensual}`;
@@ -45108,19 +45153,51 @@ function erroresIndirectos(v) {
45108
45153
  if (new Set(ids).size !== ids.length) errores.push("indirectos: hay dos costos con el mismo id");
45109
45154
  return errores;
45110
45155
  }
45156
+ function porcentajeDescuento(v) {
45157
+ const n2 = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v.replace(",", ".")) : NaN;
45158
+ return Number.isFinite(n2) && n2 >= 0 && n2 <= 60 ? Math.round(n2 * 10) / 10 : null;
45159
+ }
45160
+ function leerDescuentos(v) {
45161
+ if (!v || typeof v !== "object" || Array.isArray(v)) return {};
45162
+ const out = {};
45163
+ for (const [k, x] of Object.entries(v)) {
45164
+ const p = porcentajeDescuento(x);
45165
+ if (k.trim() && k.length <= 120 && p !== null) out[k.trim()] = p;
45166
+ }
45167
+ return out;
45168
+ }
45169
+ function descuentoDe(op, financiador) {
45170
+ return op.descuentoPorFinanciador[financiador] ?? op.descuento;
45171
+ }
45172
+ var validarDescuento = (v) => porcentajeDescuento(v);
45111
45173
  function opcionesRentabilidad(op) {
45112
45174
  return {
45113
45175
  umbral: enteroEntre(op.umbral_margen, 0, 90) ?? UMBRAL_MARGEN_POR_DEFECTO,
45114
45176
  mesesPrecio: enteroEntre(op.meses_precio, 1, 36) ?? MESES_PRECIO_POR_DEFECTO,
45115
45177
  indirectos: leerIndirectos(op.indirectos),
45116
45178
  medirCon: op.medir_con === "total" ? "total" : "directo",
45117
- perdidaMenor: enteroEntre(op.perdida_menor, 0, 1e7) ?? PERDIDA_MENOR_POR_DEFECTO
45179
+ perdidaMenor: enteroEntre(op.perdida_menor, 0, 1e7) ?? PERDIDA_MENOR_POR_DEFECTO,
45180
+ sinInsumos: Array.isArray(op.practicas_sin_insumos) ? op.practicas_sin_insumos.map(String).slice(0, 5e3) : [],
45181
+ descuento: porcentajeDescuento(op.descuento_cobro) ?? 0,
45182
+ descuentoPorFinanciador: leerDescuentos(op.descuentos_financiador)
45118
45183
  };
45119
45184
  }
45120
45185
  async function leerBases(ctx, companyId) {
45121
45186
  const r = (await ctx.db.query(`SELECT bases FROM ${t3(ctx, "rentabilidad_base")} WHERE company_id = $1 LIMIT 1`, [companyId]))[0];
45122
45187
  return r && r.bases && typeof r.bases === "object" ? r.bases : null;
45123
45188
  }
45189
+ async function recalcularBases(ctx, companyId, costos) {
45190
+ const ms = await listarMediciones(ctx, companyId);
45191
+ if (!ms.length) return null;
45192
+ const lecturas = ms.map((m) => ({ minutos: m.minutos, tipo: m.tipo, volumen: m.evaluacion.volumen ?? 0, dias: m.evaluacion.dias ?? 90, componentes: m.componentes }));
45193
+ const bases = basesIndirectos(lecturas, costos);
45194
+ await ctx.db.execute(
45195
+ `INSERT INTO ${t3(ctx, "rentabilidad_base")} (company_id, bases, practicas, calculada_el) VALUES ($1, $2::jsonb, $3, now())
45196
+ ON CONFLICT (company_id) DO UPDATE SET bases = EXCLUDED.bases, practicas = EXCLUDED.practicas, calculada_el = now()`,
45197
+ [companyId, JSON.stringify(bases), lecturas.length]
45198
+ );
45199
+ return bases;
45200
+ }
45124
45201
  async function guardarBases(ctx, companyId, lecturas, costos) {
45125
45202
  const bases = basesIndirectos(lecturas, costos);
45126
45203
  await ctx.db.execute(
@@ -45155,7 +45232,7 @@ async function listarMediciones(ctx, companyId) {
45155
45232
  }
45156
45233
  async function ultimosPedidos(ctx, companyId) {
45157
45234
  const filas = await ctx.db.query(
45158
- `SELECT DISTINCT ON (referencia) referencia, estado, cerrado_at, datos->>'aceptada_hasta' AS aceptada_hasta, datos->>'perdida_aceptada' AS perdida_aceptada
45235
+ `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
45159
45236
  FROM ${t3(ctx, "caso")} WHERE company_id = $1 AND workflow = 'rentabilidad_practica' AND referencia IS NOT NULL
45160
45237
  ORDER BY referencia, created_at DESC`,
45161
45238
  [companyId]
@@ -45163,11 +45240,12 @@ ORDER BY referencia, created_at DESC`,
45163
45240
  const out = /* @__PURE__ */ new Map();
45164
45241
  for (const r of filas) {
45165
45242
  const perdida = r.perdida_aceptada === null || r.perdida_aceptada === void 0 ? null : Number(r.perdida_aceptada);
45166
- 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 });
45243
+ 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 });
45167
45244
  }
45168
45245
  return out;
45169
45246
  }
45170
45247
  function decidirApertura(e, ultimo, hoy) {
45248
+ if (e.no_es_practica) return { abrir: false, motivo: "es un medicamento o un descartable facturado suelto: no se mide como pr\xE1ctica" };
45171
45249
  const mala = e.clase === "perdida" || e.clase === "margen_bajo";
45172
45250
  if (!ultimo) return { abrir: true, motivo: "no tiene pedido" };
45173
45251
  if (ultimo.abierto) return { abrir: false, motivo: "tiene un pedido abierto: se vuelve a medir ese" };
@@ -45176,7 +45254,8 @@ function decidirApertura(e, ultimo, hoy) {
45176
45254
  if (ultimo.aceptada_hasta && hoy >= ultimo.aceptada_hasta) return { abrir: true, motivo: `venci\xF3 la aceptaci\xF3n (${ultimo.aceptada_hasta})` };
45177
45255
  const antes = ultimo.perdida_aceptada;
45178
45256
  const ahora = e.impacto_mensual;
45179
- if (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" };
45257
+ const mismoCalculo = !ultimo.calculo_aceptado || !e.calculo || ultimo.calculo_aceptado === e.calculo;
45258
+ 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" };
45180
45259
  if (!ultimo.aceptada_hasta && antes === null) return { abrir: false, motivo: "la direcci\xF3n la acept\xF3 as\xED" };
45181
45260
  return { abrir: false, motivo: `aceptada por la direcci\xF3n${ultimo.aceptada_hasta ? ` hasta el ${ultimo.aceptada_hasta}` : ""}` };
45182
45261
  }
@@ -45248,12 +45327,12 @@ var ABREN = /* @__PURE__ */ new Set(["ingreso", "pase_cama", "reingreso"]);
45248
45327
  var CIERRAN = /* @__PURE__ */ new Set(["egreso"]);
45249
45328
  var ORDEN_EVENTO = { egreso: 0, ingreso: 1, reingreso: 1, pase_cama: 2 };
45250
45329
  var claveServicio = (s) => s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/\s+/g, " ").trim();
45251
- function repartir2(pesos4, unidades) {
45252
- const suma = pesos4.reduce((a, b) => a + b, 0);
45253
- const exactas = pesos4.map((p) => suma > 0 ? p / suma * unidades : 0);
45330
+ function repartir2(pesos5, unidades) {
45331
+ const suma = pesos5.reduce((a, b) => a + b, 0);
45332
+ const exactas = pesos5.map((p) => suma > 0 ? p / suma * unidades : 0);
45254
45333
  const enteras = exactas.map((x) => Math.floor(x));
45255
45334
  let resto = unidades - enteras.reduce((a, b) => a + b, 0);
45256
- const orden = pesos4.map((_, i) => i).sort((a, b) => exactas[b] - enteras[b] - (exactas[a] - enteras[a]) || pesos4[b] - pesos4[a] || a - b);
45335
+ const orden = pesos5.map((_, i) => i).sort((a, b) => exactas[b] - enteras[b] - (exactas[a] - enteras[a]) || pesos5[b] - pesos5[a] || a - b);
45257
45336
  for (const i of orden) {
45258
45337
  if (resto <= 0) break;
45259
45338
  enteras[i] += 1;
@@ -45261,12 +45340,12 @@ function repartir2(pesos4, unidades) {
45261
45340
  }
45262
45341
  return enteras;
45263
45342
  }
45264
- function repartirDecimas(pesos4, total) {
45343
+ function repartirDecimas(pesos5, total) {
45265
45344
  const decimas = Math.round(total * 10);
45266
- if (pesos4.length > decimas) return repartir2(pesos4, decimas);
45267
- const suma = pesos4.reduce((a, b) => a + b, 0);
45268
- const excedentes = pesos4.map((p) => Math.max(0, p / suma * decimas - 1));
45269
- return repartir2(excedentes, decimas - pesos4.length).map((d) => d + 1);
45345
+ if (pesos5.length > decimas) return repartir2(pesos5, decimas);
45346
+ const suma = pesos5.reduce((a, b) => a + b, 0);
45347
+ const excedentes = pesos5.map((p) => Math.max(0, p / suma * decimas - 1));
45348
+ return repartir2(excedentes, decimas - pesos5.length).map((d) => d + 1);
45270
45349
  }
45271
45350
  function diasPorServicio(l, hoy) {
45272
45351
  const inicio = Date.parse(l.ingreso);
@@ -45560,6 +45639,19 @@ function describirOpciones(antes, despues) {
45560
45639
  if (antes.meses_precio !== despues.meses_precio && despues.meses_precio !== void 0) partes.push(`meses para avisar un precio de insumo viejo: ${String(despues.meses_precio)}`);
45561
45640
  if (antes.medir_con !== despues.medir_con && despues.medir_con !== void 0) partes.push(despues.medir_con === "total" ? "mide con el margen total (despu\xE9s de los costos indirectos)" : "mide con el margen directo (antes de los costos indirectos)");
45562
45641
  if (antes.perdida_menor !== despues.perdida_menor && despues.perdida_menor !== void 0) partes.push(`p\xE9rdida menor por mes: $ ${Number(despues.perdida_menor).toLocaleString("es-AR")}`);
45642
+ if (antes.descuento_cobro !== despues.descuento_cobro && despues.descuento_cobro !== void 0) partes.push(`se pierde al cobrar: ${Number(despues.descuento_cobro).toLocaleString("es-AR")} % de lo facturado`);
45643
+ if (JSON.stringify(antes.descuentos_financiador ?? {}) !== JSON.stringify(despues.descuentos_financiador ?? {}) && despues.descuentos_financiador !== void 0) {
45644
+ const d = Object.entries(despues.descuentos_financiador ?? {});
45645
+ partes.push(`descuento por financiador: ${d.length ? d.map(([f, p]) => `${f} ${p.toLocaleString("es-AR")} %`).join(", ") : "ninguno"}`);
45646
+ }
45647
+ if (JSON.stringify(antes.practicas_sin_insumos ?? []) !== JSON.stringify(despues.practicas_sin_insumos ?? []) && despues.practicas_sin_insumos !== void 0) {
45648
+ const a = new Set(Array.isArray(antes.practicas_sin_insumos) ? antes.practicas_sin_insumos : []);
45649
+ const b = Array.isArray(despues.practicas_sin_insumos) ? despues.practicas_sin_insumos : [];
45650
+ const nuevas = b.filter((c) => !a.has(c));
45651
+ const quitadas = [...a].filter((c) => !b.includes(c));
45652
+ if (nuevas.length) partes.push(`no usan insumos: ${nuevas.join(", ")}`);
45653
+ if (quitadas.length) partes.push(`vuelven a necesitar insumos: ${quitadas.join(", ")}`);
45654
+ }
45563
45655
  if (JSON.stringify(antes.indirectos ?? []) !== JSON.stringify(despues.indirectos ?? []) && despues.indirectos !== void 0) {
45564
45656
  const lista3 = Array.isArray(despues.indirectos) ? despues.indirectos : [];
45565
45657
  partes.push(`costos indirectos: ${lista3.length ? lista3.map((c) => `${String(c.nombre)} $ ${Number(c.mensual).toLocaleString("es-AR")} por mes${c.activo === false ? " (apagado)" : ""}`).join(", ") : "ninguno"}`);
@@ -45605,8 +45697,21 @@ function pareceNoProfesional(nombre) {
45605
45697
  const [apellido = "", nombres = ""] = n2.split(",").map((x) => x.trim().toLowerCase());
45606
45698
  return /^[^a-záéíóúñü]/i.test(n2) || /\d/.test(n2) || apellido !== "" && apellido === nombres || /\b(admin|administrador|prueba|test|sistema|usuario)\b/i.test(n2);
45607
45699
  }
45700
+ function resultadoPedido(d, e) {
45701
+ const accion = str2(d.accion) ?? "pedir_aumento";
45702
+ const pedido = typeof d.precio_propuesto === "number" ? d.precio_propuesto : null;
45703
+ if (accion === "subir_precio" || accion === "pedir_aumento") return { accion, pedido, ahora: e.valor, cumple: pedido !== null ? e.valor >= pedido - 0.5 : null };
45704
+ if (accion === "revisar_honorario") {
45705
+ const previo = d.evaluacion && typeof d.evaluacion === "object" ? d.evaluacion : {};
45706
+ const objetivo = typeof previo.honorario_objetivo === "number" ? previo.honorario_objetivo : null;
45707
+ const prof = Array.isArray(previo.causa_profesionales) ? previo.causa_profesionales[0] : void 0;
45708
+ const ahora = prof ? e.reparto.find((r) => r.profesional_id === prof.profesional_id)?.honorario ?? null : null;
45709
+ return { accion, pedido: objetivo, ahora, cumple: objetivo !== null && ahora !== null ? ahora <= objetivo + 0.5 : null };
45710
+ }
45711
+ return { accion, pedido: null, ahora: null, cumple: e.clase === "rentable" ? true : null };
45712
+ }
45608
45713
  function evaluarConOpciones(l, hoy, op, bases) {
45609
- return evaluarRentabilidad({ lectura: l, hoy, umbral: op.umbral, mesesPrecio: op.mesesPrecio, indirectos: op.indirectos, bases, medirCon: op.medirCon, perdidaMenor: op.perdidaMenor });
45714
+ return evaluarRentabilidad({ lectura: l, hoy, umbral: op.umbral, mesesPrecio: op.mesesPrecio, indirectos: op.indirectos, bases, medirCon: op.medirCon, perdidaMenor: op.perdidaMenor, sinInsumos: op.sinInsumos.includes(normalizarCodigo(l.codigo) ?? ""), descuento: descuentoDe(op, l.financiador) });
45610
45715
  }
45611
45716
  async function evaluarRentabilidades(ctx, companyId, lecturas) {
45612
45717
  if (!Array.isArray(lecturas)) throw new OpsError("validacion", "lecturas: tiene que ser una lista");
@@ -45638,7 +45743,7 @@ async function evaluarRentabilidades(ctx, companyId, lecturas) {
45638
45743
  antes: previa ? { valor: previa.evaluacion.valor, honorarios: (previa.evaluacion.honorarios ?? []).map((h) => ({ profesional_id: h.profesional_id, profesional: h.profesional, honorario: h.honorario })) } : null,
45639
45744
  despues: { valor: evaluacion.valor, honorarios: evaluacion.honorarios.map((h) => ({ profesional_id: h.profesional_id, profesional: h.profesional, honorario: h.honorario })) }
45640
45745
  });
45641
- medidas.push({ lectura: l.lectura, evaluacion });
45746
+ if (!evaluacion.no_es_practica) medidas.push({ lectura: l.lectura, evaluacion });
45642
45747
  const apertura = decidirApertura(evaluacion, pedidos.get(referencia) ?? null, hoy);
45643
45748
  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 });
45644
45749
  }
@@ -45747,7 +45852,18 @@ async function cambiarPolitica(ctx, input2) {
45747
45852
  if (v === null) throw new OpsError("validacion", "perdida_menor: tiene que ser un monto entero entre 0 y 10.000.000");
45748
45853
  opciones.perdida_menor = v;
45749
45854
  }
45855
+ if (opciones.descuento_cobro !== void 0) {
45856
+ const v = validarDescuento(opciones.descuento_cobro);
45857
+ if (v === null) throw new OpsError("validacion", "descuento_cobro: tiene que ser un porcentaje entre 0 y 60");
45858
+ opciones.descuento_cobro = v;
45859
+ }
45860
+ if (opciones.descuentos_financiador !== void 0) opciones.descuentos_financiador = leerDescuentos(opciones.descuentos_financiador);
45861
+ if (opciones.practicas_sin_insumos !== void 0) {
45862
+ if (!Array.isArray(opciones.practicas_sin_insumos)) throw new OpsError("validacion", "practicas_sin_insumos: tiene que ser una lista de c\xF3digos");
45863
+ opciones.practicas_sin_insumos = [...new Set(opciones.practicas_sin_insumos.map((c) => normalizarCodigo(String(c ?? ""))).filter((c) => Boolean(c)))].slice(0, 5e3);
45864
+ }
45750
45865
  }
45866
+ if (input2.workflow === "rentabilidad_practica" && JSON.stringify(opcionesAntes.indirectos ?? []) !== JSON.stringify(opciones.indirectos ?? [])) await recalcularBases(ctx, input2.companyId, leerIndirectos(opciones.indirectos));
45751
45867
  const cambioOpciones = describirOpciones(opcionesAntes, opciones);
45752
45868
  const motivo = [input2.motivo ?? null, cambioOpciones].filter(Boolean).join(" \xB7 ") || null;
45753
45869
  const existe = await ctx.db.query(
@@ -46368,7 +46484,13 @@ async function transicionar(ctx, input2) {
46368
46484
  delete datos.lectura;
46369
46485
  }
46370
46486
  if (caso.workflow === "rentabilidad_practica") {
46371
- for (const k of ["evaluacion", "oracion", "practica", "financiador", "codigo", "componentes", "insumos", "honorarios", "volumen_90d", "comprobado_el", "tipo", "minutos", "aceptada_hasta", "perdida_aceptada", "hecho_el", "pedido_enviado_el"]) delete datos[k];
46487
+ 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];
46488
+ if (resultado.via === "no_se_pudo") {
46489
+ datos.no_se_pudo = { motivo: str2(datos.motivo) ?? "", accion: str2(caso.datos.accion) ?? null, el: contexto.hoy ?? ahoraEnHospital().hoy };
46490
+ datos.accion = null;
46491
+ datos.precio_propuesto = null;
46492
+ }
46493
+ if (resultado.via === "aprobar") datos.no_se_pudo = null;
46372
46494
  if (resultado.via === "hecho") {
46373
46495
  datos.hecho_el = contexto.hoy ?? ahoraEnHospital().hoy;
46374
46496
  if (str2(caso.datos.accion) === "pedir_aumento" || !str2(caso.datos.accion)) datos.pedido_enviado_el = datos.hecho_el;
@@ -46378,6 +46500,7 @@ async function transicionar(ctx, input2) {
46378
46500
  const ev = caso.datos.evaluacion && typeof caso.datos.evaluacion === "object" ? caso.datos.evaluacion : {};
46379
46501
  datos.aceptada_hasta = sumarMeses(contexto.hoy ?? ahoraEnHospital().hoy, meses);
46380
46502
  datos.perdida_aceptada = typeof ev.impacto_mensual === "number" ? ev.impacto_mensual : null;
46503
+ datos.calculo_aceptado = typeof ev.calculo === "string" ? ev.calculo : null;
46381
46504
  }
46382
46505
  const r = contexto.rentabilidad;
46383
46506
  const l = leerLecturaRentabilidad(datos.lectura);
@@ -46394,6 +46517,8 @@ async function transicionar(ctx, input2) {
46394
46517
  tipo: l.lectura.tipo,
46395
46518
  minutos: l.lectura.minutos,
46396
46519
  evaluacion: r.evaluacion,
46520
+ // Si el equipo ya hizo lo que pidió la dirección: ¿el HIS tiene el valor pedido? (no solo «cambió la clase»).
46521
+ ...caso.datos.hecho_el ? { resultado_pedido: resultadoPedido(caso.datos, r.evaluacion) } : {},
46397
46522
  oracion: oracionRentabilidad(l.lectura, r.evaluacion),
46398
46523
  comprobado_el: contexto.hoy ?? null
46399
46524
  });
@@ -48519,6 +48644,8 @@ async function manejarApi(ctx, input2) {
48519
48644
  }
48520
48645
  case "cuestionar":
48521
48646
  return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "cuestionar", motivo: motivoConPersona(str3(body.motivo)), datos: conPersona({}), actor }));
48647
+ case "no-se-pudo":
48648
+ return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "no_se_pudo", motivo: motivoConPersona(str3(body.motivo)), datos: conPersona({ motivo: str3(body.motivo) }), actor }));
48522
48649
  case "contactos":
48523
48650
  return respuesta(200, await responsablesPendientes(ctx, companyId, (qs("workflows") ?? "").split(",").map((w) => w.trim()).filter(Boolean)));
48524
48651
  case "guardar-contacto": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperclip-plugin-autopilot-ops",
3
- "version": "0.1.57",
3
+ "version": "0.1.58",
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": [