paperclip-plugin-autopilot-ops 0.1.34 → 0.1.35

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.34";
3
+ var PLUGIN_VERSION = "0.1.35";
4
4
  var DB_NAMESPACE_SLUG = "autopilot_ops";
5
5
  var PAGE_ROUTE = "autopilot";
6
6
  var TOOL_NAMES = {
@@ -133,7 +133,7 @@ var manifest = {
133
133
  {
134
134
  name: TOOL_NAMES.abrirCaso,
135
135
  displayName: "Abrir caso",
136
- description: 'Abre un caso nuevo en un workflow, en su estado inicial. Workflows: `internacion_al_dia` (una internaci\xF3n abierta; referencia obligatoria `internacion:<id>`; datos: internacion_id), `honorario_en_cero` (referencia `honorarios:<profesional_id>`; datos: profesional_id, profesional), `debito_profesional` (referencia `debito:<profesional_id>:<nota_credito_id>`; datos: profesional_id, profesional, nota_credito_id, obra_social), `liquidacion_profesional` (referencia `liquidacion:<profesional_id>:<AAAA-MM-DD>`; datos: profesional_id, profesional, liquidado_el, institucion, mail_his), `cobranza_financiador` (un caso abierto por financiador; referencia obligatoria `cobranza:<obra_social_id>`; datos: obra_social (nombre como en el HIS), obra_social_id, institucion (nombre de la instituci\xF3n para el reclamo)), `circular_aranceles` (una circular de aranceles entera; referencia obligatoria `circular:<obra social>:<hash corto del archivo>`; datos: obra_social, convenio (nombre en el HIS), archivo {nombre, hash, paginas}, vigencia_desde AAAA-MM-DD, porcentaje_declarado y filas_declaradas si la circular los dice), `demanda_pendiente` (referencia obligatoria `orden:<id>`, `solicitud:<id>` o `control:<persona_id>:<regla_id>`; datos: regla_id (regla activa) y candidato tal cual lo devuelve buscar-demanda.mjs; el plugin verifica la regla con el candidato (si no la cumple, no abre) y fija regla_version, canal, intentos, vence_el; nunca nombre, documento ni tel\xE9fono del paciente),`actualizacion_arancel`, `tarea_operativa`, `recupero_turno` (datos: fecha AAAA-MM-DD, hora HH:MM, profesional, especialidad del turno libre; referencia "AAAA-MM-DD HH:MM \xB7 Profesional"), `cuenta_completa` (una prestaci\xF3n pendiente de facturar con faltantes; referencia obligatoria "bono:<n\xFAmero>", datos: bono_id, bono_numero, obra_social, plan, prestacion, fecha_prestacion AAAA-MM-DD, monto_pesos, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades), `rentabilidad_practica` (referencia obligatoria `rentabilidad:<prestacion_id>:<financiador_id>`; datos: prestacion_id, financiador_id, codigo, practica, financiador), `rentabilidad_internacion` (una internaci\xF3n egresada; referencia obligatoria `rentabilidad_internacion:<internacion_id>`; datos: internacion_id, financiador_id, financiador). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese (creado: false). En `rentabilidad_internacion` se devuelve el caso existente aunque est\xE9 cerrado: una internaci\xF3n se mide una sola vez. Pasar `idempotencyKey` para reintentos seguros.',
136
+ description: 'Abre un caso nuevo en un workflow, en su estado inicial. Workflows: `internacion_al_dia` (una internaci\xF3n abierta; referencia obligatoria `internacion:<id>`; datos: internacion_id), `honorario_en_cero` (referencia `honorarios:<profesional_id>`; datos: profesional_id, profesional), `debito_profesional` (referencia `debito:<profesional_id>:<nota_credito_id>`; datos: profesional_id, profesional, nota_credito_id, obra_social), `liquidacion_profesional` (referencia `liquidacion:<profesional_id>:<AAAA-MM-DD>`; datos: profesional_id, profesional, liquidado_el, institucion, mail_his), `cobranza_financiador` (un caso abierto por financiador; referencia obligatoria `cobranza:<obra_social_id>`; datos: obra_social (nombre como en el HIS), obra_social_id, institucion (nombre de la instituci\xF3n para el reclamo)), `circular_aranceles` (una circular de aranceles entera; referencia obligatoria `circular:<obra social>:<hash corto del archivo>`; datos: obra_social, convenio (nombre en el HIS), archivo {nombre, hash, paginas}, vigencia_desde AAAA-MM-DD, porcentaje_declarado y filas_declaradas si la circular los dice), `ajuste_convenio` (el ajuste de un convenio por \xEDndice; referencia obligatoria `ajuste:<convenio>:<AAAA-MM de la vigencia>`; datos: financiador, convenio (nombre en el HIS), vigencia_desde AAAA-MM-DD, ultimo_ajuste AAAA-MM (el mes desde el que se mide la variaci\xF3n), institucion), `demanda_pendiente` (referencia obligatoria `orden:<id>`, `solicitud:<id>` o `control:<persona_id>:<regla_id>`; datos: regla_id (regla activa) y candidato tal cual lo devuelve buscar-demanda.mjs; el plugin verifica la regla con el candidato (si no la cumple, no abre) y fija regla_version, canal, intentos, vence_el; nunca nombre, documento ni tel\xE9fono del paciente),`actualizacion_arancel`, `tarea_operativa`, `recupero_turno` (datos: fecha AAAA-MM-DD, hora HH:MM, profesional, especialidad del turno libre; referencia "AAAA-MM-DD HH:MM \xB7 Profesional"), `cuenta_completa` (una prestaci\xF3n pendiente de facturar con faltantes; referencia obligatoria "bono:<n\xFAmero>", datos: bono_id, bono_numero, obra_social, plan, prestacion, fecha_prestacion AAAA-MM-DD, monto_pesos, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades), `rentabilidad_practica` (referencia obligatoria `rentabilidad:<prestacion_id>:<financiador_id>`; datos: prestacion_id, financiador_id, codigo, practica, financiador), `rentabilidad_internacion` (una internaci\xF3n egresada; referencia obligatoria `rentabilidad_internacion:<internacion_id>`; datos: internacion_id, financiador_id, financiador). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese (creado: false). En `rentabilidad_internacion` se devuelve el caso existente aunque est\xE9 cerrado: una internaci\xF3n se mide una sola vez. Pasar `idempotencyKey` para reintentos seguros.',
137
137
  parametersSchema: {
138
138
  type: "object",
139
139
  properties: {
@@ -150,7 +150,7 @@ var manifest = {
150
150
  {
151
151
  name: TOOL_NAMES.transicionar,
152
152
  displayName: "Transicionar caso",
153
- description: 'Aplica una transici\xF3n del workflow al caso (p. ej. `proponer`, `aplicar`, `verificar`). El plugin valida contra la tabla de transiciones, la pol\xEDtica vigente (observa / propone / ejecuta / corte) y las reglas de negocio; si algo falla, el caso no cambia y se devuelve el motivo. Las transiciones humanas (aprobar, rechazar, corregir) no est\xE1n disponibles para agentes. Cuando una persona de la instituci\xF3n carga a mano lo aprobado, el caso queda en `aplicado`: el agente lo comprueba releyendo el sistema y llamando `verificar` con `datos.valor_leido`. En `recupero_turno`: `proponer` con solicitud_id, solicitud_estado, solicitud_especialidad, cola (la lista de espera pendiente de esa especialidad tal como se vio: [{solicitud_id, prioridad, alta}]; el plugin la ordena y exige justificaci\xF3n para no elegir la primera) y fuente; si la instituci\xF3n pide consulta previa (ops_leer_caso lo dice), `ofrecer` con canal y esperar `aceptar`/`no_acepta`; **antes de escribir en el sistema, `autorizar`** (sin esa autorizaci\xF3n no se escribe); despu\xE9s de escribir, `asignar` con accion_id; `verificar` con leido_fecha, leido_hora, leido_profesional y leido_solicitud_id; `sin_candidato` y `descartar` con motivo. En `cuenta_completa` el agente solo lee: `observar` con lectura (el bono tal cual lo devuelve revisar.mjs) y lectura_id (conector "base"); `comprobar` con la lectura nueva y lectura_id; el plugin decide los faltantes; `descartar` con motivo si el bono se elimin\xF3 o anul\xF3. En `facturacion_pendiente` (solo lee): `observar` con bono_id, bono_estado, fecha_prestacion, obra_social, lectura_id (conector "base") y fuente; `comprobar` con bono_estado_leido y lectura_id (facturado/refacturado \u2192 facturada; prefacturado \u2192 en prefactura; si no, sigue por presentar); `descartar` con motivo. En `cobranza_financiador`, `circular_aranceles`, `internacion_al_dia` y los de honorarios los datos de cada v\xEDa est\xE1n en la skill del puesto (cobranzas, aranceles, internaciones, honorarios): el agente informa lo que ley\xF3 del HIS o del documento tal cual y el plugin decide qu\xE9 se reclama o se carga. En `demanda_pendiente`: `contactar` (nivel ejecuta; datos: canal igual al de la regla y accion_id de la escritura registrada; el plugin controla regla activa, intentos, espera entre intentos y horario), `responder` (respuesta: acepta|rechaza|no_contesta), `comprobar` (lectura_id de la relectura del HIS, turno_id y turno_estado del turno encontrado), `cerrar` (motivo; solo con los intentos agotados o el plazo vencido), `descartar` (motivo). `credencial_profesional` (solo lee): `verificar` con profesional_id, matricula y tipo_matricula (nacional|provincial) si hay matr\xEDcula, resultado = el objeto que devuelve verificar-matriculas.mjs tal cual ({sin_matricula, encontrada, coincide_nombre, situacion, sancion, profesion, provincia, emisor}), lectura_id (conector "api", la consulta al registro) y fuente; el plugin decide vigente u observada y fija la observaci\xF3n (sin_matricula, no_figura, otra_persona, inhabilitada, sancion); `descartar` con motivo si el profesional ya no est\xE1 activo.',
153
+ description: 'Aplica una transici\xF3n del workflow al caso (p. ej. `proponer`, `aplicar`, `verificar`). El plugin valida contra la tabla de transiciones, la pol\xEDtica vigente (observa / propone / ejecuta / corte) y las reglas de negocio; si algo falla, el caso no cambia y se devuelve el motivo. Las transiciones humanas (aprobar, rechazar, corregir) no est\xE1n disponibles para agentes. Cuando una persona de la instituci\xF3n carga a mano lo aprobado, el caso queda en `aplicado`: el agente lo comprueba releyendo el sistema y llamando `verificar` con `datos.valor_leido`. En `recupero_turno`: `proponer` con solicitud_id, solicitud_estado, solicitud_especialidad, cola (la lista de espera pendiente de esa especialidad tal como se vio: [{solicitud_id, prioridad, alta}]; el plugin la ordena y exige justificaci\xF3n para no elegir la primera) y fuente; si la instituci\xF3n pide consulta previa (ops_leer_caso lo dice), `ofrecer` con canal y esperar `aceptar`/`no_acepta`; **antes de escribir en el sistema, `autorizar`** (sin esa autorizaci\xF3n no se escribe); despu\xE9s de escribir, `asignar` con accion_id; `verificar` con leido_fecha, leido_hora, leido_profesional y leido_solicitud_id; `sin_candidato` y `descartar` con motivo. En `cuenta_completa` el agente solo lee: `observar` con lectura (el bono tal cual lo devuelve revisar.mjs) y lectura_id (conector "base"); `comprobar` con la lectura nueva y lectura_id; el plugin decide los faltantes; `descartar` con motivo si el bono se elimin\xF3 o anul\xF3. En `facturacion_pendiente` (solo lee): `observar` con bono_id, bono_estado, fecha_prestacion, obra_social, lectura_id (conector "base") y fuente; `comprobar` con bono_estado_leido y lectura_id (facturado/refacturado \u2192 facturada; prefacturado \u2192 en prefactura; si no, sigue por presentar); `descartar` con motivo. En `cobranza_financiador`, `circular_aranceles`, `ajuste_convenio`, `internacion_al_dia` y los de honorarios los datos de cada v\xEDa est\xE1n en la skill del puesto (cobranzas, aranceles, internaciones, honorarios): el agente informa lo que ley\xF3 del HIS o del documento tal cual y el plugin decide qu\xE9 se reclama o se carga. En `demanda_pendiente`: `contactar` (nivel ejecuta; datos: canal igual al de la regla y accion_id de la escritura registrada; el plugin controla regla activa, intentos, espera entre intentos y horario), `responder` (respuesta: acepta|rechaza|no_contesta), `comprobar` (lectura_id de la relectura del HIS, turno_id y turno_estado del turno encontrado), `cerrar` (motivo; solo con los intentos agotados o el plazo vencido), `descartar` (motivo). `credencial_profesional` (solo lee): `verificar` con profesional_id, matricula y tipo_matricula (nacional|provincial) si hay matr\xEDcula, resultado = el objeto que devuelve verificar-matriculas.mjs tal cual ({sin_matricula, encontrada, coincide_nombre, situacion, sancion, profesion, provincia, emisor}), lectura_id (conector "api", la consulta al registro) y fuente; el plugin decide vigente u observada y fija la observaci\xF3n (sin_matricula, no_figura, otra_persona, inhabilitada, sancion); `descartar` con motivo si el profesional ya no est\xE1 activo.',
154
154
  parametersSchema: {
155
155
  type: "object",
156
156
  properties: {
@@ -450,6 +450,22 @@ var manifest = {
450
450
  capability: "api.routes.register",
451
451
  companyResolution: { from: "body", key: "companyId" }
452
452
  },
453
+ {
454
+ routeKey: "ajustar",
455
+ method: "POST",
456
+ path: "/casos/:casoId/ajustar",
457
+ auth: "board",
458
+ capability: "api.routes.register",
459
+ companyResolution: { from: "body", key: "companyId" }
460
+ },
461
+ {
462
+ routeKey: "envio-nota",
463
+ method: "POST",
464
+ path: "/casos/:casoId/envio-nota",
465
+ auth: "board",
466
+ capability: "api.routes.register",
467
+ companyResolution: { from: "body", key: "companyId" }
468
+ },
453
469
  {
454
470
  routeKey: "respuesta-financiador",
455
471
  method: "POST",
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.34";
11
+ var PLUGIN_VERSION = "0.1.35";
12
12
  var DB_NAMESPACE_SLUG = "autopilot_ops";
13
13
  var PAGE_ROUTE = "autopilot";
14
14
  var TOOL_NAMES = {
@@ -141,7 +141,7 @@ var manifest = {
141
141
  {
142
142
  name: TOOL_NAMES.abrirCaso,
143
143
  displayName: "Abrir caso",
144
- description: 'Abre un caso nuevo en un workflow, en su estado inicial. Workflows: `internacion_al_dia` (una internaci\xF3n abierta; referencia obligatoria `internacion:<id>`; datos: internacion_id), `honorario_en_cero` (referencia `honorarios:<profesional_id>`; datos: profesional_id, profesional), `debito_profesional` (referencia `debito:<profesional_id>:<nota_credito_id>`; datos: profesional_id, profesional, nota_credito_id, obra_social), `liquidacion_profesional` (referencia `liquidacion:<profesional_id>:<AAAA-MM-DD>`; datos: profesional_id, profesional, liquidado_el, institucion, mail_his), `cobranza_financiador` (un caso abierto por financiador; referencia obligatoria `cobranza:<obra_social_id>`; datos: obra_social (nombre como en el HIS), obra_social_id, institucion (nombre de la instituci\xF3n para el reclamo)), `circular_aranceles` (una circular de aranceles entera; referencia obligatoria `circular:<obra social>:<hash corto del archivo>`; datos: obra_social, convenio (nombre en el HIS), archivo {nombre, hash, paginas}, vigencia_desde AAAA-MM-DD, porcentaje_declarado y filas_declaradas si la circular los dice), `demanda_pendiente` (referencia obligatoria `orden:<id>`, `solicitud:<id>` o `control:<persona_id>:<regla_id>`; datos: regla_id (regla activa) y candidato tal cual lo devuelve buscar-demanda.mjs; el plugin verifica la regla con el candidato (si no la cumple, no abre) y fija regla_version, canal, intentos, vence_el; nunca nombre, documento ni tel\xE9fono del paciente),`actualizacion_arancel`, `tarea_operativa`, `recupero_turno` (datos: fecha AAAA-MM-DD, hora HH:MM, profesional, especialidad del turno libre; referencia "AAAA-MM-DD HH:MM \xB7 Profesional"), `cuenta_completa` (una prestaci\xF3n pendiente de facturar con faltantes; referencia obligatoria "bono:<n\xFAmero>", datos: bono_id, bono_numero, obra_social, plan, prestacion, fecha_prestacion AAAA-MM-DD, monto_pesos, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades), `rentabilidad_practica` (referencia obligatoria `rentabilidad:<prestacion_id>:<financiador_id>`; datos: prestacion_id, financiador_id, codigo, practica, financiador), `rentabilidad_internacion` (una internaci\xF3n egresada; referencia obligatoria `rentabilidad_internacion:<internacion_id>`; datos: internacion_id, financiador_id, financiador). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese (creado: false). En `rentabilidad_internacion` se devuelve el caso existente aunque est\xE9 cerrado: una internaci\xF3n se mide una sola vez. Pasar `idempotencyKey` para reintentos seguros.',
144
+ description: 'Abre un caso nuevo en un workflow, en su estado inicial. Workflows: `internacion_al_dia` (una internaci\xF3n abierta; referencia obligatoria `internacion:<id>`; datos: internacion_id), `honorario_en_cero` (referencia `honorarios:<profesional_id>`; datos: profesional_id, profesional), `debito_profesional` (referencia `debito:<profesional_id>:<nota_credito_id>`; datos: profesional_id, profesional, nota_credito_id, obra_social), `liquidacion_profesional` (referencia `liquidacion:<profesional_id>:<AAAA-MM-DD>`; datos: profesional_id, profesional, liquidado_el, institucion, mail_his), `cobranza_financiador` (un caso abierto por financiador; referencia obligatoria `cobranza:<obra_social_id>`; datos: obra_social (nombre como en el HIS), obra_social_id, institucion (nombre de la instituci\xF3n para el reclamo)), `circular_aranceles` (una circular de aranceles entera; referencia obligatoria `circular:<obra social>:<hash corto del archivo>`; datos: obra_social, convenio (nombre en el HIS), archivo {nombre, hash, paginas}, vigencia_desde AAAA-MM-DD, porcentaje_declarado y filas_declaradas si la circular los dice), `ajuste_convenio` (el ajuste de un convenio por \xEDndice; referencia obligatoria `ajuste:<convenio>:<AAAA-MM de la vigencia>`; datos: financiador, convenio (nombre en el HIS), vigencia_desde AAAA-MM-DD, ultimo_ajuste AAAA-MM (el mes desde el que se mide la variaci\xF3n), institucion), `demanda_pendiente` (referencia obligatoria `orden:<id>`, `solicitud:<id>` o `control:<persona_id>:<regla_id>`; datos: regla_id (regla activa) y candidato tal cual lo devuelve buscar-demanda.mjs; el plugin verifica la regla con el candidato (si no la cumple, no abre) y fija regla_version, canal, intentos, vence_el; nunca nombre, documento ni tel\xE9fono del paciente),`actualizacion_arancel`, `tarea_operativa`, `recupero_turno` (datos: fecha AAAA-MM-DD, hora HH:MM, profesional, especialidad del turno libre; referencia "AAAA-MM-DD HH:MM \xB7 Profesional"), `cuenta_completa` (una prestaci\xF3n pendiente de facturar con faltantes; referencia obligatoria "bono:<n\xFAmero>", datos: bono_id, bono_numero, obra_social, plan, prestacion, fecha_prestacion AAAA-MM-DD, monto_pesos, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades), `rentabilidad_practica` (referencia obligatoria `rentabilidad:<prestacion_id>:<financiador_id>`; datos: prestacion_id, financiador_id, codigo, practica, financiador), `rentabilidad_internacion` (una internaci\xF3n egresada; referencia obligatoria `rentabilidad_internacion:<internacion_id>`; datos: internacion_id, financiador_id, financiador). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese (creado: false). En `rentabilidad_internacion` se devuelve el caso existente aunque est\xE9 cerrado: una internaci\xF3n se mide una sola vez. Pasar `idempotencyKey` para reintentos seguros.',
145
145
  parametersSchema: {
146
146
  type: "object",
147
147
  properties: {
@@ -158,7 +158,7 @@ var manifest = {
158
158
  {
159
159
  name: TOOL_NAMES.transicionar,
160
160
  displayName: "Transicionar caso",
161
- description: 'Aplica una transici\xF3n del workflow al caso (p. ej. `proponer`, `aplicar`, `verificar`). El plugin valida contra la tabla de transiciones, la pol\xEDtica vigente (observa / propone / ejecuta / corte) y las reglas de negocio; si algo falla, el caso no cambia y se devuelve el motivo. Las transiciones humanas (aprobar, rechazar, corregir) no est\xE1n disponibles para agentes. Cuando una persona de la instituci\xF3n carga a mano lo aprobado, el caso queda en `aplicado`: el agente lo comprueba releyendo el sistema y llamando `verificar` con `datos.valor_leido`. En `recupero_turno`: `proponer` con solicitud_id, solicitud_estado, solicitud_especialidad, cola (la lista de espera pendiente de esa especialidad tal como se vio: [{solicitud_id, prioridad, alta}]; el plugin la ordena y exige justificaci\xF3n para no elegir la primera) y fuente; si la instituci\xF3n pide consulta previa (ops_leer_caso lo dice), `ofrecer` con canal y esperar `aceptar`/`no_acepta`; **antes de escribir en el sistema, `autorizar`** (sin esa autorizaci\xF3n no se escribe); despu\xE9s de escribir, `asignar` con accion_id; `verificar` con leido_fecha, leido_hora, leido_profesional y leido_solicitud_id; `sin_candidato` y `descartar` con motivo. En `cuenta_completa` el agente solo lee: `observar` con lectura (el bono tal cual lo devuelve revisar.mjs) y lectura_id (conector "base"); `comprobar` con la lectura nueva y lectura_id; el plugin decide los faltantes; `descartar` con motivo si el bono se elimin\xF3 o anul\xF3. En `facturacion_pendiente` (solo lee): `observar` con bono_id, bono_estado, fecha_prestacion, obra_social, lectura_id (conector "base") y fuente; `comprobar` con bono_estado_leido y lectura_id (facturado/refacturado \u2192 facturada; prefacturado \u2192 en prefactura; si no, sigue por presentar); `descartar` con motivo. En `cobranza_financiador`, `circular_aranceles`, `internacion_al_dia` y los de honorarios los datos de cada v\xEDa est\xE1n en la skill del puesto (cobranzas, aranceles, internaciones, honorarios): el agente informa lo que ley\xF3 del HIS o del documento tal cual y el plugin decide qu\xE9 se reclama o se carga. En `demanda_pendiente`: `contactar` (nivel ejecuta; datos: canal igual al de la regla y accion_id de la escritura registrada; el plugin controla regla activa, intentos, espera entre intentos y horario), `responder` (respuesta: acepta|rechaza|no_contesta), `comprobar` (lectura_id de la relectura del HIS, turno_id y turno_estado del turno encontrado), `cerrar` (motivo; solo con los intentos agotados o el plazo vencido), `descartar` (motivo). `credencial_profesional` (solo lee): `verificar` con profesional_id, matricula y tipo_matricula (nacional|provincial) si hay matr\xEDcula, resultado = el objeto que devuelve verificar-matriculas.mjs tal cual ({sin_matricula, encontrada, coincide_nombre, situacion, sancion, profesion, provincia, emisor}), lectura_id (conector "api", la consulta al registro) y fuente; el plugin decide vigente u observada y fija la observaci\xF3n (sin_matricula, no_figura, otra_persona, inhabilitada, sancion); `descartar` con motivo si el profesional ya no est\xE1 activo.',
161
+ description: 'Aplica una transici\xF3n del workflow al caso (p. ej. `proponer`, `aplicar`, `verificar`). El plugin valida contra la tabla de transiciones, la pol\xEDtica vigente (observa / propone / ejecuta / corte) y las reglas de negocio; si algo falla, el caso no cambia y se devuelve el motivo. Las transiciones humanas (aprobar, rechazar, corregir) no est\xE1n disponibles para agentes. Cuando una persona de la instituci\xF3n carga a mano lo aprobado, el caso queda en `aplicado`: el agente lo comprueba releyendo el sistema y llamando `verificar` con `datos.valor_leido`. En `recupero_turno`: `proponer` con solicitud_id, solicitud_estado, solicitud_especialidad, cola (la lista de espera pendiente de esa especialidad tal como se vio: [{solicitud_id, prioridad, alta}]; el plugin la ordena y exige justificaci\xF3n para no elegir la primera) y fuente; si la instituci\xF3n pide consulta previa (ops_leer_caso lo dice), `ofrecer` con canal y esperar `aceptar`/`no_acepta`; **antes de escribir en el sistema, `autorizar`** (sin esa autorizaci\xF3n no se escribe); despu\xE9s de escribir, `asignar` con accion_id; `verificar` con leido_fecha, leido_hora, leido_profesional y leido_solicitud_id; `sin_candidato` y `descartar` con motivo. En `cuenta_completa` el agente solo lee: `observar` con lectura (el bono tal cual lo devuelve revisar.mjs) y lectura_id (conector "base"); `comprobar` con la lectura nueva y lectura_id; el plugin decide los faltantes; `descartar` con motivo si el bono se elimin\xF3 o anul\xF3. En `facturacion_pendiente` (solo lee): `observar` con bono_id, bono_estado, fecha_prestacion, obra_social, lectura_id (conector "base") y fuente; `comprobar` con bono_estado_leido y lectura_id (facturado/refacturado \u2192 facturada; prefacturado \u2192 en prefactura; si no, sigue por presentar); `descartar` con motivo. En `cobranza_financiador`, `circular_aranceles`, `ajuste_convenio`, `internacion_al_dia` y los de honorarios los datos de cada v\xEDa est\xE1n en la skill del puesto (cobranzas, aranceles, internaciones, honorarios): el agente informa lo que ley\xF3 del HIS o del documento tal cual y el plugin decide qu\xE9 se reclama o se carga. En `demanda_pendiente`: `contactar` (nivel ejecuta; datos: canal igual al de la regla y accion_id de la escritura registrada; el plugin controla regla activa, intentos, espera entre intentos y horario), `responder` (respuesta: acepta|rechaza|no_contesta), `comprobar` (lectura_id de la relectura del HIS, turno_id y turno_estado del turno encontrado), `cerrar` (motivo; solo con los intentos agotados o el plazo vencido), `descartar` (motivo). `credencial_profesional` (solo lee): `verificar` con profesional_id, matricula y tipo_matricula (nacional|provincial) si hay matr\xEDcula, resultado = el objeto que devuelve verificar-matriculas.mjs tal cual ({sin_matricula, encontrada, coincide_nombre, situacion, sancion, profesion, provincia, emisor}), lectura_id (conector "api", la consulta al registro) y fuente; el plugin decide vigente u observada y fija la observaci\xF3n (sin_matricula, no_figura, otra_persona, inhabilitada, sancion); `descartar` con motivo si el profesional ya no est\xE1 activo.',
162
162
  parametersSchema: {
163
163
  type: "object",
164
164
  properties: {
@@ -458,6 +458,22 @@ var manifest = {
458
458
  capability: "api.routes.register",
459
459
  companyResolution: { from: "body", key: "companyId" }
460
460
  },
461
+ {
462
+ routeKey: "ajustar",
463
+ method: "POST",
464
+ path: "/casos/:casoId/ajustar",
465
+ auth: "board",
466
+ capability: "api.routes.register",
467
+ companyResolution: { from: "body", key: "companyId" }
468
+ },
469
+ {
470
+ routeKey: "envio-nota",
471
+ method: "POST",
472
+ path: "/casos/:casoId/envio-nota",
473
+ auth: "board",
474
+ capability: "api.routes.register",
475
+ companyResolution: { from: "body", key: "companyId" }
476
+ },
461
477
  {
462
478
  routeKey: "respuesta-financiador",
463
479
  method: "POST",
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.34";
39935
+ var PLUGIN_VERSION = "0.1.35";
39936
39936
  var DB_NAMESPACE_SLUG = "autopilot_ops";
39937
39937
  var PAGE_ROUTE = "autopilot";
39938
39938
  var TOOL_NAMES = {
@@ -40065,7 +40065,7 @@ var manifest = {
40065
40065
  {
40066
40066
  name: TOOL_NAMES.abrirCaso,
40067
40067
  displayName: "Abrir caso",
40068
- description: 'Abre un caso nuevo en un workflow, en su estado inicial. Workflows: `internacion_al_dia` (una internaci\xF3n abierta; referencia obligatoria `internacion:<id>`; datos: internacion_id), `honorario_en_cero` (referencia `honorarios:<profesional_id>`; datos: profesional_id, profesional), `debito_profesional` (referencia `debito:<profesional_id>:<nota_credito_id>`; datos: profesional_id, profesional, nota_credito_id, obra_social), `liquidacion_profesional` (referencia `liquidacion:<profesional_id>:<AAAA-MM-DD>`; datos: profesional_id, profesional, liquidado_el, institucion, mail_his), `cobranza_financiador` (un caso abierto por financiador; referencia obligatoria `cobranza:<obra_social_id>`; datos: obra_social (nombre como en el HIS), obra_social_id, institucion (nombre de la instituci\xF3n para el reclamo)), `circular_aranceles` (una circular de aranceles entera; referencia obligatoria `circular:<obra social>:<hash corto del archivo>`; datos: obra_social, convenio (nombre en el HIS), archivo {nombre, hash, paginas}, vigencia_desde AAAA-MM-DD, porcentaje_declarado y filas_declaradas si la circular los dice), `demanda_pendiente` (referencia obligatoria `orden:<id>`, `solicitud:<id>` o `control:<persona_id>:<regla_id>`; datos: regla_id (regla activa) y candidato tal cual lo devuelve buscar-demanda.mjs; el plugin verifica la regla con el candidato (si no la cumple, no abre) y fija regla_version, canal, intentos, vence_el; nunca nombre, documento ni tel\xE9fono del paciente),`actualizacion_arancel`, `tarea_operativa`, `recupero_turno` (datos: fecha AAAA-MM-DD, hora HH:MM, profesional, especialidad del turno libre; referencia "AAAA-MM-DD HH:MM \xB7 Profesional"), `cuenta_completa` (una prestaci\xF3n pendiente de facturar con faltantes; referencia obligatoria "bono:<n\xFAmero>", datos: bono_id, bono_numero, obra_social, plan, prestacion, fecha_prestacion AAAA-MM-DD, monto_pesos, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades), `rentabilidad_practica` (referencia obligatoria `rentabilidad:<prestacion_id>:<financiador_id>`; datos: prestacion_id, financiador_id, codigo, practica, financiador), `rentabilidad_internacion` (una internaci\xF3n egresada; referencia obligatoria `rentabilidad_internacion:<internacion_id>`; datos: internacion_id, financiador_id, financiador). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese (creado: false). En `rentabilidad_internacion` se devuelve el caso existente aunque est\xE9 cerrado: una internaci\xF3n se mide una sola vez. Pasar `idempotencyKey` para reintentos seguros.',
40068
+ description: 'Abre un caso nuevo en un workflow, en su estado inicial. Workflows: `internacion_al_dia` (una internaci\xF3n abierta; referencia obligatoria `internacion:<id>`; datos: internacion_id), `honorario_en_cero` (referencia `honorarios:<profesional_id>`; datos: profesional_id, profesional), `debito_profesional` (referencia `debito:<profesional_id>:<nota_credito_id>`; datos: profesional_id, profesional, nota_credito_id, obra_social), `liquidacion_profesional` (referencia `liquidacion:<profesional_id>:<AAAA-MM-DD>`; datos: profesional_id, profesional, liquidado_el, institucion, mail_his), `cobranza_financiador` (un caso abierto por financiador; referencia obligatoria `cobranza:<obra_social_id>`; datos: obra_social (nombre como en el HIS), obra_social_id, institucion (nombre de la instituci\xF3n para el reclamo)), `circular_aranceles` (una circular de aranceles entera; referencia obligatoria `circular:<obra social>:<hash corto del archivo>`; datos: obra_social, convenio (nombre en el HIS), archivo {nombre, hash, paginas}, vigencia_desde AAAA-MM-DD, porcentaje_declarado y filas_declaradas si la circular los dice), `ajuste_convenio` (el ajuste de un convenio por \xEDndice; referencia obligatoria `ajuste:<convenio>:<AAAA-MM de la vigencia>`; datos: financiador, convenio (nombre en el HIS), vigencia_desde AAAA-MM-DD, ultimo_ajuste AAAA-MM (el mes desde el que se mide la variaci\xF3n), institucion), `demanda_pendiente` (referencia obligatoria `orden:<id>`, `solicitud:<id>` o `control:<persona_id>:<regla_id>`; datos: regla_id (regla activa) y candidato tal cual lo devuelve buscar-demanda.mjs; el plugin verifica la regla con el candidato (si no la cumple, no abre) y fija regla_version, canal, intentos, vence_el; nunca nombre, documento ni tel\xE9fono del paciente),`actualizacion_arancel`, `tarea_operativa`, `recupero_turno` (datos: fecha AAAA-MM-DD, hora HH:MM, profesional, especialidad del turno libre; referencia "AAAA-MM-DD HH:MM \xB7 Profesional"), `cuenta_completa` (una prestaci\xF3n pendiente de facturar con faltantes; referencia obligatoria "bono:<n\xFAmero>", datos: bono_id, bono_numero, obra_social, plan, prestacion, fecha_prestacion AAAA-MM-DD, monto_pesos, internacion_id opcional; en faltantes, `responsable` {tipo profesional, id, nombre} opcional para que el plugin avise a quien resuelve), `facturacion_pendiente` (una prestaci\xF3n sin facturar; misma referencia `bono:<n\xFAmero>` y mismos datos; el plugin calcula plazo_dias, vence_el y plazo_confirmado con los plazos por obra social de la instituci\xF3n), `credencial_profesional` (un profesional con agenda; referencia obligatoria `profesional:<id>`; datos: profesional_id, profesional (apellido y nombre, es personal de la instituci\xF3n), matricula_nacional, matricula_provincial, codigo_provincia, especialidades), `rentabilidad_practica` (referencia obligatoria `rentabilidad:<prestacion_id>:<financiador_id>`; datos: prestacion_id, financiador_id, codigo, practica, financiador), `rentabilidad_internacion` (una internaci\xF3n egresada; referencia obligatoria `rentabilidad_internacion:<internacion_id>`; datos: internacion_id, financiador_id, financiador). `referencia` identifica el objeto real (convenio, cuenta, turno) y evita duplicados: si ya existe un caso abierto con la misma referencia, se devuelve ese (creado: false). En `rentabilidad_internacion` se devuelve el caso existente aunque est\xE9 cerrado: una internaci\xF3n se mide una sola vez. Pasar `idempotencyKey` para reintentos seguros.',
40069
40069
  parametersSchema: {
40070
40070
  type: "object",
40071
40071
  properties: {
@@ -40082,7 +40082,7 @@ var manifest = {
40082
40082
  {
40083
40083
  name: TOOL_NAMES.transicionar,
40084
40084
  displayName: "Transicionar caso",
40085
- description: 'Aplica una transici\xF3n del workflow al caso (p. ej. `proponer`, `aplicar`, `verificar`). El plugin valida contra la tabla de transiciones, la pol\xEDtica vigente (observa / propone / ejecuta / corte) y las reglas de negocio; si algo falla, el caso no cambia y se devuelve el motivo. Las transiciones humanas (aprobar, rechazar, corregir) no est\xE1n disponibles para agentes. Cuando una persona de la instituci\xF3n carga a mano lo aprobado, el caso queda en `aplicado`: el agente lo comprueba releyendo el sistema y llamando `verificar` con `datos.valor_leido`. En `recupero_turno`: `proponer` con solicitud_id, solicitud_estado, solicitud_especialidad, cola (la lista de espera pendiente de esa especialidad tal como se vio: [{solicitud_id, prioridad, alta}]; el plugin la ordena y exige justificaci\xF3n para no elegir la primera) y fuente; si la instituci\xF3n pide consulta previa (ops_leer_caso lo dice), `ofrecer` con canal y esperar `aceptar`/`no_acepta`; **antes de escribir en el sistema, `autorizar`** (sin esa autorizaci\xF3n no se escribe); despu\xE9s de escribir, `asignar` con accion_id; `verificar` con leido_fecha, leido_hora, leido_profesional y leido_solicitud_id; `sin_candidato` y `descartar` con motivo. En `cuenta_completa` el agente solo lee: `observar` con lectura (el bono tal cual lo devuelve revisar.mjs) y lectura_id (conector "base"); `comprobar` con la lectura nueva y lectura_id; el plugin decide los faltantes; `descartar` con motivo si el bono se elimin\xF3 o anul\xF3. En `facturacion_pendiente` (solo lee): `observar` con bono_id, bono_estado, fecha_prestacion, obra_social, lectura_id (conector "base") y fuente; `comprobar` con bono_estado_leido y lectura_id (facturado/refacturado \u2192 facturada; prefacturado \u2192 en prefactura; si no, sigue por presentar); `descartar` con motivo. En `cobranza_financiador`, `circular_aranceles`, `internacion_al_dia` y los de honorarios los datos de cada v\xEDa est\xE1n en la skill del puesto (cobranzas, aranceles, internaciones, honorarios): el agente informa lo que ley\xF3 del HIS o del documento tal cual y el plugin decide qu\xE9 se reclama o se carga. En `demanda_pendiente`: `contactar` (nivel ejecuta; datos: canal igual al de la regla y accion_id de la escritura registrada; el plugin controla regla activa, intentos, espera entre intentos y horario), `responder` (respuesta: acepta|rechaza|no_contesta), `comprobar` (lectura_id de la relectura del HIS, turno_id y turno_estado del turno encontrado), `cerrar` (motivo; solo con los intentos agotados o el plazo vencido), `descartar` (motivo). `credencial_profesional` (solo lee): `verificar` con profesional_id, matricula y tipo_matricula (nacional|provincial) si hay matr\xEDcula, resultado = el objeto que devuelve verificar-matriculas.mjs tal cual ({sin_matricula, encontrada, coincide_nombre, situacion, sancion, profesion, provincia, emisor}), lectura_id (conector "api", la consulta al registro) y fuente; el plugin decide vigente u observada y fija la observaci\xF3n (sin_matricula, no_figura, otra_persona, inhabilitada, sancion); `descartar` con motivo si el profesional ya no est\xE1 activo.',
40085
+ description: 'Aplica una transici\xF3n del workflow al caso (p. ej. `proponer`, `aplicar`, `verificar`). El plugin valida contra la tabla de transiciones, la pol\xEDtica vigente (observa / propone / ejecuta / corte) y las reglas de negocio; si algo falla, el caso no cambia y se devuelve el motivo. Las transiciones humanas (aprobar, rechazar, corregir) no est\xE1n disponibles para agentes. Cuando una persona de la instituci\xF3n carga a mano lo aprobado, el caso queda en `aplicado`: el agente lo comprueba releyendo el sistema y llamando `verificar` con `datos.valor_leido`. En `recupero_turno`: `proponer` con solicitud_id, solicitud_estado, solicitud_especialidad, cola (la lista de espera pendiente de esa especialidad tal como se vio: [{solicitud_id, prioridad, alta}]; el plugin la ordena y exige justificaci\xF3n para no elegir la primera) y fuente; si la instituci\xF3n pide consulta previa (ops_leer_caso lo dice), `ofrecer` con canal y esperar `aceptar`/`no_acepta`; **antes de escribir en el sistema, `autorizar`** (sin esa autorizaci\xF3n no se escribe); despu\xE9s de escribir, `asignar` con accion_id; `verificar` con leido_fecha, leido_hora, leido_profesional y leido_solicitud_id; `sin_candidato` y `descartar` con motivo. En `cuenta_completa` el agente solo lee: `observar` con lectura (el bono tal cual lo devuelve revisar.mjs) y lectura_id (conector "base"); `comprobar` con la lectura nueva y lectura_id; el plugin decide los faltantes; `descartar` con motivo si el bono se elimin\xF3 o anul\xF3. En `facturacion_pendiente` (solo lee): `observar` con bono_id, bono_estado, fecha_prestacion, obra_social, lectura_id (conector "base") y fuente; `comprobar` con bono_estado_leido y lectura_id (facturado/refacturado \u2192 facturada; prefacturado \u2192 en prefactura; si no, sigue por presentar); `descartar` con motivo. En `cobranza_financiador`, `circular_aranceles`, `ajuste_convenio`, `internacion_al_dia` y los de honorarios los datos de cada v\xEDa est\xE1n en la skill del puesto (cobranzas, aranceles, internaciones, honorarios): el agente informa lo que ley\xF3 del HIS o del documento tal cual y el plugin decide qu\xE9 se reclama o se carga. En `demanda_pendiente`: `contactar` (nivel ejecuta; datos: canal igual al de la regla y accion_id de la escritura registrada; el plugin controla regla activa, intentos, espera entre intentos y horario), `responder` (respuesta: acepta|rechaza|no_contesta), `comprobar` (lectura_id de la relectura del HIS, turno_id y turno_estado del turno encontrado), `cerrar` (motivo; solo con los intentos agotados o el plazo vencido), `descartar` (motivo). `credencial_profesional` (solo lee): `verificar` con profesional_id, matricula y tipo_matricula (nacional|provincial) si hay matr\xEDcula, resultado = el objeto que devuelve verificar-matriculas.mjs tal cual ({sin_matricula, encontrada, coincide_nombre, situacion, sancion, profesion, provincia, emisor}), lectura_id (conector "api", la consulta al registro) y fuente; el plugin decide vigente u observada y fija la observaci\xF3n (sin_matricula, no_figura, otra_persona, inhabilitada, sancion); `descartar` con motivo si el profesional ya no est\xE1 activo.',
40086
40086
  parametersSchema: {
40087
40087
  type: "object",
40088
40088
  properties: {
@@ -40382,6 +40382,22 @@ var manifest = {
40382
40382
  capability: "api.routes.register",
40383
40383
  companyResolution: { from: "body", key: "companyId" }
40384
40384
  },
40385
+ {
40386
+ routeKey: "ajustar",
40387
+ method: "POST",
40388
+ path: "/casos/:casoId/ajustar",
40389
+ auth: "board",
40390
+ capability: "api.routes.register",
40391
+ companyResolution: { from: "body", key: "companyId" }
40392
+ },
40393
+ {
40394
+ routeKey: "envio-nota",
40395
+ method: "POST",
40396
+ path: "/casos/:casoId/envio-nota",
40397
+ auth: "board",
40398
+ capability: "api.routes.register",
40399
+ companyResolution: { from: "body", key: "companyId" }
40400
+ },
40385
40401
  {
40386
40402
  routeKey: "respuesta-financiador",
40387
40403
  method: "POST",
@@ -41032,6 +41048,154 @@ function compararRelectura(aCargar, leidos) {
41032
41048
  return { diferencias, comprobadas: aCargar.length };
41033
41049
  }
41034
41050
 
41051
+ // src/ajuste.ts
41052
+ var SERIES = {
41053
+ ipc_general: { nombre: "IPC nivel general", id: "148.3_INIVELNAL_DICI_M_26" },
41054
+ ipc_salud: { nombre: "IPC salud", id: "146.3_ISALUDNAL_DICI_M_18" }
41055
+ };
41056
+ var FORMULA_POR_DEFECTO = { componentes: [{ serie: "ipc_general", peso: 100 }] };
41057
+ var TOPE_AJUSTE_POR_DEFECTO = 15;
41058
+ var LIMITES_TOPE_AJUSTE = [1, 100];
41059
+ var LIMITES_PORCENTAJE = [-50, 100];
41060
+ var redondear2 = (n, dec = 2) => Math.round(n * 10 ** dec) / 10 ** dec;
41061
+ var periodoValido = (p) => typeof p === "string" && /^\d{4}-(0[1-9]|1[0-2])$/.test(p);
41062
+ var porcentajeValido = (n) => typeof n === "number" && Number.isFinite(n) && n > LIMITES_PORCENTAJE[0] && n <= LIMITES_PORCENTAJE[1];
41063
+ function leerFormula(v) {
41064
+ if (v === void 0 || v === null) return FORMULA_POR_DEFECTO;
41065
+ if (typeof v !== "object" || Array.isArray(v)) return { error: "la f\xF3rmula tiene que ser {componentes: [{serie, peso}]} o {fijo: porcentaje}" };
41066
+ const o = v;
41067
+ if ("fijo" in o) {
41068
+ const f = leerMonto(o.fijo);
41069
+ return f !== null && porcentajeValido(f) ? { fijo: redondear2(f) } : { error: `el porcentaje fijo tiene que estar entre ${LIMITES_PORCENTAJE[0]} y ${LIMITES_PORCENTAJE[1]}` };
41070
+ }
41071
+ if (!Array.isArray(o.componentes) || o.componentes.length === 0) return { error: "la f\xF3rmula necesita al menos un \xEDndice" };
41072
+ const componentes = [];
41073
+ for (const c of o.componentes) {
41074
+ const r = c ?? {};
41075
+ const serie2 = typeof r.serie === "string" ? r.serie : "";
41076
+ const peso = leerMonto(r.peso);
41077
+ if (!SERIES[serie2]) return { error: `\xEDndice desconocido: ${serie2 || "(vac\xEDo)"}; los que hay: ${Object.keys(SERIES).join(", ")}` };
41078
+ if (peso === null || peso <= 0) return { error: `el peso de ${serie2} tiene que ser mayor a 0` };
41079
+ if (componentes.some((x) => x.serie === serie2)) return { error: `${serie2} est\xE1 repetido en la f\xF3rmula` };
41080
+ componentes.push({ serie: serie2, peso });
41081
+ }
41082
+ const suma = componentes.reduce((a, c) => a + c.peso, 0);
41083
+ if (Math.abs(suma - 100) > 0.01) return { error: `los pesos tienen que sumar 100 (suman ${suma})` };
41084
+ return { componentes };
41085
+ }
41086
+ function leerIndices(v) {
41087
+ if (!Array.isArray(v)) return { error: "indices tiene que ser una lista [{serie, desde: {periodo, valor}, hasta: {periodo, valor}, fuente}]" };
41088
+ const indices = [];
41089
+ for (const [i, e] of v.entries()) {
41090
+ const r = e ?? {};
41091
+ const serie2 = typeof r.serie === "string" ? r.serie : "";
41092
+ if (!SERIES[serie2]) return { error: `indices[${i}]: serie desconocida ${serie2 || "(vac\xEDa)"}` };
41093
+ if (indices.some((x) => x.serie === serie2)) return { error: `indices[${i}]: ${serie2} est\xE1 repetida` };
41094
+ const d = r.desde ?? {};
41095
+ const h = r.hasta ?? {};
41096
+ const vd = leerMonto(d.valor);
41097
+ const vh = leerMonto(h.valor);
41098
+ if (!periodoValido(d.periodo) || !periodoValido(h.periodo) || d.periodo >= h.periodo) return { error: `indices[${i}] (${serie2}): desde y hasta son meses AAAA-MM, desde antes que hasta` };
41099
+ if (vd === null || vh === null || vd <= 0 || vh <= 0) return { error: `indices[${i}] (${serie2}): los valores del \xEDndice tienen que ser n\xFAmeros mayores a 0` };
41100
+ if (typeof r.fuente !== "string" || !r.fuente.trim()) return { error: `indices[${i}] (${serie2}): falta la fuente` };
41101
+ indices.push({ serie: serie2, desde: { periodo: d.periodo, valor: vd }, hasta: { periodo: h.periodo, valor: vh }, fuente: r.fuente.trim().slice(0, 200) });
41102
+ }
41103
+ return { indices };
41104
+ }
41105
+ function porcentajeSegunFormula(formula, indices, desde, mesActual) {
41106
+ if ("fijo" in formula) return { porcentaje: formula.fijo, detalle: { fijo: formula.fijo }, hasta: null };
41107
+ const detalle = [];
41108
+ let hasta = null;
41109
+ for (const c of formula.componentes) {
41110
+ const ind = indices.find((x) => x.serie === c.serie);
41111
+ if (!ind) return { error: `falta el \xEDndice ${c.serie} (${SERIES[c.serie].nombre}) que usa la f\xF3rmula de la instituci\xF3n` };
41112
+ if (ind.desde.periodo !== desde) return { error: `${c.serie}: el per\xEDodo empieza en ${ind.desde.periodo} y el \xFAltimo ajuste fue en ${desde}` };
41113
+ if (ind.hasta.periodo >= mesActual) return { error: `${c.serie}: ${ind.hasta.periodo} todav\xEDa no est\xE1 publicado` };
41114
+ if (hasta && ind.hasta.periodo !== hasta) return { error: "los \xEDndices no terminan en el mismo mes" };
41115
+ hasta = ind.hasta.periodo;
41116
+ detalle.push({ serie: c.serie, nombre: SERIES[c.serie].nombre, variacion: redondear2((ind.hasta.valor / ind.desde.valor - 1) * 100), peso: c.peso });
41117
+ }
41118
+ const porcentaje3 = redondear2(detalle.reduce((a, d) => a + d.variacion * d.peso / 100, 0));
41119
+ if (!porcentajeValido(porcentaje3)) return { error: `el porcentaje calculado (${porcentaje3} %) est\xE1 fuera de rango: revisar los valores de los \xEDndices` };
41120
+ return { porcentaje: porcentaje3, detalle, hasta };
41121
+ }
41122
+ function armarAjuste(actual, porcentaje3) {
41123
+ const lectura = leerActual(actual);
41124
+ if ("error" in lectura) return { ok: false, error: lectura.error };
41125
+ if (!porcentajeValido(porcentaje3)) return { ok: false, error: `el porcentaje tiene que estar entre ${LIMITES_PORCENTAJE[0]} y ${LIMITES_PORCENTAJE[1]}` };
41126
+ const items = [];
41127
+ for (const p of lectura.practicas) {
41128
+ const valorActual = redondear2(p.componentes.reduce((a, c) => a + c.monto, 0));
41129
+ if (valorActual <= 0) continue;
41130
+ const valorNuevo = redondear2(valorActual * (1 + porcentaje3 / 100));
41131
+ const componentes = repartir(p.componentes, valorNuevo);
41132
+ items.push({
41133
+ codigo: p.codigo,
41134
+ codigo_his: p.codigo_his,
41135
+ nombre: p.nombre,
41136
+ valor_a: null,
41137
+ valor_b: null,
41138
+ valor_actual: valorActual,
41139
+ valor_nuevo: valorNuevo,
41140
+ variacion: redondear2((valorNuevo / valorActual - 1) * 100, 1),
41141
+ estado: componentes ? "propuesta" : "revisar",
41142
+ causa: componentes ? null : "componentes_en_cero",
41143
+ coinciden: true,
41144
+ motivo: componentes ? null : "no se puede repartir entre sus componentes: cargar a mano",
41145
+ componentes_nuevos: componentes
41146
+ });
41147
+ }
41148
+ if (items.length === 0) return { ok: false, error: "el convenio no tiene pr\xE1cticas con valor" };
41149
+ return { ok: true, items, actual: lectura.practicas };
41150
+ }
41151
+ function resumenAjuste(items, excluir, porcentaje3, porcentajeCalculado, tope) {
41152
+ const fuera = new Set(Array.isArray(excluir) ? excluir.map(normalizarCodigo) : []);
41153
+ const dentro = items.filter((i) => !fuera.has(i.codigo));
41154
+ const cargan = dentro.filter((i) => i.estado === "propuesta");
41155
+ return {
41156
+ porcentaje: porcentaje3,
41157
+ porcentaje_calculado: porcentajeCalculado,
41158
+ practicas: cargan.length,
41159
+ excluidas: items.length - dentro.length,
41160
+ revisar: dentro.length - cargan.length,
41161
+ total_actual: redondear2(cargan.reduce((a, i) => a + (i.valor_actual ?? 0), 0)),
41162
+ total_nuevo: redondear2(cargan.reduce((a, i) => a + (i.valor_nuevo ?? 0), 0)),
41163
+ tope,
41164
+ supera_tope: Math.abs(porcentaje3) > tope
41165
+ };
41166
+ }
41167
+ function textoIndices(detalle, desde, hasta) {
41168
+ if (!Array.isArray(detalle)) return "lo acordado en el convenio";
41169
+ const mes = (p) => (/* @__PURE__ */ new Date(`${p}-15T12:00:00Z`)).toLocaleDateString("es-AR", { month: "long", year: "numeric", timeZone: "UTC" });
41170
+ const pct = (n) => `${n.toLocaleString("es-AR", { maximumFractionDigits: 2 })} %`;
41171
+ const partes = detalle.map((d) => `${d.nombre} ${pct(d.variacion)}`);
41172
+ const pesos4 = detalle.length > 1 ? ` (ponderados ${detalle.map((d) => `${d.peso} %`).join(" y ")})` : "";
41173
+ const periodo = desde && hasta ? `, de ${mes(desde)} a ${mes(hasta)}` : "";
41174
+ return `${partes.join(" y ")}${pesos4}${periodo}`;
41175
+ }
41176
+ var NOTA_POR_DEFECTO = {
41177
+ asunto: "Actualizaci\xF3n de aranceles \xB7 {convenio} \xB7 {institucion}",
41178
+ texto: "Estimados de {financiador}:\n\nDe acuerdo con la variaci\xF3n de {indices}, proponemos actualizar los aranceles del convenio {convenio} en un {porcentaje}, con vigencia desde el {vigencia}.\n\nAdjuntamos el detalle de las {practicas} pr\xE1cticas con sus valores actuales y los propuestos.\n\nQuedamos a la espera de su conformidad.\n\nSaludos cordiales,\n{firma}\n{institucion}"
41179
+ };
41180
+ function armarNota(plantilla, d) {
41181
+ const p = plantilla && typeof plantilla === "object" ? plantilla : {};
41182
+ const [a, m, dia5] = d.vigencia.split("-");
41183
+ const valores = {
41184
+ institucion: d.institucion,
41185
+ financiador: d.financiador,
41186
+ convenio: d.convenio,
41187
+ porcentaje: `${d.porcentaje.toLocaleString("es-AR", { maximumFractionDigits: 2 })} %`,
41188
+ indices: d.indices,
41189
+ vigencia: dia5 ? `${dia5}/${m}/${a}` : d.vigencia,
41190
+ firma: d.firma,
41191
+ practicas: String(d.practicas)
41192
+ };
41193
+ const llenar = (t6) => t6.replace(/\{(\w+)\}/g, (x, k) => valores[k] ?? x);
41194
+ const asunto = typeof p.asunto === "string" && p.asunto.trim() ? p.asunto : NOTA_POR_DEFECTO.asunto;
41195
+ const texto10 = typeof p.texto === "string" && p.texto.trim() ? p.texto : NOTA_POR_DEFECTO.texto;
41196
+ return { asunto: llenar(asunto).slice(0, 200), texto: llenar(texto10).slice(0, 5e3) };
41197
+ }
41198
+
41035
41199
  // src/internacion.ts
41036
41200
  var HORA_CORTE_POR_DEFECTO = "13:00";
41037
41201
  var DIAS_SIN_ACTIVIDAD_POR_DEFECTO = 3;
@@ -41157,7 +41321,7 @@ function estadoInternacion(e) {
41157
41321
  }
41158
41322
 
41159
41323
  // src/honorarios.ts
41160
- var redondear2 = (n) => Math.round(n * 100) / 100;
41324
+ var redondear3 = (n) => Math.round(n * 100) / 100;
41161
41325
  var pesos2 = (n) => `$ ${n.toLocaleString("es-AR", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
41162
41326
  var fecha3 = (iso5) => `${iso5.slice(8, 10)}/${iso5.slice(5, 7)}/${iso5.slice(0, 4)}`;
41163
41327
  function texto2(v) {
@@ -41192,13 +41356,13 @@ function leerRegla(v) {
41192
41356
  if (!rid) return null;
41193
41357
  const prof = num3(r.prof);
41194
41358
  const inst = num3(r.inst);
41195
- return { id: rid, prof: prof === null ? null : redondear2(prof), inst: inst === null ? null : redondear2(inst) };
41359
+ return { id: rid, prof: prof === null ? null : redondear3(prof), inst: inst === null ? null : redondear3(inst) };
41196
41360
  }
41197
41361
  function honorarioDe(input2) {
41198
41362
  const { regla, monto, cantidad: cantidad2 } = input2;
41199
41363
  if (!regla) return 0;
41200
- if (regla.prof !== null) return redondear2(regla.prof * cantidad2);
41201
- return redondear2(monto * cantidad2 * ((regla.inst ?? 0) / 100));
41364
+ if (regla.prof !== null) return redondear3(regla.prof * cantidad2);
41365
+ return redondear3(monto * cantidad2 * ((regla.inst ?? 0) / 100));
41202
41366
  }
41203
41367
  function motivoCero(regla, honorario, delHis = false) {
41204
41368
  if (honorario > 0) return null;
@@ -41208,7 +41372,7 @@ function motivoCero(regla, honorario, delHis = false) {
41208
41372
  function honorarioLeido(r, regla, monto, cantidad2) {
41209
41373
  if (r.honorario === void 0 || r.honorario === null || r.honorario === "") return { honorario: honorarioDe({ regla, monto, cantidad: cantidad2 }), delHis: false };
41210
41374
  const h = num3(r.honorario);
41211
- return h === null || h < 0 ? null : { honorario: redondear2(h), delHis: true };
41375
+ return h === null || h < 0 ? null : { honorario: redondear3(h), delHis: true };
41212
41376
  }
41213
41377
  function leerPrestaciones(v) {
41214
41378
  if (!Array.isArray(v)) return { error: "prestaciones tiene que ser la lista que devuelve leer-honorarios.mjs" };
@@ -41225,7 +41389,7 @@ function leerPrestaciones(v) {
41225
41389
  if (r.regla !== null && r.regla !== void 0 && !leerRegla(r.regla)) return { error: `prestaciones[${i}].regla: tiene que ser {id, prof, inst} o null` };
41226
41390
  const h = honorarioLeido(r, leerRegla(r.regla), monto, cantidad2 > 0 ? cantidad2 : 1);
41227
41391
  if (!h) return { error: `prestaciones[${i}].honorario: tiene que ser un n\xFAmero mayor o igual a 0` };
41228
- out.push({ item_id: item, bono_id: bono, fecha: f, prestacion: texto2(r.prestacion)?.slice(0, 160) ?? "Prestaci\xF3n", obra_social: texto2(r.obra_social)?.slice(0, 120) ?? null, monto: redondear2(monto), cantidad: cantidad2 > 0 ? cantidad2 : 1, a_liquidar: verdad2(r.a_liquidar), liquidada: verdad2(r.liquidada), regla: leerRegla(r.regla), honorario_his: h.delHis ? h.honorario : null });
41392
+ out.push({ item_id: item, bono_id: bono, fecha: f, prestacion: texto2(r.prestacion)?.slice(0, 160) ?? "Prestaci\xF3n", obra_social: texto2(r.obra_social)?.slice(0, 120) ?? null, monto: redondear3(monto), cantidad: cantidad2 > 0 ? cantidad2 : 1, a_liquidar: verdad2(r.a_liquidar), liquidada: verdad2(r.liquidada), regla: leerRegla(r.regla), honorario_his: h.delHis ? h.honorario : null });
41229
41393
  }
41230
41394
  return { prestaciones: out };
41231
41395
  }
@@ -41244,7 +41408,7 @@ function prestacionesEnCero(ps) {
41244
41408
  prestaciones: items.length,
41245
41409
  sin_regla: items.filter((p) => p.motivo === "sin_regla").length,
41246
41410
  regla_sin_valor: items.filter((p) => p.motivo === "regla_sin_valor").length,
41247
- monto: redondear2(items.reduce((a, p) => a + p.monto * p.cantidad, 0)),
41411
+ monto: redondear3(items.reduce((a, p) => a + p.monto * p.cantidad, 0)),
41248
41412
  obras_sociales: [...new Set(items.map((p) => p.obra_social).filter((x) => Boolean(x)))].sort(),
41249
41413
  desde: fechas[0] ?? null,
41250
41414
  hasta: fechas[fechas.length - 1] ?? null
@@ -41270,7 +41434,7 @@ function leerDebitos(v) {
41270
41434
  const hl = honorarioLeido(r, regla, montoItem, cantidad2);
41271
41435
  if (!hl) return { error: `debitos[${i}].honorario: tiene que ser un n\xFAmero mayor o igual a 0` };
41272
41436
  const hon = liquidada ? hl.honorario : null;
41273
- const imp = liquidada ? redondear2(montoItem * cantidad2) : null;
41437
+ const imp = liquidada ? redondear3(montoItem * cantidad2) : null;
41274
41438
  out.push({
41275
41439
  item_id: item,
41276
41440
  bono_id: bono,
@@ -41278,13 +41442,13 @@ function leerDebitos(v) {
41278
41442
  fecha_prestacion: dia(r.fecha_prestacion),
41279
41443
  obra_social: texto2(r.obra_social)?.slice(0, 120) ?? null,
41280
41444
  nota_credito: { id: ncId, numero: texto2(nc?.numero), fecha: dia(nc?.fecha), motivo: texto2(nc?.motivo)?.slice(0, 300) ?? null },
41281
- monto_debitado: redondear2(Math.abs(monto)),
41445
+ monto_debitado: redondear3(Math.abs(monto)),
41282
41446
  liquidada,
41283
- monto: redondear2(montoItem),
41447
+ monto: redondear3(montoItem),
41284
41448
  cantidad: cantidad2 > 0 ? cantidad2 : 1,
41285
41449
  regla,
41286
41450
  liquidado: hon !== null && imp !== null ? { honorario: hon, importe: imp } : null,
41287
- ya_trasladado: redondear2(Math.abs(num3(r.ya_trasladado) ?? 0))
41451
+ ya_trasladado: redondear3(Math.abs(num3(r.ya_trasladado) ?? 0))
41288
41452
  });
41289
41453
  }
41290
41454
  return { debitos: out };
@@ -41293,12 +41457,12 @@ function evaluarDebito(d) {
41293
41457
  if (!d.liquidado) return { ...d, estado: "sin_liquidar", a_descontar: 0 };
41294
41458
  if (d.liquidado.importe <= 0 || d.liquidado.honorario <= 0 || d.monto_debitado <= 0) return { ...d, estado: "sin_monto", a_descontar: 0 };
41295
41459
  const proporcional = Math.min(d.liquidado.honorario, d.liquidado.honorario * d.monto_debitado / d.liquidado.importe);
41296
- const falta = redondear2(proporcional - d.ya_trasladado);
41460
+ const falta = redondear3(proporcional - d.ya_trasladado);
41297
41461
  return falta >= 0.01 ? { ...d, estado: "a_trasladar", a_descontar: falta } : { ...d, estado: "ya_trasladado", a_descontar: 0 };
41298
41462
  }
41299
41463
  function resumenDebitos(ds) {
41300
41464
  const a = ds.filter((d) => d.estado === "a_trasladar");
41301
- return { a_trasladar: a.length, total: redondear2(a.reduce((s, d) => s + d.a_descontar, 0)), sin_liquidar: ds.filter((d) => d.estado === "sin_liquidar").length, ya_trasladados: ds.filter((d) => d.estado === "ya_trasladado").length };
41465
+ return { a_trasladar: a.length, total: redondear3(a.reduce((s, d) => s + d.a_descontar, 0)), sin_liquidar: ds.filter((d) => d.estado === "sin_liquidar").length, ya_trasladados: ds.filter((d) => d.estado === "ya_trasladado").length };
41302
41466
  }
41303
41467
  function leerLineasLiquidacion(v) {
41304
41468
  if (!Array.isArray(v) || v.length === 0) return { error: "lineas tiene que ser la lista (no vac\xEDa) de bonos de la liquidaci\xF3n, como la devuelve leer-honorarios.mjs" };
@@ -41323,7 +41487,7 @@ function leerLineasLiquidacion(v) {
41323
41487
  honorario += h;
41324
41488
  if (h <= 0) enCero += 1;
41325
41489
  }
41326
- out.push({ bono_id: bono, fecha: dia(r.fecha), obra_social: texto2(r.obra_social)?.slice(0, 120) ?? null, importe: redondear2(importe), honorario: redondear2(honorario), en_cero: enCero });
41490
+ out.push({ bono_id: bono, fecha: dia(r.fecha), obra_social: texto2(r.obra_social)?.slice(0, 120) ?? null, importe: redondear3(importe), honorario: redondear3(honorario), en_cero: enCero });
41327
41491
  }
41328
41492
  return { lineas: out };
41329
41493
  }
@@ -41333,14 +41497,14 @@ function resumirLiquidacion(lineas) {
41333
41497
  const k = l.obra_social ?? "Particular / sin obra social";
41334
41498
  const e = por.get(k) ?? { bonos: 0, honorario: 0 };
41335
41499
  e.bonos += 1;
41336
- e.honorario = redondear2(e.honorario + l.honorario);
41500
+ e.honorario = redondear3(e.honorario + l.honorario);
41337
41501
  por.set(k, e);
41338
41502
  }
41339
41503
  const fechas = lineas.map((l) => l.fecha).filter((x) => Boolean(x)).sort();
41340
41504
  return {
41341
41505
  bonos: lineas.length,
41342
- honorario: redondear2(lineas.reduce((a, l) => a + l.honorario, 0)),
41343
- importe: redondear2(lineas.reduce((a, l) => a + l.importe, 0)),
41506
+ honorario: redondear3(lineas.reduce((a, l) => a + l.honorario, 0)),
41507
+ importe: redondear3(lineas.reduce((a, l) => a + l.importe, 0)),
41344
41508
  en_cero: lineas.reduce((a, l) => a + l.en_cero, 0),
41345
41509
  por_obra_social: [...por.entries()].map(([obra_social, e]) => ({ obra_social, ...e })).sort((a, b) => b.honorario - a.honorario),
41346
41510
  desde: fechas[0] ?? null,
@@ -41376,7 +41540,7 @@ var ATRASO_EXCEPCION = 365;
41376
41540
  var MAX_RECLAMOS_POR_DEFECTO = 2;
41377
41541
  var DIAS_ENTRE_RECLAMOS_POR_DEFECTO = 15;
41378
41542
  var GRACIA_COMPROMISO = 7;
41379
- var redondear3 = (n) => Math.round(n * 100) / 100;
41543
+ var redondear4 = (n) => Math.round(n * 100) / 100;
41380
41544
  var pesos3 = (n) => `$ ${n.toLocaleString("es-AR", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
41381
41545
  var fecha4 = (iso5) => `${iso5.slice(8, 10)}/${iso5.slice(5, 7)}/${iso5.slice(0, 4)}`;
41382
41546
  var normalizar = (s) => s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/\s+/g, " ").trim();
@@ -41416,7 +41580,7 @@ function leerFacturas(v) {
41416
41580
  vistas.add(id3);
41417
41581
  if (!emision) return { error: `factura ${id3}: fecha_emision tiene que ser AAAA-MM-DD` };
41418
41582
  if (monto === null || saldo === null) return { error: `factura ${id3}: monto_pesos y saldo_pesos tienen que ser n\xFAmeros` };
41419
- out.push({ factura_id: id3, numero_comprobante: texto3(r.numero_comprobante) ?? id3, fecha_emision: emision, periodo_desde: dia2(r.periodo_desde), periodo_hasta: dia2(r.periodo_hasta), monto_pesos: redondear3(monto), saldo_pesos: redondear3(saldo), anulada: r.anulada === true || r.anulada === 1 || r.anulada === "1" });
41583
+ out.push({ factura_id: id3, numero_comprobante: texto3(r.numero_comprobante) ?? id3, fecha_emision: emision, periodo_desde: dia2(r.periodo_desde), periodo_hasta: dia2(r.periodo_hasta), monto_pesos: redondear4(monto), saldo_pesos: redondear4(saldo), anulada: r.anulada === true || r.anulada === 1 || r.anulada === "1" });
41420
41584
  }
41421
41585
  return { facturas: out };
41422
41586
  }
@@ -41448,7 +41612,7 @@ function evaluarFacturas(input2) {
41448
41612
  };
41449
41613
  });
41450
41614
  const reclamadas = items.filter((i) => i.estado === "reclamada").sort((a, b) => b.dias_atraso - a.dias_atraso);
41451
- const total = redondear3(reclamadas.reduce((s, i) => s + i.saldo_pesos, 0));
41615
+ const total = redondear4(reclamadas.reduce((s, i) => s + i.saldo_pesos, 0));
41452
41616
  return {
41453
41617
  items: [...reclamadas, ...items.filter((i) => i.estado !== "reclamada")],
41454
41618
  resumen: {
@@ -41467,7 +41631,7 @@ function evaluarFacturas(input2) {
41467
41631
  }
41468
41632
  function redactarReclamo(input2) {
41469
41633
  const reclamadas = input2.items.filter((i) => i.estado === "reclamada" && i.cobro !== "cobrada");
41470
- const total = redondear3(reclamadas.reduce((s, i) => s + i.saldo_pesos, 0));
41634
+ const total = redondear4(reclamadas.reduce((s, i) => s + i.saldo_pesos, 0));
41471
41635
  const asunto = `${input2.numero > 1 ? "Segundo reclamo" : "Reclamo"} de pago \xB7 ${input2.institucion} \xB7 ${reclamadas.length} ${reclamadas.length === 1 ? "factura" : "facturas"} \xB7 ${pesos3(total)}`;
41472
41636
  const lineas = reclamadas.map((i) => `- Factura ${i.numero_comprobante} del ${fecha4(i.fecha_emision)}${i.periodo ? ` (per\xEDodo ${i.periodo})` : ""}: saldo ${pesos3(i.saldo_pesos)}, vencida hace ${i.dias_atraso} d\xEDas.`);
41473
41637
  const cuerpo = [
@@ -41493,7 +41657,7 @@ function leerSaldos(v) {
41493
41657
  const id3 = texto3(r.factura_id);
41494
41658
  const saldo = num4(r.saldo_pesos);
41495
41659
  if (!id3 || saldo === null) return { error: `leidos[${i}]: hace falta factura_id y saldo_pesos` };
41496
- saldos.set(id3, { saldo: redondear3(saldo), notasCredito: redondear3(num4(r.notas_credito_pesos) ?? 0), recibos: redondear3(num4(r.recibos_aplicados_pesos) ?? 0) });
41660
+ saldos.set(id3, { saldo: redondear4(saldo), notasCredito: redondear4(num4(r.notas_credito_pesos) ?? 0), recibos: redondear4(num4(r.recibos_aplicados_pesos) ?? 0) });
41497
41661
  }
41498
41662
  return { saldos };
41499
41663
  }
@@ -41509,14 +41673,14 @@ function compararSaldos(items, resumen, leidos, notasCreditoAntes = {}) {
41509
41673
  return i;
41510
41674
  }
41511
41675
  const saldo = Math.max(0, s.saldo);
41512
- const nc = Math.max(0, redondear3(s.notasCredito - (notasCreditoAntes[i.factura_id] ?? 0)));
41676
+ const nc = Math.max(0, redondear4(s.notasCredito - (notasCreditoAntes[i.factura_id] ?? 0)));
41513
41677
  const cobro = saldo <= SALDO_MINIMO ? "cobrada" : saldo < i.saldo_inicial - SALDO_MINIMO ? "parcial" : "pendiente";
41514
41678
  return { ...i, saldo_pesos: saldo, cobro, notas_credito_nuevas: nc };
41515
41679
  });
41516
41680
  const reclamadas = nuevos.filter((i) => i.estado === "reclamada");
41517
- const pendiente = redondear3(reclamadas.reduce((s, i) => s + (i.cobro === "cobrada" ? 0 : i.saldo_pesos), 0));
41518
- const debitos = redondear3(reclamadas.reduce((s, i) => s + i.notas_credito_nuevas, 0));
41519
- const cobrado = redondear3(Math.max(0, resumen.total_reclamado - pendiente - debitos));
41681
+ const pendiente = redondear4(reclamadas.reduce((s, i) => s + (i.cobro === "cobrada" ? 0 : i.saldo_pesos), 0));
41682
+ const debitos = redondear4(reclamadas.reduce((s, i) => s + i.notas_credito_nuevas, 0));
41683
+ const cobrado = redondear4(Math.max(0, resumen.total_reclamado - pendiente - debitos));
41520
41684
  return { items: nuevos, resumen: { ...resumen, cobrado, pendiente, posibles_debitos: debitos }, todoCobrado: reclamadas.length > 0 && reclamadas.every((i) => i.cobro === "cobrada") && faltan.length === 0, faltan };
41521
41685
  }
41522
41686
  var RESPUESTAS_FINANCIADOR = ["paga", "observa", "no_responde"];
@@ -42614,6 +42778,209 @@ var circularAranceles = {
42614
42778
  }
42615
42779
  ]
42616
42780
  };
42781
+ var RESPUESTAS_AJUSTE = ["aprobado", "aprobado_otro", "rechazado"];
42782
+ var CANALES_NOTA = ["correo", "portal_del_financiador", "nota", "otro"];
42783
+ function calcularAjuste(caso, datos, contexto) {
42784
+ const formula = contexto.formulaAjuste ?? FORMULA_POR_DEFECTO;
42785
+ if ("error" in formula) return { ok: false, error: `la f\xF3rmula de la instituci\xF3n est\xE1 mal configurada: ${formula.error}` };
42786
+ const desde = texto4(caso.datos.ultimo_ajuste);
42787
+ if (!desde || !periodoValido(desde)) return { ok: false, error: "el caso no tiene el mes del \xFAltimo ajuste (ultimo_ajuste: AAAA-MM)" };
42788
+ let indices = [];
42789
+ if (!("fijo" in formula)) {
42790
+ const l = leerIndices(datos.indices);
42791
+ if ("error" in l) return { ok: false, error: l.error };
42792
+ indices = l.indices;
42793
+ }
42794
+ const mesActual = (contexto.hoy ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)).slice(0, 7);
42795
+ const p = porcentajeSegunFormula(formula, indices, desde, mesActual);
42796
+ if ("error" in p) return { ok: false, error: p.error };
42797
+ const a = armarAjuste(datos.actual, p.porcentaje);
42798
+ if (!a.ok) return { ok: false, error: a.error };
42799
+ return { ok: true, porcentaje: p.porcentaje, detalle: p.detalle, hasta: p.hasta, items: a.items };
42800
+ }
42801
+ function resumenAjusteDe(caso) {
42802
+ const r = caso.datos.resumen;
42803
+ return r && typeof r === "object" ? r : null;
42804
+ }
42805
+ function validarPorcentajeHumano(v, regla) {
42806
+ const n = num5(v);
42807
+ return n !== null && porcentajeValido(n) ? ok(regla, `${n} %`) : falla(regla, "porcentaje: un n\xFAmero entre -50 y 100");
42808
+ }
42809
+ var ajusteConvenio = {
42810
+ clave: "ajuste_convenio",
42811
+ nombre: "Ajuste de convenios por \xEDndice",
42812
+ puesto: "Convenios y facturaci\xF3n",
42813
+ descripcion: "Calcula el aumento de un convenio con los \xEDndices publicados (IPC de INDEC) y la f\xF3rmula de la instituci\xF3n, arma los valores nuevos de cada pr\xE1ctica y la nota al financiador; la direcci\xF3n revisa y aprueba, se registra la respuesta del financiador, se carga con el archivo que genera el plugin y el puesto comprueba releyendo cada valor.",
42814
+ inicial: "abierto",
42815
+ estados: ["abierto", "calculado", "pendiente_aprobacion", "por_enviar", "enviado", "acordado", "aplicado", "verificado", "con_diferencias", "rechazado", "rechazado_financiador", "cancelado"],
42816
+ finalesOk: ["verificado"],
42817
+ finalesNoOk: ["rechazado", "rechazado_financiador", "cancelado"],
42818
+ minutosAbsorbidos: 40,
42819
+ // Buscar los índices, calcular, armar la nota y comprobar cada valor (tope: una jornada).
42820
+ minutosSegun: (caso) => Math.min(480, 40 + 2 * cantidad(caso.datos.items)),
42821
+ transiciones: [
42822
+ {
42823
+ via: "calcular",
42824
+ de: ["abierto", "calculado"],
42825
+ a: "calculado",
42826
+ actor: ["agente", "humano"],
42827
+ nivelRequerido: "observa",
42828
+ descripcion: "Informar los valores publicados de los \xEDndices (indices: [{serie, desde: {periodo, valor}, hasta: {periodo, valor}, fuente}], desde el mes del \xFAltimo ajuste hasta el \xFAltimo publicado) y lo que tiene hoy el convenio en el HIS (actual). El plugin calcula el porcentaje con la f\xF3rmula de la instituci\xF3n y los valores nuevos.",
42829
+ validar: (caso, datos, contexto) => {
42830
+ const c = calcularAjuste(caso, datos, contexto);
42831
+ const fijo = contexto.formulaAjuste && "fijo" in contexto.formulaAjuste;
42832
+ return [
42833
+ texto4(datos.lectura_id) ? ok("lectura_registrada") : falla("lectura_registrada", "calcular requiere lectura_id: la lectura del convenio en el HIS registrada con ops_registrar_accion"),
42834
+ fijo || texto4(datos.lectura_indices_id) ? ok("indices_registrados") : falla("indices_registrados", "calcular requiere lectura_indices_id: la consulta de los \xEDndices (conector api) registrada con ops_registrar_accion"),
42835
+ c.ok ? ok("calculo_ajuste", `${c.porcentaje} % sobre ${c.items.length} pr\xE1cticas`) : falla("calculo_ajuste", c.error)
42836
+ ];
42837
+ }
42838
+ },
42839
+ {
42840
+ via: "proponer",
42841
+ de: ["calculado"],
42842
+ a: "pendiente_aprobacion",
42843
+ actor: ["agente", "humano"],
42844
+ nivelRequerido: "propone",
42845
+ descripcion: "Pedir la aprobaci\xF3n de la direcci\xF3n con el porcentaje y los valores que calcul\xF3 el plugin. Un ajuste de convenio siempre lo aprueba una persona.",
42846
+ validar: (caso) => {
42847
+ const r = resumenAjusteDe(caso);
42848
+ return [r && r.practicas > 0 ? ok("hay_que_cargar", `${r.practicas} pr\xE1cticas`) : falla("hay_que_cargar", "no hay ninguna pr\xE1ctica para ajustar: revisar la lectura del convenio o cancelar")];
42849
+ }
42850
+ },
42851
+ {
42852
+ via: "ajustar",
42853
+ de: ["pendiente_aprobacion"],
42854
+ a: "pendiente_aprobacion",
42855
+ actor: ["humano"],
42856
+ descripcion: "La direcci\xF3n cambia el porcentaje (porcentaje, con motivo) o deja pr\xE1cticas afuera (excluir: [c\xF3digos]); el plugin recalcula los valores.",
42857
+ validar: (caso, datos) => {
42858
+ const out = [];
42859
+ if (datos.porcentaje === void 0 && datos.excluir === void 0) return [falla("cambio_presente", "ajustar necesita porcentaje o excluir")];
42860
+ if (datos.porcentaje !== void 0) {
42861
+ out.push(validarPorcentajeHumano(datos.porcentaje, "porcentaje_valido"));
42862
+ out.push(texto4(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "cambiar el porcentaje calculado requiere motivo"));
42863
+ }
42864
+ if (datos.excluir !== void 0) {
42865
+ if (!Array.isArray(datos.excluir)) out.push(falla("excluir_valido", "excluir tiene que ser una lista de c\xF3digos"));
42866
+ else {
42867
+ const items = leerItems(caso.datos.items);
42868
+ const codigos = new Set(items.map((i) => i.codigo));
42869
+ const ajenos = datos.excluir.map(normalizarCodigo).filter((c) => !codigos.has(c));
42870
+ out.push(ajenos.length === 0 ? ok("excluir_valido") : falla("excluir_valido", `c\xF3digos que no est\xE1n en el convenio: ${ajenos.join(", ")}`));
42871
+ out.push(itemsACargar(items, datos.excluir).length > 0 ? ok("hay_que_cargar") : falla("hay_que_cargar", "excluyendo esas no queda nada para ajustar: rechazar el ajuste"));
42872
+ }
42873
+ }
42874
+ return out;
42875
+ }
42876
+ },
42877
+ {
42878
+ via: "aprobar",
42879
+ de: ["pendiente_aprobacion"],
42880
+ a: "por_enviar",
42881
+ actor: ["humano"],
42882
+ descripcion: "La direcci\xF3n aprueba el ajuste; el plugin arma la nota al financiador. Si el porcentaje supera el tope de la instituci\xF3n hay que confirmarlo (confirmo_tope: true).",
42883
+ validar: (caso, datos) => {
42884
+ const r = resumenAjusteDe(caso);
42885
+ return [
42886
+ r && r.practicas > 0 ? ok("hay_que_cargar", `${r.practicas} pr\xE1cticas`) : falla("hay_que_cargar", "no hay ninguna pr\xE1ctica para ajustar"),
42887
+ !r?.supera_tope || datos.confirmo_tope === true ? ok("tope_confirmado", r ? `${r.porcentaje} % (tope ${r.tope} %)` : void 0) : falla("tope_confirmado", `el ${r.porcentaje} % supera el tope de ${r.tope} % de la instituci\xF3n: confirmarlo de forma expresa`)
42888
+ ];
42889
+ }
42890
+ },
42891
+ {
42892
+ via: "rechazar",
42893
+ de: ["pendiente_aprobacion"],
42894
+ a: "rechazado",
42895
+ actor: ["humano"],
42896
+ descripcion: "La direcci\xF3n no hace el ajuste; con motivo.",
42897
+ validar: (_caso, datos) => [texto4(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "el rechazo requiere motivo")]
42898
+ },
42899
+ {
42900
+ via: "enviar",
42901
+ de: ["por_enviar"],
42902
+ a: "enviado",
42903
+ actor: ["humano", "agente"],
42904
+ nivelRequerido: "ejecuta",
42905
+ descripcion: "Registrar que la nota se mand\xF3 al financiador: por d\xF3nde (canal) y a qui\xE9n (destinatario).",
42906
+ validar: (_caso, datos) => [
42907
+ CANALES_NOTA.includes(datos.canal) ? ok("canal_valido") : falla("canal_valido", `canal: ${CANALES_NOTA.join(", ")}`),
42908
+ texto4(datos.destinatario) ? ok("destinatario_presente") : falla("destinatario_presente", "destinatario (correo, \xE1rea o persona del financiador) es obligatorio")
42909
+ ]
42910
+ },
42911
+ {
42912
+ via: "responder",
42913
+ de: ["enviado"],
42914
+ a: (_caso, datos) => datos.respuesta === "rechazado" ? "rechazado_financiador" : "acordado",
42915
+ actor: ["humano"],
42916
+ descripcion: "Registrar la respuesta del financiador: aprobado, aprobado con otro porcentaje (porcentaje) o rechazado (motivo).",
42917
+ validar: (_caso, datos) => {
42918
+ const r = datos.respuesta;
42919
+ if (!RESPUESTAS_AJUSTE.includes(r)) return [falla("respuesta_valida", `respuesta: ${RESPUESTAS_AJUSTE.join(", ")}`)];
42920
+ if (r === "aprobado_otro") return [ok("respuesta_valida"), validarPorcentajeHumano(datos.porcentaje, "porcentaje_valido")];
42921
+ if (r === "rechazado") return [ok("respuesta_valida"), texto4(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "anotar qu\xE9 respondi\xF3 el financiador")];
42922
+ return [ok("respuesta_valida")];
42923
+ }
42924
+ },
42925
+ {
42926
+ via: "aplicar",
42927
+ de: ["acordado"],
42928
+ a: "aplicado",
42929
+ actor: ["agente", "humano"],
42930
+ nivelRequerido: "ejecuta",
42931
+ cargaManual: true,
42932
+ descripcion: "Cargar en el HIS el archivo que gener\xF3 el plugin (Actualizaci\xF3n con archivo) y registrarlo, con el hash del archivo cargado.",
42933
+ validar: (caso, datos) => {
42934
+ const esperado = texto4(caso.datos.archivo_csv?.hash);
42935
+ const informado = texto4(datos.archivo_hash);
42936
+ return [
42937
+ texto4(datos.accion_id) ? ok("accion_registrada") : falla("accion_registrada", "aplicar requiere accion_id de la carga registrada"),
42938
+ esperado && informado === esperado ? ok("archivo_aprobado") : falla("archivo_aprobado", esperado ? "el archivo cargado no es el que gener\xF3 el plugin para este ajuste (archivo_hash distinto): descargarlo de nuevo y cargar ese" : "el ajuste no tiene archivo generado")
42939
+ ];
42940
+ }
42941
+ },
42942
+ {
42943
+ via: "verificar",
42944
+ de: ["aplicado"],
42945
+ a: (caso, datos) => {
42946
+ const r = compararRelectura(itemsACargar(leerItems(caso.datos.items), caso.datos.excluir), datos.leidos);
42947
+ return "error" in r || r.diferencias.length > 0 ? "con_diferencias" : "verificado";
42948
+ },
42949
+ actor: ["agente", "humano", "sistema"],
42950
+ nivelRequerido: "observa",
42951
+ descripcion: "Releer el convenio en el HIS despu\xE9s de la carga e informar cada pr\xE1ctica con sus componentes (leidos). El plugin compara contra lo acordado.",
42952
+ validar: (_caso, datos) => {
42953
+ const r = compararRelectura([], datos.leidos);
42954
+ return [
42955
+ texto4(datos.lectura_id) ? ok("lectura_registrada") : falla("lectura_registrada", "verificar requiere lectura_id: la relectura del convenio registrada"),
42956
+ "error" in r ? falla("leidos_validos", r.error) : ok("leidos_validos")
42957
+ ];
42958
+ }
42959
+ },
42960
+ {
42961
+ via: "corregir",
42962
+ de: ["con_diferencias"],
42963
+ a: "aplicado",
42964
+ actor: ["humano"],
42965
+ cargaManual: true,
42966
+ descripcion: "Corregir en el HIS las pr\xE1cticas que no coincidieron y volver a comprobar.",
42967
+ validar: (_caso, datos) => [texto4(datos.accion_id) ? ok("accion_registrada") : falla("accion_registrada", "corregir requiere accion_id de la correcci\xF3n registrada")]
42968
+ },
42969
+ {
42970
+ via: "cancelar",
42971
+ de: ["abierto", "calculado", "pendiente_aprobacion", "por_enviar", "enviado", "acordado", "con_diferencias"],
42972
+ a: "cancelado",
42973
+ actor: ["humano", "agente"],
42974
+ nivelRequerido: "observa",
42975
+ descripcion: "Cancelar el ajuste (repetido, el convenio cambi\xF3 por otra v\xEDa, se renegoci\xF3).",
42976
+ validar: (_caso, datos) => [texto4(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "la cancelaci\xF3n requiere motivo")]
42977
+ }
42978
+ ]
42979
+ };
42980
+ function leerTopeAjuste(v) {
42981
+ const n = num5(v);
42982
+ return n !== null && n >= 1 && n <= 100 ? Math.round(n) : TOPE_AJUSTE_POR_DEFECTO;
42983
+ }
42617
42984
  var CANALES_RECLAMO = ["correo", "portal_del_financiador", "nota", "telefono", "otro"];
42618
42985
  function resumenCobranzaDe(caso) {
42619
42986
  const r = caso.datos.resumen;
@@ -43296,6 +43663,7 @@ var WORKFLOWS = {
43296
43663
  [credencialProfesional.clave]: credencialProfesional,
43297
43664
  [demandaPendiente.clave]: demandaPendiente,
43298
43665
  [circularAranceles.clave]: circularAranceles,
43666
+ [ajusteConvenio.clave]: ajusteConvenio,
43299
43667
  [cobranzaFinanciador.clave]: cobranzaFinanciador,
43300
43668
  [internacionAlDia.clave]: internacionAlDia,
43301
43669
  [honorarioEnCero.clave]: honorarioEnCero,
@@ -43795,7 +44163,7 @@ async function informarPruebaRegla(ctx, input2) {
43795
44163
  // src/rentabilidad.ts
43796
44164
  var UMBRAL_MARGEN_POR_DEFECTO = 15;
43797
44165
  var MESES_PRECIO_POR_DEFECTO = 6;
43798
- var redondear4 = (n) => Math.round(n * 100) / 100;
44166
+ var redondear5 = (n) => Math.round(n * 100) / 100;
43799
44167
  function texto5(v) {
43800
44168
  return typeof v === "string" && v.trim() ? v.trim() : typeof v === "number" && Number.isFinite(v) ? String(v) : null;
43801
44169
  }
@@ -43824,7 +44192,7 @@ function leerLecturaRentabilidad(v) {
43824
44192
  const x = c ?? {};
43825
44193
  const monto = num6(x.monto);
43826
44194
  if (monto === null || monto < 0) return { error: `lectura.componentes[${i}].monto: tiene que ser un n\xFAmero \u2265 0` };
43827
- componentes.push({ arancel: texto5(x.arancel) ?? "", monto: redondear4(monto) });
44195
+ componentes.push({ arancel: texto5(x.arancel) ?? "", monto: redondear5(monto) });
43828
44196
  }
43829
44197
  const insumos = [];
43830
44198
  for (const [i, c] of r.insumos.entries()) {
@@ -43832,7 +44200,7 @@ function leerLecturaRentabilidad(v) {
43832
44200
  const cantidad2 = num6(x.cantidad);
43833
44201
  if (cantidad2 === null || cantidad2 <= 0) return { error: `lectura.insumos[${i}].cantidad: tiene que ser un n\xFAmero > 0` };
43834
44202
  const precio = num6(x.precio);
43835
- insumos.push({ articulo_id: texto5(x.articulo_id) ?? String(i), articulo: texto5(x.articulo) ?? "", cantidad: cantidad2, precio: precio === null ? null : redondear4(precio), modificado_en: dia3(x.modificado_en) });
44203
+ insumos.push({ articulo_id: texto5(x.articulo_id) ?? String(i), articulo: texto5(x.articulo) ?? "", cantidad: cantidad2, precio: precio === null ? null : redondear5(precio), modificado_en: dia3(x.modificado_en) });
43836
44204
  }
43837
44205
  const honorarios = [];
43838
44206
  for (const [i, c] of r.honorarios.entries()) {
@@ -43870,7 +44238,7 @@ function evaluarRentabilidad(input2) {
43870
44238
  const faltas = [];
43871
44239
  const avisos = [];
43872
44240
  const insumosARevisar = [];
43873
- const valor = redondear4(l.componentes.reduce((a, c) => a + c.monto, 0));
44241
+ const valor = redondear5(l.componentes.reduce((a, c) => a + c.monto, 0));
43874
44242
  if (valor <= 0) faltas.push("sin_valor_convenio");
43875
44243
  let insumos = 0;
43876
44244
  for (const i of l.insumos) {
@@ -43885,7 +44253,7 @@ function evaluarRentabilidad(input2) {
43885
44253
  insumosARevisar.push(`${i.articulo || i.articulo_id} (precio ${i.modificado_en ? `del ${i.modificado_en.slice(8, 10)}/${i.modificado_en.slice(5, 7)}/${i.modificado_en.slice(0, 4)}` : "sin fecha"})`);
43886
44254
  }
43887
44255
  }
43888
- insumos = redondear4(insumos);
44256
+ insumos = redondear5(insumos);
43889
44257
  if (l.insumos.length === 0) avisos.push("sin_insumos");
43890
44258
  const debajo = [];
43891
44259
  const honorarios = [];
@@ -43909,10 +44277,10 @@ function evaluarRentabilidad(input2) {
43909
44277
  }
43910
44278
  const honorarioMin = honorarios.length ? Math.min(...honorarios) : null;
43911
44279
  const honorarioMax = honorarios.length ? Math.max(...honorarios) : null;
43912
- const margenPeor = honorarioMax !== null && valor > 0 ? redondear4(valor - insumos - honorarioMax) : null;
43913
- const margenMejor = honorarioMin !== null && valor > 0 ? redondear4(valor - insumos - honorarioMin) : null;
44280
+ const margenPeor = honorarioMax !== null && valor > 0 ? redondear5(valor - insumos - honorarioMax) : null;
44281
+ const margenMejor = honorarioMin !== null && valor > 0 ? redondear5(valor - insumos - honorarioMin) : null;
43914
44282
  const margenPct = margenPeor !== null && valor > 0 ? Math.round(margenPeor / valor * 1e3) / 10 : null;
43915
- const impacto = margenPeor !== null ? redondear4(margenPeor * l.volumen_90d / 3) : null;
44283
+ const impacto = margenPeor !== null ? redondear5(margenPeor * l.volumen_90d / 3) : null;
43916
44284
  let clase2;
43917
44285
  if (faltas.length > 0 || margenPct === null) clase2 = "datos_incompletos";
43918
44286
  else if (margenPeor < 0) clase2 = "perdida";
@@ -44050,7 +44418,7 @@ var num7 = (v) => {
44050
44418
  const n = typeof v === "number" ? v : typeof v === "string" && v.trim() ? Number(v) : NaN;
44051
44419
  return Number.isFinite(n) ? n : null;
44052
44420
  };
44053
- var redondear5 = (n) => Math.round(n * 100) / 100;
44421
+ var redondear6 = (n) => Math.round(n * 100) / 100;
44054
44422
  var fechaHora2 = (v) => {
44055
44423
  const s = texto7(v);
44056
44424
  return s && /^\d{4}-\d{2}-\d{2}([ T]\d{2}:\d{2}(:\d{2})?)?/.test(s) ? s.replace(" ", "T").slice(0, 19) : null;
@@ -44081,7 +44449,7 @@ function leerLecturaCostoInternacion(v) {
44081
44449
  if (cantidad2 === null || cantidad2 < 0 || monto === null || monto < 0) return { error: `lectura.facturado[${i}]: cantidad y monto tienen que ser n\xFAmeros \u2265 0` };
44082
44450
  if (f.regla !== null && f.regla !== void 0 && !leerRegla(f.regla)) return { error: `lectura.facturado[${i}].regla: tiene que ser {id, prof, inst} o null` };
44083
44451
  const noLiquidable = f.liquidable === false || f.liquidable === 0 || f.liquidable === "0" || f.liquidable === "false";
44084
- facturado.push({ prestacion_id: texto7(f.prestacion_id) ?? String(i), nombre: texto7(f.nombre) ?? "", cantidad: cantidad2, monto: redondear5(monto), profesional_id: texto7(f.profesional_id), regla: leerRegla(f.regla), ...noLiquidable ? { liquidable: false } : {} });
44452
+ facturado.push({ prestacion_id: texto7(f.prestacion_id) ?? String(i), nombre: texto7(f.nombre) ?? "", cantidad: cantidad2, monto: redondear6(monto), profesional_id: texto7(f.profesional_id), regla: leerRegla(f.regla), ...noLiquidable ? { liquidable: false } : {} });
44085
44453
  }
44086
44454
  const consumos = [];
44087
44455
  for (const [i, x] of r.consumos.entries()) {
@@ -44167,7 +44535,7 @@ function evaluarCostoInternacion(input2) {
44167
44535
  const umbral = input2.umbral ?? UMBRAL_MARGEN_POR_DEFECTO;
44168
44536
  const faltas = [];
44169
44537
  const avisos = [];
44170
- const facturado = redondear5(l.facturado.reduce((a, f) => a + f.monto * f.cantidad, 0));
44538
+ const facturado = redondear6(l.facturado.reduce((a, f) => a + f.monto * f.cantidad, 0));
44171
44539
  if (facturado <= 0) faltas.push("sin_facturar");
44172
44540
  if (!l.egreso) avisos.push("en_curso");
44173
44541
  let honorarios = 0;
@@ -44177,7 +44545,7 @@ function evaluarCostoInternacion(input2) {
44177
44545
  if (!f.regla || f.regla.prof === null && f.regla.inst === null) sinRegla += 1;
44178
44546
  honorarios += honorarioDe({ regla: f.regla, monto: f.monto, cantidad: f.cantidad });
44179
44547
  }
44180
- honorarios = redondear5(honorarios);
44548
+ honorarios = redondear6(honorarios);
44181
44549
  if (sinRegla > 0) avisos.push("prestaciones_sin_regla");
44182
44550
  let insumos = 0;
44183
44551
  const sinCosto = [];
@@ -44190,7 +44558,7 @@ function evaluarCostoInternacion(input2) {
44190
44558
  }
44191
44559
  insumos += cantidad2 * c.costo_unitario;
44192
44560
  }
44193
- insumos = redondear5(insumos);
44561
+ insumos = redondear6(insumos);
44194
44562
  if (sinCosto.length) faltas.push("insumo_sin_costo");
44195
44563
  const { tramos, total, exactos } = diasPorServicio(l, hoy);
44196
44564
  const porServicio = input2.costoDiaPorServicio ?? {};
@@ -44205,18 +44573,18 @@ function evaluarCostoInternacion(input2) {
44205
44573
  else cama += (exactos.get(servicio) ?? dias) * costo_dia;
44206
44574
  return { servicio, dias, costo_dia: costo_dia ?? null };
44207
44575
  });
44208
- cama = redondear5(cama);
44576
+ cama = redondear6(cama);
44209
44577
  if (faltaCama) faltas.push("sin_costo_cama");
44210
44578
  const minutos = l.cirugias.reduce((a, c) => a + (c.minutos ?? 0), 0);
44211
44579
  let quirofano = 0;
44212
44580
  if (l.cirugias.length > 0) {
44213
44581
  if (input2.costoHoraQuirofano === null || input2.costoHoraQuirofano === void 0) faltas.push("sin_costo_quirofano");
44214
- else quirofano = redondear5(minutos / 60 * input2.costoHoraQuirofano);
44582
+ else quirofano = redondear6(minutos / 60 * input2.costoHoraQuirofano);
44215
44583
  }
44216
44584
  if (l.cirugias.some((c) => c.minutos === null)) faltas.push("cirugia_sin_duracion");
44217
- const costo = redondear5(honorarios + insumos + cama + quirofano);
44585
+ const costo = redondear6(honorarios + insumos + cama + quirofano);
44218
44586
  const calculable = faltas.length === 0 && facturado > 0;
44219
- const margen = calculable ? redondear5(facturado - costo) : null;
44587
+ const margen = calculable ? redondear6(facturado - costo) : null;
44220
44588
  const margen_pct = margen !== null ? Math.round(margen / facturado * 1e3) / 10 : null;
44221
44589
  let clase2;
44222
44590
  if (margen === null || margen_pct === null) clase2 = "datos_incompletos";
@@ -44383,6 +44751,13 @@ function describirOpciones(antes, despues) {
44383
44751
  if (antes.hora_corte !== despues.hora_corte && despues.hora_corte !== void 0) partes.push(`hora de corte de la evoluci\xF3n: ${String(despues.hora_corte)}`);
44384
44752
  if (antes.dias_sin_actividad !== despues.dias_sin_actividad && despues.dias_sin_actividad !== void 0) partes.push(`d\xEDas sin actividad para sospechar un egreso sin registrar: ${String(despues.dias_sin_actividad)}`);
44385
44753
  if (antes.tope_variacion !== despues.tope_variacion && despues.tope_variacion !== void 0) partes.push(`tope de variaci\xF3n de las circulares: ${String(despues.tope_variacion)} %`);
44754
+ if (JSON.stringify(antes.formula) !== JSON.stringify(despues.formula) && despues.formula !== void 0) {
44755
+ const f = despues.formula;
44756
+ partes.push(`f\xF3rmula del ajuste: ${f.fijo !== void 0 ? `${f.fijo} % fijo` : (f.componentes ?? []).map((c) => `${c.serie.replace(/_/g, " ")} ${c.peso} %`).join(" + ")}`);
44757
+ }
44758
+ if (antes.tope_ajuste !== despues.tope_ajuste && despues.tope_ajuste !== void 0) partes.push(`tope del ajuste por \xEDndice: ${String(despues.tope_ajuste)} %`);
44759
+ if (JSON.stringify(antes.nota) !== JSON.stringify(despues.nota) && despues.nota !== void 0) partes.push("texto de la nota al financiador");
44760
+ for (const k of ["firma", "institucion"]) if (antes[k] !== despues[k] && despues[k] !== void 0) partes.push(`${k === "firma" ? "firma de la nota" : "nombre de la instituci\xF3n en la nota"}: ${String(despues[k] ?? "sin fijar")}`);
44386
44761
  if (antes.umbral_margen !== despues.umbral_margen && despues.umbral_margen !== void 0) partes.push(`margen m\xEDnimo: ${String(despues.umbral_margen)} %`);
44387
44762
  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)}`);
44388
44763
  if (antes.costo_dia_cama !== despues.costo_dia_cama && despues.costo_dia_cama !== void 0) partes.push(`costo del d\xEDa cama: ${despues.costo_dia_cama === null ? "sin fijar" : `$ ${String(despues.costo_dia_cama)}`}`);
@@ -44480,6 +44855,23 @@ async function cambiarPolitica(ctx, input2) {
44480
44855
  if (v === null) throw new OpsError("validacion", "dias_sin_actividad: tiene que ser un n\xFAmero entero entre 1 y 15");
44481
44856
  opciones.dias_sin_actividad = v;
44482
44857
  }
44858
+ if (input2.workflow === "ajuste_convenio") {
44859
+ if (opciones.formula !== void 0) {
44860
+ const f = leerFormula(opciones.formula);
44861
+ if ("error" in f) throw new OpsError("validacion", `formula: ${f.error}`);
44862
+ opciones.formula = f;
44863
+ }
44864
+ if (opciones.tope_ajuste !== void 0) {
44865
+ const v = enteroEntre(opciones.tope_ajuste, LIMITES_TOPE_AJUSTE[0], LIMITES_TOPE_AJUSTE[1]);
44866
+ if (v === null) throw new OpsError("validacion", `tope_ajuste: tiene que ser un n\xFAmero entero entre ${LIMITES_TOPE_AJUSTE[0]} y ${LIMITES_TOPE_AJUSTE[1]}`);
44867
+ opciones.tope_ajuste = v;
44868
+ }
44869
+ if (opciones.nota !== void 0) {
44870
+ const n = opciones.nota && typeof opciones.nota === "object" ? opciones.nota : {};
44871
+ opciones.nota = { asunto: str2(n.asunto)?.slice(0, 200) ?? null, texto: str2(n.texto)?.slice(0, 5e3) ?? null };
44872
+ }
44873
+ for (const k of ["firma", "institucion"]) if (opciones[k] !== void 0) opciones[k] = str2(opciones[k])?.slice(0, 200) ?? null;
44874
+ }
44483
44875
  if (opciones.tope_variacion !== void 0) {
44484
44876
  const tope = leerTope(opciones.tope_variacion);
44485
44877
  if (tope === null) throw new OpsError("validacion", `tope_variacion: tiene que ser un n\xFAmero entre ${LIMITES_TOPE[0]} y ${LIMITES_TOPE[1]}`);
@@ -44667,6 +45059,15 @@ async function abrirCaso(ctx, input2) {
44667
45059
  if (faltan.length) throw new OpsError("validacion", `circular_aranceles necesita ${faltan.join(", ")}`);
44668
45060
  if (!/^\d{4}-\d{2}-\d{2}$/.test(String(d.vigencia_desde))) throw new OpsError("validacion", "vigencia_desde: AAAA-MM-DD");
44669
45061
  }
45062
+ if (wf.clave === "ajuste_convenio") {
45063
+ const d = input2.datos ?? {};
45064
+ const faltan = ["financiador", "convenio", "vigencia_desde", "ultimo_ajuste"].filter((k) => !str2(d[k]));
45065
+ if (faltan.length) throw new OpsError("validacion", `ajuste_convenio necesita ${faltan.join(", ")}`);
45066
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(String(d.vigencia_desde))) throw new OpsError("validacion", "vigencia_desde: AAAA-MM-DD");
45067
+ if (!/^\d{4}-(0[1-9]|1[0-2])$/.test(String(d.ultimo_ajuste))) throw new OpsError("validacion", "ultimo_ajuste: el mes del \xFAltimo ajuste, AAAA-MM");
45068
+ const esperada = `ajuste:${(normalizarCodigo(d.convenio) || String(d.convenio).trim()).toLowerCase()}:${String(d.vigencia_desde).slice(0, 7)}`;
45069
+ if (referencia !== esperada) throw new OpsError("referencia_requerida", `en ajuste_convenio la referencia es obligatoria: ${esperada}`);
45070
+ }
44670
45071
  const extra = {};
44671
45072
  if (wf.clave === "honorario_en_cero" || wf.clave === "debito_profesional" || wf.clave === "liquidacion_profesional") {
44672
45073
  const p = leerProfesional(input2.datos?.profesional) ?? leerProfesional({ id: input2.datos?.profesional_id, nombre: input2.datos?.profesional });
@@ -44781,6 +45182,12 @@ async function contextoDe(ctx, companyId, caso, via, datos) {
44781
45182
  contexto.topeVariacion = leerTope((await obtenerOpciones(ctx, companyId, caso.workflow)).tope_variacion);
44782
45183
  return contexto;
44783
45184
  }
45185
+ if (caso.workflow === "ajuste_convenio") {
45186
+ const op = await obtenerOpciones(ctx, companyId, caso.workflow);
45187
+ contexto.formulaAjuste = leerFormula(op.formula);
45188
+ contexto.topeAjuste = leerTopeAjuste(op.tope_ajuste);
45189
+ return contexto;
45190
+ }
44784
45191
  if (caso.workflow === "rentabilidad_practica") {
44785
45192
  if (via === "evaluar" || via === "comprobar") {
44786
45193
  const op = await obtenerOpciones(ctx, companyId, caso.workflow);
@@ -44890,7 +45297,7 @@ async function transicionar(ctx, input2) {
44890
45297
  );
44891
45298
  const lectura = rows[0];
44892
45299
  let vieja = false;
44893
- if (lectura && (resultado.via === "comprobar" || caso.workflow === "circular_aranceles" && resultado.via === "verificar")) {
45300
+ if (lectura && (resultado.via === "comprobar" || (caso.workflow === "circular_aranceles" || caso.workflow === "ajuste_convenio") && resultado.via === "verificar")) {
44894
45301
  const ultima = await ctx.db.query(
44895
45302
  `SELECT max(created_at) AS ultima FROM ${t3(ctx, "transicion")} WHERE company_id = $1 AND caso_id = $2`,
44896
45303
  [input2.companyId, caso.id]
@@ -44945,6 +45352,80 @@ async function transicionar(ctx, input2) {
44945
45352
  datos.verificada_el = contexto.hoy ?? null;
44946
45353
  }
44947
45354
  }
45355
+ if (caso.workflow === "ajuste_convenio") {
45356
+ for (const k of ["items", "resumen", "archivo_csv", "diferencias", "nota", "detalle", "periodo_hasta", "porcentaje_calculado", "porcentaje_acordado", "indices_usados"]) delete datos[k];
45357
+ const tope = contexto.topeAjuste ?? leerTopeAjuste(void 0);
45358
+ const porcentajeAntes = typeof caso.datos.porcentaje === "number" ? caso.datos.porcentaje : 0;
45359
+ const calculado = typeof caso.datos.porcentaje_calculado === "number" ? caso.datos.porcentaje_calculado : porcentajeAntes;
45360
+ const recalcular = (porcentaje3, excluir) => {
45361
+ const a = armarAjuste(caso.datos.actual, porcentaje3);
45362
+ if (!a.ok) return;
45363
+ datos.porcentaje = porcentaje3;
45364
+ datos.items = a.items;
45365
+ datos.resumen = resumenAjuste(a.items, excluir, porcentaje3, calculado, tope);
45366
+ };
45367
+ const pedido = typeof datos.porcentaje === "number" ? datos.porcentaje : typeof datos.porcentaje === "string" && datos.porcentaje.trim() ? Number(datos.porcentaje) : null;
45368
+ delete datos.porcentaje;
45369
+ if (resultado.via === "calcular") {
45370
+ delete datos.excluir;
45371
+ const c = calcularAjuste(caso, datos, contexto);
45372
+ const act = leerActual(datos.actual);
45373
+ if (c.ok && "practicas" in act) {
45374
+ Object.assign(datos, {
45375
+ porcentaje: c.porcentaje,
45376
+ porcentaje_calculado: c.porcentaje,
45377
+ detalle: c.detalle,
45378
+ periodo_hasta: c.hasta,
45379
+ items: c.items,
45380
+ excluir: [],
45381
+ resumen: resumenAjuste(c.items, [], c.porcentaje, c.porcentaje, tope),
45382
+ // Lo que tenía el convenio antes del ajuste queda guardado en el caso: es el respaldo para volver atrás.
45383
+ actual: act.practicas,
45384
+ indices_usados: Array.isArray(datos.indices) ? datos.indices : []
45385
+ });
45386
+ }
45387
+ delete datos.indices;
45388
+ } else {
45389
+ delete datos.actual;
45390
+ delete datos.indices;
45391
+ }
45392
+ if (resultado.via === "ajustar") {
45393
+ const excluir = Array.isArray(datos.excluir) ? datos.excluir.map(normalizarCodigo) : caso.datos.excluir ?? [];
45394
+ datos.excluir = excluir;
45395
+ recalcular(pedido ?? porcentajeAntes, excluir);
45396
+ } else if (resultado.via !== "calcular") delete datos.excluir;
45397
+ if (resultado.via === "responder" && datos.respuesta === "aprobado_otro" && pedido !== null) {
45398
+ datos.porcentaje_acordado = pedido;
45399
+ recalcular(pedido, caso.datos.excluir);
45400
+ }
45401
+ if (resultado.via === "responder" && datos.respuesta === "aprobado") datos.porcentaje_acordado = porcentajeAntes;
45402
+ if (resultado.a === "por_enviar") {
45403
+ const op = await obtenerOpciones(ctx, input2.companyId, caso.workflow);
45404
+ const r = caso.datos.resumen;
45405
+ datos.nota = armarNota(op.nota, {
45406
+ institucion: str2(op.institucion) ?? str2(caso.datos.institucion) ?? "",
45407
+ financiador: str2(caso.datos.financiador) ?? "",
45408
+ convenio: str2(caso.datos.convenio) ?? "",
45409
+ porcentaje: porcentajeAntes,
45410
+ indices: textoIndices(caso.datos.detalle ?? { fijo: porcentajeAntes }, str2(caso.datos.ultimo_ajuste), str2(caso.datos.periodo_hasta)),
45411
+ vigencia: str2(caso.datos.vigencia_desde) ?? "",
45412
+ firma: str2(op.firma) ?? "",
45413
+ practicas: r?.practicas ?? 0
45414
+ });
45415
+ }
45416
+ if (resultado.a === "acordado") {
45417
+ const items = datos.items ?? leerItems(caso.datos.items);
45418
+ const aCargar = itemsACargar(items, caso.datos.excluir);
45419
+ const actual = leerActual(caso.datos.actual);
45420
+ const archivo = archivoActualizacion(aCargar, "practicas" in actual ? actual.practicas : []);
45421
+ datos.archivo_csv = { contenido: archivo.contenido, hash: archivo.hash, filas: archivo.filas, aranceles: archivo.aranceles, nombre: `ajuste-${String(caso.datos.convenio ?? "convenio").toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${String(caso.datos.vigencia_desde ?? "")}.csv` };
45422
+ }
45423
+ if (resultado.via === "verificar") {
45424
+ const r = compararRelectura(itemsACargar(leerItems(caso.datos.items), caso.datos.excluir), datos.leidos);
45425
+ datos.diferencias = "error" in r ? [] : r.diferencias;
45426
+ datos.verificada_el = contexto.hoy ?? null;
45427
+ }
45428
+ }
44948
45429
  if (caso.workflow === "honorario_en_cero") {
44949
45430
  for (const k of ["items", "resumen", "comprobado_el"]) delete datos[k];
44950
45431
  if (resultado.via === "observar" || resultado.via === "comprobar") {
@@ -45530,7 +46011,7 @@ async function registrarCargaManual(ctx, input2) {
45530
46011
  ...caso.workflow === "cuenta_completa" ? { faltantes_que_completo: caso.datos.faltantes ?? [] } : {},
45531
46012
  ...caso.workflow === "facturacion_pendiente" ? { bono: caso.datos.bono_numero ?? caso.datos.bono_id, vence_el: caso.datos.vence_el } : {},
45532
46013
  ...caso.workflow === "cobranza_financiador" ? { canal: canalReclamo(input2.canal), destinatario: str2(input2.destinatario), reclamo: caso.datos.reclamo?.numero ?? 1, total: caso.datos.resumen?.pendiente ?? null } : {},
45533
- ...caso.workflow === "circular_aranceles" ? { archivo_hash: archivoHash, archivo: caso.datos.archivo_csv?.nombre ?? null, practicas: caso.datos.archivo_csv?.filas ?? null, convenio: caso.datos.convenio ?? null } : {},
46014
+ ...caso.workflow === "circular_aranceles" || caso.workflow === "ajuste_convenio" ? { archivo_hash: archivoHash, archivo: caso.datos.archivo_csv?.nombre ?? null, practicas: caso.datos.archivo_csv?.filas ?? null, convenio: caso.datos.convenio ?? null } : {},
45534
46015
  ...caso.workflow === "credencial_profesional" ? { profesional: caso.datos.profesional ?? caso.datos.profesional_id, observacion_que_corrigio: caso.datos.observacion ?? null } : {},
45535
46016
  ...comentario ? { nota: comentario } : {}
45536
46017
  },
@@ -45830,6 +46311,29 @@ async function registrarEnvioLiquidacion(ctx, input2) {
45830
46311
  });
45831
46312
  return transicionar(ctx, { companyId: input2.companyId, casoId: caso.id, via: "enviar", datos: { aviso_id: accion.id, canal, ...destinatario ? { destinatario } : {}, ...persona ? { registrado_por: persona } : {} }, actor: input2.actor });
45832
46313
  }
46314
+ async function registrarEnvioNota(ctx, input2) {
46315
+ const caso = await leerCaso(ctx, input2.companyId, input2.casoId);
46316
+ if (!caso) throw new OpsError("caso_inexistente", `no existe el caso ${input2.casoId} en esta instituci\xF3n`);
46317
+ if (caso.workflow !== "ajuste_convenio") throw new OpsError("workflow_invalido", "solo los ajustes de convenio llevan nota al financiador");
46318
+ if (caso.estado !== "por_enviar") throw new OpsError("estado_invalido", caso.estado === "enviado" ? "la nota ya figura como enviada" : `el ajuste est\xE1 ${caso.estado}: la nota se env\xEDa cuando la direcci\xF3n lo aprueba`);
46319
+ if (input2.actor.tipo !== "humano") throw new OpsError("actor_no_permitido", "el env\xEDo lo registra una persona");
46320
+ const canal = typeof input2.canal === "string" && CANALES_NOTA.includes(input2.canal) ? input2.canal : "otro";
46321
+ const destinatario = str2(input2.destinatario);
46322
+ if (!destinatario) throw new OpsError("validacion", "destinatario_presente: anotar a qui\xE9n se mand\xF3 la nota (correo, \xE1rea o persona del financiador)", { validaciones: [{ regla: "destinatario_presente", ok: false, detalle: "falta el destinatario" }], estado: caso.estado });
46323
+ const persona = str2(input2.persona);
46324
+ const nota = caso.datos.nota;
46325
+ const accion = await registrarAccion(ctx, {
46326
+ companyId: input2.companyId,
46327
+ casoId: caso.id,
46328
+ tipo: "aviso",
46329
+ conector: canal === "correo" ? "mail" : "manual",
46330
+ objetivo: `Nota de ajuste a ${str2(caso.datos.financiador) ?? "el financiador"}`,
46331
+ resultado: "ok",
46332
+ evidencia: { canal, destinatario, asunto: nota?.asunto ?? null, porcentaje: caso.datos.porcentaje ?? null, ...persona ? { registrado_por: persona } : {} },
46333
+ actor: input2.actor
46334
+ });
46335
+ return transicionar(ctx, { companyId: input2.companyId, casoId: caso.id, via: "enviar", datos: { aviso_id: accion.id, canal, destinatario, ...persona ? { registrado_por: persona } : {} }, actor: input2.actor });
46336
+ }
45833
46337
  function canalReclamo(v) {
45834
46338
  return typeof v === "string" && CANALES_RECLAMO.includes(v) ? v : "otro";
45835
46339
  }
@@ -46817,7 +47321,7 @@ async function manejarApi(ctx, input2) {
46817
47321
  return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "no_corresponde", motivo: motivoConPersona(str3(body.motivo)), datos: conPersona(obj(body.datos)), idempotencyKey: str3(body.idempotencyKey), actor }));
46818
47322
  }
46819
47323
  case "version":
46820
- return respuesta(200, { version: manifest_default.version, capacidades: ["carga-manual", "recupero-turnos", "ofrecer-primero", "cuenta-completa", "metricas-certificadas", "facturacion-pendiente", "credenciales", "demanda-pendiente", "reglas", "avisos", "circular-aranceles", "cobranzas", "internaciones", "honorarios"], workflows: listarWorkflows().map((w) => w.clave) });
47324
+ return respuesta(200, { version: manifest_default.version, capacidades: ["carga-manual", "recupero-turnos", "ofrecer-primero", "cuenta-completa", "metricas-certificadas", "facturacion-pendiente", "credenciales", "demanda-pendiente", "reglas", "avisos", "circular-aranceles", "ajuste-convenio", "cobranzas", "internaciones", "honorarios"], workflows: listarWorkflows().map((w) => w.clave) });
46821
47325
  case "politicas":
46822
47326
  return respuesta(200, { politicas: await listarPoliticas(ctx, companyId) });
46823
47327
  case "serie":
@@ -46857,13 +47361,22 @@ async function manejarApi(ctx, input2) {
46857
47361
  if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "el env\xEDo lo registra una persona" });
46858
47362
  return respuesta(200, await registrarEnvioLiquidacion(ctx, { companyId, casoId: input2.params.casoId ?? "", canal: body.canal, destinatario: str3(body.destinatario), persona: decididoPor, actor }));
46859
47363
  }
47364
+ case "ajustar": {
47365
+ if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "el porcentaje y las pr\xE1cticas del ajuste los cambia la direcci\xF3n" });
47366
+ const datosA = { ...body.porcentaje !== void 0 ? { porcentaje: body.porcentaje } : {}, ...Array.isArray(body.excluir) ? { excluir: body.excluir } : {} };
47367
+ return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "ajustar", motivo: motivoConPersona(str3(body.motivo)), datos: conPersona({ ...datosA, ...str3(body.motivo) ? { motivo: str3(body.motivo) } : {} }), idempotencyKey: str3(body.idempotencyKey), actor }));
47368
+ }
47369
+ case "envio-nota": {
47370
+ if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "el env\xEDo lo registra una persona" });
47371
+ return respuesta(200, await registrarEnvioNota(ctx, { companyId, casoId: input2.params.casoId ?? "", canal: body.canal, destinatario: str3(body.destinatario), persona: decididoPor, actor }));
47372
+ }
46860
47373
  case "respuesta-financiador": {
46861
47374
  if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "la respuesta del financiador la registra una persona" });
46862
47375
  if (str3(body.respuesta) === "cerrar") {
46863
47376
  if (!str3(body.detalle)) return respuesta(422, { error: "validacion", motivo: "motivo_presente: cerrar la cobranza requiere motivo" });
46864
47377
  return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "cerrar", motivo: motivoConPersona(str3(body.detalle)), datos: conPersona({}), actor }));
46865
47378
  }
46866
- const datosR = { respuesta: str3(body.respuesta), ...str3(body.fechaCompromiso) ? { fecha_compromiso: str3(body.fechaCompromiso) } : {}, ...str3(body.detalle) ? { detalle: str3(body.detalle) } : {} };
47379
+ const datosR = { respuesta: str3(body.respuesta), ...str3(body.fechaCompromiso) ? { fecha_compromiso: str3(body.fechaCompromiso) } : {}, ...str3(body.detalle) ? { detalle: str3(body.detalle) } : {}, ...body.porcentaje !== void 0 ? { porcentaje: body.porcentaje } : {} };
46867
47380
  return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "responder", motivo: motivoConPersona(str3(body.detalle)), datos: conPersona(datosR), actor }));
46868
47381
  }
46869
47382
  case "insumos-practica":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperclip-plugin-autopilot-ops",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
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": [