Files
reformix-hackaton/mvp/b2c/tests/budget/edit.test.ts
Carlos Narro ec141cdd6e Añade revisión pre-envío del reformista y PDF de presupuesto pulido
Adelanta de F1.5 a F2 la validación pre-envío: el panel permite elegir
modo de envío (automático/revisión), editar los conceptos del
presupuesto y enviar al cliente por WhatsApp (simulado).

Añade datos de empresa y logo configurables en /panel/empresa y genera
el presupuesto como PDF real descargable con esa marca vía
@react-pdf/renderer, sustituyendo la vista HTML imprimible.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 22:27:05 +02:00

61 lines
2.3 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { applyConceptoEdits, AVISO_EDITADO } from '@/budget/edit';
import type { BudgetResult } from '@/budget/types';
const base: BudgetResult = {
partidas: [
{ key: 'demolicion', label: 'Demolición', importe: 100000 },
{ key: 'alicatado', label: 'Alicatado y solado', importe: 200000 },
],
subtotal: 300000,
factorZona: 1.2,
total: 360000,
rango: { min: 324000, max: 396000 },
confianza: 'media',
materialesRender: ['azulejo blanco'],
avisos: ['Sin precio para pintura'],
};
describe('applyConceptoEdits', () => {
it('recalcula subtotal y total aplicando el factor de zona', () => {
const result = applyConceptoEdits(base, [
{ key: 'demolicion', label: 'Demolición', importe: 150000 },
{ key: 'alicatado', label: 'Alicatado y solado', importe: 200000 },
]);
expect(result.subtotal).toBe(350000);
expect(result.total).toBe(420000); // 350000 * 1.2
});
it('permite añadir partidas libres y quitar existentes', () => {
const result = applyConceptoEdits(base, [
{ key: 'demolicion', label: 'Demolición', importe: 100000 },
{ key: 'custom-1', label: 'Imprevistos de obra', importe: 50000 },
]);
expect(result.partidas).toHaveLength(2);
expect(result.partidas[1]).toEqual({ key: 'custom-1', label: 'Imprevistos de obra', importe: 50000 });
expect(result.subtotal).toBe(150000);
});
it('descarta partidas sin etiqueta y normaliza importes (>=0, enteros)', () => {
const result = applyConceptoEdits(base, [
{ key: 'a', label: ' ', importe: 999 },
{ key: 'b', label: 'Válida', importe: -500 },
{ key: 'c', label: 'Decimal', importe: 123.7 },
]);
expect(result.partidas).toEqual([
{ key: 'b', label: 'Válida', importe: 0 },
{ key: 'c', label: 'Decimal', importe: 124 },
]);
});
it('marca el presupuesto como ajustado a mano (aviso idempotente, confianza alta, rango = total)', () => {
const once = applyConceptoEdits(base, base.partidas);
expect(once.confianza).toBe('alta');
expect(once.avisos).toContain(AVISO_EDITADO);
expect(once.rango).toEqual({ min: once.total, max: once.total });
const twice = applyConceptoEdits(once, once.partidas);
expect(twice.avisos.filter((a) => a === AVISO_EDITADO)).toHaveLength(1);
});
});