paperclip-plugin-autopilot-ops 0.1.34 → 0.1.36

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.36";
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.36";
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.36";
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,170 @@ 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
+ function leerVolumenes(v, dias) {
41061
+ if (v === void 0 || v === null) return null;
41062
+ if (!Array.isArray(v)) return { error: "volumenes tiene que ser una lista [{codigo, cantidad}]" };
41063
+ const d = typeof dias === "number" && Number.isInteger(dias) && dias >= 7 && dias <= 999 ? dias : null;
41064
+ if (d === null) return { error: "volumenes necesita dias (el per\xEDodo le\xEDdo, de 7 a 999)" };
41065
+ const porCodigo = {};
41066
+ for (const [i, e] of v.entries()) {
41067
+ const r = e ?? {};
41068
+ const codigo = normalizarCodigo(r.codigo);
41069
+ const cantidad2 = typeof r.cantidad === "number" ? r.cantidad : Number(r.cantidad);
41070
+ if (!codigo || !Number.isFinite(cantidad2) || cantidad2 < 0) return { error: `volumenes[${i}]: c\xF3digo y cantidad mayor o igual a 0` };
41071
+ porCodigo[codigo] = (porCodigo[codigo] ?? 0) + cantidad2;
41072
+ }
41073
+ return { porCodigo, dias: d };
41074
+ }
41075
+ var redondear2 = (n, dec = 2) => Math.round(n * 10 ** dec) / 10 ** dec;
41076
+ var periodoValido = (p) => typeof p === "string" && /^\d{4}-(0[1-9]|1[0-2])$/.test(p);
41077
+ var porcentajeValido = (n) => typeof n === "number" && Number.isFinite(n) && n > LIMITES_PORCENTAJE[0] && n <= LIMITES_PORCENTAJE[1];
41078
+ function leerFormula(v) {
41079
+ if (v === void 0 || v === null) return FORMULA_POR_DEFECTO;
41080
+ if (typeof v !== "object" || Array.isArray(v)) return { error: "la f\xF3rmula tiene que ser {componentes: [{serie, peso}]} o {fijo: porcentaje}" };
41081
+ const o = v;
41082
+ if ("fijo" in o) {
41083
+ const f = leerMonto(o.fijo);
41084
+ return f !== null && porcentajeValido(f) ? { fijo: redondear2(f) } : { error: `el porcentaje fijo tiene que estar entre ${LIMITES_PORCENTAJE[0]} y ${LIMITES_PORCENTAJE[1]}` };
41085
+ }
41086
+ if (!Array.isArray(o.componentes) || o.componentes.length === 0) return { error: "la f\xF3rmula necesita al menos un \xEDndice" };
41087
+ const componentes = [];
41088
+ for (const c of o.componentes) {
41089
+ const r = c ?? {};
41090
+ const serie2 = typeof r.serie === "string" ? r.serie : "";
41091
+ const peso = leerMonto(r.peso);
41092
+ if (!SERIES[serie2]) return { error: `\xEDndice desconocido: ${serie2 || "(vac\xEDo)"}; los que hay: ${Object.keys(SERIES).join(", ")}` };
41093
+ if (peso === null || peso <= 0) return { error: `el peso de ${serie2} tiene que ser mayor a 0` };
41094
+ if (componentes.some((x) => x.serie === serie2)) return { error: `${serie2} est\xE1 repetido en la f\xF3rmula` };
41095
+ componentes.push({ serie: serie2, peso });
41096
+ }
41097
+ const suma = componentes.reduce((a, c) => a + c.peso, 0);
41098
+ if (Math.abs(suma - 100) > 0.01) return { error: `los pesos tienen que sumar 100 (suman ${suma})` };
41099
+ return { componentes };
41100
+ }
41101
+ function leerIndices(v) {
41102
+ if (!Array.isArray(v)) return { error: "indices tiene que ser una lista [{serie, desde: {periodo, valor}, hasta: {periodo, valor}, fuente}]" };
41103
+ const indices = [];
41104
+ for (const [i, e] of v.entries()) {
41105
+ const r = e ?? {};
41106
+ const serie2 = typeof r.serie === "string" ? r.serie : "";
41107
+ if (!SERIES[serie2]) return { error: `indices[${i}]: serie desconocida ${serie2 || "(vac\xEDa)"}` };
41108
+ if (indices.some((x) => x.serie === serie2)) return { error: `indices[${i}]: ${serie2} est\xE1 repetida` };
41109
+ const d = r.desde ?? {};
41110
+ const h = r.hasta ?? {};
41111
+ const vd = leerMonto(d.valor);
41112
+ const vh = leerMonto(h.valor);
41113
+ 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` };
41114
+ 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` };
41115
+ if (typeof r.fuente !== "string" || !r.fuente.trim()) return { error: `indices[${i}] (${serie2}): falta la fuente` };
41116
+ indices.push({ serie: serie2, desde: { periodo: d.periodo, valor: vd }, hasta: { periodo: h.periodo, valor: vh }, fuente: r.fuente.trim().slice(0, 200) });
41117
+ }
41118
+ return { indices };
41119
+ }
41120
+ function porcentajeSegunFormula(formula, indices, desde, mesActual) {
41121
+ if ("fijo" in formula) return { porcentaje: formula.fijo, detalle: { fijo: formula.fijo }, hasta: null };
41122
+ const detalle = [];
41123
+ let hasta = null;
41124
+ for (const c of formula.componentes) {
41125
+ const ind = indices.find((x) => x.serie === c.serie);
41126
+ if (!ind) return { error: `falta el \xEDndice ${c.serie} (${SERIES[c.serie].nombre}) que usa la f\xF3rmula de la instituci\xF3n` };
41127
+ if (ind.desde.periodo !== desde) return { error: `${c.serie}: el per\xEDodo empieza en ${ind.desde.periodo} y el \xFAltimo ajuste fue en ${desde}` };
41128
+ if (ind.hasta.periodo >= mesActual) return { error: `${c.serie}: ${ind.hasta.periodo} todav\xEDa no est\xE1 publicado` };
41129
+ if (hasta && ind.hasta.periodo !== hasta) return { error: "los \xEDndices no terminan en el mismo mes" };
41130
+ hasta = ind.hasta.periodo;
41131
+ detalle.push({ serie: c.serie, nombre: SERIES[c.serie].nombre, variacion: redondear2((ind.hasta.valor / ind.desde.valor - 1) * 100), peso: c.peso });
41132
+ }
41133
+ const porcentaje3 = redondear2(detalle.reduce((a, d) => a + d.variacion * d.peso / 100, 0));
41134
+ if (!porcentajeValido(porcentaje3)) return { error: `el porcentaje calculado (${porcentaje3} %) est\xE1 fuera de rango: revisar los valores de los \xEDndices` };
41135
+ return { porcentaje: porcentaje3, detalle, hasta };
41136
+ }
41137
+ function armarAjuste(actual, porcentaje3) {
41138
+ const lectura = leerActual(actual);
41139
+ if ("error" in lectura) return { ok: false, error: lectura.error };
41140
+ if (!porcentajeValido(porcentaje3)) return { ok: false, error: `el porcentaje tiene que estar entre ${LIMITES_PORCENTAJE[0]} y ${LIMITES_PORCENTAJE[1]}` };
41141
+ const items = [];
41142
+ for (const p of lectura.practicas) {
41143
+ const valorActual = redondear2(p.componentes.reduce((a, c) => a + c.monto, 0));
41144
+ if (valorActual <= 0) continue;
41145
+ const valorNuevo = redondear2(valorActual * (1 + porcentaje3 / 100));
41146
+ const componentes = repartir(p.componentes, valorNuevo);
41147
+ items.push({
41148
+ codigo: p.codigo,
41149
+ codigo_his: p.codigo_his,
41150
+ nombre: p.nombre,
41151
+ valor_a: null,
41152
+ valor_b: null,
41153
+ valor_actual: valorActual,
41154
+ valor_nuevo: valorNuevo,
41155
+ variacion: redondear2((valorNuevo / valorActual - 1) * 100, 1),
41156
+ estado: componentes ? "propuesta" : "revisar",
41157
+ causa: componentes ? null : "componentes_en_cero",
41158
+ coinciden: true,
41159
+ motivo: componentes ? null : "no se puede repartir entre sus componentes: cargar a mano",
41160
+ componentes_nuevos: componentes
41161
+ });
41162
+ }
41163
+ if (items.length === 0) return { ok: false, error: "el convenio no tiene pr\xE1cticas con valor" };
41164
+ return { ok: true, items, actual: lectura.practicas };
41165
+ }
41166
+ function resumenAjuste(items, excluir, porcentaje3, porcentajeCalculado, tope, volumenes = null) {
41167
+ const fuera = new Set(Array.isArray(excluir) ? excluir.map(normalizarCodigo) : []);
41168
+ const dentro = items.filter((i) => !fuera.has(i.codigo));
41169
+ const cargan = dentro.filter((i) => i.estado === "propuesta");
41170
+ return {
41171
+ porcentaje: porcentaje3,
41172
+ porcentaje_calculado: porcentajeCalculado,
41173
+ practicas: cargan.length,
41174
+ excluidas: items.length - dentro.length,
41175
+ revisar: dentro.length - cargan.length,
41176
+ total_actual: redondear2(cargan.reduce((a, i) => a + (i.valor_actual ?? 0), 0)),
41177
+ total_nuevo: redondear2(cargan.reduce((a, i) => a + (i.valor_nuevo ?? 0), 0)),
41178
+ tope,
41179
+ supera_tope: Math.abs(porcentaje3) > tope,
41180
+ impacto_mensual: volumenes ? redondear2(cargan.reduce((a, i) => a + ((i.valor_nuevo ?? 0) - (i.valor_actual ?? 0)) * (volumenes.porCodigo[i.codigo] ?? 0), 0) * 30 / volumenes.dias, 0) : null
41181
+ };
41182
+ }
41183
+ function textoIndices(detalle, desde, hasta) {
41184
+ if (!Array.isArray(detalle)) return "lo acordado en el convenio";
41185
+ const mes = (p) => (/* @__PURE__ */ new Date(`${p}-15T12:00:00Z`)).toLocaleDateString("es-AR", { month: "long", year: "numeric", timeZone: "UTC" });
41186
+ const pct = (n) => `${n.toLocaleString("es-AR", { maximumFractionDigits: 2 })} %`;
41187
+ const partes = detalle.map((d) => `${d.nombre} ${pct(d.variacion)}`);
41188
+ const pesos4 = detalle.length > 1 ? ` (ponderados ${detalle.map((d) => `${d.peso} %`).join(" y ")})` : "";
41189
+ const periodo = desde && hasta ? `, de ${mes(desde)} a ${mes(hasta)}` : "";
41190
+ return `${partes.join(" y ")}${pesos4}${periodo}`;
41191
+ }
41192
+ var NOTA_POR_DEFECTO = {
41193
+ asunto: "Actualizaci\xF3n de aranceles \xB7 {convenio} \xB7 {institucion}",
41194
+ 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}"
41195
+ };
41196
+ function armarNota(plantilla, d) {
41197
+ const p = plantilla && typeof plantilla === "object" ? plantilla : {};
41198
+ const [a, m, dia5] = d.vigencia.split("-");
41199
+ const valores = {
41200
+ institucion: d.institucion,
41201
+ financiador: d.financiador,
41202
+ convenio: d.convenio,
41203
+ porcentaje: `${d.porcentaje.toLocaleString("es-AR", { maximumFractionDigits: 2 })} %`,
41204
+ indices: d.indices,
41205
+ vigencia: dia5 ? `${dia5}/${m}/${a}` : d.vigencia,
41206
+ firma: d.firma,
41207
+ practicas: String(d.practicas)
41208
+ };
41209
+ const llenar = (t6) => t6.replace(/\{(\w+)\}/g, (x, k) => valores[k] ?? x);
41210
+ const asunto = typeof p.asunto === "string" && p.asunto.trim() ? p.asunto : NOTA_POR_DEFECTO.asunto;
41211
+ const texto10 = typeof p.texto === "string" && p.texto.trim() ? p.texto : NOTA_POR_DEFECTO.texto;
41212
+ return { asunto: llenar(asunto).slice(0, 200), texto: llenar(texto10).slice(0, 5e3) };
41213
+ }
41214
+
41035
41215
  // src/internacion.ts
