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

{/* Filters */} {/* Grid */}
} />
{filtered.length} resultados
{}} tabs={[ { id: 'match', label: 'Match', icon: }, { id: 'new', label: 'Nuevos' }, { id: 'avail', label: 'Disponibles' }, ]} />
{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 (
{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 && ( )}
{/* Active chips */} {activeChips.length > 0 && (
{activeChips.map((chip) => ( ))}
)} {/* Scrollable groups */}
{allSkills.map((s) => { const on = filters.skills.includes(s); return ( ); })}
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
{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 */}
); } // ---------- Helpers ---------- function CheckRow({ checked, onChange, label }) { return ( ); } function Toggle({ checked, onChange }) { return ( ); } 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}
onChange([Math.min(+e.target.value, hi), hi])} className="absolute inset-0 w-full opacity-0 cursor-pointer pointer-events-auto" style={{ zIndex: lo > max - 1 ? 5 : 3 }} /> onChange([lo, Math.max(+e.target.value, lo)])} className="absolute inset-0 w-full opacity-0 cursor-pointer pointer-events-auto z-[4]" />
{min}{max}+
); } function CandidateCard({ c, onOpen, onChat, onCall }) { return (
{c.shortlistedFor && }
{c.role}
{c.city}, {c.country} · {c.yearsExperience}y
{c.matchScore}
Match
{c.skills.slice(0, 3).map((s) => {s})}
} title="Shortlist" />
); } function Mini({ label, value }) { return (
{label}
{value}
); } // ----------------- 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 (
); } 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}
    ); } function CvSection({ title, children }) { return (
    {title}
    {children}
    ); } 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
    Candidato
    Requerido
    {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 &&
    }
    {e.t}
    {e.title}
    {e.body}
    ); })}
    ); } Object.assign(window, { CandidatesScreen, CandidateDetail });