fix(cancelar): botones Cancelación Apartado y Cancelo funcionan con motivo

- ModalDescarteGenerico restaurado (motivo + comentario)
- Cancelación Apartado: abre modal → confirma → cambia estado
- Cancelo en Escrituración: abre modal → confirma → cambia estado
- onFase intercepta ambas fases y abre el modal de cancelación
This commit is contained in:
2026-09-11 00:26:52 +00:00
parent cfffb36965
commit 46d20657ea

View File

@@ -233,6 +233,73 @@ function FilaOportunidad({
} }
/** Fila de apartado con botones de fase del pipeline Enkontrol */ /** Fila de apartado con botones de fase del pipeline Enkontrol */
function ModalDescarteGenerico({
item,
titulo,
onCancelar,
onConfirmar,
enviando,
}: {
item: { id: string; nombre: string }
titulo: string
onCancelar: () => void
onConfirmar: (motivo: string, subcategoria: string | undefined, comentario: string) => void
enviando: boolean
}) {
const [motivo, setMotivo] = useState<string>('Timing')
const [subcategoria] = useState('')
const [comentario, setComentario] = useState('')
return (
<div className="fixed inset-0 z-10 flex items-center justify-center bg-black/40 px-4" role="dialog" aria-modal="true">
<div className="w-full max-w-md rounded-2xl border border-[#E5E7EB] bg-white p-6 dark:border-[#1E293B] dark:bg-[#1E293B] shadow-xl">
<p className="text-xs font-semibold uppercase tracking-wider text-[#64748B] dark:text-[#94A3B8]">{titulo}</p>
<p className="mt-2 text-lg font-medium text-[#111827] dark:text-[#E2E8F0]">{item.nombre}</p>
<form
className="mt-6 space-y-4"
onSubmit={(e) => {
e.preventDefault()
onConfirmar(motivo, subcategoria === '' ? undefined : subcategoria, comentario.trim())
}}
>
<div>
<label htmlFor="motivo-cancel" className="block mb-2 text-xs font-medium text-[#4B5563] dark:text-[#CBD5E1]">Motivo</label>
<select
id="motivo-cancel"
value={motivo}
onChange={(e) => setMotivo(e.target.value)}
className="w-full rounded-lg border border-[#D1D5DB] bg-white px-3 dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0] py-2.5 text-sm"
>
{['Timing', 'Cliente', 'Competencia', 'Financiamiento', 'Ubicación', 'Producto', 'Precio'].map((m) => (
<option key={m} value={m}>{m}</option>
))}
</select>
</div>
<div>
<label htmlFor="comentario-cancel" className="block mb-2 text-xs font-medium text-[#4B5563] dark:text-[#CBD5E1]">Comentario (opcional)</label>
<textarea
id="comentario-cancel"
value={comentario}
onChange={(e) => setComentario(e.target.value)}
rows={3}
className="w-full rounded-lg border border-[#D1D5DB] bg-white px-3 dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0] py-2.5 text-sm"
/>
</div>
<div className="flex justify-end gap-3">
<button type="button" onClick={onCancelar} disabled={enviando}
className="rounded-lg border border-[#D1D5DB] bg-white px-5 py-2 text-sm font-medium text-[#374151] dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0] hover:bg-[#F9FAFB] disabled:opacity-50">
Cancelar
</button>
<button type="submit" disabled={enviando}
className="rounded-lg bg-red-600 px-5 py-2 text-sm font-medium text-white shadow-sm hover:bg-red-700 disabled:opacity-50">
{enviando ? 'Procesando…' : 'Confirmar'}
</button>
</div>
</form>
</div>
</div>
)
}
function FilaApartado({ function FilaApartado({
apartado, apartado,
onFase, onFase,
@@ -290,6 +357,7 @@ function FilaApartado({
return return
} }
if (fase === 'Cancelacion Apartado') { if (fase === 'Cancelacion Apartado') {
onFase(fase)
return return
} }
onFase(fase) onFase(fase)
@@ -593,6 +661,7 @@ export default function MiDiaPage({ accionesSesion }: { accionesSesion: Acciones
} | null>(null) } | null>(null)
const [tabCrm, setTabCrm] = useState<TabCrm>('leads') const [tabCrm, setTabCrm] = useState<TabCrm>('leads')
const [leadSeleccionado, setLeadSeleccionado] = useState<LeadMiDia | OportunidadMiDia | ApartadoMiDia | null>(null) const [leadSeleccionado, setLeadSeleccionado] = useState<LeadMiDia | OportunidadMiDia | ApartadoMiDia | null>(null)
const [apartadoCancelando, setApartadoCancelando] = useState<{ id: string; nombre: string } | null>(null)
const [filtros, setFiltros] = useState<FiltrosCRM>({ texto: '', desarrollo: '', origen: '', fechaDesde: '', fechaHasta: '' }) const [filtros, setFiltros] = useState<FiltrosCRM>({ texto: '', desarrollo: '', origen: '', fechaDesde: '', fechaHasta: '' })
const [offline, setOffline] = useState(!navigator.onLine) const [offline, setOffline] = useState(!navigator.onLine)
const [offlineFecha, setOfflineFecha] = useState<string | null>(null) const [offlineFecha, setOfflineFecha] = useState<string | null>(null)
@@ -1153,6 +1222,7 @@ export default function MiDiaPage({ accionesSesion }: { accionesSesion: Acciones
disabled={enviando} disabled={enviando}
onClick={() => { onClick={() => {
if (fase === 'Cancelo') { if (fase === 'Cancelo') {
setApartadoCancelando({ id: apartado.id, nombre: apartado.nombre })
return return
} }
void ejecutar(() => cambiarEstadoApartado(apartado.id, fase), 'No se pudo actualizar') void ejecutar(() => cambiarEstadoApartado(apartado.id, fase), 'No se pudo actualizar')
@@ -1496,6 +1566,23 @@ export default function MiDiaPage({ accionesSesion }: { accionesSesion: Acciones
{apartadoCancelando && (
<ModalDescarteGenerico
item={apartadoCancelando}
titulo="Cancelar apartado"
enviando={enviando}
onCancelar={() => setApartadoCancelando(null)}
onConfirmar={(motivo, subcategoria, comentario) => {
const item = apartadoCancelando
setApartadoCancelando(null)
void ejecutar(
() => cambiarEstadoApartado(item.id, 'Cancelacion Apartado', motivo, subcategoria, comentario),
'No se pudo cancelar el apartado',
)
}}
/>
)}
{leadSeleccionado && ( {leadSeleccionado && (
<DetalleLeadModal <DetalleLeadModal
item={leadSeleccionado} item={leadSeleccionado}