// Candidate Pool + Candidate Detail (slide-over). LIGHT.
const { useState: usC, useMemo: umC } = React;
function CandidatesScreen({ onOpenCandidate, onOpenChat, onOpenCall, onOpenCampaign }) {
const [search, setSearch] = usC('');
const [reqId, setReqId] = usC(DATA.REQUISITIONS[0].id);
const [filters, setFilters] = usC({
skills: [],
experience: [0, 10],
languages: [],
location: 'Todas',
salary: 2500,
availability: [],
source: [],
shortlistOnly: false,
});
const toggleArr = (key, val) =>
setFilters((f) => ({
...f,
[key]: f[key].includes(val) ? f[key].filter((x) => x !== val) : [...f[key], val],
}));
const allSkills = ['Inglés C1', 'Inglés B2', 'Negociación', 'Salesforce', 'Power BI', 'POS', 'WhatsApp Business', 'Coaching'];
const allLangs = ['Español', 'Inglés', 'Portugués', 'Francés'];
const allCities = ['Todas', 'San Salvador', 'Guatemala City', 'San Pedro Sula', 'Tegucigalpa', 'Mixco'];
const allAvail = ['Inmediata', '15 días', '30 días', 'A convenir'];
const allSources = ['Portal SOFIA', 'Referido', 'LinkedIn', 'WhatsApp Campaign', 'Job Fair'];
const filtered = umC(() => {
return DATA.CANDIDATES.filter((c) => {
if (search && !c.name.toLowerCase().includes(search.toLowerCase()) &&
!c.role.toLowerCase().includes(search.toLowerCase())) return false;
if (filters.skills.length && !filters.skills.some((s) => c.skills.includes(s))) return false;
if (c.yearsExperience < filters.experience[0] || c.yearsExperience > filters.experience[1]) return false;
if (filters.languages.length && !filters.languages.some((l) => c.languages.includes(l))) return false;
if (filters.location !== 'Todas' && c.city !== filters.location) return false;
if (c.salaryNum > filters.salary) return false;
if (filters.availability.length && !filters.availability.includes(c.availability)) return false;
if (filters.source.length && !filters.source.includes(c.source)) return false;
if (filters.shortlistOnly && !c.shortlistedFor) return false;
return true;
}).sort((a, b) => b.matchScore - a.matchScore);
}, [search, filters]);
return (
Pool de candidatos
Candidatos disponibles
{DATA.CANDIDATES.length} perfiles · rankeados por match score para la vacante seleccionada.
setReqId(e.target.value)}
className="h-9 bg-white border border-line rounded-lg px-3 text-sm text-ink-900 focus-ring hover:border-ink-200"
>
{DATA.REQUISITIONS.map((r) => (
Match contra: {r.title} · {r.client}
))}
} onClick={onOpenCampaign}>Campaña masiva
{/* Filters */}
{/* Grid */}
{filtered.length === 0 ? (
Sin candidatos para estos filtros
Prueba ampliando rangos o usa SOFIA para descubrir perfiles afines.
) : (
{filtered.map((c) => (
onOpenCandidate(c)}
onChat={() => onOpenChat(c)}
onCall={() => onOpenCall(c)} />
))}
)}
);
}
function FilterGroup({ title, badge, children, defaultOpen = true }) {
const [open, setOpen] = React.useState(defaultOpen);
return (
setOpen((v) => !v)}
className="w-full flex items-center justify-between py-3 group"
>
{title}
{badge != null && badge > 0 && (
{badge}
)}
{open &&
{children}
}
);
}
// ---------- Filters panel ----------
function FiltersPanel({ filters, setFilters, toggleArr, allSkills, allLangs, allCities, allAvail, allSources }) {
const activeCount =
filters.skills.length +
filters.languages.length +
filters.availability.length +
filters.source.length +
(filters.location !== 'Todas' ? 1 : 0) +
(filters.salary !== 2500 ? 1 : 0) +
(filters.experience[0] !== 0 || filters.experience[1] !== 10 ? 1 : 0) +
(filters.shortlistOnly ? 1 : 0);
const reset = () => setFilters({
skills: [], experience: [0, 10], languages: [], location: 'Todas',
salary: 2500, availability: [], source: [], shortlistOnly: false,
});
// Active filter chips
const activeChips = [];
filters.skills.forEach((s) => activeChips.push({ key: `s-${s}`, label: s, onRemove: () => toggleArr('skills', s) }));
filters.languages.forEach((s) => activeChips.push({ key: `l-${s}`, label: s, onRemove: () => toggleArr('languages', s) }));
filters.availability.forEach((s) => activeChips.push({ key: `a-${s}`, label: s, onRemove: () => toggleArr('availability', s) }));
filters.source.forEach((s) => activeChips.push({ key: `o-${s}`, label: s, onRemove: () => toggleArr('source', s) }));
if (filters.location !== 'Todas') activeChips.push({ key: 'city', label: filters.location, onRemove: () => setFilters({ ...filters, location: 'Todas' }) });
if (filters.salary !== 2500) activeChips.push({ key: 'sal', label: `≤ $${filters.salary}`, onRemove: () => setFilters({ ...filters, salary: 2500 }) });
if (filters.experience[0] !== 0 || filters.experience[1] !== 10) activeChips.push({ key: 'exp', label: `${filters.experience[0]}–${filters.experience[1]} años`, onRemove: () => setFilters({ ...filters, experience: [0, 10] }) });
if (filters.shortlistOnly) activeChips.push({ key: 'sl', label: 'Shortlist', onRemove: () => setFilters({ ...filters, shortlistOnly: false }) });
return (
{/* Header */}
Filtros
{activeCount > 0 ? `${activeCount} activo${activeCount !== 1 ? 's' : ''}` : 'Ninguno aplicado'}
{activeCount > 0 && (
Limpiar
)}
{/* Active chips */}
{activeChips.length > 0 && (
{activeChips.map((chip) => (
{chip.label}
))}
)}
{/* Scrollable groups */}
{allSkills.map((s) => {
const on = filters.skills.includes(s);
return (
toggleArr('skills', s)}
className={`text-[11px] h-7 px-2.5 rounded-full border transition-colors inline-flex items-center gap-1 ${
on ? 'bg-brand-50 text-brand-700 border-brand-200' : 'bg-white text-ink-700 border-line hover:border-ink-200'
}`}>
{on && }
{s}
);
})}
setFilters({ ...filters, experience: v })}
/>
Hasta
USD ${filters.salary.toLocaleString()}
setFilters({ ...filters, salary: +e.target.value })}
className="w-full h-1.5 accent-brand-500" />
$500 $2,500
setFilters({ ...filters, location: e.target.value })}
className="w-full h-9 bg-white border border-line rounded-lg px-2.5 text-xs text-ink-900 focus-ring hover:border-ink-200">
{allCities.map((c) => {c} )}
{allLangs.map((l) => (
toggleArr('languages', l)} label={l} />
))}
{allAvail.map((a) => (
toggleArr('availability', a)} label={a} />
))}
{allSources.map((s) => (
toggleArr('source', s)} label={s} />
))}
{/* Footer toggle */}
Solo en shortlist
setFilters({ ...filters, shortlistOnly: v })} />
);
}
// ---------- Helpers ----------
function CheckRow({ checked, onChange, label }) {
return (
onChange?.(!checked)}
className={`h-4 w-4 rounded border flex items-center justify-center transition-colors shrink-0 ${
checked ? 'bg-brand-500 border-brand-500' : 'bg-white border-ink-200 group-hover:border-ink-300'
}`}
>
{checked && }
{label}
);
}
function Toggle({ checked, onChange }) {
return (
onChange?.(!checked)}
className={`relative h-5 w-9 rounded-full transition-colors ${checked ? 'bg-brand-500' : 'bg-ink-200'}`}
>
);
}
function RangeDual({ min, max, value, unit, onChange }) {
const [lo, hi] = value;
const span = max - min;
const leftPct = ((lo - min) / span) * 100;
const rightPct = ((hi - min) / span) * 100;
return (
Rango
{lo}–{hi} {unit}
{min} {max}+
);
}
function CandidateCard({ c, onOpen, onChat, onCall }) {
return (
{c.name}
{c.shortlistedFor && }
{c.role}
{c.city}, {c.country}
·
{c.yearsExperience}y
{c.skills.slice(0, 3).map((s) =>
{s} )}
} onClick={onChat}>Chat
} onClick={onCall}>Call
} title="Shortlist" />
}>Ver perfil
);
}
function Mini({ label, value }) {
return (
);
}
// ----------------- Candidate Detail (slide-over) -----------------
function CandidateDetail({ candidate, open, onClose, onOpenChat, onOpenCall, onOpenSchedule, onOpenKit, onOpenSubmit }) {
const [tab, setTab] = usC('summary');
if (!candidate || !open) return null;
const radarAxes = ['Inglés', 'Negociación', 'CRM', 'Atención', 'Liderazgo', 'POS'];
const radarData = {
candidate: [88, 84, 76, 92, 68, 60],
requirement: [85, 80, 70, 80, 60, 55],
};
const timeline = [
{ icon: 'CheckCircle', tone: 'emerald', t: 'Hoy · 10:23', title: 'Screening completado por SOFIA Call', body: 'Recomendación: Advance · 92% conf.' },
{ icon: 'WhatsApp', tone: 'emerald', t: 'Hoy · 10:42', title: 'WhatsApp recibido', body: '"Perfecto, confirmo la entrevista para el jueves a las 3pm"' },
{ icon: 'Bot', tone: 'brand', t: 'Hoy · 09:30', title: 'SOFIA generó kit de entrevista', body: 'Caso role-play + validación inglés C1' },
{ icon: 'WhatsApp', tone: 'emerald', t: 'Ayer · 18:14', title: 'Invitación enviada vía SOFIA Chat', body: 'Template "Invitación a entrevista"' },
{ icon: 'UserPlus', tone: 'sky', t: 'Hace 4 días', title: 'Aplicación recibida', body: 'Portal SOFIA · referido por Camila A.' },
];
return (
CANDIDATE-{candidate.id.slice(-4).toUpperCase()}
{candidate.name}
{candidate.role} · {candidate.yearsExperience} años de experiencia
}>{candidate.city}, {candidate.country}
}>{candidate.education}
}>{candidate.languages.join(' · ')}
}>{candidate.availability}
}>{candidate.salaryExpected}
{candidate.matchScore}%
SOFIA Match
{/* Action bar */}
} onClick={() => onOpenChat(candidate)}>SOFIA Chat
} onClick={() => onOpenSchedule(candidate)}>Agendar SOFIA Call
} onClick={() => onOpenKit(candidate)}>Generar kit
} onClick={() => onOpenSubmit(candidate)}>Submitir a cliente
} title="Shortlist" />
} />
},
{ id: 'cv', label: 'CV', icon: },
{ id: 'fit', label: 'Skills fit', icon: },
{ id: 'timeline', label: 'Touchpoints', icon: },
]}
/>
{tab === 'summary' && }
{tab === 'cv' && }
{tab === 'fit' && }
{tab === 'timeline' && }
);
}
function SummaryTab({ c }) {
return (
SOFIA · La toma
{c.name.split(' ')[0]} es un fuerte fit del {c.matchScore}% para roles bilingües de cobranza outbound. Demuestra
manejo de objeciones avanzado y un patrón consistente de superar cuotas mensuales (~115%). El screening de SOFIA Call mostró
tono cálido y firmeza al mismo tiempo , sin lapsus de inglés.
Fortalezas
▪ Inglés C1 verificado en vivo
▪ 4 años de experiencia outbound · cartera US
▪ Cuota cumplida 9 de últimos 12 meses
▪ Manejo de CRM (Salesforce + Zoho)
Brechas / a validar
▪ Sin experiencia en finanzas — entrenamiento de producto requerido
▪ Expectativa salarial ligeramente arriba del rango
▪ Modalidad: prefiere híbrido (vacante es on-site)
Preguntas sugeridas para entrevista
Skills declaradas
{c.skills.map((s) =>
{s} )}
Métricas clave
SOFIA confianza
{Math.min(99, c.matchScore + 5)}%
Basado en 1,240 contrataciones históricas con perfiles análogos.
);
}
function SuggestQ({ n, q }) {
return (
{n}
{q}
);
}
function KV({ k, v, last }) {
return (
{k}
{v}
);
}
function CVTab({ c }) {
return (
{c.name}
{c.role} · {c.city}, {c.country}
{c.languages.join(' · ')} · {c.education}
{c.education}
Universidad Centroamericana · 2018 – 2022
TOEFL iBT 102/120
Salesforce Trailhead
Six Sigma White Belt
Documento
{c.name}_CV.pdf
2.4 MB · sub. {c.appliedAt}
} className="flex-1">Descargar
}>Abrir
);
}
function CvSection({ title, children }) {
return (
);
}
function CvJob({ role, company, range, bullets }) {
return (
{role} · {company}
{range}
{bullets.map((b, i) => (
▪ {b}
))}
);
}
function FitTab({ c, radarAxes, radarData }) {
return (
SOFIA Skills Fit
Candidato vs. perfil ideal
{radarAxes.map((a, i) => {
const v = radarData.candidate[i], r = radarData.requirement[i];
const delta = v - r;
return (
{a}
= 0 ? 'text-emerald-600' : 'text-ai-700'
}`}>
{delta >= 0 ? '+' : ''}{delta}
);
})}
);
}
function TimelineTab({ events }) {
return (
{events.map((e, i) => {
const I = Icon[e.icon] || Icon.Dot;
const tones = {
emerald: 'bg-emerald-50 text-emerald-700 border-emerald-100',
brand: 'bg-brand-50 text-brand-600 border-brand-100',
sky: 'bg-sky-50 text-sky-700 border-sky-100',
amber: 'bg-ai-50 text-ai-700 border-ai-100',
};
return (
{i < events.length - 1 &&
}
);
})}
);
}
Object.assign(window, { CandidatesScreen, CandidateDetail });