feat(mi-dia): seccion 'Mis capturas de afluencia' en el panel del vendedor
Nueva tabla LeadCaptura (quien capturo cada lead, algo que Monday no guarda porque el creador del item siempre es el token del API). El POST /api/leads la registra con el usuario del token y el resumen de Mi dia la enriquece con el estado/origen actual del lead en Monday. La captura rapida refresca el panel al registrar.
This commit is contained in:
@@ -1,13 +1,26 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { Request, Response } from 'express'
|
||||
import {
|
||||
buscarLeads,
|
||||
buscarLeadsEndpoint,
|
||||
cambiarEstadoLead,
|
||||
crearLead,
|
||||
crearLeadEndpoint,
|
||||
descartarLead,
|
||||
ESTADOS_LEAD_PERMITIDOS,
|
||||
} from './leads.controller.js'
|
||||
import type { LeadMonday } from '../services/monday.service.js'
|
||||
import { mondayService, type LeadMonday } from '../services/monday.service.js'
|
||||
import { funnelService } from '../services/funnel.service.js'
|
||||
|
||||
// ---------- Prisma mockeado (los tests no tocan la BD) ----------
|
||||
|
||||
const prismaMock = vi.hoisted(() => ({
|
||||
leadCaptura: {
|
||||
create: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('../services/prisma.js', () => ({ prisma: prismaMock }))
|
||||
|
||||
/** Monday mockeado: registra escrituras y devuelve catálogos controlados */
|
||||
function mondayMock(
|
||||
@@ -306,6 +319,69 @@ describe('descartarLead (POST /api/leads/:itemId/descartar)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('crearLeadEndpoint (POST /api/leads)', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// El singleton Monday responde como si creara el lead; el funnel liga la oportunidad
|
||||
vi.spyOn(mondayService, 'buscarLeadPorTelefono').mockResolvedValue(null)
|
||||
vi.spyOn(mondayService, 'crearLead').mockResolvedValue('lead-nuevo-9')
|
||||
vi.spyOn(funnelService, 'asegurarOportunidadAfluencia').mockResolvedValue('op-9')
|
||||
})
|
||||
|
||||
function reqFake(overrides: Partial<Request> = {}): Request {
|
||||
return {
|
||||
body: { nombre: 'Lead Prueba', telefono: '4771234567' },
|
||||
usuarioToken: { sub: 'u-1', username: 'asesor uno', rol: 'vendedor' },
|
||||
...overrides,
|
||||
} as Request
|
||||
}
|
||||
|
||||
function resFake() {
|
||||
const json = vi.fn()
|
||||
const status = vi.fn().mockReturnValue({ json })
|
||||
return { res: { json, status } as unknown as Response, status, json }
|
||||
}
|
||||
|
||||
it('201: registra la captura local con el usuario del token', async () => {
|
||||
prismaMock.leadCaptura.create.mockResolvedValue({})
|
||||
const { res, status, json } = resFake()
|
||||
|
||||
await crearLeadEndpoint(reqFake(), res)
|
||||
|
||||
expect(prismaMock.leadCaptura.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
leadId: 'lead-nuevo-9',
|
||||
oportunidadId: 'op-9',
|
||||
usuarioId: 'u-1',
|
||||
nombreLead: 'Lead Prueba',
|
||||
telefono: '4771234567',
|
||||
},
|
||||
})
|
||||
expect(status).toHaveBeenCalledWith(201)
|
||||
expect(json).toHaveBeenCalledWith({ id: 'lead-nuevo-9', estado: 'NUEVO', oportunidadId: 'op-9' })
|
||||
})
|
||||
|
||||
it('sin token (AUTH_ENABLED=false) no registra captura y responde 201 igual', async () => {
|
||||
const { res, status } = resFake()
|
||||
|
||||
await crearLeadEndpoint(reqFake({ usuarioToken: undefined }), res)
|
||||
|
||||
expect(prismaMock.leadCaptura.create).not.toHaveBeenCalled()
|
||||
expect(status).toHaveBeenCalledWith(201)
|
||||
})
|
||||
|
||||
it('un fallo del registro local no rompe la respuesta (el lead ya quedó en Monday)', async () => {
|
||||
prismaMock.leadCaptura.create.mockRejectedValue(new Error('SQLite bloqueado'))
|
||||
const espia = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const { res, status } = resFake()
|
||||
|
||||
await crearLeadEndpoint(reqFake(), res)
|
||||
|
||||
expect(status).toHaveBeenCalledWith(201)
|
||||
espia.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
describe('buscarLeads (GET /api/leads/buscar)', () => {
|
||||
const lead = (overrides: Partial<LeadMonday>): LeadMonday => ({
|
||||
id: 'lead-1',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { z } from 'zod'
|
||||
import { config } from '../config/index.js'
|
||||
import { mondayService, type LeadMonday, type MondayService } from '../services/monday.service.js'
|
||||
import { funnelService, type FunnelService } from '../services/funnel.service.js'
|
||||
import { prisma } from '../services/prisma.js'
|
||||
import { MOTIVOS_NO_COMPRA } from './oportunidades.controller.js'
|
||||
import { AppError } from '../utils/errors.js'
|
||||
|
||||
@@ -236,6 +237,25 @@ export async function crearLeadEndpoint(req: Request, res: Response): Promise<vo
|
||||
})
|
||||
return
|
||||
}
|
||||
// Registro local del capturista para "Mis capturas" del panel Mi día (Monday
|
||||
// no guarda quién dio de alta el lead). Un fallo aquí no rompe la respuesta:
|
||||
// el lead ya quedó creado en Monday.
|
||||
if (req.usuarioToken?.sub) {
|
||||
try {
|
||||
await prisma.leadCaptura.create({
|
||||
data: {
|
||||
leadId: resultado.id,
|
||||
oportunidadId: resultado.oportunidadId,
|
||||
usuarioId: req.usuarioToken.sub,
|
||||
nombreLead: body.nombre,
|
||||
telefono: body.telefono ?? null,
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`No se pudo registrar la captura del lead ${resultado.id}:`, err)
|
||||
}
|
||||
}
|
||||
res.status(201).json({ id: resultado.id, estado: 'NUEVO', oportunidadId: resultado.oportunidadId })
|
||||
}
|
||||
|
||||
|
||||
@@ -26,11 +26,18 @@ export async function resumenMiDiaEndpoint(req: Request, res: Response): Promise
|
||||
mapeados.filter((m) => m.mondayUserId !== null).map((m) => [m.mondayUserId!, m.nombre]),
|
||||
)
|
||||
|
||||
// Capturas de afluencia registradas por el usuario (registro local)
|
||||
const capturas = await prisma.leadCaptura.findMany({
|
||||
where: { usuarioId: usuario.id },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
|
||||
res.json(
|
||||
await resumenMiDia(
|
||||
mondayService,
|
||||
{ nombre: usuario.nombre, rol: usuario.rol, mondayUserId: usuario.mondayUserId },
|
||||
nombresPorMondayId,
|
||||
capturas,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user