41036
41216
  var HORA_CORTE_POR_DEFECTO = "13:00";
41037
41217
  var DIAS_SIN_ACTIVIDAD_POR_DEFECTO = 3;
@@ -41157,7 +41337,7 @@ function estadoInternacion(e) {
41157
41337
  }
41158
41338
 
41159
41339
  // src/honorarios.ts
41160
- var redondear2 = (n) => Math.round(n * 100) / 100;
41340
+ var redondear3 = (n) => Math.round(n * 100) / 100;
41161
41341
  var pesos2 = (n) => `$ ${n.toLocaleString("es-AR", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
41162
41342
  var fecha3 = (iso5) => `${iso5.slice(8, 10)}/${iso5.slice(5, 7)}/${iso5.slice(0, 4)}`;
41163
41343
  function texto2(v) {
@@ -41192,13 +41372,13 @@ function leerRegla(v) {
41192
41372
  if (!rid) return null;
41193
41373
  const prof = num3(r.prof);
41194
41374
  const inst = num3(r.inst);
41195
- return { id: rid, prof: prof === null ? null : redondear2(prof), inst: inst === null ? null : redondear2(inst) };
41375
+ return { id: rid, prof: prof === null ? null : redondear3(prof), inst: inst === null ? null : redondear3(inst) };
41196
41376
  }
41197
41377
  function honorarioDe(input2) {
41198
41378
  const { regla, monto, cantidad: cantidad2 } = input2;
41199
41379
  if (!regla) return 0;
41200
- if (regla.prof !== null) return redondear2(regla.prof * cantidad2);
41201
- return redondear2(monto * cantidad2 * ((regla.inst ?? 0) / 100));
41380
+ if (regla.prof !== null) return redondear3(regla.prof * cantidad2);
41381
+ return redondear3(monto * cantidad2 * ((regla.inst ?? 0) / 100));
41202
41382
  }
41203
41383
  function motivoCero(regla, honorario, delHis = false) {
41204
41384
  if (honorario > 0) return null;
@@ -41208,7 +41388,7 @@ function motivoCero(regla, honorario, delHis = false) {
41208
41388
  function honorarioLeido(r, regla, monto, cantidad2) {
41209
41389
  if (r.honorario === void 0 || r.honorario === null || r.honorario === "") return { honorario: honorarioDe({ regla, monto, cantidad: cantidad2 }), delHis: false };
41210
41390
  const h = num3(r.honorario);
41211
- return h === null || h < 0 ? null : { honorario: redondear2(h), delHis: true };
41391
+ return h === null || h < 0 ? null : { honorario: redondear3(h), delHis: true };
41212
41392
  }
41213
41393
  function leerPrestaciones(v) {
41214
41394
  if (!Array.isArray(v)) return { error: "prestaciones tiene que ser la lista que devuelve leer-honorarios.mjs" };
@@ -41225,7 +41405,7 @@ function leerPrestaciones(v) {
41225
41405
  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
41406
  const h = honorarioLeido(r, leerRegla(r.regla), monto, cantidad2 > 0 ? cantidad2 : 1);
41227
41407
  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 });
41408
+ 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
41409
  }
41230
41410
  return { prestaciones: out };
41231
41411
  }
@@ -41244,7 +41424,7 @@ function prestacionesEnCero(ps) {
41244
41424
  prestaciones: items.length,
41245
41425
  sin_regla: items.filter((p) => p.motivo === "sin_regla").length,
41246
41426
  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)),
41427
+ monto: redondear3(items.reduce((a, p) => a + p.monto * p.cantidad, 0)),
41248
41428
  obras_sociales: [...new Set(items.map((p) => p.obra_social).filter((x) => Boolean(x)))].sort(),
41249
41429
  desde: fechas[0] ?? null,
41250
41430
  hasta: fechas[fechas.length - 1] ?? null
@@ -41270,7 +41450,7 @@ function leerDebitos(v) {
41270
41450
  const hl = honorarioLeido(r, regla, montoItem, cantidad2);
41271
41451
  if (!hl) return { error: `debitos[${i}].honorario: tiene que ser un n\xFAmero mayor o igual a 0` };
41272
41452
  const hon = liquidada ? hl.honorario : null;
41273
- const imp = liquidada ? redondear2(montoItem * cantidad2) : null;
41453
+ const imp = liquidada ? redondear3(montoItem * cantidad2) : null;
41274
41454
  out.push({
41275
41455
  item_id: item,
41276
41456
  bono_id: bono,
@@ -41278,13 +41458,13 @@ function leerDebitos(v) {
41278
41458
  fecha_prestacion: dia(r.fecha_prestacion),
41279
41459
  obra_social: texto2(r.obra_social)?.slice(0, 120) ?? null,
41280
41460
  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)),
41461
+ monto_debitado: redondear3(Math.abs(monto)),
41282
41462
  liquidada,
41283
- monto: redondear2(montoItem),
41463
+ monto: redondear3(montoItem),
41284
41464
  cantidad: cantidad2 > 0 ? cantidad2 : 1,
41285
41465
  regla,
41286
41466
  liquidado: hon !== null && imp !== null ? { honorario: hon, importe: imp } : null,
41287
- ya_trasladado: redondear2(Math.abs(num3(r.ya_trasladado) ?? 0))
41467
+ ya_trasladado: redondear3(Math.abs(num3(r.ya_trasladado) ?? 0))
41288
41468
  });
41289
41469
  }
41290
41470
  return { debitos: out };
@@ -41293,12 +41473,12 @@ function evaluarDebito(d) {
41293
41473
  if (!d.liquidado) return { ...d, estado: "sin_liquidar", a_descontar: 0 };
41294
41474
  if (d.liquidado.importe <= 0 || d.liquidado.honorario <= 0 || d.monto_debitado <= 0) return { ...d, estado: "sin_monto", a_descontar: 0 };
41295
41475
  const proporcional = Math.min(d.liquidado.honorario, d.liquidado.honorario * d.monto_debitado / d.liquidado.importe);
41296
- const falta = redondear2(proporcional - d.ya_trasladado);
41476
+ const falta = redondear3(proporcional - d.ya_trasladado);
41297
41477
  return falta >= 0.01 ? { ...d, estado: "a_trasladar", a_descontar: falta } : { ...d, estado: "ya_trasladado", a_descontar: 0 };
41298
41478
  }
41299
41479
  function resumenDebitos(ds) {
41300
41480
  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 };
41481
+ 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
41482
  }
41303
41483
  function leerLineasLiquidacion(v) {
41304
41484
  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 +41503,7 @@ function leerLineasLiquidacion(v) {
41323
41503
  honorario += h;
41324
41504
  if (h <= 0) enCero += 1;
41325
41505
  }
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 });
41506
+ 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
41507
  }
41328
41508
  return { lineas: out };
41329
41509
  }
@@ -41333,14 +41513,14 @@ function resumirLiquidacion(lineas) {
41333
41513
  const k = l.obra_social ?? "Particular / sin obra social";
41334
41514
  const e = por.get(k) ?? { bonos: 0, honorario: 0 };
41335
41515
  e.bonos += 1;
41336
- e.honorario = redondear2(e.honorario + l.honorario);
41516
+ e.honorario = redondear3(e.honorario + l.honorario);
41337
41517
  por.set(k, e);
41338
41518
  }
41339
41519
  const fechas = lineas.map((l) => l.fecha).filter((x) => Boolean(x)).sort();
41340
41520
  return {
41341
41521
  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)),
41522
+ honorario: redondear3(lineas.reduce((a, l) => a + l.honorario, 0)),
41523
+ importe: redondear3(lineas.reduce((a, l) => a + l.importe, 0)),
41344
41524
  en_cero: lineas.reduce((a, l) => a + l.en_cero, 0),
41345
41525
  por_obra_social: [...por.entries()].map(([obra_social, e]) => ({ obra_social, ...e })).sort((a, b) => b.honorario - a.honorario),
41346
41526
  desde: fechas[0] ?? null,
@@ -41376,7 +41556,7 @@ var ATRASO_EXCEPCION = 365;
41376
41556
  var MAX_RECLAMOS_POR_DEFECTO = 2;
41377
41557
  var DIAS_ENTRE_RECLAMOS_POR_DEFECTO = 15;
41378
41558
  var GRACIA_COMPROMISO = 7;
41379
- var redondear3 = (n) => Math.round(n * 100) / 100;
41559
+ var redondear4 = (n) => Math.round(n * 100) / 100;
41380
41560
  var pesos3 = (n) => `$ ${n.toLocaleString("es-AR", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
41381
41561
  var fecha4 = (iso5) => `${iso5.slice(8, 10)}/${iso5.slice(5, 7)}/${iso5.slice(0, 4)}`;
41382
41562
  var normalizar = (s) => s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/\s+/g, " ").trim();
@@ -41416,7 +41596,7 @@ function leerFacturas(v) {
41416
41596
  vistas.add(id3);
41417
41597
  if (!emision) return { error: `factura ${id3}: fecha_emision tiene que ser AAAA-MM-DD` };
41418
41598
  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" });
41599
+ 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
41600
  }
41421
41601
  return { facturas: out };
41422
41602
  }
