-
Notifications
You must be signed in to change notification settings - Fork 0
/
row-validator.ts
335 lines (324 loc) · 15.3 KB
/
row-validator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
"use strict";
export interface Opcion<V>{
salto?:V|null // Destino del salto para la opción seleccionada
}
export type RowData<V extends string, D> = Record<V, D>
export type FuncionHabilitar<V extends string, D> = (formData: RowData<V, D>) => boolean
export type FuncionValoradora<V extends string, D> = (formData: RowData<V, D>) => D | null
export interface Variable<V extends string, D, FIN>{
optativa?:boolean // Obligatoriedad el ingreso de la variable
salto?:V|FIN|null // Destino del salto en caso de saltos icondicionales
tipo:'opciones'|'numerico'|'texto'|string
opciones?:Record<string|number, Opcion<V|FIN>>
maximo?:number|null // Máximo valor válido
minimo?:number|null // Míximo valor válido
// Para variables de especifique dependientes de una opción:
subordinadaVar?:V|null // variable de la que depende
subordinadaValor?:D|null // valor que la activa
saltoNsNr?:V|FIN|null // Salto en el caso de no respuesta o NS/NC
calculada?:boolean|null // Si la variable es calculada (no ingresada)
funcionHabilitar?:FuncionHabilitar<V,D>|string|null // Determina la habilitación dinámica
libre?:boolean|null // Posibilidad de ingresarla aunque esté salteada
funcionAutoIngresar?:FuncionValoradora<V,D>|string|null // Determina un cálculo para el valor inicial de una variable que se muestra al ser actual
}
export interface Structure<V extends string, D, FIN = true>{
variables:Record<V, Variable<V, D, FIN>>
marcaFin?:FIN
}
export type EstadoVariableNormales = 'actual'|'valida'|'todavia_no'|'calculada'|'salteada'|'optativa_sd'
export type EstadoVariableErroneas = 'invalida'|'omitida'|'fuera_de_rango'|'fuera_de_flujo_por_omitida'|'fuera_de_flujo_por_salto'
export type EstadoVariable = EstadoVariableNormales | EstadoVariableErroneas
export type Feedback<V extends string, FIN>={
estado:EstadoVariable
siguiente:V|FIN|null|undefined
apagada:boolean
inhabilitada:boolean
conDato:boolean
conProblema:boolean
pendiente:boolean|null
}
export type FormStructureState<V extends string, D, FIN> = {
resumen:'vacio'|'con problemas'|'incompleto'|'ok'
feedbackResumen:Omit<Feedback<V,FIN>,'siguiente'|'apagada'|'inhabilitada'>
feedback:Record<V, Feedback<V,FIN>>
estados:Partial<Record<V, EstadoVariable>>
siguientes:Partial<Record<V, V|FIN|null>>
actual:V|null
primeraVacia?:V|null
primeraFalla:V|null
autoIngresadas?:Partial<RowData<V,D>>
};
export interface RowValidatorSetup<V extends string, D>{ // TODO PARAMETRIZR LOS TIPOS
getFuncionHabilitar: (name:string) => FuncionHabilitar<V, D>
getFuncionValorar: (name:string) => FuncionValoradora<V, D>
nsnrTipicos:Record<string, boolean>
multiEstado:boolean|null
}
export type OpcionesRowValidator={
autoIngreso?: boolean
}
export function getRowValidator<V extends string, D, FIN>(_setup:Partial<RowValidatorSetup<V, D>>){
var setup:RowValidatorSetup<V, D>={
getFuncionHabilitar:(nombre:string)=>{
throw new Error('rowValidator error. No existe la funcion habilitadora '+nombre);
},
getFuncionValorar:(nombre:string)=>{
throw new Error('rowValidator error. No existe la funcion valoradora '+nombre);
},
nsnrTipicos:{
"-1":true,
"-9":true,
},
multiEstado:null,
..._setup
};
return function rowValidator(estructura:Structure<V, D, FIN>, formData:RowData<V, D>, opts?:OpcionesRowValidator){
let getFuncionHabilitar = (nameOrFun: null | undefined | string | FuncionHabilitar<V,D>) =>
nameOrFun == null ? ()=>true :
nameOrFun instanceof Function ? nameOrFun : setup.getFuncionHabilitar(nameOrFun);
let getFuncionValorar = (nameOrFun: null | undefined | string | FuncionValoradora<V,D>) =>
nameOrFun == null ? ()=>null :
nameOrFun instanceof Function ? nameOrFun : setup.getFuncionValorar(nameOrFun);
var rta:FormStructureState<V, D, FIN>={
feedback:{} as FormStructureState<V, D, FIN>['feedback'],
feedbackResumen:{} as Feedback<V,FIN>,
estados:{},
siguientes:{},
actual:null, primeraFalla:null, resumen:'vacio'
};
if(opts?.autoIngreso){
rta.autoIngresadas = {}
}
var respuestas=0;
var libres=0;
var problemas=0;
var variableAnterior=null;
var yaPasoLaActual=false; // si ya vi la variable "actual"
var enSaltoAVariable=null; // null si no estoy saltando y el destino del salto si estoy dentro de un salto.
var conOmitida=false; // para poner naranja
var miVariable:V; // variable actual del ciclo
var variableOrigenSalto:V|null = null;
for(miVariable in estructura.variables){
let apagada:boolean=false;
const feedback={
conProblema:false,
inhabilitada:false,
pendiente:null,
apagada:false,
} as Feedback<V,FIN>;
var falla=function(estado:EstadoVariable){
problemas++;
feedback.conProblema=true;
feedback.estado=estado;
if(!rta.primeraFalla){
rta.primeraFalla=miVariable;
}
};
var revisar_saltos_especiales=false;
var valor=formData[miVariable];
const estructuraVar = estructura.variables[miVariable];
feedback.conDato=valor!=null;
if(estructuraVar.calculada){
apagada=true;
feedback.estado='calculada';
}else if(conOmitida){
falla('fuera_de_flujo_por_omitida');
}else if(enSaltoAVariable && miVariable!=enSaltoAVariable && (estructuraVar.subordinadaVar != variableOrigenSalto || estructuraVar.subordinadaValor != formData[variableOrigenSalto!])){
apagada=true;
// estoy dentro de un salto válido, no debería haber datos ingresados.
if(valor == null || estructuraVar.libre){
feedback.estado='salteada';
}else{
falla('fuera_de_flujo_por_salto');
}
}else if(yaPasoLaActual){
if(valor == null || estructuraVar.libre){
feedback.estado='todavia_no';
}else{
conOmitida=true;
if(!rta.primeraFalla){
rta.primeraFalla=rta.actual;
}
falla('fuera_de_flujo_por_omitida');
feedback.conProblema=feedback.conDato;
}
}else if(
/* caso 1 */
estructuraVar.subordinadaVar!=null
&& formData[estructuraVar.subordinadaVar]!=estructuraVar.subordinadaValor
|| /* caso 2*/
estructuraVar.tipo != 'filtro'
&& !getFuncionHabilitar(estructuraVar.funcionHabilitar)(formData)
){ // la variable está inhabilitada ya sea por:
// 1) está subordinada y no es el valor que la activa
// 2) la expresión habilitar falla
apagada=true;
if(valor == null){
feedback.estado='salteada';
}else{
falla('fuera_de_flujo_por_salto');
}
feedback.inhabilitada=true;
}else{
// no estoy en una variable salteada y estoy dentro del flujo normal (no hubo omitidas hasta ahora).
enSaltoAVariable=null; // si estaba en un salto acá se acaba
if(valor == null){
if(estructuraVar.tipo=='filtro'){
/* istanbul ignore if */
if(yaPasoLaActual){
feedback.estado='todavia_no';
}else{
// hay que calcular si el filtro salta
let habilitado = getFuncionHabilitar(estructuraVar.funcionHabilitar)(formData)
if(habilitado){
feedback.estado='valida';
}else{
enSaltoAVariable=estructuraVar.salto;
feedback.estado='salteada';
variableOrigenSalto = miVariable;
}
}
}else{
if(!rta.primeraVacia){
rta.primeraVacia=miVariable;
}
if(opts && opts.autoIngreso){
let nuevoValor = getFuncionValorar(estructuraVar.funcionAutoIngresar)(formData);
if(nuevoValor != null){
rta.autoIngresadas![miVariable] = nuevoValor;
}
}
if(!estructuraVar.optativa){
feedback.estado='actual';
feedback.pendiente=true;
rta.actual=miVariable;
yaPasoLaActual=miVariable!==null;
}else{
feedback.estado='optativa_sd';
if(estructuraVar.salto){
enSaltoAVariable=estructuraVar.salto;
variableOrigenSalto = miVariable;
}
}
}
}else{
respuestas++;
if(estructuraVar.libre){
libres++;
}
// hay algo ingresado hay que validarlo
if(setup.nsnrTipicos[valor as unknown as string]){
feedback.estado='valida';
if(estructuraVar.saltoNsNr){
enSaltoAVariable=estructuraVar.saltoNsNr;
variableOrigenSalto = miVariable;
}
feedback.pendiente=false;
}else if(estructuraVar.tipo=='opciones'){
if(estructuraVar.opciones==null){
throw new Error('rowValidator error. Variable "'+miVariable+'" sin opciones')
}
if(estructuraVar.opciones[valor as unknown as string]){
feedback.estado='valida';
feedback.pendiente=false;
if(estructuraVar.opciones[valor as unknown as string].salto){
enSaltoAVariable=estructuraVar.opciones[valor as unknown as string].salto;
variableOrigenSalto = miVariable;
}
}else{
falla('invalida');
}
}else if(estructuraVar.tipo=='numerico'){
var valorNumerico = Number(valor)
// @ts-expect-error No hay manera (por ahora) de que sepa que este valor en particular es un número
valor=valorNumerico
if(estructuraVar.maximo !=null && valorNumerico > estructuraVar.maximo
|| estructuraVar.minimo != null && valorNumerico < estructuraVar.minimo){
falla('fuera_de_rango');
}else{
feedback.estado='valida';
feedback.pendiente=false;
}
}else{
// las de texto o de ingreso libre son válidas si no se invalidaron antes por problemas de flujo
feedback.estado='valida';
feedback.pendiente=false;
}
if(enSaltoAVariable==null && estructuraVar.salto){
enSaltoAVariable=estructuraVar.salto;
variableOrigenSalto = miVariable;
}
revisar_saltos_especiales=true;
}
if (revisar_saltos_especiales){
}
}
/* istanbul ignore next */
if(feedback.estado==null){
throw new Error('No se pudo validar la variable '+miVariable);
}
if(apagada){
feedback.pendiente=false;
}else if(estructuraVar.tipo!='filtro'){
if(variableAnterior && !rta.siguientes[variableAnterior]){
rta.feedback[variableAnterior].siguiente=miVariable;
}
variableAnterior=miVariable;
}
if(!estructuraVar.calculada){
feedback.siguiente=enSaltoAVariable; // es null si no hay salto (o sea sigue con la próxima o es la última)
}else{
feedback.siguiente=null;
}
if(!feedback.inhabilitada && !getFuncionHabilitar!(estructuraVar.funcionHabilitar)(formData)){
feedback.inhabilitada=true;
}
rta.feedback[miVariable]=feedback;
}
for(miVariable in estructura.variables){
let feedback=rta.feedback[miVariable];
if(conOmitida){
if(feedback.estado=='actual'){
feedback.estado='omitida';
}else if(feedback.estado=='todavia_no'){
feedback.estado='fuera_de_flujo_por_omitida';
}
}
if(setup.multiEstado!==true){
rta.estados[miVariable]=feedback.estado;
rta.siguientes[miVariable]=feedback.siguiente;
}
if(feedback.estado=='todavia_no'){
if(feedback.inhabilitada){
feedback.pendiente=false;
}else{
feedback.pendiente=null;
}
}
rta.feedbackResumen.estado = rta.feedbackResumen.conProblema?rta.feedbackResumen.estado:feedback.estado;
rta.feedbackResumen.conDato = rta.feedbackResumen.conDato || feedback.conDato;
rta.feedbackResumen.conProblema = rta.feedbackResumen.conProblema || feedback.conProblema;
rta.feedbackResumen.pendiente = rta.feedbackResumen.pendiente || feedback.pendiente;
}
if(problemas){
rta.resumen='con problemas';
}else{
if(rta.actual){
if(respuestas>libres){
rta.resumen='incompleto';
}else{
rta.resumen='vacio';
}
}else{
rta.resumen='ok';
}
}
if(setup.multiEstado===false){
// @ts-ignore
delete rta.feedbackResumen;
// @ts-ignore
delete rta.feedback;
}
return rta;
}
}