everest-config
v3.1.2
Published
Generic form and section configuration manager
Maintainers
Readme
everest-config
Librería TypeScript puro, zero-dependency para gestión de parametrización genérica de formularios y secciones. Equivalente frontend de la librería .NET EverestConfig (NuGet).
Compatible con Angular 6, 7, 8, 16, 20 y 21 y con cualquier proyecto JavaScript/TypeScript sin framework. Diseño agnóstico: recibe el cliente HTTP como inyección de dependencia.
Compatible con TypeScript 3.5+ (Angular 8).
Instalación
npm install everest-configEndpoint que consume
GET /api/Configuracion/GetConfig?key={key}&epsId={epsId}&contratoId={contratoId}El backend filtra los campos por epsId y contratoId antes de responder. La librería recibe la lista ya resuelta e indexa todos los campos sin re-filtrar en cliente.
Wrapper Angular (escribirlo una vez en el proyecto)
El mismo wrapper funciona en Angular 6–8 (NgModule) y Angular 16+ (standalone):
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { EverestConfigService } from 'everest-config';
@Injectable({ providedIn: 'root' })
export class ConfigService extends EverestConfigService {
constructor(private http: HttpClient) {
super(
(url) => this.http.get<any>(url).toPromise(),
'https://mi-api.com'
);
}
}Uso en un componente Angular 8
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ConfigHandle, FieldValidator } from 'everest-config';
import { ConfigService } from './config.service';
@Component({ selector: 'app-morbilidad', templateUrl: './morbilidad.component.html' })
export class MorbilidadComponent implements OnInit {
form: FormGroup;
config: ConfigHandle;
constructor(private fb: FormBuilder, private configService: ConfigService) {}
async ngOnInit() {
this.config = await this.configService.getConfig('hc.morbilidad', 21, 1);
if (!this.config) return;
this.form = this.fb.group({
ordenamientos: [''],
diagnosticos: [''],
prescripciones: ['']
});
// Aplicar visibilidad, readonly y required al formulario en una sola pasada
for (const fieldId in this.form.controls) {
const field = this.config.getFieldConfig(fieldId);
if (!field) continue;
const control = this.form.get(fieldId);
if (!field.visible || field.readonly) control.disable();
if (field.required) control.setValidators([Validators.required]);
control.updateValueAndValidity();
}
}
isVisible(fieldId: string): boolean {
return this.config ? this.config.isVisible(fieldId) : false;
}
onSubmit() {
if (this.form.invalid) return;
// enviar
}
}<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div *ngIf="isVisible('ordenamientos')">
<label>Ordenamientos</label>
<select formControlName="ordenamientos">
<option value="">Seleccione...</option>
</select>
</div>
<div *ngIf="isVisible('diagnosticos')">
<label>Diagnósticos</label>
<select formControlName="diagnosticos">
<option value="">Seleccione...</option>
</select>
</div>
<button type="submit">Guardar</button>
</form>Uso sin Angular (fetch nativo)
import { EverestConfigService } from 'everest-config';
const service = new EverestConfigService(
(url) => fetch(url).then(r => r.json()),
'https://mi-api.com'
);
const config = await service.getConfig('hc.morbilidad', 21, 1);
if (config) {
config.isVisible('ordenamientos');
config.getCustomProperty<boolean>('ordenamientos', 'nota');
}Firma de getConfig
getConfig(key: string, epsId?: number, contratoId?: number): Promise<ConfigHandle | null>| Parámetro | Tipo | Obligatorio | |---|---|---| | key | string | ✅ | | epsId | number | ❌ | | contratoId | number | ❌ |
Retorna null si la API no encontró configuración o hubo un error de red.
// Solo key
const config = await service.getConfig('hc.morbilidad');
// Key + epsId
const config = await service.getConfig('hc.morbilidad', 21);
// Key + epsId + contratoId
const config = await service.getConfig('hc.morbilidad', 21, 1);API — ConfigHandle
Cada llamada a getConfig retorna un ConfigHandle independiente. Todas las operaciones son O(1) — el índice se construye una sola vez al cargar.
// Verificar existencia
config.has('ordenamientos') // boolean
// Objeto completo
config.getFieldConfig('ordenamientos') // FieldConfig | null
// Estados
config.isVisible('ordenamientos') // boolean
config.isRequired('ordenamientos') // boolean
config.isReadonly('ordenamientos') // boolean
// Tipo
config.getFieldType('ordenamientos') // FieldType | undefined
// Propiedad custom con tipo genérico
config.getCustomProperty<boolean>('ordenamientos', 'nota') // boolean | undefined
config.getCustomProperty<string[]>('ordenamientos', 'tipos') // string[] | undefined
// Todos los campos indexados
config.getAll() // ReadonlyMap<string, FieldConfig>
// Campos por sección
config.getFieldsBySection('conducta.ordenamientos') // FieldConfig[]Múltiples configs independientes
// Dos configs cargadas simultáneamente — no se interfieren
const morbilidad = await service.getConfig('hc.morbilidad', 21, 1);
const cronicos = await service.getConfig('hc.cronicos', 21, 1);
morbilidad.isVisible('ordenamientos');
cronicos.isVisible('tension');Aplicar config al formulario en una pasada
// Iterar todos los campos indexados y aplicar sus estados
for (const [fieldId, field] of this.config.getAll()) {
const control = this.form.get(fieldId);
if (!control) continue;
if (!field.visible || field.readonly) control.disable();
if (field.required) control.setValidators([Validators.required]);
control.updateValueAndValidity();
}CustomProperties con tipado
// Tipos de ordenamiento con propiedades personalizadas
interface TipoOrdenamiento {
name: string;
posfechado: boolean;
nota: boolean;
cantidad: boolean;
}
const tipos = config.getCustomProperty<TipoOrdenamiento[]>('ordenamientos', 'types');
if (tipos) {
tipos.forEach(t => {
console.log(t.name, t.posfechado);
});
}FieldValidator
Valida un valor contra la configuración del campo. Los mensajes de error usan el label del campo si está disponible.
import { FieldValidator, FieldConfig, FieldType } from 'everest-config';
const field: FieldConfig = {
id: 'ordenamientos',
label: 'Ordenamientos',
visible: true,
required: true,
type: FieldType.Select
};
FieldValidator.validate('laboratorio', field); // { valid: true }
FieldValidator.validate('', field); // { valid: false, message: 'El campo Ordenamientos requiere una selección' }
FieldValidator.validate(null, field); // { valid: false, message: 'El campo Ordenamientos es requerido' }Modelos públicos
interface FieldConfig {
id: string;
visible: boolean;
required: boolean | null;
readonly?: boolean;
label?: string;
description?: string;
type?: FieldType;
section?: string;
validationRegex?: string | null;
customProperties?: Record<string, unknown>;
}
enum FieldType {
String = 'string',
Number = 'number',
Boolean = 'boolean',
Date = 'date',
Datetime = 'datetime',
Select = 'select',
Card = 'card',
Custom = 'custom'
}Caché en memoria
La misma combinación de parámetros nunca hace dos llamadas HTTP. La clave de caché es key_epsId_contratoId.
await service.getConfig('hc.morbilidad', 21, 1); // fetch → guarda en caché
await service.getConfig('hc.morbilidad', 21, 1); // devuelve desde caché — sin HTTP
await service.getConfig('hc.morbilidad', 21, 2); // fetch distinto (contratoId diferente)
await service.getConfig('hc.cronicos', 21, 1); // fetch distinto (key diferente)Logs
[EverestConfig] getConfig: configuración no encontrada | key: "hc.morbilidad" | epsId: 21 | contratoId: 1
[EverestConfig] isVisible: campo no encontrado | key: "hc.morbilidad" | fieldId: "ordenamientos"
[EverestConfig] getCustomProperty: propiedad no definida | key: "hc.morbilidad" | fieldId: "ordenamientos" | property: "tipos"JSON de respuesta esperado
[
{
"key": "hc.morbilidad",
"version": "1.0",
"fields": [
{
"id": "ordenamientos",
"section": "conducta.ordenamientos",
"label": "Laboratorios",
"visible": true,
"required": false,
"readonly": false,
"type": "select",
"validationRegex": null,
"customProperties": {
"types": [
{ "name": "laboratorio", "posfechado": true, "nota": false, "cantidad": false },
{ "name": "consulta", "posfechado": true, "nota": true, "cantidad": true },
{ "name": "imagenologia", "posfechado": true, "nota": true, "cantidad": true }
]
}
},
{
"id": "diagnosticos",
"section": "conducta.diagnosticos",
"label": "Diagnósticos",
"visible": true,
"required": false,
"type": "select",
"customProperties": {}
},
{
"id": "prescripciones",
"section": "conducta.medicamentos",
"label": "Últimas Prescripciones",
"visible": false,
"required": false,
"readonly": true,
"type": "select",
"customProperties": {}
}
]
}
]Changelog
v2.2.0
- El endpoint devuelve un objeto único en lugar de un array —
fetcherpasa dePromise<EverestConfigEntry[]>aPromise<EverestConfigEntry | null> - Wrapper Angular: cambiar
get<any[]>porget<any>
v2.1.0
getConfig(key, epsId?, contratoId?)—keypasa a ser el primer parámetro;epsIdycontratoIdopcionales- Filtrado delegado al backend — el cliente indexa todos los campos devueltos sin re-filtrar
FieldConfigya no exponeepsId/contratoId(campos internos del backend)- Nuevo campo
validationRegexenFieldConfig - Eliminado operador
??yexport type {}— compatible con TypeScript 3.5 (Angular 8)
v2.0.0
- Patrón
ConfigHandle— handles independientes por config, lookups O(1) - Filtrado por
epsIden cliente (soportenumber,number[],undefined) - Campo
readonly, tipoDatetime,required: boolean | null - Zero dependencies, compatible Angular 6–21
Licencia
MIT
