feat: parámetros de solo lectura en los reportes (se ven con su valor por defecto y no se pueden cambiar; el servidor los ignora)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-20 16:57:33 -05:00
1 parent f71577e792
commit a472731277
6 files changed
+27 -7

No files matched your search

@@ -24,7 +24,8 @@ export function parseHtmlValues(def: HtmlReportDef, raw: Readonly<Record<string,
const missing: string[] = [];
for (const p of def.params) {
const v = raw[p.name];
let text = typeof v === 'string' ? v.trim() : typeof v === 'number' ? String(v) : '';
// un parámetro de solo lectura vale siempre su valor por defecto, escriba lo que escriba el cliente
let text = p.readOnly ? '' : typeof v === 'string' ? v.trim() : typeof v === 'number' ? String(v) : '';
if (text === '') text = p.defaultValue ?? '';
if (text === '') {
if (p.required) missing.push(p.label);
@@ -48,11 +48,18 @@ describe('registro de reportes HTML', () => {
expect(() => validateHtmlReports([{ ...def, params: [P({ name: 'a' }), P({ name: 'a' })] }])).toThrow(/repite el parámetro/);
expect(() => validateHtmlReports([{ ...def, template: '../fuera.njk' }])).toThrow(/dentro de la carpeta/);
expect(() => validateHtmlReports([{ ...def, template: '/etc/passwd' }])).toThrow(/dentro de la carpeta/);
expect(() => validateHtmlReports([{ ...def, params: [P({ name: 'papel', readOnly: true })] }])).toThrow(/solo lectura y necesita un valor por defecto/);
expect(validateHtmlReports([def])).toHaveLength(1);
});
});
describe('valores de los parámetros', () => {
it('un parámetro de solo lectura vale siempre su valor por defecto, aunque el cliente mande otro', () => {
const fijo = { ...def, params: [P({ name: 'papel', label: 'Papel', kind: 'select', options: [{ value: 'Carta', label: 'Carta' }, { value: 'A4', label: 'A4' }], defaultValue: 'Carta', readOnly: true })] };
expect(parseHtmlValues(fijo, {})).toEqual({ papel: 'Carta' });
expect(parseHtmlValues(fijo, { papel: 'A4' })).toEqual({ papel: 'Carta' });
});
it('exige los obligatorios, aplica el valor por defecto y comprueba tipos', () => {
expect(parseHtmlValues(def, { doc: '144153', cantidad: 6 })).toEqual({ doc: '144153', cantidad: '6', papel: 'Carta', fecha: '' });
expect(() => parseHtmlValues(def, { cantidad: '6' })).toThrow(/Falta indicar: Entrega/);
+1
View File
@@ -16,6 +16,7 @@ export function validateHtmlReports(reports: readonly HtmlReportDef[]): readonly
for (const p of r.params) {
if (names.has(p.name)) throw new Error(`El reporte ${r.code} repite el parámetro ${p.name}`);
names.add(p.name);
if (p.readOnly && !p.defaultValue) throw new Error(`El parámetro ${p.name} de ${r.code} es de solo lectura y necesita un valor por defecto`);
}
}
return reports;
@@ -60,6 +60,15 @@ describe('ReportParamField', () => {
expect(select.value).toBe('2');
});
it('un parámetro de solo lectura se ve con su valor pero no se puede cambiar', async () => {
await show(param({ kind: 'select', label: 'Papel', options: [{ value: 'Carta', label: 'Carta' }], readOnly: true }), 'Carta');
const select = el().querySelector('select') as HTMLSelectElement;
expect(select.value).toBe('Carta');
expect(select.disabled || select.hasAttribute('readonly') || select.closest('[readonly]') !== null).toBe(true);
await show(param({ label: 'Nombre', readOnly: true }), 'fijo');
expect(inputs()[0].readOnly).toBe(true);
});
it('un rango pide desde y hasta y los emite como lista', async () => {
await show(param({ kind: 'date', range: true }), ['2026-01-01', '2026-01-31']);
expect(labels().join(' ')).toContain('desde / hasta');
@@ -24,23 +24,23 @@ const asList = (v: Value): readonly string[] => (typeof v === 'string' ? (v ===
@case ('single') {
@switch (p.kind) {
@case ('number') {
<sbo-number-field [label]="p.label" [required]="p.required" [value]="num(single())" (valueChange)="value.set($event === null ? '' : '' + $event)" />
<sbo-number-field [label]="p.label" [required]="p.required" [readonly]="!!p.readOnly" [value]="num(single())" (valueChange)="value.set($event === null ? '' : '' + $event)" />
}
@case ('date') {
<sbo-date-field [label]="p.label" [required]="p.required" [value]="single()" (valueChange)="value.set($event)" />
<sbo-date-field [label]="p.label" [required]="p.required" [readonly]="!!p.readOnly" [value]="single()" (valueChange)="value.set($event)" />
}
@case ('select') {
<sbo-select-field [label]="p.label" [required]="p.required" [options]="p.options ?? []" placeholder="" [value]="single() || null" (valueChange)="value.set($event === null ? '' : '' + $event)" />
<sbo-select-field [label]="p.label" [required]="p.required" [readonly]="!!p.readOnly" [options]="p.options ?? []" placeholder="" [value]="single() || null" (valueChange)="value.set($event === null ? '' : '' + $event)" />
}
@case ('lookup') {
@if (catalog(); as c) {
<sbo-lookup-field [label]="p.label" [required]="p.required" [lookup]="c.lookup" resolveOnLeave [value]="single()" (valueChange)="value.set($event)" />
<sbo-lookup-field [label]="p.label" [required]="p.required" [readonly]="!!p.readOnly" [lookup]="c.lookup" resolveOnLeave [value]="single()" (valueChange)="value.set($event)" />
} @else {
<sbo-text-field [label]="p.label" [required]="p.required" [value]="single()" (valueChange)="value.set($event)" />
<sbo-text-field [label]="p.label" [required]="p.required" [readonly]="!!p.readOnly" [value]="single()" (valueChange)="value.set($event)" />
}
}
@default {
<sbo-text-field [label]="p.label" [required]="p.required" [placeholder]="p.placeholder ?? ''" [value]="single()" (valueChange)="value.set($event)" />
<sbo-text-field [label]="p.label" [required]="p.required" [readonly]="!!p.readOnly" [placeholder]="p.placeholder ?? ''" [value]="single()" (valueChange)="value.set($event)" />
}
}
}