fix(mi-dia): admin reasigna/mueve leads, capturista real en cards, datos en apartados, INE obligatoria y bitácora persistente

This commit is contained in:
2026-09-17 06:54:57 +00:00
parent b99707689f
commit 31f83b0504
11 changed files with 162 additions and 14 deletions

View File

@@ -115,8 +115,8 @@ describe('cambiarEstadoApartadoEndpoint (POST /api/apartados/:id/estado) — loc
])
})
it('los tipos de documento de la precarga son capturas obligatorias + generales', () => {
expect(TIPOS_DOCUMENTO_APARTADO).toEqual(['CAPTURA_COTIZADOR', 'CAPTURA_PAGO_APARTADO', 'OTRO'])
it('los tipos de documento de la precarga son capturas obligatorias + INE + generales', () => {
expect(TIPOS_DOCUMENTO_APARTADO).toEqual(['CAPTURA_COTIZADOR', 'CAPTURA_PAGO_APARTADO', 'INE', 'OTRO'])
})
})

View File

@@ -50,7 +50,7 @@ export async function cambiarEstadoApartadoEndpoint(req: Request, res: Response)
* obligatorios del proceso; OTRO cubre los documentos generales (INE, CURP,
* comprobantes, etc.). El tipo viaja en el campo `tipo` del multipart.
*/
export const TIPOS_DOCUMENTO_APARTADO = ['CAPTURA_COTIZADOR', 'CAPTURA_PAGO_APARTADO', 'OTRO'] as const
export const TIPOS_DOCUMENTO_APARTADO = ['CAPTURA_COTIZADOR', 'CAPTURA_PAGO_APARTADO', 'INE', 'OTRO'] as const
const MIME_PERMITIDOS_APARTADO = new Set(['application/pdf', 'image/jpeg', 'image/jpg', 'image/png'])
const EXT_POR_MIME_APARTADO: Record<string, string> = {

View File

@@ -6,7 +6,7 @@ import { crmLocal, esIdLocal } from '../services/crm-local.service.js'
import * as auditoria from '../services/auditoria.service.js'
import { MOTIVOS_NO_COMPRA, SUBCATEGORIAS_NO_COMPRA } from './oportunidades.controller.js'
import { AppError, NotFoundError } from '../utils/errors.js'
import { recordatorioVendedorService } from '../services/recordatorio-vendedor.service.js'
import { recordatorioVendedorService, calcularClienteClave } from '../services/recordatorio-vendedor.service.js'
/**
* Captura y acciones del CRM ligero, sobre DOS tableros locales (tabla
@@ -163,11 +163,14 @@ export async function crearLeadEndpoint(req: Request, res: Response): Promise<vo
})
return
}
// La nota del alta se persiste como recordatorio en la bitácora del lead
// La nota del alta se persiste como recordatorio en la bitácora del lead;
// con clienteClave (nombre+teléfono) la nota sigue al lead en las fases
// posteriores (oportunidad → apartado → escrituración).
if (body.notas !== undefined && body.notas.trim() !== '') {
await recordatorioVendedorService.crear(usuarioSub ?? 'sistema', {
titulo: body.notas,
leadId: resultado.id,
clienteClave: calcularClienteClave(body.nombre, body.telefono),
fechaRecordar: new Date().toISOString(),
})
}
@@ -205,6 +208,16 @@ export async function crearAfluenciaEndpoint(req: Request, res: Response): Promi
res.json({ id: resultado.id, estado: 'Afluencia', oportunidadId: resultado.oportunidadId, actualizada: true })
return
}
// La nota del alta de afluencia también vive en la bitácora (con clave de
// cliente para que siga al contacto en las fases siguientes).
if (body.notas !== undefined && body.notas.trim() !== '') {
await recordatorioVendedorService.crear(usuarioSub ?? 'sistema', {
titulo: body.notas,
leadId: resultado.id,
clienteClave: calcularClienteClave(body.nombre, body.telefono),
fechaRecordar: new Date().toISOString(),
})
}
res.status(201).json({
id: resultado.id,
estado: 'Afluencia',