diff --git a/mvp/b2c/src/budget/compute.ts b/mvp/b2c/src/budget/compute.ts index f729df3..756166f 100644 --- a/mvp/b2c/src/budget/compute.ts +++ b/mvp/b2c/src/budget/compute.ts @@ -81,10 +81,12 @@ export function computeBudget( })); const subtotal = partidas.reduce((s, p) => s + p.importe, 0); - const factorZona = (inputs.provincia && config.factorZona[inputs.provincia]) || 1; + const factorZona = config.factorZona[inputs.provincia ?? ''] ?? 1; const total = Math.round(subtotal * factorZona); - const hasExact = Object.keys(inputs.materialSelections).length > 0; + const hasExact = (Object.values(inputs.materialSelections) as string[]).some( + (id) => catalog.some((c) => c.id === id), + ); const hasM2 = inputs.m2Suelo != null && inputs.m2Suelo > 0; let confianza: BudgetResult['confianza']; let band: number; diff --git a/mvp/b2c/tests/budget/compute.test.ts b/mvp/b2c/tests/budget/compute.test.ts index 34ea6aa..601c9d9 100644 --- a/mvp/b2c/tests/budget/compute.test.ts +++ b/mvp/b2c/tests/budget/compute.test.ts @@ -76,4 +76,29 @@ describe('computeBudget', () => { const r = computeBudget(inputs({}), config, sinPintura); expect(r.avisos.some((a) => a.includes('pintura'))).toBe(true); }); + + it('usa factor zona 1 para provincia desconocida', () => { + const r = computeBudget(inputs({ provincia: 'Cuenca' }), config, catalog); + expect(r.factorZona).toBe(1); + expect(r.total).toBe(536000); // subtotal sin factor + }); + + it('respeta un factor zona explícito de 0', () => { + const configZero: PricingConfig = { ...config, factorZona: { ...config.factorZona, Madrid: 0 } }; + const r = computeBudget(inputs({ provincia: 'Madrid' }), configZero, catalog); + expect(r.factorZona).toBe(0); + expect(r.total).toBe(0); + }); + + it('no sube la confianza si la selección apunta a un material inexistente', () => { + const r = computeBudget(inputs({ materialSelections: { suelo: 'no-existe' } }), config, catalog); + expect(r.confianza).toBe('media'); // tiene m² pero la selección no resuelve -> no es alta + }); + + it('omite carpintería y no rompe para tipos sin mobiliario (salón)', () => { + const r = computeBudget(inputs({ tipoReforma: 'salon' }), config, catalog); + const byKey = Object.fromEntries(r.partidas.map((p) => [p.key, p.importe])); + expect(byKey.carpinteria).toBeUndefined(); + expect(r.total).toBeGreaterThan(0); + }); });