Tu información a la mano

import { useState, useEffect } from "react"; const ACC = "5838761000000570055"; const MCP = "https://claude-zohocrm.zohomcp.com/mcp/message"; const PG = 30; async function callCRM(prompt) { const r = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: "claude-sonnet-4-20250514", max_tokens: 8000, system: "Execute Zoho CRM COQL queries with executeCOQLQuery tool and return ONLY a valid JSON array. No explanation, no markdown, no code blocks.", messages: [{ role: "user", content: prompt }], mcp_servers: [{ type: "url", url: MCP, name: "zcr" }] }) }); const d = await r.json(); if (!r.ok) throw new Error(d.error?.message || "API error " + r.status); const txt = (d.content || []).filter(b => b.type === "text").map(b => b.text).join(""); const m = txt.match(/\[[\s\S]*?\]/); if (!m) throw new Error("Respuesta no JSON del CRM"); return JSON.parse(m[0]); } export default function App() { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(false); const [err, setErr] = useState(""); const [ts, setTs] = useState(""); const [q, setQ] = useState(""); const [fp, setFp] = useState(""); const [fe, setFe] = useState(""); const [pg, setPg] = useState(1); const [step, setStep] = useState(""); useEffect(() => { (async () => { try { const c = await window.storage.get("monks_crm"); if (c) { const p = JSON.parse(c.value); setRows(p.rows || []); setTs(p.ts || ""); } } catch {} })(); }, []); const refresh = async () => { setLoading(true); setErr(""); setPg(1); try { setStep("Obteniendo titulares del CRM…"); const tArr = await callCRM( `Ejecuta esta consulta COQL con executeCOQLQuery. Si more_records=true, repite con OFFSET 200, 400... hasta tener todos. Concatena en un solo array. SELECT id, N_mero_de_registro, P_liza, Name, Estatus_de_p_liza, Inicio_de_cobertura FROM P_lizas1 WHERE Nombre_de_la_cuenta.id = '${ACC}' AND Estatus_de_p_liza in ('Activa','Pendiente') ORDER BY N_mero_de_registro ASC LIMIT 200 OFFSET 0 Devuelve SOLO este array JSON: [{"i":"id","f":"N_mero_de_registro","c":"P_liza","n":"Name","e":"Estatus_de_p_liza","s":"Inicio_de_cobertura"},...]` ); setStep("Obteniendo familiares del CRM…"); const dArr = await callCRM( `Ejecuta esta consulta COQL con executeCOQLQuery: SELECT Parentesco, Asegurado, Inicio_de_cobertura, Parent_Id FROM lista_dependientes WHERE Parent_Id.Nombre_de_la_cuenta.id = '${ACC}' LIMIT 200 OFFSET 0 Devuelve SOLO este array JSON: [{"p":"Parent_Id.id","n":"Asegurado.name","r":"Parentesco","s":"Inicio_de_cobertura"},...]` ); setStep("Procesando…"); const dMap = {}; (Array.isArray(dArr) ? dArr : []).forEach(d => { if (d.p) { if (!dMap[d.p]) dMap[d.p] = []; dMap[d.p].push(d); } }); const newRows = []; (Array.isArray(tArr) ? tArr : []).forEach(t => { if (!t.f) return; newRows.push({ folio: t.f, cert: t.c || "", nombre: t.n, parentesco: "Titular", estatus: t.e, inicio: t.s || "" }); (dMap[t.i] || []).forEach(dep => { newRows.push({ folio: t.f, cert: t.c || "", nombre: dep.n || "", parentesco: dep.r || "Familiar", estatus: t.e, inicio: dep.s || "" }); }); }); setRows(newRows); const now = new Date().toLocaleString("es-MX"); setTs(now); try { await window.storage.set("monks_crm", JSON.stringify({ rows: newRows, ts: now })); } catch {} } catch (e) { setErr(e.message || "Error de conexión con el CRM"); } finally { setLoading(false); setStep(""); } }; const filtered = rows.filter(r => { const ql = q.toLowerCase(); if (fp && r.parentesco !== fp) return false; if (fe && r.estatus !== fe) return false; if (ql && !r.folio.toLowerCase().includes(ql) && !r.cert.toLowerCase().includes(ql) && !r.nombre.toLowerCase().includes(ql)) return false; return true; }); const pages = Math.max(1, Math.ceil(filtered.length / PG)); const slice = filtered.slice((pg - 1) * PG, pg * PG); const nT = rows.filter(r => r.parentesco === "Titular").length; const nC = rows.filter(r => r.parentesco === "Conyugé" || r.parentesco === "Conyuge").length; const nD = rows.filter(r => r.parentesco === "Dependiente").length; const rowBg = r => { if (r.estatus === "Pendiente") return "#FFF8E1"; if (r.parentesco === "Titular") return "#fff"; if (r.parentesco === "Conyugé" || r.parentesco === "Conyuge") return "#E3F2FD"; return "#E8F5E9"; }; const pBg = p => p === "Titular" ? "#dbeafe" : (p === "Conyugé" || p === "Conyuge") ? "#bfdbfe" : p === "Dependiente" ? "#bbf7d0" : "#e9d5ff"; const pCl = p => p === "Titular" ? "#1e40af" : (p === "Conyugé" || p === "Conyuge") ? "#1e3a8a" : p === "Dependiente" ? "#166534" : "#6b21a8"; const H = ({ children, style = {} }) =>
{children}
; return (
{/* ── Header ── */}
Fremma Seguros · Bupa México · Póliza 4797
Monks — Certificados asegurados
{ts &&
Última actualización: {ts}
}
{/* ── Estado de carga ── */} {loading && step && (
⏳ {step}
)} {/* ── Error ── */} {err && (
Error: {err}
)} {/* ── Stats ── */}
{[["Total", rows.length], ["Titulares", nT], ["Cónyuge", nC], ["Dependientes", nD]].map(([l, v]) => (
{l}
{v}
))}
{/* ── Estado vacío ── */} {rows.length === 0 && !loading && (
📋
Haz clic en ↻ Actualizar CRM para consultar los datos en tiempo real.
)} {/* ── Tabla ── */} {rows.length > 0 && ( <>
{ setQ(e.target.value); setPg(1); }} placeholder="Buscar nombre, folio o certificado…" style={{ flex: 1, minWidth: 170 }} />
{["Folio", "Certificado", "Nombre", "Parentesco", "Estatus", "Inicio cobertura"].map(h => ( ))} {slice.length === 0 ? ( ) : slice.map((r, i) => ( ))}
{h}
Sin resultados
{r.folio}{r.cert || "—"}{r.nombre}{r.parentesco}{r.estatus}{r.inicio || "—"}
{filtered.length} resultado{filtered.length !== 1 ? "s" : ""} · pág. {pg} de {pages}
)}
); }