feat(alta): borrador, pre-llenado desde cotización y liga remota de firma
- Borradores por vendedor (guardar/continuar/eliminar), se limpia al registrar - Pre-llenado con ?cotizacion=<id> (cliente, contacto, proyecto, montos); botón 'Iniciar alta' en Mis cotizaciones del cotizador - Liga remota pública /client/firma/<token> (JWT scope firma-remota, 72h, rate limit, solo datos mínimos sin PLD): firma digital + subida de documentos que prende checkboxes en Monday Apartado - Tokens de liga no sirven como sesión y viceversa - Tests: api 228, alta 82, cotizador 82
This commit is contained in:
54
apps/alta-clientes/src/lib/cotizacionAlta.test.ts
Normal file
54
apps/alta-clientes/src/lib/cotizacionAlta.test.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { cotizacionAPrellenado, type CotizacionParaAlta } from './cotizacionAlta'
|
||||
import { PROYECTOS } from '../data/constantes'
|
||||
|
||||
const base: CotizacionParaAlta = {
|
||||
clienteNombre: 'María Elena López García',
|
||||
clienteEmail: 'maria@example.com',
|
||||
clienteTelefono: '4421234567',
|
||||
desarrollo: 'DIAGONAL',
|
||||
unidadCodigo: 'U-101',
|
||||
precioFinal: 1450000,
|
||||
enganche: 217500,
|
||||
}
|
||||
|
||||
describe('cotizacionAPrellenado', () => {
|
||||
it('prellena nombre y contacto del cliente', () => {
|
||||
const pre = cotizacionAPrellenado(base)
|
||||
expect(pre.datosGenerales.nombreCompleto).toBe('MARÍA ELENA LÓPEZ GARCÍA')
|
||||
expect(pre.datosGenerales.email).toBe('maria@example.com')
|
||||
expect(pre.datosGenerales.celular).toBe('4421234567')
|
||||
})
|
||||
|
||||
it('omite email/celular cuando la cotización no los trae', () => {
|
||||
const pre = cotizacionAPrellenado({ ...base, clienteEmail: null, clienteTelefono: null })
|
||||
expect(pre.datosGenerales).not.toHaveProperty('email')
|
||||
expect(pre.datosGenerales).not.toHaveProperty('celular')
|
||||
})
|
||||
|
||||
it('mapea un desarrollo conocido al dropdown de proyecto', () => {
|
||||
const pre = cotizacionAPrellenado(base)
|
||||
expect(PROYECTOS).toContain('DIAGONAL')
|
||||
expect(pre.viviendaCredito.proyecto).toBe('DIAGONAL')
|
||||
expect(pre.viviendaCredito.proyectoOtro).toBe('')
|
||||
})
|
||||
|
||||
it('un desarrollo fuera de PROYECTOS cae en OTRO con el nombre en texto libre', () => {
|
||||
const pre = cotizacionAPrellenado({ ...base, desarrollo: 'JURIQUILLA' })
|
||||
expect(pre.viviendaCredito.proyecto).toBe('OTRO')
|
||||
expect(pre.viviendaCredito.proyectoOtro).toBe('JURIQUILLA')
|
||||
})
|
||||
|
||||
it('prellena unidad, valor de venta y enganche', () => {
|
||||
const pre = cotizacionAPrellenado(base)
|
||||
expect(pre.viviendaCredito.ubicacion).toBe('U-101')
|
||||
expect(pre.viviendaCredito.valorVenta).toBe('1450000')
|
||||
expect(pre.viviendaCredito.enganche).toBe('217500')
|
||||
})
|
||||
|
||||
it('montos en cero quedan vacíos (no inventa "0")', () => {
|
||||
const pre = cotizacionAPrellenado({ ...base, precioFinal: 0, enganche: 0 })
|
||||
expect(pre.viviendaCredito.valorVenta).toBe('')
|
||||
expect(pre.viviendaCredito.enganche).toBe('')
|
||||
})
|
||||
})
|
||||
44
apps/alta-clientes/src/lib/cotizacionAlta.ts
Normal file
44
apps/alta-clientes/src/lib/cotizacionAlta.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { DatosGenerales, ViviendaCredito } from '../types'
|
||||
import { PROYECTOS } from '../data/constantes'
|
||||
|
||||
/** Shape mínimo de GET /api/cotizaciones/:id que usa el pre-llenado del alta */
|
||||
export interface CotizacionParaAlta {
|
||||
clienteNombre: string
|
||||
clienteEmail: string | null
|
||||
clienteTelefono: string | null
|
||||
desarrollo: string
|
||||
unidadCodigo: string
|
||||
precioFinal: number
|
||||
enganche: number
|
||||
}
|
||||
|
||||
export interface PrellenadoAlta {
|
||||
datosGenerales: Partial<DatosGenerales>
|
||||
viviendaCredito: Partial<ViviendaCredito>
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformación pura cotización → campos del wizard de alta.
|
||||
* El desarrollo se mapea al dropdown de proyecto; si no está en PROYECTOS cae
|
||||
* en OTRO con el nombre en texto libre. La "ubicación" del formato es el
|
||||
* código de unidad (U-101, etc.). Los montos se prellenan como texto.
|
||||
*/
|
||||
export function cotizacionAPrellenado(c: CotizacionParaAlta): PrellenadoAlta {
|
||||
const desarrollo = c.desarrollo.trim().toUpperCase()
|
||||
const conocido = (PROYECTOS as readonly string[]).includes(desarrollo)
|
||||
|
||||
return {
|
||||
datosGenerales: {
|
||||
nombreCompleto: c.clienteNombre.trim().toUpperCase(),
|
||||
...(c.clienteEmail ? { email: c.clienteEmail.trim() } : {}),
|
||||
...(c.clienteTelefono ? { celular: c.clienteTelefono.trim() } : {}),
|
||||
},
|
||||
viviendaCredito: {
|
||||
proyecto: conocido ? desarrollo : 'OTRO',
|
||||
proyectoOtro: conocido ? '' : desarrollo,
|
||||
ubicacion: c.unidadCodigo,
|
||||
valorVenta: c.precioFinal > 0 ? String(c.precioFinal) : '',
|
||||
enganche: c.enganche > 0 ? String(c.enganche) : '',
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user