// Interviews & Outreach — SOFIA Chat (WhatsApp) inbox. LIGHT enterprise.
const { useState: usI, useEffect: ueI, useRef: urI } = React;
function InterviewsScreen({ onOpenCampaign }) {
const [tab, setTab] = usI('all');
const [search, setSearch] = usI('');
const [selectedId, setSelectedId] = usI(DATA.THREADS[0].id);
const [draft, setDraft] = usI('');
const [railOpen, setRailOpen] = usI(true);
const [userToggledRail, setUserToggledRail] = usI(false);
const containerRef = urI(null);
const toast = useToast();
const endRef = urI(null);
ueI(() => { setDraft(''); }, [selectedId]);
ueI(() => { endRef.current?.scrollIntoView?.({ block: 'end' }); }, [selectedId]);
// Auto-collapse right rail at narrow widths (only if user hasn't manually toggled)
ueI(() => {
if (!containerRef.current) return;
const ro = new ResizeObserver(([entry]) => {
if (userToggledRail) return;
const w = entry.contentRect.width;
setRailOpen(w >= 980);
});
ro.observe(containerRef.current);
return () => ro.disconnect();
}, [userToggledRail]);
const handleRailToggle = (open) => { setUserToggledRail(true); setRailOpen(open); };
const thread = DATA.THREADS.find((t) => t.id === selectedId);
const candidate = thread ? DATA.CANDIDATES.find((x) => x.id === thread.candidateId) : null;
const clientName = thread?.role.split('·')[1]?.trim() || 'el cliente';
const roleName = thread?.role.split('·')[0]?.trim() || '';
const filtered = DATA.THREADS.filter((t) => {
if (tab === 'unread' && !t.unread) return false;
if (tab === 'pinned' && !t.pinned) return false;
if (search && !t.candidateName.toLowerCase().includes(search.toLowerCase()) &&
!t.snippet.toLowerCase().includes(search.toLowerCase())) return false;
return true;
});
const aiSuggestion = thread?.id === 'thr-004'
? '¡Claro Valeria! Te propongo el miércoles 21 a las 11am o el jueves 22 a las 3pm, ambas opciones por videollamada. ¿Cuál te funciona mejor?'
: thread?.id === 'thr-002'
? 'Gracias Karla. Adjunto el calendar invite con los detalles de la entrevista final. Cualquier duda estoy en línea.'
: 'Gracias por tu mensaje. ¿Te parece si avanzamos con el siguiente paso esta semana? Tengo disponibilidad jueves y viernes.';
const sendDraft = () => {
if (!draft.trim()) return;
toast?.push({ title: 'Mensaje enviado vía SOFIA Chat', body: `Entregado a ${thread.candidateName}.`, tone: 'success' });
setDraft('');
};
const fillTemplate = (t) => {
if (!candidate || !thread) return;
setDraft(
t.body
.replace('{{nombre}}', candidate.name.split(' ')[0])
.replace('{{rol}}', roleName || candidate.role)
.replace('{{cliente}}', clientName)
.replace('{{salario}}', 'USD $950')
.replace('{{modalidad}}', 'Híbrido')
.replace('{{fecha}}', 'mañana 10:00 am')
);
};
return (
Entrevistas & Outreach · powered by SOFIA Chat
Inbox de candidatos
Conversaciones WhatsApp orquestadas por SOFIA Chat · 38 activas, 9 sin leer.
}>Filtros
} onClick={onOpenCampaign}>Campaña masiva
{/* ============ Threads ============ */}
{/* ============ Conversation ============ */}
{thread && candidate ? (
) : (
Selecciona una conversación
)}
{/* ============ Right rail ============ */}
{railOpen ? (
handleRailToggle(false)}
/>
) : (
handleRailToggle(true)} />
)}
);
}
// ============================================================
// Thread list
// ============================================================
function ThreadList({ search, setSearch, tab, setTab, threads, selectedId, setSelectedId }) {
return (
} size="sm" />
t.unread).length },
{ id: 'pinned', label: 'Pin', count: DATA.THREADS.filter(t=>t.pinned).length },
]}
/>
{threads.map((t) => {
const c = DATA.CANDIDATES.find((x) => x.id === t.candidateId);
const active = selectedId === t.id;
return (
);
})}
{threads.length === 0 && (
Sin conversaciones
)}
);
}
// ============================================================
// Conversation
// ============================================================
function ConversationPane({ thread, candidate, draft, setDraft, sendDraft, aiSuggestion, endRef, railOpen, setRailOpen }) {
const clientLabel = thread.role.split('·')[1]?.trim() || '';
const roleLabel = thread.role.split('·')[0]?.trim() || '';
return (
<>
{/* Header */}
{thread.candidateName}
}>WhatsApp
{thread.online ? 'en línea' : 'visto hace 2h'}
{roleLabel && <>·{roleLabel}>}
{clientLabel && <>·{clientLabel}>}
} title="Llamar" />
} title="Buscar" />
} title="Más" />
{!railOpen && (
} title="Mostrar panel" onClick={() => setRailOpen(true)} />
)}
{/* Messages */}
{thread.messages[0]?.t?.split('·')[0]?.trim() || 'hoy'}
{thread.messages.map((m, i) => (
{m.text}
{m.t.split('·')[1]?.trim() || m.t}
{m.from === 'op' && }
))}
{/* SOFIA suggestion */}
SOFIA sugiere
{aiSuggestion}
} onClick={() => setDraft(aiSuggestion)}>Usar
} title="Descartar" />
{/* Composer */}
} title="Adjuntar" />
} title="Emoji" />
} onClick={sendDraft} disabled={!draft.trim()}>Enviar
Cifrado E2E
Vía SOFIA Chat · WhatsApp Business
⏎ enviar · Shift+⏎ salto
>
);
}
// ============================================================
// Right rail
// ============================================================
function RightRail({ candidate, thread, onFillTemplate, onCollapse }) {
if (!candidate || !thread) return null;
return (
{/* Header */}
Panel del candidato
} title="Ocultar panel" onClick={onCollapse} />
{/* Scrollable */}
{/* Candidate card */}
{candidate.name}
{candidate.role}
} k="Ubicación" v={`${candidate.city}, ${candidate.country}`} />
} k="Disponibilidad" v={candidate.availability} />
} k="Salario" v={candidate.salaryExpected} />
} k="Idiomas" v={candidate.languages.join(' · ')} />
} k="Fuente" v={candidate.source} />
{/* Templates */}
Plantillas rápidas
{DATA.TEMPLATES.slice(0, 5).map((t) => (
))}
{/* AI Insight (per thread) */}
Última actividad: WhatsApp hace 8 min. Sentimiento del candidato: positivo. SOFIA Call previo recomendó Advance.
{/* Footer actions (sticky) */}
Acciones rápidas
} className="w-full justify-start">Agendar entrevista
}>Kit
}>Submitir
);
}
function CollapsedRail({ onExpand }) {
return (
} title="Mostrar panel" onClick={onExpand} />
} title="Candidato" onClick={onExpand} />
} title="Plantillas" onClick={onExpand} />
} title="SOFIA insights" onClick={onExpand} />
);
}
function KVRow({ icon, k, v }) {
return (
{icon}
{k}
{v}
);
}
window.InterviewsScreen = InterviewsScreen;