/* global React, NavIcon, MCP_RULES, MCP_THREAD, MCP_QUICK */
const { useState: useStateM, useRef: useRefM, useEffect: useEffectM } = React;
// ============ Widget renderer ============
const Widget = ({ w }) => {
if (!w || !w.kind) return null;
if (w.kind === "bars") {
const data = w.data || [];
const max = Math.max(1, ...data.map(d => Number(d.value) || 0));
return (
{m.role === "sofia" ? "S" : "AC"}
{m.role === "sofia" ? "SOF.IA" : "Alejandra Carrillo"}
· {m.time}
{m.role === "sofia" && MCP }
{m.loading ? (
consultando sistemas conectados…
) : (
<>
$1").replace(/\n/g,"
")}}/>
{m.widget &&
}
{m.sources && m.sources.length > 0 && (
{m.sources.map(s => ↘ {sourceLabel[s] || s} )}
)}
>
)}
);
};
// ============ Prompt for SOF.IA ============
const SYSTEM_PROMPT = `Eres SOF.IA, una agente de IA omnicanal de Optipixel que automatiza el soporte técnico de impresoras para Canella (empresa guatemalteca). Estás respondiendo a Alejandra Carrillo, Dispatcher de Canella, en su consola interna "MCP Console". Hablas en español de Guatemala, profesional, conciso, técnico pero claro.
Tienes acceso vía MCP a:
- tickets: bandeja activa y resueltos (canales: WhatsApp, llamada, web chat, correo)
- equipos: BD de impresoras (HP LaserJet Pro M404dn, HP LaserJet M428fdw, HP Color LaserJet M283fdw, HP OfficeJet Pro 9015e, HP LaserJet M111w, Canon imageRUNNER 2630i, Canon LBP6230dw)
- contratos: arrendamiento (ARR-*) y mantenimiento (MANT-*)
- kb: Knowledge Base (artículos KB-HP-*, KB-CAN-*, KB-GEN-*)
- agenda: calendario técnicos (Eddy R. Marroquín zona 11, Walter A. Galindo zona 10)
Reglas activas que debes respetar al razonar:
- Si entra un HP sin serie → escalar a Dispatcher
- Arrendamiento → Conectividad
- Cliente facturado/cotización → "fuera de alcance fase 1"
- HW/SW con consulta técnica → KB o Taller
- Visita técnica → Dispatcher
- Equipos HP tienen series largas alfanuméricas (10 caracteres tipo VNB3K12089)
Datos contextuales que puedes usar libremente (inventa cifras realistas si te las piden):
- Volumen típico: ~128 tickets/día, 80% resueltos sin humano, ~17% escalados
- Canales dominantes: WhatsApp y llamada
- Falla más común: atascos de papel en HP M404dn (rodillo bandeja 2, ~80k impresiones)
- Tiempo medio captura: 42 segundos
- Operadores Canella: Alejandra Carrillo (Dispatcher), Marcos R. (Optipixel)
Responde SOLO con JSON válido en este formato exacto:
{
"text": "tu respuesta en máximo 3 frases cortas, puede usar **negrita** y saltos de línea",
"sources": ["tickets","kb","equipos","contratos","agenda","reglas"],
"widget": null | { "kind": "bars"|"table"|"rank"|"kpis", ...campos }
}
Widgets:
- bars: { "kind":"bars", "title":"...", "data":[{"label":"L","value":4},...], "note":"breve insight" }
- table: { "kind":"table", "title":"...", "cols":["Col1","Col2"], "rows":[["a","b"],...], "note":"..." }
- rank: { "kind":"rank", "title":"...", "data":[{"name":"...","count":N},...], "note":"..." }
- kpis: { "kind":"kpis", "title":"...", "data":[{"label":"...","value":"...","unit":"..."},...], "note":"..." }
Usa widget cuando la pregunta pida datos cuantitativos o comparaciones. Para preguntas conceptuales o de proceso, devuelve "widget": null.
Incluye 1-3 sources relevantes. Sé directa, profesional, datos realistas. NUNCA salgas del JSON.`;
// ============ MAIN ============
const McpScreen = () => {
const [rules, setRules] = useStateM(MCP_RULES);
const [newRule, setNewRule] = useStateM("");
const [input, setInput] = useStateM("");
const [messages, setMessages] = useStateM(MCP_THREAD);
const [busy, setBusy] = useStateM(false);
const threadRef = useRefM(null);
useEffectM(() => {
if (threadRef.current) {
threadRef.current.scrollTop = threadRef.current.scrollHeight;
}
}, [messages, busy]);
const toggleRule = (id) => {
setRules(rs => rs.map(r => r.id === id ? {...r, active: !r.active} : r));
};
const addRule = () => {
if (!newRule.trim()) return;
setRules(rs => [...rs, {
id: `R-${String(rs.length + 1).padStart(3,"0")}`,
text: newRule.trim(),
author: "Alejandra Carrillo",
created: "hoy",
triggers: 0,
active: true,
category: "Custom",
}]);
setNewRule("");
};
const fmtTime = () => {
const d = new Date();
return `${String(d.getHours()).padStart(2,"0")}:${String(d.getMinutes()).padStart(2,"0")}`;
};
const send = async (text) => {
const q = (text || input).trim();
if (!q || busy) return;
setInput("");
setBusy(true);
const userMsg = { role:"user", text:q, time:fmtTime() };
const loadingMsg = { role:"sofia", loading:true, time:fmtTime() };
setMessages(ms => [...ms, userMsg, loadingMsg]);
try {
const raw = await window.claude.complete({
messages: [
{ role:"user", content: SYSTEM_PROMPT + "\n\nPregunta del operador:\n" + q }
],
});
// Try to parse JSON from response
let parsed = null;
try {
// strip code fences if present
let cleaned = raw.trim();
const fence = cleaned.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
if (fence) cleaned = fence[1];
// find outermost JSON
const first = cleaned.indexOf("{");
const last = cleaned.lastIndexOf("}");
if (first >= 0 && last > first) cleaned = cleaned.slice(first, last+1);
parsed = JSON.parse(cleaned);
} catch (e) {
parsed = { text: raw, sources: ["tickets"], widget: null };
}
const sofiaMsg = {
role: "sofia",
text: parsed.text || "(sin respuesta)",
sources: parsed.sources || [],
widget: parsed.widget || null,
time: fmtTime(),
};
setMessages(ms => [...ms.slice(0,-1), sofiaMsg]);
} catch (err) {
setMessages(ms => [...ms.slice(0,-1), {
role: "sofia",
text: "No pude consultar los sistemas en este momento. Intenta de nuevo en unos segundos.",
sources: [],
time: fmtTime(),
}]);
} finally {
setBusy(false);
}
};
const onKey = (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
send();
}
};
return (
Preguntar a SOF.IA · Copilot MCP
Lenguaje natural sobre tickets, equipos, KB y agenda · conectado vía MCP
MCP · 4 SISTEMAS CONECTADOS
{/* Chat */}
{messages.map((m, i) => )}
{MCP_QUICK.map(c => (
send(c)}>↳ {c}
))}
setInput(e.target.value)}
onKeyDown={onKey}
placeholder={busy ? "SOF.IA está respondiendo…" : "Pregúntale a SOF.IA, crea una regla, pide un reporte…"}
disabled={busy}
/>
send()} disabled={busy || !input.trim()}>
{busy ? "…" : "Enviar ⏎"}
SOF.IA lee tickets, equipos, KB y calendario. Las consultas son auditadas.
⏎ enviar · ⇧⏎ nueva línea
{/* Side: rules + connected systems */}
Reglas activas
{rules.filter(r => r.active).length}/{rules.length}
{rules.map(r => (
toggleRule(r.id)}/>
{r.text}
{r.category}
{r.author}
· {r.triggers} disparos
))}
setNewRule(e.target.value)}
onKeyDown={e => e.key === "Enter" && addRule()}
/>
+ Crear
Sistemas conectados (MCP)
en vivo
tickets.canella.gt 14 ms
equipos.canella.gt 9 ms
contratos.canella.gt 22 ms
kb.canella.gt 7 ms
calendar.dispatch.gt 38 ms
Reportes recientes
Volumen omnicanal · mayo hace 2h
Top fallas HP M404 ayer
SLA por canal · sem. 21 23 may
);
};
window.McpScreen = McpScreen;