feat(mi-dia): tabs Mis oportunidades/Mis apartados con fases y cotizador vinculado
- Resumen Mi día: misOportunidades.items (lista de trabajo con perfil y último cambio) y misApartados (Vendedor = yo, estados no finales) - Nuevos endpoints: POST /oportunidades/:id/etapa (Agendo Cita, Afluencia, Cotizador, Aparto, No Asistio, Abandono; con fecha de cita) y POST /apartados/:id/estado (pipeline Enkontrol: Pago Apartado → Firma Cliente (Venta) / Cancelacion Apartado); ambos mueven el item al grupo Monday correspondiente - Mi día: tabs Mis leads / Mis oportunidades / Mis apartados; botón Cotizar abre /quote con el cliente prellenado y la oportunidad ligada - Cotizador: prefill desde query params (cliente, teléfono, email, desarrollo, unidad) y envío de oportunidadId; al guardar, el API actualiza ESE item (Precio final, Tipo propuesta, etapa Cotizador) en vez de buscar por teléfono o crear duplicado - grupos-estado: 'Cancelacion Apartado' → Historico - Estados finales de apartado: agrega Firma Cliente (Venta) y Cancelacion Apartado - Tests: 455 API + 14 mi-dia + 88 cotizador, todo en verde
This commit is contained in:
@@ -51,6 +51,30 @@ export interface ClienteMiDia {
|
||||
estadoApartado: string | null
|
||||
}
|
||||
|
||||
/** Oportunidad asignada al asesor (tab "Mis oportunidades") */
|
||||
export interface OportunidadMiDia {
|
||||
id: string
|
||||
nombre: string
|
||||
telefono: string
|
||||
email: string
|
||||
etapa: string
|
||||
desarrollo: string
|
||||
modeloInteres: string
|
||||
unidadCodigo: string
|
||||
updatedAt: string
|
||||
ultimoCambio: { fechaISO: string; usuario: string | null; detalle: string } | null
|
||||
}
|
||||
|
||||
/** Apartado asignado al vendedor (tab "Mis apartados") */
|
||||
export interface ApartadoMiDia {
|
||||
id: string
|
||||
nombre: string
|
||||
unidadCodigo: string
|
||||
estado: string
|
||||
fechaLimite: string // 'YYYY-MM-DD' ('' si no tiene)
|
||||
ultimoCambio: { fechaISO: string; usuario: string | null; detalle: string } | null
|
||||
}
|
||||
|
||||
export interface ResumenMiDia {
|
||||
generadoEn: string
|
||||
sinMapeoMonday: boolean
|
||||
@@ -59,7 +83,8 @@ export interface ResumenMiDia {
|
||||
misClientes: ClienteMiDia[]
|
||||
misActividadesHoy: ActividadMiDia[]
|
||||
apartadosPorVencer: ApartadoPorVencer[]
|
||||
misOportunidades: { porEtapa: Record<string, number> } // conteo de MIS oportunidades por etapa
|
||||
misOportunidades: { porEtapa: Record<string, number>; items: OportunidadMiDia[] } // conteo + lista de trabajo
|
||||
misApartados: ApartadoMiDia[]
|
||||
conteos: {
|
||||
leadsNuevosSemana: number
|
||||
actividadesAbiertas: number
|
||||
@@ -94,6 +119,59 @@ export const MOTIVOS_NO_COMPRA = [
|
||||
|
||||
export type MotivoNoCompra = (typeof MOTIVOS_NO_COMPRA)[number]
|
||||
|
||||
// Fases del funnel de Oportunidades (labels REALES del board; el API las valida tal cual)
|
||||
export const FASES_OPORTUNIDAD = ['Agendo Cita', 'Afluencia', 'Cotizador', 'Aparto', 'No Asistio', 'Abandono'] as const
|
||||
export type FaseOportunidad = (typeof FASES_OPORTUNIDAD)[number]
|
||||
|
||||
// Fases del pipeline Enkontrol del tablero Apartado (labels REALES del board)
|
||||
export const FASES_APARTADO = [
|
||||
'Pago Apartado',
|
||||
'Alta Enkontrol',
|
||||
'Entrego Documentacion',
|
||||
'Cita Clientes',
|
||||
'Contrato Enkontrol',
|
||||
'Firma Cliente (Venta)',
|
||||
'Cancelacion Apartado',
|
||||
] as const
|
||||
export type FaseApartado = (typeof FASES_APARTADO)[number]
|
||||
|
||||
// Texto bonito de los botones (el valor que se manda al API es el label real)
|
||||
export const ETIQUETA_FASE_APARTADO: Record<FaseApartado, string> = {
|
||||
'Pago Apartado': 'Pago apartado',
|
||||
'Alta Enkontrol': 'Alta Enkontrol',
|
||||
'Entrego Documentacion': 'Entregó documentación',
|
||||
'Cita Clientes': 'Cita cliente',
|
||||
'Contrato Enkontrol': 'Contrato Enkontrol',
|
||||
'Firma Cliente (Venta)': 'Firma cliente (venta)',
|
||||
'Cancelacion Apartado': 'Cancelación',
|
||||
}
|
||||
|
||||
// Texto bonito de los botones de oportunidad
|
||||
export const ETIQUETA_FASE_OPORTUNIDAD: Record<FaseOportunidad, string> = {
|
||||
'Agendo Cita': 'Agendó cita',
|
||||
Afluencia: 'Afluencia',
|
||||
Cotizador: 'Cotizador',
|
||||
Aparto: 'Apartó',
|
||||
'No Asistio': 'No asistió',
|
||||
Abandono: 'Abandono',
|
||||
}
|
||||
|
||||
/**
|
||||
* URL del cotizador (/quote/) con los datos del cliente prellenados y el id de
|
||||
* la oportunidad: al guardar, la cotización queda vinculada a ese cliente en
|
||||
* Monday (el API actualiza ese item en vez de crear uno nuevo).
|
||||
*/
|
||||
export function urlCotizadorParaOportunidad(op: OportunidadMiDia): string {
|
||||
const params = new URLSearchParams()
|
||||
params.set('cliente', op.nombre)
|
||||
if (op.telefono !== '') params.set('telefono', op.telefono)
|
||||
if (op.email !== '') params.set('email', op.email)
|
||||
if (op.desarrollo !== '') params.set('desarrollo', op.desarrollo)
|
||||
if (op.unidadCodigo !== '') params.set('unidad', op.unidadCodigo)
|
||||
params.set('oportunidadId', op.id)
|
||||
return `/quote/?${params.toString()}`
|
||||
}
|
||||
|
||||
const ROLES_CON_EQUIPO = ['coordinador', 'corporativo', 'admin']
|
||||
|
||||
/** ¿El rol ve la sección de equipo? (misma regla que el API) */
|
||||
|
||||
Reference in New Issue
Block a user