feat(7-mejoras): notas persisten + soporte + captura visible

1. Notas propagan entre etapas (convertirLead/apartar updateMany)
2. Afluencia: conexión → aviso amarillo
3. Escrituración: click nombre abre bitácora
4. Co-acreditado: opción 'no tiene'
5. Nota del alta de lead → bitácora
6. Módulo Soporte: formulario + listado solicitudes
7. Captura visible para todos
This commit is contained in:
2026-09-11 23:17:25 +00:00
parent 1ba7aa49da
commit c8357fe582
22 changed files with 693 additions and 19 deletions

View File

@@ -1,6 +1,7 @@
import { randomUUID } from 'node:crypto'
import { AppError, NotFoundError } from '../utils/errors.js'
import { claveApartadoCreado } from './automatizaciones.service.js'
import { calcularClienteClave } from './recordatorio-vendedor.service.js'
import { prisma } from './prisma.js'
/**
@@ -256,11 +257,13 @@ export class CrmLocalService {
})
}
/**
/**
* Conversión local lead → oportunidad (botón "Agendo" de Mis leads): el lead
* queda 'Calificado', se crea la oportunidad local en 'Agendo Cita' copiando
* los datos y se liga LeadOportunidad + `oportunidadId` en la captura. Si el
* lead ya fue convertido → 409. Devuelve la fila local de la oportunidad.
* los datos y se liga LeadOportunidad + `oportunidadId` en la captura. Las
* notas (recordatorios) del lead pasan a apuntar a la oportunidad para que
* sobrevivan al cambio de etapa. Si el lead ya fue convertido → 409. Devuelve
* la fila local de la oportunidad.
*/
async convertirLead(leadIdLocal: string, _usuarioId?: string) {
const lead = await prisma.leadCaptura.findUnique({ where: { leadId: leadIdLocal } })
@@ -291,6 +294,11 @@ export class CrmLocalService {
},
}),
prisma.leadOportunidad.create({ data: { leadId: leadIdLocal, oportunidadId } }),
// Las notas del lead viejo siguen a la nueva oportunidad (bitácora persiste)
prisma.recordatorioVendedor.updateMany({
where: { leadId: leadIdLocal },
data: { leadId: oportunidadId, clienteClave: calcularClienteClave(lead.nombreLead, lead.telefono) },
}),
])
return resultados[1]
}
@@ -298,8 +306,10 @@ export class CrmLocalService {
/**
* "Aparto" local: la oportunidad pasa a etapa 'Aparto' y se crea el apartado
* local (board=APARTADO, etapa 'Pago Apartado', primer paso del pipeline
* Enkontrol) con los datos conocidos. Idempotente con el mismo dedup
* `apartado-creado:<opId>` de las automatizaciones.
* Enkontrol) con los datos conocidos. Las notas (recordatorios) de la
* oportunidad pasan a apuntar al apartado para que sobrevivan al cambio de
* board. Idempotente con el mismo dedup `apartado-creado:<opId>` de las
* automatizaciones.
*/
async apartar(oportunidadIdLocal: string, usuarioId?: string) {
// Sombra para ids heredados sin fila local
@@ -343,6 +353,11 @@ export class CrmLocalService {
update: { valor: apartadoId },
create: { clave, valor: apartadoId },
}),
// Las notas de la oportunidad siguen al apartado (bitácora persiste)
prisma.recordatorioVendedor.updateMany({
where: { leadId: oportunidadIdLocal },
data: { leadId: apartadoId, clienteClave: calcularClienteClave(oportunidad.nombreLead, oportunidad.telefono) },
}),
])
return resultados[1]
}