feat(pendientes): cotizador (mensualidades, bloqueo de unidad, totales, numMensualidades), cobranza (recibos, atrasos, mora configurable, recordatorios), soporte (adjuntos, filtros, estados), clientes maestro (catalogos reales), contactos (ficha y duplicados) y permisos de captura por rol

This commit is contained in:
2026-09-21 08:34:53 +00:00
parent b5a20aed4e
commit 2fc80e7f9e
66 changed files with 3792 additions and 436 deletions

View File

@@ -87,10 +87,27 @@ export interface GuardarCotizacionPayload {
acabados: boolean
apartado?: number
descuento?: number
/** Mensualidades elegidas por el vendedor (sin él, el API usa el plazo del desarrollo) */
numMensualidades?: number
/** Item del tablero Oportunidades a vincular (botón "Cotizar" de Mi día) */
oportunidadId?: string
}
/**
* Error del API al guardar una cotización (status HTTP + mensaje del servidor).
* Se distingue de los fallos de red: el servidor SÍ contestó y rechazó la
* cotización (p. ej. la unidad ya no está disponible), así que no se guarda.
*/
export class ErrorApiCotizacion extends Error {
constructor(
public readonly status: number,
message: string,
) {
super(message)
this.name = 'ErrorApiCotizacion'
}
}
/**
* URL pública de una cotización (destino del QR del PDF). Debe ser ABSOLUTA:
* un QR con ruta relativa ("/api/...") no es navegable al escanearlo desde el
@@ -111,7 +128,11 @@ export async function guardarCotizacion(payload: GuardarCotizacionPayload): Prom
body: JSON.stringify(payload),
}))
manejar401(resp)
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
if (!resp.ok) {
// El API explica el rechazo (unidad no disponible, datos inválidos, …)
const cuerpo = (await resp.json().catch(() => null)) as { error?: string } | null
throw new ErrorApiCotizacion(resp.status, cuerpo?.error ?? `No se pudo guardar la cotización (HTTP ${resp.status})`)
}
return (await resp.json()) as CotizacionGuardada
}