1. Cancelar con motivo: Mis apartados y Escrituración piden motivo (modal reutilizado) 2. Origen BROCKER agregado en alta afluencia y captura rápida 3. Etapas oportunidades reordenadas: Agendo Cita → Afluencia → Cotizador 4. Cotizador: mensualidades diferibles (6, 12, 14, 18, 24 meses) 5. Calendario semanal por vendedor en Mi día 6. Hora en citas (datetime-local en vez de solo fecha) 7. Click en nombre → modal DetalleLeadModal (info + notas + historial)
136 lines
4.7 KiB
TypeScript
136 lines
4.7 KiB
TypeScript
// Cliente del API para la app "Mi día" (mismas convenciones que las demás apps)
|
|
import type { ResumenMiDia } from '../lib/mi-dia'
|
|
import { conAuth, manejar401 } from './auth'
|
|
|
|
const API_URL = import.meta.env.VITE_API_URL ?? '/api'
|
|
|
|
async function fetchJson<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
const res = await fetch(`${API_URL}${path}`, conAuth(init))
|
|
manejar401(res)
|
|
if (!res.ok) {
|
|
const body = (await res.json().catch(() => null)) as { error?: string } | null
|
|
throw new Error(body?.error ?? `Error ${res.status}`)
|
|
}
|
|
return (await res.json()) as T
|
|
}
|
|
|
|
export function fetchResumenMiDia(): Promise<ResumenMiDia> {
|
|
return fetchJson<ResumenMiDia>('/mi-dia/resumen')
|
|
}
|
|
|
|
export interface LeadCreado {
|
|
id: string
|
|
estado: string
|
|
/** 'capturista' = asignado a quien lo registró; 'round-robin' = entra al reparto */
|
|
asignacion?: 'capturista' | 'round-robin'
|
|
}
|
|
|
|
/** Crea un lead; en duplicado (409) lanza Error con el mensaje del API */
|
|
export async function crearLead(datos: {
|
|
nombre: string
|
|
telefono?: string
|
|
email?: string
|
|
origen?: string
|
|
desarrollo?: string
|
|
notas?: string
|
|
}): Promise<LeadCreado> {
|
|
return fetchJson<LeadCreado>('/leads', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(datos),
|
|
})
|
|
}
|
|
|
|
export function cambiarEstadoLead(itemId: string, estado: string, fecha?: string, fuente?: 'lead' | 'oportunidad'): Promise<unknown> {
|
|
return fetchJson(`/leads/${itemId}/estado`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
estado,
|
|
...(fecha !== undefined && fecha !== '' ? { fecha } : {}),
|
|
...(fuente !== undefined ? { fuente } : {}),
|
|
}),
|
|
})
|
|
}
|
|
|
|
export function descartarLead(itemId: string, motivo: string, comentario?: string, fuente?: 'lead' | 'oportunidad', subcategoria?: string): Promise<unknown> {
|
|
return fetchJson(`/leads/${itemId}/descartar`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
motivo,
|
|
...(subcategoria !== undefined && subcategoria !== '' ? { subcategoria } : {}),
|
|
...(comentario !== undefined && comentario !== '' ? { comentario } : {}),
|
|
...(fuente !== undefined ? { fuente } : {}),
|
|
}),
|
|
})
|
|
}
|
|
|
|
/** POST /oportunidades/:itemId/etapa — botones de fase de "Mis oportunidades" */
|
|
export function cambiarEtapaOportunidad(itemId: string, etapa: string, fecha?: string): Promise<unknown> {
|
|
return fetchJson(`/oportunidades/${itemId}/etapa`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
etapa,
|
|
...(fecha !== undefined && fecha !== '' ? { fecha } : {}),
|
|
}),
|
|
})
|
|
}
|
|
|
|
/** POST /leads/:itemId/convertir — botón "Agendo": convierte el lead en oportunidad */
|
|
export function convertirLead(itemId: string): Promise<{ oportunidadId: string }> {
|
|
return fetchJson(`/leads/${itemId}/convertir`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({}),
|
|
})
|
|
}
|
|
|
|
/** POST /apartados/:itemId/estado — botones de fase de "Mis apartados" */
|
|
export function cambiarEstadoApartado(
|
|
itemId: string,
|
|
estado: string,
|
|
motivo?: string,
|
|
subcategoria?: string,
|
|
comentario?: string,
|
|
): Promise<unknown> {
|
|
return fetchJson(`/apartados/${itemId}/estado`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
estado,
|
|
...(motivo !== undefined && motivo !== '' ? { motivo } : {}),
|
|
...(subcategoria !== undefined && subcategoria !== '' ? { subcategoria } : {}),
|
|
...(comentario !== undefined && comentario !== '' ? { comentario } : {}),
|
|
}),
|
|
})
|
|
}
|
|
|
|
// ---------- Precarga de documentos del expediente (Mis apartados) ----------
|
|
|
|
export interface DocumentoApartado {
|
|
id: string
|
|
tipo: string // CAPTURA_COTIZADOR | CAPTURA_PAGO_APARTADO | OTRO | INE | ...
|
|
nombreArchivo: string
|
|
createdAt: string // ISO
|
|
}
|
|
|
|
/** GET /apartados/:itemId/documentos — documentos ya cargados del cliente ligado */
|
|
export function listarDocumentosApartado(itemId: string): Promise<DocumentoApartado[]> {
|
|
return fetchJson<DocumentoApartado[]>(`/apartados/${itemId}/documentos`)
|
|
}
|
|
|
|
/** POST /apartados/:itemId/documentos — multipart; el archivo se guarda en el cliente */
|
|
export async function subirDocumentoApartado(itemId: string, tipo: string, archivo: File): Promise<void> {
|
|
const form = new FormData()
|
|
form.append('tipo', tipo)
|
|
form.append('archivos', archivo)
|
|
const res = await fetch(`${API_URL}/apartados/${itemId}/documentos`, conAuth({ method: 'POST', body: form }))
|
|
manejar401(res)
|
|
if (!res.ok) {
|
|
const body = (await res.json().catch(() => null)) as { error?: string } | null
|
|
throw new Error(body?.error ?? `Error ${res.status}`)
|
|
}
|
|
}
|