@@ -41448,7 +41628,7 @@ function evaluarFacturas(input2) {
41448
41628
  };
41449
41629
  });
41450
41630
  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));
41631
+ const total = redondear4(reclamadas.reduce((s, i) => s + i.saldo_pesos, 0));
41452
41632
  return {
41453
41633
  items: [...reclamadas, ...items.filter((i) => i.estado !== "reclamada")],
41454
41634
  resumen: {
@@ -41467,7 +41647,7 @@ function evaluarFacturas(input2) {
41467
41647
  }
41468
41648
  function redactarReclamo(input2) {
41469
41649
  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));
41650
+ const total = redondear4(reclamadas.reduce((s, i) => s + i.saldo_pesos, 0));
41471
41651
  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
41652
  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
41653
  const cuerpo = [
@@ -41493,7 +41673,7 @@ function leerSaldos(v) {
41493
41673
  const id3 = texto3(r.factura_id);
41494
41674
  const saldo = num4(r.saldo_pesos);
41495
41675
  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) });
41676
+ saldos.set(id3, { saldo: redondear4(saldo), notasCredito: redondear4(num4(r.notas_credito_pesos) ?? 0), recibos: redondear4(num4(r.recibos_aplicados_pesos) ?? 0) });
41497
41677
  }
41498
41678
  return { saldos };
41499
41679
  }
@@ -41509,14 +41689,14 @@ function compararSaldos(items, resumen, leidos, notasCreditoAntes = {}) {
41509
41689
  return i;
41510
41690
  }
41511
41691
  const saldo = Math.max(0, s.saldo);
41512
- const nc = Math.max(0, redondear3(s.notasCredito - (notasCreditoAntes[i.factura_id] ?? 0)));
41692
+ const nc = Math.max(0, redondear4(s.notasCredito - (notasCreditoAntes[i.factura_id] ?? 0)));
41513
41693
  const cobro = saldo <= SALDO_MINIMO ? "cobrada" : saldo < i.saldo_inicial - SALDO_MINIMO ? "parcial" : "pendiente";
41514
41694
  return { ...i, saldo_pesos: saldo, cobro, notas_credito_nuevas: nc };
