/* global React, ChIcon, NavIcon, CHANNELS */
const { useState: useStateF } = React;
const FLOW_NODES = [
{
id: "ingesta",
step: "01",
title: "Ingesta Omnicanal",
desc: "Recibe mensajes de los 4 canales activos y normaliza el formato.",
stat: "234",
statU: "mensajes hoy",
tone: "cream",
ico: ( ),
connLabel: "ticket entrante",
},
{
id: "triage",
step: "02",
title: "Triage · SOF.IA",
desc: "Clasifica intención, detecta urgencia y asigna confianza.",
stat: "0.94",
statU: "confianza media",
tone: "lavender",
ico: ( ),
connLabel: "intent + score",
},
{
id: "bd",
step: "03",
title: "Validación BD",
desc: "Busca serie, NIT, contrato y cliente en BD Canella.",
stat: "11ms",
statU: "latencia p50",
tone: "pink",
ico: ( ),
connLabel: "cliente OK",
},
{
id: "kb",
step: "04",
title: "Knowledge Base",
desc: "Recupera artículos relevantes con score de similitud.",
stat: "214",
statU: "artículos indexados",
tone: "mint",
ico: ( ),
connLabel: "KB match",
},
{
id: "decision",
step: "05",
title: "Decisión",
desc: "Auto-resuelve si supera umbral, o escala a operador humano.",
stat: "80 / 20",
statU: "auto / escalado",
tone: "yellow",
ico: ( ),
connLabel: "ruta elegida",
},
{
id: "accion",
step: "06",
title: "Acción",
desc: "Agenda visita, redacta correo y cierra el ticket con trazabilidad.",
stat: "5:14",
statU: "tiempo medio",
tone: "stone",
ico: ( ),
},
];
const CHANNEL_CONFIGS = [
{ id:"whatsapp", on:true, business:"L-V 7:00 – 19:00 · sáb 8:00 – 14:00", account:"+502 2447 9100 · WABA verificada", vol:108 },
{ id:"call", on:true, business:"L-V 7:00 – 19:00", account:"PBX Canella · 4 líneas SIP", vol:62 },
{ id:"web", on:true, business:"24 / 7 · widget en canella.gt", account:"widget_v3.canella.gt", vol:31 },
{ id:"email", on:true, business:"24 / 7 · respuesta en <4h hábiles", account:"soporte@canella.gt", vol:33 },
];
const TRIAGE_RULES = [
{ id:"T1", on:true, text:"HP sin serie reconocible → escalar a Dispatcher", trig:34 },
{ id:"T2", on:true, text:"Arrendamiento (ARR-*) → área de Conectividad", trig:218 },
{ id:"T3", on:true, text:"Contrato vencido o no grabado → notificar al operador antes de proponer visita", trig:47 },
{ id:"T4", on:true, text:"Cliente facturado / cotización → marcar “fuera de alcance fase 1”", trig:91 },
{ id:"T5", on:true, text:"Visita matutina por defecto entre 9:00 y 12:00", trig:62 },
{ id:"T6", on:false, text:"Atascos repetidos del mismo equipo (>2 en 30 días) → escalar a Taller", trig:4 },
];
const BD_STEPS = [
{ id:"B1", title:"Match exacto por número de serie", sub:"equipos.serie = ? · index B-tree" },
{ id:"B2", title:"Match por últimos 4 dígitos + verificación de nombre y dirección", sub:"para series parcialmente legibles o dictadas en voz" },
{ id:"B3", title:"Búsqueda por NIT del cliente", sub:"fallback cuando la serie no aparece o el cliente compró al contado" },
{ id:"B4", title:"Búsqueda por nombre comercial / # de cliente", sub:"último recurso · siempre escala a operador para confirmar" },
];
const ESC_REASONS_DEF = [
{ id:"r1", label:"Equipo retirado, robado o no se encuentra", on:true },
{ id:"r2", label:"Serie no aparece / compra al contado", on:true },
{ id:"r3", label:"Cliente no puede dar la serie", on:true },
{ id:"r4", label:"Da IP / chasis ADF / cartucho en vez de serie", on:true },
{ id:"r5", label:"Pide búsqueda por NIT / nombre / # cliente", on:true },
{ id:"r6", label:"Contrato no grabado/actualizado", on:true },
{ id:"r7", label:"Falla amerita visita de técnico", on:true },
];
const TONE_OPTIONS = [
{ id:"formal", name:"Formal", desc:"Lenguaje neutro, distancia profesional.", sample:"Estimada Licda. Estrada, recibimos su reporte…" },
{ id:"warm", name:"Cercano", desc:"Cálido, conversacional pero profesional.", sample:"Hola María, soy SOF.IA. Lamento el inconveniente…" },
{ id:"concise", name:"Conciso", desc:"Directo, mínimo de fricción.", sample:"Ticket #CNL-2841 abierto. Necesito la serie:" },
];
// SVG arrow component for connector between nodes
const ConnectorArrow = ({ label }) => (
);
const FlowScreen = ({ setRoute }) => {
const [selectedNode, setSelectedNode] = useStateF("triage");
const [channels, setChannels] = useStateF(CHANNEL_CONFIGS);
const [rules, setRules] = useStateF(TRIAGE_RULES);
const [kbThreshold, setKbThreshold] = useStateF(70);
const [kbTopK, setKbTopK] = useStateF(3);
const [autoConfidence, setAutoConfidence] = useStateF(82);
const [reasons, setReasons] = useStateF(ESC_REASONS_DEF);
const [tone, setTone] = useStateF("warm");
const [greeting, setGreeting] = useStateF("Hola {{cliente.nombre}}, soy SOF.IA, asistente de soporte de Canella. ¿En qué te ayudo hoy?");
const [signature, setSignature] = useStateF("SOF.IA · Soporte Canella\n(generado automáticamente · {{fecha}} {{hora}})");
const toggle = (arr, setter, id) => setter(arr.map(x => x.id === id ? {...x, on: !x.on} : x));
const scrollToCfg = (nodeId) => {
setSelectedNode(nodeId);
setTimeout(() => {
const el = document.querySelector(`[data-cfg="${nodeId}"]`);
if (el) el.scrollIntoView({behavior:"smooth", block:"start"});
}, 80);
};
const sliderStyle = (v) => ({ "--p": `${v}%` });
const sectionHighlight = (id) => selectedNode === id ? "highlight" : "";
return (
Centro de Control · cerebro del sistema
Flujo de SOF.IA
Auto-resolución
80%
Mensajes hoy
234
Latencia BD
11ms
Uptime sistema
99.97%
{/* Pill tabs nav */}
Flujo
setRoute && setRoute("mcp")}>Reglas en lenguaje natural
setRoute && setRoute("kb")}>Knowledge Base
{/* === FLOW DIAGRAM === */}
Pipeline · ticket entrante → resolución
EN VIVO
4 sistemas conectados vía MCP
última sincronización · hace 3 s
{FLOW_NODES.map((n, i) => (
scrollToCfg(n.id)}
>
Nodo {n.step}
{n.title}
{n.desc}
{n.stat} {n.statU}
{i < FLOW_NODES.length - 1 && }
))}
Tickets procesados hoy
128/día
Cerrados sin humano
102/128
Escalados al operador
23
Tiempo medio resolución
5min 14 s
{/* === STICKY SECTION NAV === */}
{FLOW_NODES.map(n => (
scrollToCfg(n.id)}
>
{n.step}
{n.title}
))}
{/* === CONFIG STACK === */}
{/* 01 INGESTA · CANALES */}
Ingesta Omnicanal / 01
Configura los 4 canales activos · todos desembocan en la misma cola unificada.
4 / 4 activos
{channels.map(ch => {
const meta = CHANNELS.find(c => c.id === ch.id);
return (
{meta.label}
{ch.business}
toggle(channels, setChannels, ch.id)}/>
↳ {ch.account}
{ch.vol} msg hoy
uptime 100%
latencia <200ms
);
})}
{/* 02 TRIAGE · REGLAS */}
Triage · Reglas de SOF.IA / 02
Reglas que SOF.IA aplica antes de procesar cualquier ticket entrante.
{rules.filter(r => r.on).length} de {rules.length}
setRoute && setRoute("mcp")}>Editar en lenguaje natural →
{rules.map(r => (
toggle(rules, setRules, r.id)}/>
{r.text}
{r.id}
· {r.trig} disparos
· últimas 24 h
))}
{/* 03 VALIDACION BD */}
Validación en BD Canella / 03
Estrategia de búsqueda en orden · primera coincidencia gana.
Conectado · 11 ms
Endpoint
equipos.canella.gt/api/v2
{BD_STEPS.map((s, i) => (
{i+1}
{s.title}
{s.sub}
▲
▼
))}
Si los 4 pasos fallan, SOF.IA escala automáticamente al Dispatcher preservando todo el contexto recolectado.
{/* 04 KB */}
Knowledge Base / 04
Cuándo y cómo SOF.IA consulta la base de conocimiento para resolver.
214 artículos
setRoute && setRoute("kb")}>Ver KB →
Sugerencia activa
SOF.IA detectó 7 casos similares sin artículo formal sobre “Error 49.4C02 en HP M428fdw” . Vale formalizarlo en la KB.
Crear KB
Posponer
{/* 05 DECISION */}
Decisión · Auto vs. Escalar / 05
Define cuándo SOF.IA actúa sola y cuándo entrega el caso al operador humano.
Resultado actual
Con umbral
{(autoConfidence/100).toFixed(2)} , SOF.IA está cerrando
~80% de los tickets sin humano. Subirlo a 0.90 reduciría errores pero escalaría más casos a Dispatcher.
Motivos de escalamiento permitidos
{reasons.map(r => (
toggle(reasons, setReasons, r.id)}
>
{r.on && }
{r.label}
))}
{/* 06 ACCION · TONE */}
Acción · Tono y respuestas de SOF.IA / 06
Cómo le habla SOF.IA al cliente y cómo redacta correos formales.
Tono predeterminado
{TONE_OPTIONS.map(t => (
setTone(t.id)}
>
{t.name}
{t.desc}
{t.sample}
))}
{/* Footer actions */}
Descartar cambios
Probar en sandbox
Guardar configuración
);
};
window.FlowScreen = FlowScreen;