/* global React, ChIcon, NavIcon, CHANNELS, PHASES, LIFECYCLE_TICKETS */ const { useState: useStateLC, useMemo: useMemoLC, useRef: useRefLC, useEffect: useEffectLC } = React; const initialsLC = (name) => name.split(/\s+/).filter(Boolean).slice(0,2).map(w => w[0]).join("").toUpperCase(); // Subtle phase tint — just a top stripe color, card body stays white const PHASE_TINT = { recepcion: "#B0B5BE", validacion: "#9C8FB8", diagnostico: "#C397B0", accion: "#B8A857", escalado: "#C81E37", resuelto: "#0E8F5C", }; const OWNERS = [ { id:"all", label:"Todos", ini:"·" }, { id:"sofia", label:"SOF.IA", ini:"S" }, { id:"dispatch", label:"Dispatcher", ini:"DP" }, { id:"taller", label:"Taller", ini:"TL" }, { id:"conectividad", label:"Conectividad", ini:"CX" }, ]; const PHASE_TONE_CLS = { recepcion: "tone-cream", validacion: "tone-lavender", diagnostico: "tone-pink", accion: "tone-yellow-soft", escalado: "tone-yellow", resuelto: "tone-white", }; const KanCard = ({ t, onOpen }) => { const toneCls = PHASE_TONE_CLS[t.phase] || "tone-white"; const isSofia = t.owner === "sofia" && t.phase !== "escalado"; return (
onOpen(t)} >
#{t.id}
{t.customer}
{t.company}
{t.printer}
{t.urgent && Urgente}
{isSofia ? "S" : (t.handler === "Dispatcher" ? "DP" : t.handler === "Taller" ? "TL" : t.handler === "Conectividad" ? "CX" : "OP")}
{t.handler}
= 75 ? "danger" : t.slaPct >= 50 ? "warn" : "ok"}`}> {t.slaPct === 100 ? "OK" : `${t.slaPct}%`}
); }; const LifecycleScreen = ({ openTicket, setRoute }) => { const [view, setView] = useStateLC("kanban"); const [search, setSearch] = useStateLC(""); const [filterOwner, setFilterOwner] = useStateLC("all"); const [filterChannel, setFilterChannel] = useStateLC("all"); const [filterUrgent, setFilterUrgent] = useStateLC(false); const [sortBy, setSortBy] = useStateLC("phase"); const [sortOpen, setSortOpen] = useStateLC(false); const sortRefLC = useRefLC(null); useEffectLC(() => { if (!sortOpen) return; const handler = (e) => { if (sortRefLC.current && !sortRefLC.current.contains(e.target)) setSortOpen(false); }; document.addEventListener("mousedown", handler); return () => document.removeEventListener("mousedown", handler); }, [sortOpen]); const SORT_OPTIONS_LC = [ { id: "phase", label: "Por fase" }, { id: "recent", label: "Más recientes" }, { id: "sla", label: "SLA crítico" }, { id: "channel", label: "Por canal" }, ]; const filtered = useMemoLC(() => { let r = LIFECYCLE_TICKETS; if (filterOwner !== "all") r = r.filter(t => t.owner === filterOwner); if (filterChannel !== "all") r = r.filter(t => t.channel === filterChannel); if (filterUrgent) r = r.filter(t => t.urgent); if (search.trim()) { const q = search.trim().toLowerCase(); r = r.filter(t => (t.customer + " " + t.company + " " + t.id + " " + t.printer + " " + t.handler).toLowerCase().includes(q) ); } if (sortBy === "recent") { r = [...r].sort((a, b) => (a.age || "").localeCompare(b.age || "")); } else if (sortBy === "sla") { r = [...r].sort((a, b) => (b.slaPct || 0) - (a.slaPct || 0)); } else if (sortBy === "channel") { r = [...r].sort((a, b) => a.channel.localeCompare(b.channel)); } return r; }, [filterOwner, filterChannel, filterUrgent, search, sortBy]); const byPhase = useMemoLC(() => { const g = {}; PHASES.forEach(p => { g[p.id] = []; }); filtered.forEach(t => { if (g[t.phase]) g[t.phase].push(t); }); return g; }, [filtered]); const totals = useMemoLC(() => { const t = LIFECYCLE_TICKETS; const sofia = t.filter(x => x.owner === "sofia" && x.phase !== "escalado" && x.phase !== "resuelto").length; const escalado = t.filter(x => x.phase === "escalado").length; const resueltos = t.filter(x => x.phase === "resuelto").length; const active = t.length - resueltos; return { total: t.length, active, sofia, sofiaPct: Math.round(sofia / active * 100), escalado, escaladoPct: Math.round(escalado / active * 100), resueltos, }; }, []); const channelCounts = useMemoLC(() => { const c = {}; CHANNELS.forEach(ch => { c[ch.id] = LIFECYCLE_TICKETS.filter(t => t.channel === ch.id).length; }); return c; }, []); const ownerCounts = useMemoLC(() => ({ all: LIFECYCLE_TICKETS.length, sofia: LIFECYCLE_TICKETS.filter(t => t.owner === "sofia").length, dispatch: LIFECYCLE_TICKETS.filter(t => t.owner === "dispatch").length, taller: LIFECYCLE_TICKETS.filter(t => t.owner === "taller").length, conectividad: LIFECYCLE_TICKETS.filter(t => t.owner === "conectividad").length, }), []); return (

Ciclo de vida · {filtered.length} de {LIFECYCLE_TICKETS.length} tickets

Todos los Tickets

Total {totals.total}
SOF.IA {totals.sofiaPct}%
Escalados {totals.escalado}
Resueltos {totals.resueltos}
{/* Pill tabs — cross-nav only */}
setRoute && setRoute("queue")}> Activos {totals.active} setRoute && setRoute("pending")}> Pendientes 14 setRoute && setRoute("resolved")}> Resueltos hoy 47 Todos los tickets {totals.total}
{/* === TOOLBAR === */}
setSearch(e.target.value)} placeholder="Buscar por cliente, empresa, ticket, equipo, área…" /> {search && ( )}
setView("kanban")} title="Vista tablero" > setView("tabla")} title="Vista lista" >
{sortOpen && (
{SORT_OPTIONS_LC.map(o => ( ))}
)}
{/* Inline filter bar — owner + channels in one quiet row */}
Manejado por
{OWNERS.map(o => ( setFilterOwner(o.id)} > {o.label} {ownerCounts[o.id]} ))}
Canal
setFilterChannel("all")} > Todos {LIFECYCLE_TICKETS.length} {CHANNELS.map(ch => ( setFilterChannel(filterChannel === ch.id ? "all" : ch.id)} > {ch.label} {channelCounts[ch.id]} ))}
{/* Empty state */} {filtered.length === 0 && (
— sin tickets para los filtros activos —
)} {/* KANBAN */} {view === "kanban" && filtered.length > 0 && (
{PHASES.map(p => (
{p.label} {byPhase[p.id].length}
{byPhase[p.id].length === 0 ? (
vacío
) : byPhase[p.id].map(t => ( ))}
))}
)} {/* TABLE */} {view === "tabla" && filtered.length > 0 && (
Ticket Cliente Equipo Fase Manejado por SLA
{filtered.map(t => { const tint = PHASE_TINT[t.phase] || "#B0B5BE"; return (
openTicket(t)} >
#{t.id}
{t.customer} {t.company}
{t.printer}
{t.sub}
{t.owner === "sofia" ? ( <>
S
SOF.IA ) : ( <>
OP
{t.handler} )}
= 75 ? "danger" : t.slaPct >= 50 ? "warn" : "ok"}`}>{t.slaPct === 100 ? "OK" : `${t.slaPct}%`}
); })}
)}
); }; window.LifecycleScreen = LifecycleScreen;