41515
41695
  });
41516
41696
  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));
41697
+ const pendiente = redondear4(reclamadas.reduce((s, i) => s + (i.cobro === "cobrada" ? 0 : i.saldo_pesos), 0));
41698
+ const debitos = redondear4(reclamadas.reduce((s, i) => s + i.notas_credito_nuevas, 0));
41699
+ const cobrado = redondear4(Math.max(0, resumen.total_reclamado - pendiente - debitos));
41520
41700
  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
41701
  }
41522
41702
  var RESPUESTAS_FINANCIADOR = ["paga", "observa", "no_responde"];
@@ -42614,6 +42794,213 @@ var circularAranceles = {
42614
42794
  }
42615
42795
  ]
42616
42796
  };
42797
+ var RESPUESTAS_AJUSTE = ["aprobado", "aprobado_otro", "rechazado"];
42798
+ var CANALES_NOTA = ["correo", "portal_del_financiador", "nota", "otro"];
42799
+ function calcularAjuste(caso, datos, contexto) {
42800
+ const formula = contexto.formulaAjuste ?? FORMULA_POR_DEFECTO;
42801
+ if ("error" in formula) return { ok: false, error: `la f\xF3rmula de la instituci\xF3n est\xE1 mal configurada: ${formula.error}` };
42802
+ const desde = texto4(caso.datos.ultimo_ajuste);
42803
+ if (!desde || !periodoValido(desde)) return { ok: false, error: "el caso no tiene el mes del \xFAltimo ajuste (ultimo_ajuste: AAAA-MM)" };
42804
+ let indices = [];
42805
+ if (!("fijo" in formula)) {
42806
+ const l = leerIndices(datos.indices);
42807
+ if ("error" in l) return { ok: false, error: l.error };
42808
+ indices = l.indices;
42809
+ }
42810
+ const mesActual = (contexto.hoy ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)).slice(0, 7);
42811
+ const p = porcentajeSegunFormula(formula, indices, desde, mesActual);
42812
+ if ("error" in p) return { ok: false, error: p.error };
42813
+ const a = armarAjuste(datos.actual, p.porcentaje);
42814
+ if (!a.ok) return { ok: false, error: a.error };
42815
+ return { ok: true, porcentaje: p.porcentaje, detalle: p.detalle, hasta: p.hasta, items: a.items };
42816
+ }
42817
+ function resumenAjusteDe(caso) {
42818
+ const r = caso.datos.resumen;
42819
+ return r && typeof r === "object" ? r : null;
42820
+ }
42821
+ function validarPorcentajeHumano(v, regla) {
42822
+ const n = num5(v);
42823
+ return n !== null && porcentajeValido(n) ? ok(regla, `${n} %`) : falla(regla, "porcentaje: un n\xFAmero entre -50 y 100");
42824
+ }
42825
+ var ajusteConvenio = {
42826
+ clave: "ajuste_convenio",
42827
+ nombre: "Ajuste de convenios por \xEDndice",
42828
+ puesto: "Convenios y facturaci\xF3n",
42829
+ 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.",
42830
+ inicial: "abierto",
42831
+ estados: ["abierto", "calculado", "pendiente_aprobacion", "por_enviar", "enviado", "acordado", "aplicado", "verificado", "con_diferencias", "rechazado", "rechazado_financiador", "cancelado"],
42832
+ finalesOk: ["verificado"],
42833
+ finalesNoOk: ["rechazado", "rechazado_financiador", "cancelado"],
42834
+ minutosAbsorbidos: 40,
42835
+ // Buscar los índices, calcular, armar la nota y comprobar cada valor (tope: una jornada).
42836
+ minutosSegun: (caso) => Math.min(480, 40 + 2 * cantidad(caso.datos.items)),
42837
+ transiciones: [
42838
+ {
42839
+ via: "calcular",
42840
+ de: ["abierto", "calculado"],
42841
+ a: "calculado",
42842
+ actor: ["agente", "humano"],
42843
+ nivelRequerido: "observa",
42844
+ 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.",
42845
+ validar: (caso, datos, contexto) => {
42846
+ const c = calcularAjuste(caso, datos, contexto);
42847
+ const fijo = contexto.formulaAjuste && "fijo" in contexto.formulaAjuste;
42848
+ return [
42849
+ 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"),
42850
+ 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"),
42851
+ c.ok ? ok("calculo_ajuste", `${c.porcentaje} % sobre ${c.items.length} pr\xE1cticas`) : falla("calculo_ajuste", c.error),
42852
+ ...(() => {
42853
+ const v = leerVolumenes(datos.volumenes, datos.dias_volumen);
42854
+ return v && "error" in v ? [falla("volumenes_validos", v.error)] : [ok("volumenes_validos", v ? "con lo facturado" : "sin vol\xFAmenes: no se estima el impacto en pesos")];
42855
+ })()
42856
+ ];
42857
+ }
42858
+ },
42859
+ {
42860
+ via: "proponer",
42861
+ de: ["calculado"],
42862
+ a: "pendiente_aprobacion",
42863
+ actor: ["agente", "humano"],
42864
+ nivelRequerido: "propone",
42865
+ 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.",
42866
+ validar: (caso) => {
42867
+ const r = resumenAjusteDe(caso);
42868
+ 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")];
42869
+ }
42870
+ },
42871
+ {
42872
+ via: "ajustar",
42873
+ de: ["pendiente_aprobacion"],
42874
+ a: "pendiente_aprobacion",
42875
+ actor: ["humano"],
42876
+ descripcion: "La direcci\xF3n cambia el porcentaje (porcentaje, con motivo) o deja pr\xE1cticas afuera (excluir: [c\xF3digos]); el plugin recalcula los valores.",
42877
+ validar: (caso, datos) => {
42878
+ const out = [];
42879
+ if (datos.porcentaje === void 0 && datos.excluir === void 0) return [falla("cambio_presente", "ajustar necesita porcentaje o excluir")];
42880
+ if (datos.porcentaje !== void 0) {
42881
+ out.push(validarPorcentajeHumano(datos.porcentaje, "porcentaje_valido"));
42882
+ out.push(texto4(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "cambiar el porcentaje calculado requiere motivo"));
42883
+ }
42884
+ if (datos.excluir !== void 0) {
42885
+ if (!Array.isArray(datos.excluir)) out.push(falla("excluir_valido", "excluir tiene que ser una lista de c\xF3digos"));
42886
+ else {
42887
+ const items = leerItems(caso.datos.items);
42888
+ const codigos = new Set(items.map((i) => i.codigo));
42889
+ const ajenos = datos.excluir.map(normalizarCodigo).filter((c) => !codigos.has(c));
42890
+ out.push(ajenos.length === 0 ? ok("excluir_valido") : falla("excluir_valido", `c\xF3digos que no est\xE1n en el convenio: ${ajenos.join(", ")}`));
42891
+ 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"));
42892
+ }
42893
+ }
42894
+ return out;
42895
+ }
42896
+ },
42897
+ {
42898
+ via: "aprobar",
42899
+ de: ["pendiente_aprobacion"],
42900
+ a: "por_enviar",
42901
+ actor: ["humano"],
42902
+ 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).",
42903
+ validar: (caso, datos) => {
42904
+ const r = resumenAjusteDe(caso);
42905
+ return [
42906
+ r && r.practicas > 0 ? ok("hay_que_cargar", `${r.practicas} pr\xE1cticas`) : falla("hay_que_cargar", "no hay ninguna pr\xE1ctica para ajustar"),
42907
+ !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`)
42908
+ ];
42909
+ }
42910
+ },
42911
+ {
42912
+ via: "rechazar",
42913
+ de: ["pendiente_aprobacion"],
42914
+ a: "rechazado",
42915
+ actor: ["humano"],
42916
+ descripcion: "La direcci\xF3n no hace el ajuste; con motivo.",
42917
+ validar: (_caso, datos) => [texto4(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "el rechazo requiere motivo")]
42918
+ },
42919
+ {
42920
+ via: "enviar",
42921
+ de: ["por_enviar"],
42922
+ a: "enviado",
42923
+ actor: ["humano", "agente"],
42924
+ nivelRequerido: "ejecuta",
42925
+ descripcion: "Registrar que la nota se mand\xF3 al financiador: por d\xF3nde (canal) y a qui\xE9n (destinatario).",
42926
+ validar: (_caso, datos) => [
42927
+ CANALES_NOTA.includes(datos.canal) ? ok("canal_valido") : falla("canal_valido", `canal: ${CANALES_NOTA.join(", ")}`),
42928
+ texto4(datos.destinatario) ? ok("destinatario_presente") : falla("destinatario_presente", "destinatario (correo, \xE1rea o persona del financiador) es obligatorio")
42929
+ ]
42930
+ },
42931
+ {
42932
+ via: "responder",
42933
+ de: ["enviado"],
42934
+ a: (_caso, datos) => datos.respuesta === "rechazado" ? "rechazado_financiador" : "acordado",
42935
+ actor: ["humano"],
42936
+ descripcion: "Registrar la respuesta del financiador: aprobado, aprobado con otro porcentaje (porcentaje) o rechazado (motivo).",
42937
+ validar: (_caso, datos) => {
42938
+ const r = datos.respuesta;
42939
+ if (!RESPUESTAS_AJUSTE.includes(r)) return [falla("respuesta_valida", `respuesta: ${RESPUESTAS_AJUSTE.join(", ")}`)];
42940
+ if (r === "aprobado_otro") return [ok("respuesta_valida"), validarPorcentajeHumano(datos.porcentaje, "porcentaje_valido")];
42941
+ if (r === "rechazado") return [ok("respuesta_valida"), texto4(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "anotar qu\xE9 respondi\xF3 el financiador")];
42942
+ return [ok("respuesta_valida")];
42943
+ }
42944
+ },
42945
+ {
42946
+ via: "aplicar",
42947
+ de: ["acordado"],
42948
+ a: "aplicado",
42949
+ actor: ["agente", "humano"],
42950
+ nivelRequerido: "ejecuta",
42951
+ cargaManual: true,
42952
+ descripcion: "Cargar en el HIS el archivo que gener\xF3 el plugin (Actualizaci\xF3n con archivo) y registrarlo, con el hash del archivo cargado.",
42953
+ validar: (caso, datos) => {
42954
+ const esperado = texto4(caso.datos.archivo_csv?.hash);
42955
+ const informado = texto4(datos.archivo_hash);
42956
+ return [
42957
+ texto4(datos.accion_id) ? ok("accion_registrada") : falla("accion_registrada", "aplicar requiere accion_id de la carga registrada"),
42958
+ 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")
42959
+ ];
42960
+ }
42961
+ },
42962
+ {
42963
+ via: "verificar",
42964
+ de: ["aplicado"],
42965
+ a: (caso, datos) => {
42966
+ const r = compararRelectura(itemsACargar(leerItems(caso.datos.items), caso.datos.excluir), datos.leidos);
42967
+ return "error" in r || r.diferencias.length > 0 ? "con_diferencias" : "verificado";
42968
+ },
42969
+ actor: ["agente", "humano", "sistema"],
42970
+ nivelRequerido: "observa",
42971
+ 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.",
42972
+ validar: (_caso, datos) => {
42973
+ const r = compararRelectura([], datos.leidos);
42974
+ return [
42975
+ texto4(datos.lectura_id) ? ok("lectura_registrada") : falla("lectura_registrada", "verificar requiere lectura_id: la relectura del convenio registrada"),
42976
+ "error" in r ? falla("leidos_validos", r.error) : ok("leidos_validos")
42977
+ ];
42978
+ }
42979
+ },
42980
+ {
42981
+ via: "corregir",
42982
+ de: ["con_diferencias"],
42983
+ a: "aplicado",
42984
+ actor: ["humano"],
42985
+ cargaManual: true,
42986
+ descripcion: "Corregir en el HIS las pr\xE1cticas que no coincidieron y volver a comprobar.",
42987
+ validar: (_caso, datos) => [texto4(datos.accion_id) ? ok("accion_registrada") : falla("accion_registrada", "corregir requiere accion_id de la correcci\xF3n registrada")]
42988
+ },
42989
+ {
42990
+ via: "cancelar",
42991
+ de: ["abierto", "calculado", "pendiente_aprobacion", "por_enviar", "enviado", "acordado", "con_diferencias"],
42992
+ a: "cancelado",
42993
+ actor: ["humano", "agente"],
42994
+ nivelRequerido: "observa",
42995
+ descripcion: "Cancelar el ajuste (repetido, el convenio cambi\xF3 por otra v\xEDa, se renegoci\xF3).",
42996
+ validar: (_caso, datos) => [texto4(datos.motivo) ? ok("motivo_presente") : falla("motivo_presente", "la cancelaci\xF3n requiere motivo")]
42997
+ }
42998
+ ]
42999
+ };
43000
+ function leerTopeAjuste(v) {
43001
+ const n = num5(v);
43002
+ return n !== null && n >= 1 && n <= 100 ? Math.round(n) : TOPE_AJUSTE_POR_DEFECTO;
43003
+ }
42617
43004
  var CANALES_RECLAMO = ["correo", "portal_del_financiador", "nota", "telefono", "otro"];
42618
43005
  function resumenCobranzaDe(caso) {
42619
43006
  const r = caso.datos.resumen;
@@ -43296,6 +43683,7 @@ var WORKFLOWS = {
43296
43683
  [credencialProfesional.clave]: credencialProfesional,
43297
43684
  [demandaPendiente.clave]: demandaPendiente,
43298
43685
  [circularAranceles.clave]: circularAranceles,
43686
+ [ajusteConvenio.clave]: ajusteConvenio,
43299
43687
  [cobranzaFinanciador.clave]: cobranzaFinanciador,
43300
43688
  [internacionAlDia.clave]: internacionAlDia,
43301
43689
  [honorarioEnCero.clave]: honorarioEnCero,
@@ -43795,7 +44183,7 @@ async function informarPruebaRegla(ctx, input2) {
43795
44183
  // src/rentabilidad.ts
43796
44184
  var UMBRAL_MARGEN_POR_DEFECTO = 15;
43797
44185
  var MESES_PRECIO_POR_DEFECTO = 6;
43798
- var redondear4 = (n) => Math.round(n * 100) / 100;
44186
+ var redondear5 = (n) => Math.round(n * 100) / 100;
43799
44187
  function texto5(v) {
43800
44188
  return typeof v === "string" && v.trim() ? v.trim() : typeof v === "number" && Number.isFinite(v) ? String(v) : null;
43801
44189
  }
@@ -43824,7 +44212,7 @@ function leerLecturaRentabilidad(v) {
43824
44212
  const x = c ?? {};
43825
44213
  const monto = num6(x.monto);
43826
44214
  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) });
44215
+ componentes.push({ arancel: texto5(x.arancel) ?? "", monto: redondear5(monto) });
43828
44216
  }
43829
44217
  const insumos = [];
43830
44218
  for (const [i, c] of r.insumos.entries()) {
@@ -43832,7 +44220,7 @@ function leerLecturaRentabilidad(v) {
43832
44220
  const cantidad2 = num6(x.cantidad);
43833
44221
  if (cantidad2 === null || cantidad2 <= 0) return { error: `lectura.insumos[${i}].cantidad: tiene que ser un n\xFAmero > 0` };
43834
44222
  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) });
44223
+ 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
44224
  }
43837
44225
  const honorarios = [];
43838
44226
  for (const [i, c] of r.honorarios.entries()) {
@@ -43870,7 +44258,7 @@ function evaluarRentabilidad(input2) {
43870
44258
  const faltas = [];
43871
44259
  const avisos = [];
43872
44260
  const insumosARevisar = [];
43873
- const valor = redondear4(l.componentes.reduce((a, c) => a + c.monto, 0));
44261
+ const valor = redondear5(l.componentes.reduce((a, c) => a + c.monto, 0));
43874
44262
  if (valor <= 0) faltas.push("sin_valor_convenio");
43875
44263
  let insumos = 0;
43876
44264
  for (const i of l.insumos) {
@@ -43885,7 +44273,7 @@ function evaluarRentabilidad(input2) {
43885
44273
  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
44274
  }
43887
44275
  }
43888
- insumos = redondear4(insumos);
44276
+ insumos = redondear5(insumos);
43889
44277
  if (l.insumos.length === 0) avisos.push("sin_insumos");
43890
44278
  const debajo = [];
43891
44279
  const honorarios = [];
@@ -43909,10 +44297,10 @@ function evaluarRentabilidad(input2) {
43909
44297
  }
43910
44298
  const honorarioMin = honorarios.length ? Math.min(...honorarios) : null;
43911
44299
  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;
44300
+ const margenPeor = honorarioMax !== null && valor > 0 ? redondear5(valor - insumos - honorarioMax) : null;
44301
+ const margenMejor = honorarioMin !== null && valor > 0 ? redondear5(valor - insumos - honorarioMin) : null;
43914
44302
  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;
44303
+ const impacto = margenPeor !== null ? redondear5(margenPeor * l.volumen_90d / 3) : null;
43916
44304
  let clase2;
43917
44305
  if (faltas.length > 0 || margenPct === null) clase2 = "datos_incompletos";
43918
44306
  else if (margenPeor < 0) clase2 = "perdida";
@@ -44050,7 +44438,7 @@ var num7 = (v) => {
44050
44438
  const n = typeof v === "number" ? v : typeof v === "string" && v.trim() ? Number(v) : NaN;
44051
44439
  return Number.isFinite(n) ? n : null;
44052
44440
  };
44053
- var redondear5 = (n) => Math.round(n * 100) / 100;
44441
+ var redondear6 = (n) => Math.round(n * 100) / 100;
44054
44442
  var fechaHora2 = (v) => {
44055
44443
  const s = texto7(v);
44056
44444
  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 +44469,7 @@ function leerLecturaCostoInternacion(v) {
44081
44469
  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
44470
  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
44471
  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 } : {} });
44472
+ 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
44473
  }
44086
44474
  const consumos = [];
44087
44475
  for (const [i, x] of r.consumos.entries()) {
@@ -44167,7 +44555,7 @@ function evaluarCostoInternacion(input2) {
44167
44555
  const umbral = input2.umbral ?? UMBRAL_MARGEN_POR_DEFECTO;
44168
44556
  const faltas = [];
44169
44557
  const avisos = [];
44170
- const facturado = redondear5(l.facturado.reduce((a, f) => a + f.monto * f.cantidad, 0));
44558
+ const facturado = redondear6(l.facturado.reduce((a, f) => a + f.monto * f.cantidad, 0));
44171
44559
  if (facturado <= 0) faltas.push("sin_facturar");
44172
44560
  if (!l.egreso) avisos.push("en_curso");
44173
44561
  let honorarios = 0;
@@ -44177,7 +44565,7 @@ function evaluarCostoInternacion(input2) {
44177
44565
  if (!f.regla || f.regla.prof === null && f.regla.inst === null) sinRegla += 1;
44178
44566
  honorarios += honorarioDe({ regla: f.regla, monto: f.monto, cantidad: f.cantidad });
44179
44567
  }
44180
- honorarios = redondear5(honorarios);
44568
+ honorarios = redondear6(honorarios);
44181
44569
  if (sinRegla > 0) avisos.push("prestaciones_sin_regla");
44182
44570
  let insumos = 0;
44183
44571
  const sinCosto = [];
@@ -44190,7 +44578,7 @@ function evaluarCostoInternacion(input2) {
44190
44578
  }
44191
44579
  insumos += cantidad2 * c.costo_unitario;
44192
44580
  }
44193
- insumos = redondear5(insumos);
44581
+ insumos = redondear6(insumos);
44194
44582
  if (sinCosto.length) faltas.push("insumo_sin_costo");
44195
44583
  const { tramos, total, exactos } = diasPorServicio(l, hoy);
44196
44584
  const porServicio = input2.costoDiaPorServicio ?? {};
@@ -44205,18 +44593,18 @@ function evaluarCostoInternacion(input2) {
44205
44593
  else cama += (exactos.get(servicio) ?? dias) * costo_dia;
44206
44594
  return { servicio, dias, costo_dia: costo_dia ?? null };
44207
44595
  });
44208
- cama = redondear5(cama);
44596
+ cama = redondear6(cama);
44209
44597
  if (faltaCama) faltas.push("sin_costo_cama");
44210
44598
  const minutos = l.cirugias.reduce((a, c) => a + (c.minutos ?? 0), 0);
44211
44599
  let quirofano = 0;
44212
44600
  if (l.cirugias.length > 0) {
44213
44601
  if (input2.costoHoraQuirofano === null || input2.costoHoraQuirofano === void 0) faltas.push("sin_costo_quirofano");
44214
- else quirofano = redondear5(minutos / 60 * input2.costoHoraQuirofano);
44602
+ else quirofano = redondear6(minutos / 60 * input2.costoHoraQuirofano);
44215
44603
  }
44216
44604
  if (l.cirugias.some((c) => c.minutos === null)) faltas.push("cirugia_sin_duracion");
44217
- const costo = redondear5(honorarios + insumos + cama + quirofano);
44605
+ const costo = redondear6(honorarios + insumos + cama + quirofano);
44218
44606
  const calculable = faltas.length === 0 && facturado > 0;
44219
- const margen = calculable ? redondear5(facturado - costo) : null;
44607
+ const margen = calculable ? redondear6(facturado - costo) : null;
44220
44608
  const margen_pct = margen !== null ? Math.round(margen / facturado * 1e3) / 10 : null;
44221
44609
  let clase2;
44222
44610
  if (margen === null || margen_pct === null) clase2 = "datos_incompletos";
@@ -44383,6 +44771,13 @@ function describirOpciones(antes, despues) {
44383
44771
  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
44772
  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
44773
  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)} %`);
44774
+ if (JSON.stringify(antes.formula) !== JSON.stringify(despues.formula) && despues.formula !== void 0) {
44775
+ const f = despues.formula;
44776
+ partes.push(`f\xF3rmula del ajuste: ${f.fijo !== void 0 ? `${f.fijo} % fijo` : (f.componentes ?? []).map((c) => `${c.serie.replace(/_/g, " ")} ${c.peso} %`).join(" + ")}`);
44777
+ }
44778
+ if (antes.tope_ajuste !== despues.tope_ajuste && despues.tope_ajuste !== void 0) partes.push(`tope del ajuste por \xEDndice: ${String(despues.tope_ajuste)} %`);
44779
+ if (JSON.stringify(antes.nota) !== JSON.stringify(despues.nota) && despues.nota !== void 0) partes.push("texto de la nota al financiador");
44780
+ 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
44781
  if (antes.umbral_margen !== despues.umbral_margen && despues.umbral_margen !== void 0) partes.push(`margen m\xEDnimo: ${String(despues.umbral_margen)} %`);
44387
44782
  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
44783
  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 +44875,28 @@ async function cambiarPolitica(ctx, input2) {
44480
44875
  if (v === null) throw new OpsError("validacion", "dias_sin_actividad: tiene que ser un n\xFAmero entero entre 1 y 15");
44481
44876
  opciones.dias_sin_actividad = v;
44482
44877
  }
44878
+ if (input2.workflow === "ajuste_convenio") {
44879
+ if (opciones.formula !== void 0) {
44880
+ const f = leerFormula(opciones.formula);
44881
+ if ("error" in f) throw new OpsError("validacion", `formula: ${f.error}`);
44882
+ opciones.formula = f;
44883
+ }
44884
+ if (opciones.dias_respuesta !== void 0) {
44885
+ const v = enteroEntre(opciones.dias_respuesta, 1, 60);
44886
+ if (v === null) throw new OpsError("validacion", "dias_respuesta: tiene que ser un n\xFAmero entero entre 1 y 60");
44887
+ opciones.dias_respuesta = v;
44888
+ }
44889
+ if (opciones.tope_ajuste !== void 0) {
44890
+ const v = enteroEntre(opciones.tope_ajuste, LIMITES_TOPE_AJUSTE[0], LIMITES_TOPE_AJUSTE[1]);
44891
+ 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]}`);
44892
+ opciones.tope_ajuste = v;
44893
+ }
44894
+ if (opciones.nota !== void 0) {
44895
+ const n = opciones.nota && typeof opciones.nota === "object" ? opciones.nota : {};
44896
+ opciones.nota = { asunto: str2(n.asunto)?.slice(0, 200) ?? null, texto: str2(n.texto)?.slice(0, 5e3) ?? null };
44897
+ }
44898
+ for (const k of ["firma", "institucion"]) if (opciones[k] !== void 0) opciones[k] = str2(opciones[k])?.slice(0, 200) ?? null;
44899
+ }
44483
44900
  if (opciones.tope_variacion !== void 0) {
44484
44901
  const tope = leerTope(opciones.tope_variacion);
44485
44902
  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 +45084,15 @@ async function abrirCaso(ctx, input2) {
44667
45084
  if (faltan.length) throw new OpsError("validacion", `circular_aranceles necesita ${faltan.join(", ")}`);
44668
45085
  if (!/^\d{4}-\d{2}-\d{2}$/.test(String(d.vigencia_desde))) throw new OpsError("validacion", "vigencia_desde: AAAA-MM-DD");
44669
45086
  }
45087
+ if (wf.clave === "ajuste_convenio") {
45088
+ const d = input2.datos ?? {};
45089
+ const faltan = ["financiador", "convenio", "vigencia_desde", "ultimo_ajuste"].filter((k) => !str2(d[k]));
45090
+ if (faltan.length) throw new OpsError("validacion", `ajuste_convenio necesita ${faltan.join(", ")}`);
45091
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(String(d.vigencia_desde))) throw new OpsError("validacion", "vigencia_desde: AAAA-MM-DD");
45092
+ 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");
45093
+ const esperada = `ajuste:${(normalizarCodigo(d.convenio) || String(d.convenio).trim()).toLowerCase()}:${String(d.vigencia_desde).slice(0, 7)}`;
45094
+ if (referencia !== esperada) throw new OpsError("referencia_requerida", `en ajuste_convenio la referencia es obligatoria: ${esperada}`);
45095
+ }
44670
45096
  const extra = {};
44671
45097
  if (wf.clave === "honorario_en_cero" || wf.clave === "debito_profesional" || wf.clave === "liquidacion_profesional") {
44672
45098
  const p = leerProfesional(input2.datos?.profesional) ?? leerProfesional({ id: input2.datos?.profesional_id, nombre: input2.datos?.profesional });
@@ -44781,6 +45207,12 @@ async function contextoDe(ctx, companyId, caso, via, datos) {
44781
45207
  contexto.topeVariacion = leerTope((await obtenerOpciones(ctx, companyId, caso.workflow)).tope_variacion);
44782
45208
  return contexto;
44783
45209
  }
45210
+ if (caso.workflow === "ajuste_convenio") {
45211
+ const op = await obtenerOpciones(ctx, companyId, caso.workflow);
45212
+ contexto.formulaAjuste = leerFormula(op.formula);
45213
+ contexto.topeAjuste = leerTopeAjuste(op.tope_ajuste);
45214
+ return contexto;
45215
+ }
44784
45216
  if (caso.workflow === "rentabilidad_practica") {
44785
45217
  if (via === "evaluar" || via === "comprobar") {
44786
45218
  const op = await obtenerOpciones(ctx, companyId, caso.workflow);
@@ -44890,7 +45322,7 @@ async function transicionar(ctx, input2) {
44890
45322
  );
44891
45323
  const lectura = rows[0];
44892
45324
  let vieja = false;
44893
- if (lectura && (resultado.via === "comprobar" || caso.workflow === "circular_aranceles" && resultado.via === "verificar")) {
45325
+ if (lectura && (resultado.via === "comprobar" || (caso.workflow === "circular_aranceles" || caso.workflow === "ajuste_convenio") && resultado.via === "verificar")) {
44894
45326
  const ultima = await ctx.db.query(
44895
45327
  `SELECT max(created_at) AS ultima FROM ${t3(ctx, "transicion")} WHERE company_id = $1 AND caso_id = $2`,
44896
45328
  [input2.companyId, caso.id]
@@ -44945,6 +45377,87 @@ async function transicionar(ctx, input2) {
44945
45377
  datos.verificada_el = contexto.hoy ?? null;
44946
45378
  }
44947
45379
  }
45380
+ if (caso.workflow === "ajuste_convenio") {
45381
+ for (const k of ["items", "resumen", "archivo_csv", "diferencias", "nota", "detalle", "periodo_hasta", "porcentaje_calculado", "porcentaje_acordado", "indices_usados"]) delete datos[k];
45382
+ const tope = contexto.topeAjuste ?? leerTopeAjuste(void 0);
45383
+ const porcentajeAntes = typeof caso.datos.porcentaje === "number" ? caso.datos.porcentaje : 0;
45384
+ const calculado = typeof caso.datos.porcentaje_calculado === "number" ? caso.datos.porcentaje_calculado : porcentajeAntes;
45385
+ const volGuardado = caso.datos.volumenes && typeof caso.datos.volumenes === "object" ? caso.datos.volumenes : null;
45386
+ const recalcular = (porcentaje3, excluir) => {
45387
+ const a = armarAjuste(caso.datos.actual, porcentaje3);
45388
+ if (!a.ok) return;
45389
+ datos.porcentaje = porcentaje3;
45390
+ datos.items = a.items;
45391
+ datos.resumen = resumenAjuste(a.items, excluir, porcentaje3, calculado, tope, volGuardado);
45392
+ };
45393
+ const pedido = typeof datos.porcentaje === "number" ? datos.porcentaje : typeof datos.porcentaje === "string" && datos.porcentaje.trim() ? Number(datos.porcentaje) : null;
45394
+ delete datos.porcentaje;
45395
+ if (resultado.via === "calcular") {
45396
+ delete datos.excluir;
45397
+ const c = calcularAjuste(caso, datos, contexto);
45398
+ const act = leerActual(datos.actual);
45399
+ const vl = leerVolumenes(datos.volumenes, datos.dias_volumen);
45400
+ const vol = vl && !("error" in vl) ? vl : null;
45401
+ delete datos.dias_volumen;
45402
+ if (c.ok && "practicas" in act) {
45403
+ Object.assign(datos, {
45404
+ porcentaje: c.porcentaje,
45405
+ porcentaje_calculado: c.porcentaje,
45406
+ detalle: c.detalle,
45407
+ periodo_hasta: c.hasta,
45408
+ items: c.items,
45409
+ excluir: [],
45410
+ resumen: resumenAjuste(c.items, [], c.porcentaje, c.porcentaje, tope, vol),
45411
+ volumenes: vol,
45412
+ // Lo que tenía el convenio antes del ajuste queda guardado en el caso: es el respaldo para volver atrás.
45413
+ actual: act.practicas,
45414
+ indices_usados: Array.isArray(datos.indices) ? datos.indices : []
45415
+ });
45416
+ } else delete datos.volumenes;
45417
+ delete datos.indices;
45418
+ } else {
45419
+ delete datos.actual;
45420
+ delete datos.indices;
45421
+ delete datos.volumenes;
45422
+ delete datos.dias_volumen;
45423
+ }
45424
+ if (resultado.via === "ajustar") {
45425
+ const excluir = Array.isArray(datos.excluir) ? datos.excluir.map(normalizarCodigo) : caso.datos.excluir ?? [];
45426
+ datos.excluir = excluir;
45427
+ recalcular(pedido ?? porcentajeAntes, excluir);
45428
+ } else if (resultado.via !== "calcular") delete datos.excluir;
45429
+ if (resultado.via === "responder" && datos.respuesta === "aprobado_otro" && pedido !== null) {
45430
+ datos.porcentaje_acordado = pedido;
45431
+ recalcular(pedido, caso.datos.excluir);
45432
+ }
45433
+ if (resultado.via === "responder" && datos.respuesta === "aprobado") datos.porcentaje_acordado = porcentajeAntes;
45434
+ if (resultado.a === "por_enviar") {
45435
+ const op = await obtenerOpciones(ctx, input2.companyId, caso.workflow);
45436
+ const r = caso.datos.resumen;
45437
+ datos.nota = armarNota(op.nota, {
45438
+ institucion: str2(op.institucion) ?? str2(caso.datos.institucion) ?? "",
45439
+ financiador: str2(caso.datos.financiador) ?? "",
45440
+ convenio: str2(caso.datos.convenio) ?? "",
45441
+ porcentaje: porcentajeAntes,
45442
+ indices: textoIndices(caso.datos.detalle ?? { fijo: porcentajeAntes }, str2(caso.datos.ultimo_ajuste), str2(caso.datos.periodo_hasta)),
45443
+ vigencia: str2(caso.datos.vigencia_desde) ?? "",
45444
+ firma: str2(op.firma) ?? "",
45445
+ practicas: r?.practicas ?? 0
45446
+ });
45447
+ }
45448
+ if (resultado.a === "acordado") {
45449
+ const items = datos.items ?? leerItems(caso.datos.items);
45450
+ const aCargar = itemsACargar(items, caso.datos.excluir);
45451
+ const actual = leerActual(caso.datos.actual);
45452
+ const archivo = archivoActualizacion(aCargar, "practicas" in actual ? actual.practicas : []);
45453
+ 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` };
45454
+ }
45455
+ if (resultado.via === "verificar") {
45456
+ const r = compararRelectura(itemsACargar(leerItems(caso.datos.items), caso.datos.excluir), datos.leidos);
45457
+ datos.diferencias = "error" in r ? [] : r.diferencias;
45458
+ datos.verificada_el = contexto.hoy ?? null;
45459
+ }
45460
+ }
44948
45461
  if (caso.workflow === "honorario_en_cero") {
44949
45462
  for (const k of ["items", "resumen", "comprobado_el"]) delete datos[k];
44950
45463
  if (resultado.via === "observar" || resultado.via === "comprobar") {
@@ -45530,7 +46043,7 @@ async function registrarCargaManual(ctx, input2) {
45530
46043
  ...caso.workflow === "cuenta_completa" ? { faltantes_que_completo: caso.datos.faltantes ?? [] } : {},
45531
46044
  ...caso.workflow === "facturacion_pendiente" ? { bono: caso.datos.bono_numero ?? caso.datos.bono_id, vence_el: caso.datos.vence_el } : {},
45532
46045
  ...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 } : {},
46046
+ ...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
46047
  ...caso.workflow === "credencial_profesional" ? { profesional: caso.datos.profesional ?? caso.datos.profesional_id, observacion_que_corrigio: caso.datos.observacion ?? null } : {},
45535
46048
  ...comentario ? { nota: comentario } : {}
45536
46049
  },
@@ -45830,6 +46343,29 @@ async function registrarEnvioLiquidacion(ctx, input2) {
45830
46343
  });
