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:
@@ -45,6 +45,8 @@ import {
|
||||
fetchArchivoDocumentoApartado,
|
||||
fetchResumenMiDia,
|
||||
listarDocumentosApartado,
|
||||
moverLead as moverLeadApi,
|
||||
reasignarLead as reasignarLeadApi,
|
||||
subirDocumentoApartado,
|
||||
type DocumentoApartado,
|
||||
} from '../services/mi-dia'
|
||||
@@ -378,6 +380,12 @@ function FilaApartado({
|
||||
<span className="text-xs font-normal text-[#94A3B8]">{apartado.unidadCodigo}</span>
|
||||
) : null}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-[#64748B] dark:text-[#94A3B8]">
|
||||
<EnlacesContacto nombre={apartado.nombre} telefono={apartado.telefono ?? ''} />
|
||||
{apartado.desarrollo ? ` · ${apartado.desarrollo}` : ''}
|
||||
{apartado.origen ? ` · ${apartado.origen}` : ''}
|
||||
{apartado.email ? ` · ${apartado.email}` : ''}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-[#64748B] dark:text-[#94A3B8]">
|
||||
{apartado.fechaLimite !== '' ? `Fecha límite: ${formatoFechaCorta(apartado.fechaLimite)}` : 'Sin fecha límite'}
|
||||
</p>
|
||||
@@ -523,6 +531,15 @@ function PanelDocumentosApartado({ itemId }: { itemId: string }) {
|
||||
<p className="text-xs font-semibold uppercase tracking-wider text-[#64748B] dark:text-[#94A3B8]">
|
||||
Expediente del cliente
|
||||
</p>
|
||||
{documentos !== null &&
|
||||
SLOTS_DOCS_APARTADO.filter((s) => s.obligatorio && !(documentos ?? []).some((d) => d.tipo === s.tipo)).length > 0 && (
|
||||
<p className="mt-2 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-700 dark:border-amber-800 dark:bg-amber-900/30 dark:text-amber-300">
|
||||
Documentos obligatorios pendientes:{' '}
|
||||
{SLOTS_DOCS_APARTADO.filter((s) => s.obligatorio && !(documentos ?? []).some((d) => d.tipo === s.tipo))
|
||||
.map((s) => s.etiqueta)
|
||||
.join(', ')}
|
||||
</p>
|
||||
)}
|
||||
{error && (
|
||||
<p role="alert" className="mt-2 rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700">
|
||||
{error}
|
||||
@@ -675,6 +692,10 @@ function FilaLead({
|
||||
onVerDetalle,
|
||||
enviando,
|
||||
acciones,
|
||||
esAdmin,
|
||||
vendedores,
|
||||
onReasignar,
|
||||
onMover,
|
||||
}: {
|
||||
lead: LeadMiDia
|
||||
onAccion: (estado: string, fecha?: string) => void
|
||||
@@ -683,6 +704,11 @@ function FilaLead({
|
||||
onVerDetalle?: () => void
|
||||
enviando: boolean
|
||||
acciones?: string[]
|
||||
/** Solo admin: reasignar a cualquier vendedor y mover entre tableros */
|
||||
esAdmin?: boolean
|
||||
vendedores?: { id: string; nombre: string }[]
|
||||
onReasignar?: (usuarioId: string) => void
|
||||
onMover?: () => void
|
||||
}) {
|
||||
return (
|
||||
<li className="rounded-xl border border-[#E5E7EB] bg-white p-4 dark:border-[#1E293B] dark:bg-[#1E293B] shadow-sm">
|
||||
@@ -752,6 +778,30 @@ function FilaLead({
|
||||
Descartar
|
||||
</button>
|
||||
</div>
|
||||
{/* Admin: reasignar a cualquier vendedor y mover entre tableros */}
|
||||
{esAdmin && (
|
||||
<div className="mt-2 flex flex-wrap items-center gap-2 border-t border-dashed border-[#E5E7EB] pt-2 dark:border-[#334155]">
|
||||
<span className="text-[11px] font-medium uppercase tracking-wider text-[#94A3B8]">Admin</span>
|
||||
<select
|
||||
value=""
|
||||
disabled={enviando}
|
||||
onChange={(e) => {
|
||||
if (e.target.value !== '' && onReasignar) onReasignar(e.target.value)
|
||||
e.target.value = ''
|
||||
}}
|
||||
className="rounded-lg border border-[#D1D5DB] bg-white px-2 py-1 text-xs text-[#374151] transition-colors focus:border-accent focus:outline-none dark:bg-[#0F172A] dark:border-[#334155] dark:text-[#CBD5E1]"
|
||||
aria-label="Reasignar lead a vendedor"
|
||||
>
|
||||
<option value="">Reasignar a…</option>
|
||||
{(vendedores ?? []).map((v) => (
|
||||
<option key={v.id} value={v.id}>{v.nombre}</option>
|
||||
))}
|
||||
</select>
|
||||
<button type="button" disabled={enviando} onClick={onMover} className={CLASE_BOTON_ACCION}>
|
||||
{lead.fuente === 'lead' ? '→ Oportunidad' : '→ Leads'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
@@ -994,6 +1044,23 @@ export default function MiDiaPage({ accionesSesion }: { accionesSesion: Acciones
|
||||
window.open(urlCotizadorParaOportunidad(op), '_blank')
|
||||
}
|
||||
|
||||
/** Reasignar lead a otro vendedor (solo admin) */
|
||||
async function reasignar(lead: LeadMiDia, usuarioId: string) {
|
||||
await ejecutar(async () => {
|
||||
const nombre = await reasignarLeadApi(lead.id, usuarioId)
|
||||
toast.success(`Lead reasignado a ${nombre || 'vendedor'}`)
|
||||
}, 'No se pudo reasignar el lead')
|
||||
}
|
||||
|
||||
/** Mover lead entre tableros Leads ↔ Oportunidades (solo admin) */
|
||||
async function mover(lead: LeadMiDia) {
|
||||
const destino = lead.fuente === 'lead' ? 'OPORTUNIDADES' : 'LEADS'
|
||||
await ejecutar(async () => {
|
||||
await moverLeadApi(lead.id, destino)
|
||||
toast.success(destino === 'OPORTUNIDADES' ? 'Lead movido a Oportunidades' : 'Lead movido a Leads')
|
||||
}, 'No se pudo mover el lead')
|
||||
}
|
||||
|
||||
async function enviarCaptura(e: FormEvent) {
|
||||
e.preventDefault()
|
||||
setCapEnviando(true)
|
||||
@@ -1246,6 +1313,10 @@ export default function MiDiaPage({ accionesSesion }: { accionesSesion: Acciones
|
||||
onDescartar={() => setDescartando(lead)}
|
||||
onVerDetalle={() => setLeadSeleccionado(lead)}
|
||||
acciones={acciones}
|
||||
esAdmin={rol === 'admin'}
|
||||
vendedores={resumen.equipo?.porVendedor.map((v) => ({ id: v.usuarioId, nombre: v.nombre ?? '' }))}
|
||||
onReasignar={(usuarioId) => void reasignar(lead, usuarioId)}
|
||||
onMover={() => void mover(lead)}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user