Actualizar frontend/src/pages/Asesores.jsx

This commit is contained in:
2026-09-22 10:42:20 -05:00
parent 61137b8f5b
commit 611ea35508

View File

@@ -1,8 +1,12 @@
// src/pages/Asesores.jsx
import { useState, useEffect, useCallback } from "react";
import { useState, useEffect, useCallback, Fragment } from "react";
import { api } from "../lib/api";
import { Loader, ErrorBox } from "../components/UI";
const MES_LARGO = ["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"];
const MES_CORTO = ["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic"];
const calNavBtn = { width:26, height:26, borderRadius:7, border:"1px solid #e2e8f0", background:"#f8fafc", cursor:"pointer", fontSize:13, color:"#475569" };
const ESTADO_INFO = {
online: { txt: "En Línea", color: "#10b981", bg: "#d1fae5", fg: "#065f46" },
busy: { txt: "Ocupado", color: "#f59e0b", bg: "#fef9c3", fg: "#854d0e" },
@@ -17,11 +21,47 @@ export default function Asesores() {
const [histAsesor, setHistAsesor] = useState(null); // { nombre, email }
const [histFilas, setHistFilas] = useState([]);
const [histLoading, setHistLoading] = useState(false);
const [semanaBase, setSemanaBase] = useState(null); // Date del lunes de la semana mostrada
const [calAbierto, setCalAbierto] = useState(false); // popover del mini-calendario
const [calVista, setCalVista] = useState("dias"); // "dias" | "meses" | "anios"
const [calAno, setCalAno] = useState(new Date().getFullYear());
const [calMes, setCalMes] = useState(new Date().getMonth());
const [histVista, setHistVista] = useState("calendario"); // "calendario" | "lista"
const [listaDia, setListaDia] = useState(""); // filtro día en vista lista (YYYY-MM-DD)
const [listaCalAbierto, setListaCalAbierto] = useState(false);
const [listaCalVista, setListaCalVista] = useState("dias");
const [listaCalAno, setListaCalAno] = useState(new Date().getFullYear());
const [listaCalMes, setListaCalMes] = useState(new Date().getMonth());
// Lunes de la semana que contiene la fecha dada.
const lunesDe = (d) => {
const x = new Date(d);
const dia = (x.getDay() + 6) % 7; // 0=lunes ... 6=domingo
x.setHours(0, 0, 0, 0);
x.setDate(x.getDate() - dia);
return x;
};
// Eventos ordenados de más antiguo a más reciente.
const histOrden = histFilas.slice()
.sort((a, b) => (a.fecha_hora || "").localeCompare(b.fecha_hora || ""));
// Intervalos "estuvo conectado" [desde, hasta, enCurso] a partir de los ON/OFF.
const intervalosON = (() => {
const out = [];
let ini = null;
for (const h of histOrden) {
if (h.accion === "ON" && !ini) ini = new Date(h.fecha_hora);
else if (h.accion === "OFF" && ini) { out.push([ini, new Date(h.fecha_hora), false]); ini = null; }
}
if (ini) out.push([ini, new Date(), true]); // sigue conectado hasta ahora (en curso)
return out;
})();
async function verHistorial(ag) {
const nombre = ag.name || ag.available_name || "—";
setHistAsesor({ nombre, email: ag.email });
setHistFilas([]); setHistLoading(true);
setHistFilas([]); setSemanaBase(lunesDe(new Date())); setHistLoading(true);
try {
const r = await api.asesorHistorial(ag.email);
setHistFilas(r.historial || []);
@@ -117,6 +157,21 @@ export default function Asesores() {
<div style={{ fontSize:11.5, color:"#94a3b8", whiteSpace:"nowrap",
overflow:"hidden", textOverflow:"ellipsis" }}>{ag.email || "—"}</div>
</div>
{/* Auto-offline (solo lectura) */}
{(() => {
const ao = ag.auto_offline === true || ag.auto_offline === "true";
return (
<span title="Desconexión automática por inactividad (Chatwoot)"
style={{ flexShrink:0, fontSize:10.5, fontWeight:700, borderRadius:999, padding:"3px 10px",
display:"inline-flex", alignItems:"center", gap:5,
background: ao ? "#fef3c7" : "#f1f5f9",
color: ao ? "#b45309" : "#64748b",
border: `1px solid ${ao ? "#fde68a" : "#e2e8f0"}` }}>
<span style={{ width:6, height:6, borderRadius:"50%", background: ao ? "#f59e0b" : "#94a3b8" }} />
Auto-offline: {ao ? "ON" : "OFF"}
</span>
);
})()}
{/* Historial */}
<button onClick={()=>verHistorial(ag)}
style={{ flexShrink:0, padding:"6px 12px", fontSize:11.5, fontWeight:600, borderRadius:8,
@@ -148,7 +203,7 @@ export default function Asesores() {
style={{ position:"fixed", inset:0, background:"rgba(15,23,42,.55)", backdropFilter:"blur(2px)",
display:"flex", alignItems:"center", justifyContent:"center", zIndex:1000, padding:16 }}>
<div onClick={(e)=>e.stopPropagation()}
style={{ background:"#fff", borderRadius:18, width:520, maxWidth:"94vw", maxHeight:"85vh",
style={{ background:"#fff", borderRadius:18, width:1000, maxWidth:"96vw", maxHeight:"92vh",
display:"flex", flexDirection:"column", overflow:"hidden",
boxShadow:"0 30px 80px rgba(15,23,42,.4)", border:"1px solid #e2e8f0" }}>
<div style={{ background:"linear-gradient(135deg, #1e3a5f 0%, #2d5580 100%)", color:"#fff",
@@ -157,30 +212,349 @@ export default function Asesores() {
<div style={{ fontWeight:700, fontSize:16 }}>📋 Historial de conexión</div>
<div style={{ fontSize:12.5, opacity:.85, marginTop:2 }}>{histAsesor.nombre}</div>
</div>
<div style={{ display:"flex", alignItems:"center", gap:10 }}>
{/* Toggle Calendario / Lista */}
<div style={{ display:"flex", background:"rgba(255,255,255,.15)", borderRadius:9, padding:3 }}>
{[["calendario","📅 Calendario"],["lista","📋 Lista"]].map(([v,txt])=>(
<button key={v} onClick={()=>setHistVista(v)}
style={{ border:"none", cursor:"pointer", padding:"5px 12px", borderRadius:7, fontSize:12, fontWeight:700,
background: histVista===v?"#fff":"transparent",
color: histVista===v?"#1e3a5f":"rgba(255,255,255,.85)" }}>{txt}</button>
))}
</div>
<button onClick={()=>setHistAsesor(null)} style={{ background:"rgba(255,255,255,.15)", border:"none", color:"#fff", width:30, height:30, borderRadius:8, fontSize:18, cursor:"pointer" }}>×</button>
</div>
<div style={{ padding:"18px 22px", overflowY:"auto", background:"#f8fafc" }}>
</div>
{/* Navegación de semana (solo en vista Calendario) */}
{semanaBase && histVista==="calendario" && (
<div style={{ padding:"10px 22px", borderBottom:"1px solid #e8edf3", background:"#fff",
display:"flex", alignItems:"center", justifyContent:"center", gap:14 }}>
<button onClick={()=>setSemanaBase((d)=>{ const x=new Date(d); x.setDate(x.getDate()-7); return x; })}
style={{ width:28, height:28, borderRadius:8, border:"1px solid #cbd5e1", background:"#fff", cursor:"pointer", fontSize:14 }}>‹</button>
<span style={{ fontSize:13, fontWeight:700, color:"#1e3a5f", minWidth:210, textAlign:"center" }}>
{(() => {
const fin = new Date(semanaBase); fin.setDate(fin.getDate()+6);
const f = (d)=>d.toLocaleDateString("es-PE",{day:"2-digit",month:"short"});
return `${f(semanaBase)} – ${f(fin)}`;
})()}
</span>
<button onClick={()=>setSemanaBase((d)=>{ const x=new Date(d); x.setDate(x.getDate()+7); return x; })}
style={{ width:28, height:28, borderRadius:8, border:"1px solid #cbd5e1", background:"#fff", cursor:"pointer", fontSize:14 }}>›</button>
{/* Calendario custom (botón) */}
<div style={{ position:"relative", marginLeft:8 }}>
<button title="Ir a una fecha"
onClick={()=>{ setCalAno(semanaBase.getFullYear()); setCalMes(semanaBase.getMonth()); setCalVista("dias"); setCalAbierto((v)=>!v); }}
style={{ width:34, height:30, borderRadius:8, border:"1px solid #cbd5e1",
background: calAbierto?"#eff6ff":"#f8fafc", cursor:"pointer", fontSize:15,
display:"flex", alignItems:"center", justifyContent:"center" }}>📅</button>
{calAbierto && (
<>
<div onClick={()=>setCalAbierto(false)} style={{ position:"fixed", inset:0, zIndex:1099 }} />
<div style={{ position:"absolute", top:38, left:0, zIndex:1100, width:260,
background:"#fff", borderRadius:14, border:"1px solid #e8edf3",
boxShadow:"0 16px 44px rgba(15,23,42,.22)", padding:12 }}>
{/* Cabecera del calendario */}
<div style={{ display:"flex", alignItems:"center", justifyContent:"space-between", marginBottom:10 }}>
<button onClick={()=>setCalVista(calVista==="dias"?"meses":"anios")}
style={{ border:"none", background:"transparent", cursor:"pointer", fontWeight:700,
fontSize:14, color:"#1e3a5f" }}>
{calVista==="dias" ? `${MES_LARGO[calMes]} ${calAno}` : calVista==="meses" ? calAno : "Elige año"}
</button>
{calVista==="dias" && (
<div style={{ display:"flex", gap:4 }}>
<button onClick={()=>{ const m=calMes-1; if(m<0){setCalMes(11);setCalAno(calAno-1);}else setCalMes(m); }}
style={calNavBtn}>‹</button>
<button onClick={()=>{ const m=calMes+1; if(m>11){setCalMes(0);setCalAno(calAno+1);}else setCalMes(m); }}
style={calNavBtn}>›</button>
</div>
)}
</div>
{/* Vista DÍAS */}
{calVista==="dias" && (() => {
const primero = new Date(calAno, calMes, 1);
const offset = (primero.getDay()+6)%7; // lunes=0
const diasMes = new Date(calAno, calMes+1, 0).getDate();
const celdas = [];
for (let i=0;i<offset;i++) celdas.push(null);
for (let d=1;d<=diasMes;d++) celdas.push(d);
const hoy = new Date();
return (
<div>
<div style={{ display:"grid", gridTemplateColumns:"repeat(7,1fr)", gap:2, marginBottom:4 }}>
{["L","M","M","J","V","S","D"].map((x,i)=>(
<div key={i} style={{ textAlign:"center", fontSize:10, fontWeight:700, color:"#94a3b8" }}>{x}</div>
))}
</div>
<div style={{ display:"grid", gridTemplateColumns:"repeat(7,1fr)", gap:2 }}>
{celdas.map((d,i)=>{
if(!d) return <div key={i} />;
const fecha = new Date(calAno, calMes, d);
const esHoy = fecha.toDateString()===hoy.toDateString();
const enSemana = fecha>=semanaBase && fecha < (()=>{const f=new Date(semanaBase);f.setDate(f.getDate()+7);return f;})();
return (
<button key={i}
onClick={()=>{ setSemanaBase(lunesDe(fecha)); setCalAbierto(false); }}
style={{ height:30, borderRadius:8, border:"none", cursor:"pointer", fontSize:12.5,
fontWeight: (esHoy||enSemana)?700:500,
background: enSemana?"#2563eb":(esHoy?"#eff6ff":"transparent"),
color: enSemana?"#fff":(esHoy?"#2563eb":"#334155") }}>{d}</button>
);
})}
</div>
</div>
);
})()}
{/* Vista MESES */}
{calVista==="meses" && (
<div style={{ display:"grid", gridTemplateColumns:"repeat(4,1fr)", gap:6 }}>
{MES_CORTO.map((m,i)=>(
<button key={i} onClick={()=>{ setCalMes(i); setCalVista("dias"); }}
style={{ padding:"10px 0", borderRadius:10, border:"1px solid #eef2f7", cursor:"pointer",
fontSize:12.5, fontWeight: i===calMes?700:600,
background: i===calMes?"#2563eb":"#f8fafc",
color: i===calMes?"#fff":"#475569" }}>{m}</button>
))}
</div>
)}
{/* Vista AÑOS */}
{calVista==="anios" && (
<div style={{ display:"grid", gridTemplateColumns:"repeat(4,1fr)", gap:6 }}>
{Array.from({length:12},(_,i)=>calAno-5+i).map((y)=>(
<button key={y} onClick={()=>{ setCalAno(y); setCalVista("meses"); }}
style={{ padding:"10px 0", borderRadius:10, border:"1px solid #eef2f7", cursor:"pointer",
fontSize:12.5, fontWeight: y===calAno?700:600,
background: y===calAno?"#2563eb":"#f8fafc",
color: y===calAno?"#fff":"#475569" }}>{y}</button>
))}
</div>
)}
</div>
</>
)}
</div>
</div>
)}
<div style={{ padding:"14px", overflow:"auto", background:"#f8fafc" }}>
{histLoading ? <div style={{ textAlign:"center", color:"#94a3b8", padding:20 }}>Cargando...</div> :
histFilas.length === 0 ? <div style={{ textAlign:"center", color:"#94a3b8", padding:20 }}>Sin registros.</div> :
histVista==="lista" ? (() => {
const diaLoc = (iso)=>{ if(!iso) return ""; const d=new Date(iso); const y=d.getFullYear(); const m=String(d.getMonth()+1).padStart(2,"0"); const dd=String(d.getDate()).padStart(2,"0"); return `${y}-${m}-${dd}`; };
const filas = (listaDia ? histOrden.filter((h)=>diaLoc(h.fecha_hora)===listaDia) : histOrden).slice().reverse();
return (
<div>
{/* Filtro por día (calendario custom) */}
<div style={{ display:"flex", alignItems:"center", gap:10, marginBottom:12, flexWrap:"wrap" }}>
<span style={{ fontSize:12.5, fontWeight:700, color:"#475569" }}>Filtrar por día:</span>
<div style={{ position:"relative" }}>
<button title="Elegir día"
onClick={()=>{ const b=listaDia?new Date(listaDia+"T00:00:00"):new Date(); setListaCalAno(b.getFullYear()); setListaCalMes(b.getMonth()); setListaCalVista("dias"); setListaCalAbierto((v)=>!v); }}
style={{ display:"inline-flex", alignItems:"center", gap:6, padding:"6px 12px", borderRadius:8,
border:"1px solid #cbd5e1", background: listaCalAbierto?"#eff6ff":"#f8fafc", cursor:"pointer", fontSize:12.5, fontWeight:600, color:"#475569" }}>
📅 {listaDia ? new Date(listaDia+"T00:00:00").toLocaleDateString("es-PE",{day:"2-digit",month:"short",year:"numeric"}) : "Elegir día"}
</button>
{listaCalAbierto && (
<>
<div onClick={()=>setListaCalAbierto(false)} style={{ position:"fixed", inset:0, zIndex:1099 }} />
<div style={{ position:"absolute", top:38, left:0, zIndex:1100, width:260,
background:"#fff", borderRadius:14, border:"1px solid #e8edf3",
boxShadow:"0 16px 44px rgba(15,23,42,.22)", padding:12 }}>
<div style={{ display:"flex", alignItems:"center", justifyContent:"space-between", marginBottom:10 }}>
<button onClick={()=>setListaCalVista(listaCalVista==="dias"?"meses":"anios")}
style={{ border:"none", background:"transparent", cursor:"pointer", fontWeight:700, fontSize:14, color:"#1e3a5f" }}>
{listaCalVista==="dias" ? `${MES_LARGO[listaCalMes]} ${listaCalAno}` : listaCalVista==="meses" ? listaCalAno : "Elige año"}
</button>
{listaCalVista==="dias" && (
<div style={{ display:"flex", gap:4 }}>
<button onClick={()=>{ const m=listaCalMes-1; if(m<0){setListaCalMes(11);setListaCalAno(listaCalAno-1);}else setListaCalMes(m); }} style={calNavBtn}>‹</button>
<button onClick={()=>{ const m=listaCalMes+1; if(m>11){setListaCalMes(0);setListaCalAno(listaCalAno+1);}else setListaCalMes(m); }} style={calNavBtn}>›</button>
</div>
)}
</div>
{listaCalVista==="dias" && (() => {
const primero = new Date(listaCalAno, listaCalMes, 1);
const offset = (primero.getDay()+6)%7;
const diasMes = new Date(listaCalAno, listaCalMes+1, 0).getDate();
const celdas = []; for(let i=0;i<offset;i++) celdas.push(null); for(let d=1;d<=diasMes;d++) celdas.push(d);
const hoy = new Date();
return (
<div>
<div style={{ display:"grid", gridTemplateColumns:"repeat(7,1fr)", gap:2, marginBottom:4 }}>
{["L","M","M","J","V","S","D"].map((x,i)=><div key={i} style={{ textAlign:"center", fontSize:10, fontWeight:700, color:"#94a3b8" }}>{x}</div>)}
</div>
<div style={{ display:"grid", gridTemplateColumns:"repeat(7,1fr)", gap:2 }}>
{celdas.map((d,i)=>{
if(!d) return <div key={i} />;
const fecha = new Date(listaCalAno, listaCalMes, d);
const val = `${listaCalAno}-${String(listaCalMes+1).padStart(2,"0")}-${String(d).padStart(2,"0")}`;
const esHoy = fecha.toDateString()===hoy.toDateString();
const sel = listaDia===val;
return (
<button key={i} onClick={()=>{ setListaDia(val); setListaCalAbierto(false); }}
style={{ height:30, borderRadius:8, border:"none", cursor:"pointer", fontSize:12.5,
fontWeight: (esHoy||sel)?700:500,
background: sel?"#2563eb":(esHoy?"#eff6ff":"transparent"),
color: sel?"#fff":(esHoy?"#2563eb":"#334155") }}>{d}</button>
);
})}
</div>
</div>
);
})()}
{listaCalVista==="meses" && (
<div style={{ display:"grid", gridTemplateColumns:"repeat(4,1fr)", gap:6 }}>
{MES_CORTO.map((m,i)=>(
<button key={i} onClick={()=>{ setListaCalMes(i); setListaCalVista("dias"); }}
style={{ padding:"10px 0", borderRadius:10, border:"1px solid #eef2f7", cursor:"pointer",
fontSize:12.5, fontWeight:i===listaCalMes?700:600,
background:i===listaCalMes?"#2563eb":"#f8fafc", color:i===listaCalMes?"#fff":"#475569" }}>{m}</button>
))}
</div>
)}
{listaCalVista==="anios" && (
<div style={{ display:"grid", gridTemplateColumns:"repeat(4,1fr)", gap:6 }}>
{Array.from({length:12},(_,i)=>listaCalAno-5+i).map((y)=>(
<button key={y} onClick={()=>{ setListaCalAno(y); setListaCalVista("meses"); }}
style={{ padding:"10px 0", borderRadius:10, border:"1px solid #eef2f7", cursor:"pointer",
fontSize:12.5, fontWeight:y===listaCalAno?700:600,
background:y===listaCalAno?"#2563eb":"#f8fafc", color:y===listaCalAno?"#fff":"#475569" }}>{y}</button>
))}
</div>
)}
</div>
</>
)}
</div>
{listaDia && (
<button onClick={()=>setListaDia("")}
style={{ padding:"6px 12px", border:"1px solid #cbd5e1", borderRadius:8, fontSize:12,
background:"#fff", color:"#64748b", cursor:"pointer", fontWeight:600 }}>Ver todos</button>
)}
</div>
{filas.length===0 ? <div style={{ textAlign:"center", color:"#94a3b8", padding:20 }}>Sin registros{listaDia?" para este día":""}.</div> :
<div style={{ display:"flex", flexDirection:"column", gap:8 }}>
{histFilas.map((h, i) => {
const on = h.accion === "ON";
{filas.map((h,i)=>{
const on = h.accion==="ON";
return (
<div key={i} style={{ display:"flex", alignItems:"center", gap:12, background:"#fff",
border:"1px solid #e8edf3", borderRadius:10, padding:"10px 14px" }}>
<span style={{ width:10, height:10, borderRadius:"50%", flexShrink:0,
background: on ? "#10b981" : "#94a3b8" }} />
<span style={{ fontWeight:700, fontSize:13, color: on ? "#047857" : "#64748b", width:110 }}>
<span style={{ width:10, height:10, borderRadius:"50%", flexShrink:0, background: on?"#10b981":"#94a3b8" }} />
<span style={{ fontWeight:700, fontSize:13, color: on?"#047857":"#64748b", width:120 }}>
{on ? "🟢 Se conectó" : "⚪ Se desconectó"}
</span>
<span style={{ flex:1, fontSize:12.5, color:"#334155" }}>{fmtHora(h.fecha_hora)}</span>
<span style={{ fontSize:10.5, fontWeight:700, color:"#64748b", background:"#f1f5f9",
borderRadius:999, padding:"2px 8px" }}>{h.origen}</span>
<span style={{ fontSize:10.5, fontWeight:700, color:"#64748b", background:"#f1f5f9", borderRadius:999, padding:"2px 8px" }}>{h.origen}</span>
</div>
);
})}
</div>}
</div>
);
})() :
!semanaBase ? null :
(() => {
const H_INI = 7, H_FIN = 23; // franja mostrada 7am-23h
const horas = [];
for (let h=H_INI; h<=H_FIN; h++) horas.push(h);
const dias = [];
for (let i=0; i<7; i++){ const d=new Date(semanaBase); d.setDate(d.getDate()+i); dias.push(d); }
const NOM = ["Lun","Mar","Mié","Jue","Vie","Sáb","Dom"];
const finSem = new Date(semanaBase); finSem.setDate(finSem.getDate()+7);
// ¿algún intervalo cae en esta semana?
const hayDatos = intervalosON.some(([a,b]) => b>=semanaBase && a<finSem);
return (
(() => {
const HORA_PX = 42; // alto de cada hora (px)
const totalH = (H_FIN - H_INI + 1) * HORA_PX;
const fmt = (x)=>x.toLocaleTimeString("es-PE",{hour:"2-digit",minute:"2-digit",hour12:true});
return (
<div style={{ background:"#fff", borderRadius:12, border:"1px solid #e8edf3", overflow:"hidden" }}>
{!hayDatos && (
<div style={{ textAlign:"center", color:"#94a3b8", fontSize:12.5, padding:"8px 0", borderBottom:"1px solid #eef2f7" }}>
Sin conexiones registradas esta semana.
</div>
)}
{/* Cabecera de días */}
<div style={{ display:"grid", gridTemplateColumns:"52px repeat(7, 1fr)", borderBottom:"2px solid #e8edf3" }}>
<div />
{dias.map((d,i)=>{
const esHoy = d.toDateString()===new Date().toDateString();
return (
<div key={i} style={{ textAlign:"center", padding:"10px 0",
borderLeft:"1px solid #eef2f7",
borderTop: esHoy?"3px solid #2563eb":"3px solid transparent",
background: esHoy?"#eff6ff":"#fff" }}>
<div style={{ fontSize:11, fontWeight:600, color: esHoy?"#2563eb":"#94a3b8" }}>{NOM[i]}</div>
<div style={{ fontSize:18, fontWeight:700, color: esHoy?"#2563eb":"#334155" }}>{d.getDate()}</div>
</div>
);
})}
</div>
{/* Cuerpo: horas + columnas de días con bloques posicionados */}
<div style={{ display:"grid", gridTemplateColumns:"52px repeat(7, 1fr)", position:"relative" }}>
{/* Columna de horas */}
<div>
{horas.map((h)=>(
<div key={h} style={{ height:HORA_PX, textAlign:"right", paddingRight:6,
fontSize:11, color:"#94a3b8", borderTop:"1px solid #eef2f7",
position:"relative", top:-7 }}>{h}:00</div>
))}
</div>
{/* Una columna por día */}
{dias.map((d,di)=>{
const colIni = new Date(d); colIni.setHours(H_INI,0,0,0);
// líneas de hora de fondo
return (
<div key={di} style={{ position:"relative", height:totalH, borderLeft:"1px solid #eef2f7" }}>
{horas.map((h,idx)=>(
<div key={h} style={{ position:"absolute", left:0, right:0, top:idx*HORA_PX,
borderTop:"1px solid #f1f5f9", height:1 }} />
))}
{/* Bloques ON continuos */}
{intervalosON.map(([a,b,enCurso],k)=>{
const diaFin = new Date(d); diaFin.setHours(H_FIN+1,0,0,0);
const s = Math.max(a, colIni), e = Math.min(b, diaFin);
if (e<=s) return null; // no cae en este día/franja
const top = ((s - colIni) / (60*60*1000)) * HORA_PX;
const alto = Math.max(6, ((e - s) / (60*60*1000)) * HORA_PX);
const finTxt = enCurso ? "en curso" : fmt(b);
return (
<div key={k} title={enCurso ? `Conectado desde ${fmt(a)} · en curso ahora` : `Conectado de ${fmt(a)} a ${fmt(b)}`}
style={{ position:"absolute", left:3, right:3, top, height:alto, borderRadius:8,
background: enCurso ? "linear-gradient(135deg,#a7f3d0,#6ee7b7)" : "linear-gradient(135deg,#bae6fd,#7dd3fc)",
border: enCurso ? "1px solid #10b981" : "1px solid #38bdf8",
boxShadow:"0 1px 3px rgba(14,165,233,.25)", cursor:"pointer",
overflow:"hidden", color: enCurso ? "#064e3b" : "#0c4a6e", padding:"3px 7px",
fontFamily:"system-ui, -apple-system, 'Segoe UI', sans-serif",
display:"flex", flexDirection:"column", justifyContent:"center", gap:1, lineHeight:1.2 }}>
{alto>=34 ? (
<>
<span style={{ display:"flex", alignItems:"center", gap:5, fontSize:11, fontWeight:700, letterSpacing:.2 }}>
<span style={{ width:7, height:7, borderRadius:"50%", flexShrink:0,
background: enCurso ? "#059669" : "#0284c7" }} />
{fmt(a)}
</span>
<span style={{ fontSize:9.5, fontWeight:500, opacity:.9, letterSpacing:.2, paddingLeft:12 }}>
{enCurso ? "en curso" : `hasta ${fmt(b)}`}
</span>
</>
) : alto>=18 ? (
<span style={{ fontSize:10, fontWeight:700, whiteSpace:"nowrap", letterSpacing:.2 }}>{fmt(a)} – {finTxt}</span>
) : null}
</div>
);
})}
</div>
);
})}
</div>
</div>
);
})()
);
})()}
</div>
</div>
</div>
)}