45831
46344
  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
46345
  }
46346
+ async function registrarEnvioNota(ctx, input2) {
46347
+ const caso = await leerCaso(ctx, input2.companyId, input2.casoId);
46348
+ if (!caso) throw new OpsError("caso_inexistente", `no existe el caso ${input2.casoId} en esta instituci\xF3n`);
46349
+ if (caso.workflow !== "ajuste_convenio") throw new OpsError("workflow_invalido", "solo los ajustes de convenio llevan nota al financiador");
46350
+ 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`);
46351
+ if (input2.actor.tipo !== "humano") throw new OpsError("actor_no_permitido", "el env\xEDo lo registra una persona");
46352
+ const canal = typeof input2.canal === "string" && CANALES_NOTA.includes(input2.canal) ? input2.canal : "otro";
46353
+ const destinatario = str2(input2.destinatario);
46354
+ 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 });
46355
+ const persona = str2(input2.persona);
46356
+ const nota = caso.datos.nota;
46357
+ const accion = await registrarAccion(ctx, {
46358
+ companyId: input2.companyId,
46359
+ casoId: caso.id,
46360
+ tipo: "aviso",
46361
+ conector: canal === "correo" ? "mail" : "manual",
46362
+ objetivo: `Nota de ajuste a ${str2(caso.datos.financiador) ?? "el financiador"}`,
46363
+ resultado: "ok",
46364
+ evidencia: { canal, destinatario, asunto: nota?.asunto ?? null, porcentaje: caso.datos.porcentaje ?? null, ...persona ? { registrado_por: persona } : {} },
46365
+ actor: input2.actor
46366
+ });
46367
+ 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 });
46368
+ }
45833
46369
  function canalReclamo(v) {
45834
46370
  return typeof v === "string" && CANALES_RECLAMO.includes(v) ? v : "otro";
45835
46371
  }
@@ -46817,7 +47353,7 @@ async function manejarApi(ctx, input2) {
46817
47353
  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
47354
  }
46819
47355
  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) });
47356
+ 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
47357
  case "politicas":
46822
47358
  return respuesta(200, { politicas: await listarPoliticas(ctx, companyId) });
46823
47359
  case "serie":
@@ -46857,13 +47393,22 @@ async function manejarApi(ctx, input2) {
46857
47393
  if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "el env\xEDo lo registra una persona" });
46858
47394
  return respuesta(200, await registrarEnvioLiquidacion(ctx, { companyId, casoId: input2.params.casoId ?? "", canal: body.canal, destinatario: str3(body.destinatario), persona: decididoPor, actor }));
46859
47395
  }
47396
+ case "ajustar": {
47397
+ 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" });
47398
+ const datosA = { ...body.porcentaje !== void 0 ? { porcentaje: body.porcentaje } : {}, ...Array.isArray(body.excluir) ? { excluir: body.excluir } : {} };
47399
+ 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 }));
47400
+ }
47401
+ case "envio-nota": {
47402
+ if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "el env\xEDo lo registra una persona" });
47403
+ return respuesta(200, await registrarEnvioNota(ctx, { companyId, casoId: input2.params.casoId ?? "", canal: body.canal, destinatario: str3(body.destinatario), persona: decididoPor, actor }));
47404
+ }
46860
47405
  case "respuesta-financiador": {
46861
47406
  if (actor.tipo !== "humano") return respuesta(403, { error: "actor_no_permitido", motivo: "la respuesta del financiador la registra una persona" });
46862
47407
  if (str3(body.respuesta) === "cerrar") {
46863
47408
  if (!str3(body.detalle)) return respuesta(422, { error: "validacion", motivo: "motivo_presente: cerrar la cobranza requiere motivo" });
46864
47409
  return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "cerrar", motivo: motivoConPersona(str3(body.detalle)), datos: conPersona({}), actor }));
46865
47410
  }
46866
- const datosR = { respuesta: str3(body.respuesta), ...str3(body.fechaCompromiso) ? { fecha_compromiso: str3(body.fechaCompromiso) } : {}, ...str3(body.detalle) ? { detalle: str3(body.detalle) } : {} };
47411
+ 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
47412
  return respuesta(200, await transicionar(ctx, { companyId, casoId: input2.params.casoId ?? "", via: "responder", motivo: motivoConPersona(str3(body.detalle)), datos: conPersona(datosR), actor }));
46868
47413
  }
46869
47414
  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.36",
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": [