feat(desarrollos): modulo de alta de desarrollos con inventario, reglas del cotizador por desarrollo y micrositio publico
Modulo nuevo (solo admin) para dar de alta desarrollos sin tocar codigo: - CRUD de la ficha, modelos e inventario (alta manual y generador por rango) - Reglas del cotizador por desarrollo, editables y persistidas en la BD - El cotizador consume las reglas desde /desarrollos/reglas-cotizador y cae a las constantes con aviso cuando un desarrollo aun no tiene reglas guardadas - Micrositio publico en /d/:clave (sin sesion) con ficha, modelos y disponibilidad; jamas expone datos de clientes ni cotizaciones Integracion: - La landing monta el modulo en el grid (clave 'desarrollos', solo admin) y el micrositio fuera del login; el rol manager NO accede (matriz y API con 403) - La tabla de roles de Configuracion ahora lista todos los modulos - Se alinea la FK de ReglaCotizadorDesarrollo con la BD viva (Desarrollo.id) Verificado: 708 pruebas del API, 115 del cotizador y 89 de la landing en verde; 12/12 cotizaciones identicas antes y despues del cambio de reglas; prueba en vivo del API con alta, modelos, inventario, reglas, micrositio y baja (30/30).
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest'
|
||||
import { getUnidades } from './api'
|
||||
import { getReglasCotizador, getUnidades } from './api'
|
||||
import { INVENTARIO_SEED } from '../data/unidades'
|
||||
import type { Unidad } from '../types'
|
||||
|
||||
@@ -65,3 +65,43 @@ describe('getUnidades', () => {
|
||||
expect(warn).toHaveBeenCalledOnce()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getReglasCotizador', () => {
|
||||
it('devuelve las reglas por desarrollo que manda el API', async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue(
|
||||
respuestaOk({
|
||||
reglasPorDesarrollo: {
|
||||
DIAGONAL: { nombre: 'Diagonal', propuestas: [{ tipo: 'CONTADO', descuentoDefault: 0.05 }] },
|
||||
},
|
||||
}),
|
||||
)
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
|
||||
const reglas = await getReglasCotizador()
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith('/api/desarrollos/reglas-cotizador', {})
|
||||
// Lo que manda el API gana; lo que falta se completa con la constante
|
||||
expect(reglas['DIAGONAL']?.propuestas[0]?.descuentoDefault).toBe(0.05)
|
||||
expect(reglas['DIAGONAL']?.propuestas[0]?.pctEnganche).toBe(0.9)
|
||||
expect(reglas['DIAGONAL']?.nDefault).toBe(13)
|
||||
})
|
||||
|
||||
it('sin respuesta del API devuelve un mapa vacío (todo cae a las constantes)', async () => {
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('error', { status: 500 })))
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
|
||||
const reglas = await getReglasCotizador()
|
||||
|
||||
expect(reglas).toEqual({})
|
||||
expect(warn).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('respuesta con reglas corruptas: esas entradas se descartan', async () => {
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue(respuestaOk({ reglasPorDesarrollo: { SENDA: 'roto', ATELIER: null } })),
|
||||
)
|
||||
|
||||
expect(await getReglasCotizador()).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import type { Unidad, TipoPropuesta } from '../types'
|
||||
import { INVENTARIO_SEED } from '../data/unidades'
|
||||
import { reglasDesdeApi, type ReglasDesarrollo } from '../lib/reglas'
|
||||
import { conAuth, manejar401 } from './auth'
|
||||
import { guardarUnidades, cargarUnidadesLocal } from './offline-db'
|
||||
import {
|
||||
guardarUnidades,
|
||||
cargarUnidadesLocal,
|
||||
guardarReglasLocal,
|
||||
cargarReglasLocal,
|
||||
} from './offline-db'
|
||||
|
||||
// TODO produccion: '/api' same-origin detras de NGINX
|
||||
const API_URL = import.meta.env.VITE_API_URL || '/api'
|
||||
@@ -38,6 +44,32 @@ export async function getUnidades(): Promise<GetUnidadesResult> {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Reglas comerciales del cotizador ----------
|
||||
|
||||
/**
|
||||
* Reglas comerciales EFECTIVAS por desarrollo (`GET /desarrollos/reglas-cotizador`):
|
||||
* las que el admin configuró en la BD, con las constantes de respaldo para los
|
||||
* desarrollos sin reglas guardadas. Nunca lanza: sin servidor devuelve la
|
||||
* última copia guardada y, si no hay, un mapa vacío (todo cae a las constantes).
|
||||
*/
|
||||
export async function getReglasCotizador(): Promise<ReglasDesarrollo> {
|
||||
try {
|
||||
const resp = await fetch(`${API_URL}/desarrollos/reglas-cotizador`, conAuth())
|
||||
manejar401(resp)
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
const cuerpo = (await resp.json()) as { reglasPorDesarrollo?: unknown } | null
|
||||
const reglas = reglasDesdeApi(cuerpo?.reglasPorDesarrollo)
|
||||
void guardarReglasLocal(reglas)
|
||||
return reglas
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
'[api] No se pudieron obtener las reglas del cotizador; se usan los valores por defecto.',
|
||||
err,
|
||||
)
|
||||
return reglasDesdeApi(await cargarReglasLocal())
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Cotizaciones guardadas (historial del vendedor) ----------
|
||||
|
||||
/** Tipo de propuesta tal como lo espera el API (body de POST /cotizaciones) */
|
||||
|
||||
@@ -32,6 +32,26 @@ export async function cargarUnidadesLocal(): Promise<unknown> {
|
||||
}
|
||||
}
|
||||
|
||||
// Las reglas del cotizador se guardan en la misma tienda que el inventario
|
||||
// (clave propia), sin subir la versión de la BD.
|
||||
export async function guardarReglasLocal(reglas: unknown) {
|
||||
try {
|
||||
const db = await getDB()
|
||||
await db.put('unidades', reglas, 'reglas')
|
||||
} catch {
|
||||
// Sin almacenamiento: la próxima sesión las recarga del API
|
||||
}
|
||||
}
|
||||
|
||||
export async function cargarReglasLocal(): Promise<unknown> {
|
||||
try {
|
||||
const db = await getDB()
|
||||
return db.get('unidades', 'reglas')
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export async function guardarCotizaciones(cotizaciones: unknown[]) {
|
||||
try {
|
||||
const db = await getDB()
|
||||
|
||||
Reference in New Issue
Block a user