Arquitectura del motor
Ubicación: proteus-backend/src/modules/rules-engine/ — el módulo más grande del backend. Sigue el
patrón de módulo de feature.
src/modules/rules-engine/
├── core/ # los contratos — leer primero
├── entities/ # 11 entidades
├── services/
│ ├── validators/ # 42 validadores + ruleTypeCatalog.ts
│ ├── context/ # los cortes del EvaluationContext
│ └── practice-sources/ # de dónde salen las prácticas
├── controllers/ # 6
├── dtos/
└── docs/ # glosario por archivo
El contrato: IRuleValidator
core/ define los contratos y es lo primero que hay que leer:
// core/validator.ts
export interface IRuleValidator {
readonly code: string; // ruleTypeCode del catálogo RULE_TYPE
readonly requires: ContextSlice[]; // qué cortes del contexto usa (declarativo)
validate(rule: RuleDefinition, ctx: EvaluationContext): Finding[];
}
validate es una función pura(rule, ctx) → Finding[]: sin DB, sin efectos, sin red. De ahí que se pueda testear con contextos
armados a mano, y de ahí que sumar un tipo de regla sea una clase + registrarla, con cero cambios al
núcleo.
Si un validador necesita un dato que no está en el contexto, la solución no es meter una query adentro:
es agregar el corte al contexto (services/context/) o crear una medición materializada.
Los enums transversales
core/enums.ts define los ejes (ver Ejes transversales):
| Enum | Valores |
|---|---|
Severity | DEBITO, POSIBLE_DEBITO, INFORMATIVO |
RevisionScope | EN_EL_DIA, EN_EL_EPISODIO, POR_PERIODO, POR_AUTORIZACION |
PatientModality | PROPIO, EXTERNO, TODOS |
LimitScope | DIA, MES, ANIO, EPISODIO |
DayType | PISO, UTI, UCE, GUARDIA, LIBRE |
AuthLevel | EPISODIO, PRACTICA, AMBOS |
BillingModality | EXTERNO, CAPITADO |
EnablementNodeType | práctica / módulo |
Coinciden 1:1 con los VARCHAR persistidos. No los cambies a numéricos: se rompe la lectura de todo lo
ya guardado.
El contexto de evaluación
core/context.ts + services/context/ construyen el EvaluationContext: los cortes de datos que los
validadores consumen.
| Corte | Qué trae |
|---|---|
| Prácticas | Las prácticas del episodio / día / período |
| Autorizaciones | Las autorizaciones imputables y su estado |
| Días de estadía | La ocupación de camas del episodio |
| Diagnósticos | Los diagnósticos del episodio |
| Paciente | Datos del afiliado: vigencia, sexo, edad, modalidad |
| Configuración | Reglas y configuración de prácticas vigentes para el financiador y la fecha |
| Habilitaciones | Los pares origen → destino |
| Mediciones | Los acumuladores materializados |
Cada validador declara qué cortes usa (requires), así que el contexto se puede armar cargando solo lo
necesario.
Las piezas de servicio
| Servicio | Rol |
|---|---|
RuleValidatorRegistry | Registry in-memory: código → validador |
RuleEngine | Orquesta: configuración + contexto + validadores → hallazgos |
MeasurementService + MeasurementMaterializer | Mediciones materializadas cross-episodio: lo que permite las reglas por período |
BatchEvaluationService | Evaluación por período (bandeja y débitos históricos) |
RuleEvaluationQueueService | Encola en la cola RULES_EVALUATION |
RuleFindingLogger | Historial de hallazgos (entidad RuleFindingLog) |
Entidades
FinancierRule, RuleType, RuleFinding, RuleFindingLog, RuleMeasurement,
PracticeFinancierConfig, Enablement, Nomenclador, NomencladorCode, BedDayCoverage,
BillingAuditedEntity.
Evaluación asíncrona
La evaluación arrancó corriendo dentro del request y se movió a RabbitMQ + worker.
Los hallazgos simplemente no aparecen. Nada falla ruidosamente. Es el primer sospechoso cuando "el motor no detecta nada". Ver Workers y colas.
El frontend
| Pieza | Qué es |
|---|---|
pages/posadas/rules-config/ | Las 5 pantallas del grupo "Reglas" del menú |
components/rules-engine/params-form/ | Los params de cada regla se editan con formularios por tipo de regla, no como JSON crudo |
components/rules-engine/rules-nomenclador-dialog/ | Drawer de ayuda con el nomenclador de reglas: qué hace cada tipo |
rule-findings-panel.component | Panel de hallazgos embebible en el detalle del episodio |
- La clase validadora en
services/validators/. - Registrarla en
RuleValidatorRegistry. - Sumarla al
ruleTypeCatalog.ts. - Agregar su formulario de params en
components/rules-engine/params-form/. - Documentarla en el nomenclador de reglas del drawer de ayuda.
Gotchas
- El registry es in-memory: un validador que no se registre no corre, y no falla ruidosamente.
- Los validadores son puros a propósito. Una query adentro de un validador rompe la testeabilidad y el diseño. El dato va en el contexto o en una medición.
- Los enums son strings para coincidir con lo persistido. No los toques.
- El módulo tiene DDL propio que hay que correr antes de levantar el código. Ver DDL y ops.
- Los permisos del módulo se siembran al boot: si se agregan permisos al catálogo, hay que reiniciar para que existan en la tabla. Ver Permisos y roles.