/* 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.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;