/* 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 }) => (
{label && {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)} >
{n.ico}
{n.step}
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}
{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
Umbral de confianza para citar KB

Solo usa artículos cuyo score de similitud supere este valor.

setKbThreshold(+e.target.value)} style={sliderStyle(((kbThreshold-50)/(95-50))*100)} /> {(kbThreshold/100).toFixed(2)}
Top-K artículos a recuperar

Cuántos resultados consulta antes de decidir.

setKbTopK(+e.target.value)} style={sliderStyle(((kbTopK-1)/9)*100)} /> {kbTopK}
Sugerencia activa
SOF.IA detectó 7 casos similares sin artículo formal sobre “Error 49.4C02 en HP M428fdw”. Vale formalizarlo en la KB.
{/* 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.
Umbral de confianza para auto-resolver

Por debajo de este score SOF.IA escala, aunque tenga una propuesta.

setAutoConfidence(+e.target.value)} style={sliderStyle(((autoConfidence-60)/(95-60))*100)} /> {(autoConfidence/100).toFixed(2)}
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}
))}
Saludo inicial