/* global React */
// =======================================================================
// BlazeConnector Admin v3 — Control plane (live)
// · TenantProviders — editor REAL de provider configs (PUT /configs, merge)
// · TenantHealthPanel — diagnóstico de completitud (GET /tenants/:id/health)
// · PageServiceTokens — CRUD de service tokens (S2S: BlazeERP/BlazeOCR…)
// Los secretos NUNCA llegan en claro: getConfigs los enmascara como "__SET__".
// El editor manda secretos en blanco como "__SET__" → el backend preserva el
// valor actual (merge). Ver internal/transport/http/handlers/admin/configs.go.
// =======================================================================
const { useState: _cs, useEffect: _ce, useMemo: _cm } = React;
const MASK = '__SET__';
// Esquema de campos por provider (espejo de tenant.ProviderConfigs en Go).
// type: text | secret | bool | number | select.
const PROVIDER_SCHEMA = {
wacloud: { label: 'WhatsApp Cloud', group: 'Mensajería', icon: 'message-square', fields: [
{ key: 'phone_number_id', label: 'Phone Number ID', type: 'text' },
{ key: 'token', label: 'Access Token', type: 'secret' },
{ key: 'business_id', label: 'Business ID', type: 'text' },
{ key: 'verify_token', label: 'Verify Token (webhook)', type: 'secret' },
{ key: 'app_secret', label: 'App Secret (HMAC)', type: 'secret' } ] },
telegram: { label: 'Telegram', group: 'Mensajería', icon: 'send', fields: [
{ key: 'bot_token', label: 'Bot Token', type: 'secret' },
{ key: 'default_chat_id', label: 'Default Chat ID', type: 'text' },
{ key: 'secret_token', label: 'Secret Token (webhook header)', type: 'secret' } ] },
chatwoot: { label: 'Chatwoot', group: 'Mensajería', icon: 'messages-square', fields: [
{ key: 'api_url', label: 'API URL', type: 'text' },
{ key: 'token', label: 'API Token', type: 'secret' },
{ key: 'account_id', label: 'Account ID', type: 'text' },
{ key: 'hmac_secret', label: 'HMAC Secret', type: 'secret' } ] },
mikrowisp: { label: 'Mikrowisp', group: 'Billing / OSS', icon: 'router', fields: [
{ key: 'api_url', label: 'API URL', type: 'text' }, { key: 'token', label: 'Token', type: 'secret' } ] },
wisphub: { label: 'WispHub', group: 'Billing / OSS', icon: 'router', fields: [
{ key: 'api_url', label: 'API URL', type: 'text' }, { key: 'token', label: 'Token', type: 'secret' } ] },
domiisp: { label: 'DomiISP', group: 'Billing / OSS', icon: 'router', fields: [
{ key: 'api_url', label: 'API URL (api.php)', type: 'text' }, { key: 'token', label: 'X-API-Key', type: 'secret' } ] },
smartolt: { label: 'SmartOLT', group: 'Billing / OSS', icon: 'network', fields: [
{ key: 'api_url', label: 'API URL', type: 'text' }, { key: 'token', label: 'Token', type: 'secret' } ] },
oficable: { label: 'Oficable', group: 'Billing / OSS', icon: 'router', fields: [
{ key: 'api_url', label: 'API URL', type: 'text' },
{ key: 'api_espejo', label: 'API Espejo (opcional)', type: 'text' },
{ key: 'relay_enabled', label: 'Relay v3 activo', type: 'bool' },
{ key: 'relay_cutoff', label: 'Relay cutoff (YYYY-MM-DD)', type: 'text' } ] },
voltage: { label: 'Voltage', group: 'Billing / OSS', icon: 'zap', fields: [
{ key: 'low_threshold', label: 'Umbral bajo (V)', type: 'number' },
{ key: 'high_threshold', label: 'Umbral alto (V)', type: 'number' },
{ key: 'alert_phone', label: 'Teléfono alerta (E.164)', type: 'text' },
{ key: 'alert_chat_id', label: 'Telegram chat ID', type: 'text' },
{ key: 'min_interval_sec', label: 'Intervalo mínimo (s)', type: 'number' } ] },
cardnet: { label: 'CardNET', group: 'Pagos', icon: 'credit-card', fields: [
{ key: 'api_url', label: 'API URL', type: 'text' },
{ key: 'merchant_id', label: 'Merchant ID', type: 'text' },
{ key: 'terminal_id', label: 'Terminal ID', type: 'text' },
{ key: 'api_key', label: 'API Key', type: 'secret' } ] },
azul: { label: 'Azul', group: 'Pagos', icon: 'credit-card', fields: [
{ key: 'api_url', label: 'API URL', type: 'text' },
{ key: 'merchant_id', label: 'Merchant ID', type: 'text' },
{ key: 'auth1', label: 'Auth1', type: 'secret' },
{ key: 'auth2', label: 'Auth2', type: 'secret' } ] },
paypal: { label: 'PayPal', group: 'Pagos', icon: 'credit-card', fields: [
{ key: 'client_id', label: 'Client ID', type: 'text' },
{ key: 'client_secret', label: 'Client Secret', type: 'secret' },
{ key: 'webhook_id', label: 'Webhook ID', type: 'text' },
{ key: 'mode', label: 'Modo', type: 'select', options: ['sandbox', 'live'] } ] },
cybersource: { label: 'CyberSource', group: 'Pagos', icon: 'credit-card', fields: [
{ key: 'merchant_id', label: 'Merchant ID', type: 'text' },
{ key: 'api_key', label: 'API Key (Shared Secret)', type: 'secret' },
{ key: 'secret_key', label: 'Secret Key (HTTP Sig)', type: 'secret' },
{ key: 'mode', label: 'Modo', type: 'select', options: ['test', 'live'] },
{ key: 'currency_default', label: 'Moneda default', type: 'text' } ] },
blazeteams: { label: 'BlazeTeams', group: 'Otros', icon: 'users', fields: [
{ key: 'api_url', label: 'API URL', type: 'text' },
{ key: 'token', label: 'Token (bt_…)', type: 'secret' },
{ key: 'hmac_secret', label: 'HMAC Secret (webhook)', type: 'secret' } ] },
monitoring: { label: 'Monitoring (NMS)', group: 'Otros', icon: 'activity', fields: [
{ key: 'inbound_token', label: 'Inbound Token (?token=)', type: 'secret' },
{ key: 'min_interval_sec', label: 'Anti-spam (s)', type: 'number' } ] }
};
const PROVIDER_GROUP_ORDER = ['Mensajería', 'Billing / OSS', 'Pagos', 'Otros'];
// ---- Editor modal de un provider -------------------------------------
function ProviderConfigModal({ tenant, provider, masked, onClose, onSaved }) {
const schema = PROVIDER_SCHEMA[provider];
const current = (masked && masked[provider]) || {};
const [form, setForm] = _cs(() => {
const f = {};
schema.fields.forEach((fd) => {
if (fd.type === 'secret') { f[fd.key] = ''; } // nunca pre-rellenamos secretos
else if (fd.type === 'bool') { f[fd.key] = !!current[fd.key]; }
else { f[fd.key] = current[fd.key] != null ? String(current[fd.key]) : ''; }
});
return f;
});
const [busy, setBusy] = _cs(false);
const [err, setErr] = _cs(null);
function setField(k, v) { setForm((p) => Object.assign({}, p, { [k]: v })); }
async function submit() {
setBusy(true); setErr(null);
// Construye el sub-config del provider. Secretos en blanco → MASK (mantener).
const out = {};
schema.fields.forEach((fd) => {
const v = form[fd.key];
if (fd.type === 'secret') { out[fd.key] = v === '' ? MASK : v; }
else if (fd.type === 'bool') { out[fd.key] = !!v; }
else if (fd.type === 'number') { out[fd.key] = v === '' ? 0 : Number(v); }
else { out[fd.key] = v; }
});
try {
await window.BC.updateConfigs(tenant.uuid, { [provider]: out });
onSaved && onSaved();
onClose();
} catch (e) { setErr(e.message); setBusy(false); }
}
return (
Credenciales S2S para sistemas internos (BlazeERP, BlazeOCR…)
{rows.length} tokens · {rows.filter((s) => s.status === 'active').length} activos · control plane S2S
/service/v1/* de uno o más tenants — guárdalos en un gestor de secretos.