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:
@@ -2,6 +2,7 @@ import type { Request, Response } from 'express'
|
||||
import { z } from 'zod'
|
||||
import { config } from '../config/index.js'
|
||||
import { mondayService, type MondayService } from '../services/monday.service.js'
|
||||
import { moverAGrupoSegunEstado } from '../services/grupos-estado.service.js'
|
||||
|
||||
/** Categorías del dropdown "Motivo No Compra" (vocabulario CRM FRASA) */
|
||||
export const MOTIVOS_NO_COMPRA = [
|
||||
@@ -53,3 +54,61 @@ export async function perderOportunidadEndpoint(req: Request, res: Response): Pr
|
||||
const motivoItemId = await perderOportunidad(mondayService, mondayItemId, body.motivo, body.comentario)
|
||||
res.json({ mondayItemId, etapa: 'Perdido', motivo: body.motivo, motivoItemId })
|
||||
}
|
||||
|
||||
// ---------- Cambio de etapa desde Mi día ----------
|
||||
|
||||
/**
|
||||
* Etapas del funnel que el asesor puede mover desde los botones de
|
||||
* "Mis oportunidades" (labels REALES de la columna status 'Estado' del board
|
||||
* Oportunidades — el frontend los manda tal cual).
|
||||
*/
|
||||
export const ETAPAS_OPORTUNIDAD_PERMITIDAS = [
|
||||
'Agendo Cita',
|
||||
'Afluencia',
|
||||
'Cotizador',
|
||||
'Aparto',
|
||||
'No Asistio',
|
||||
'Abandono',
|
||||
] as const
|
||||
|
||||
const cambiarEtapaSchema = z.object({
|
||||
etapa: z.enum(ETAPAS_OPORTUNIDAD_PERMITIDAS),
|
||||
// En 'Agendo Cita' puede setear la columna date 'Fecha'
|
||||
fecha: z
|
||||
.string()
|
||||
.regex(/^\d{4}-\d{2}-\d{2}$/, 'La fecha debe ser YYYY-MM-DD')
|
||||
.optional(),
|
||||
})
|
||||
|
||||
type MondayEtapa = Pick<
|
||||
MondayService,
|
||||
'cambiarValorColumna' | 'columnaIdPorTitulo' | 'getItemConGrupo' | 'moverItemAGrupo'
|
||||
>
|
||||
|
||||
/**
|
||||
* Cambia la etapa de la oportunidad (columna 'Estado'), setea la fecha de la
|
||||
* cita si aplica y mueve el item al grupo que corresponde a la etapa nueva
|
||||
* (idempotente; el webhook de change haría lo mismo, pero así la UI responde
|
||||
* al instante).
|
||||
*/
|
||||
export async function cambiarEtapaOportunidad(
|
||||
monday: MondayEtapa,
|
||||
itemId: string,
|
||||
etapa: (typeof ETAPAS_OPORTUNIDAD_PERMITIDAS)[number],
|
||||
fecha?: string,
|
||||
): Promise<void> {
|
||||
const boardOportunidades = config.monday.boards.oportunidades
|
||||
await monday.cambiarValorColumna(boardOportunidades, itemId, 'Estado', etapa)
|
||||
if (etapa === 'Agendo Cita' && fecha !== undefined) {
|
||||
await monday.cambiarValorColumna(boardOportunidades, itemId, 'Fecha', fecha)
|
||||
}
|
||||
await moverAGrupoSegunEstado(monday, boardOportunidades, itemId)
|
||||
}
|
||||
|
||||
/** POST /api/oportunidades/:itemId/etapa { etapa, fecha? } */
|
||||
export async function cambiarEtapaOportunidadEndpoint(req: Request, res: Response): Promise<void> {
|
||||
const body = cambiarEtapaSchema.parse(req.body)
|
||||
const itemId = String(req.params['itemId'])
|
||||
await cambiarEtapaOportunidad(mondayService, itemId, body.etapa, body.fecha)
|
||||
res.json({ itemId, etapa: body.etapa, ...(body.fecha !== undefined ? { fecha: body.fecha } : {}) })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user