Admin: po@gliica.com / admin123 · Cliente demo: demo@empresa.com / demo123
Gliica
Administración
SG
Sabrina Gutiérrez
Administrador
Principal
Dashboard
Clientes
Fondos / Saldos
Informes
Informes / E. Cuenta
Contratos
Configuración
Tarifario completo
Config. tipos OP
Tickets soporte0
Bitácora
Limpieza sistema
Manual admin
Config. Gliica
Alertas3
v6.2605-V · Sesión sin expiración
Gliica · 2026
Salir
Dashboard
Gliica International · Sabrina Gutiérrez · Administrador
👥
Clientes activos
4
↑ +1 este mes
📋
OPs activas
0
cargando...
💰
Volumen mayo
$47.2k
↑ +18% vs abril
📊
Comisiones cobradas
$1,369
2.9% · $218k acum.
OPs que requieren acción
Haz clic para gestionar
Saldos por cliente
Distribución OPs
Gestión de clientes
ClienteEmpresa / RIFContratoVigenciaSaldoOPs activasVol. 2026ComisiónEstado
Fondos y Saldos
Movimientos
Generador de informes automáticos
Basados en datos reales del sistema
📊
Informe mensual
OPs ejecutadas del período con cargos y comisiones
📋
Estado de cuenta
Balance corriente con saldo disponible
🤝
Referencia comercial
Carta comercial para uso ante terceros (no bancaria)
📈
Informe narrativo
Análisis ejecutivo con gráficas de tendencias y KPIs
🏢
Informe consolidado admin
Todas las empresas, comisiones Gliica e ingresos netos
Historial de informes
TipoClientePeríodoGenerado
Ref.ClienteEmpresaInicioVencimientoComisiónEstadoDías
📌 Tarifario basado en Anexo D del Contrato. Cambia de Manual a Auto para que el cargo aplique automáticamente en nuevas OPs. Los cambios de comisión requieren 30 días de notificación (Cláusula 7).
Tarifario vigente · Ajustable
Concepto (Anexo D)ValorUnidadTipo
Calculadora de cargos totales
🛠️ Define los tipos de OP visibles para los clientes. Activa/desactiva y agrega nuevas categorías.
Tipos de OP disponibles para clientes
Bitácora completa
Registro inmutable de todas las acciones
⚠️ Las limpiezas son permanentes. Úsalas solo cuando sea necesario. Haz exportación antes si quieres respaldo.
Manual del Administrador · Gliica Portal v5
Alertas activas
⚙️ Configuración Gliica
Firma, sello, referencia comercial, documentos
🖊️ Firma y sello oficial de Gliica
Se imprime en todos los contratos y referencias. Sube la firma de Sabrina Gutiérrez y el sello de Liberty Global C.A.
FIRMA
📝 Clic para subir firma
SELLO
🔵 Clic para subir sello
🤝 Texto de referencia comercial
Personaliza el cuerpo del texto de la referencia comercial.
🖨️ Imprimir documentos del cliente
📧 EmailJS — Correos automáticos
Gliica
Portal cliente
CL
Carlos López
Constructora Andina C.A.
Cliente
Principal
Dashboard
Mis OPs3
Autoservicio
Mi saldo / Fondos
Estado de cuenta
Favoritos
Documentos
Mis tarifas
Soporte
Mensajes2
Tickets soporte
Manual / FAQ
Mi perfil
Contrato
LB2026-0142
Salir
Dashboard
Portal de cliente · Gliica International
Movimientos
Estado de cuenta detallado
Con desglose de comisiones y cargos por operación
📄 Mis documentos y contratos
📋 Esta es tu tabla de tarifas según el Anexo D de tu contrato. Solo consulta — tu ejecutivo gestiona cualquier cambio.
Mis tarifas vigentes · Solo lectura
Mensajes de tu ejecutivo
Mi perfil
Nueva Orden de Operación
OP · —
1 · Tipo
2 · Datos
3 · Confirmar
Paso 1 de 3
Alta de nuevo cliente
Firma y sello se usan en todas sus OPs impresas. Credenciales para acceso al portal.
Acceso al portal
Datos de la empresa
Representante legal
Contrato
Firma y sello oficial (para OPs impresas)
📝 Clic para subir firma (PNG/JPG)
🔵 Clic para subir sello (PNG/JPG)
Nueva OP — Administrador
Para uso de emergencia a nombre de un cliente
🛡️ Esta OP se marcará como creada por el administrador.
Registrar entrada de fondos
📎 Adjuntar comprobante
Solicitar carga de fondos
Transfiere los fondos a la cuenta de Gliica y adjunta el comprobante aquí. El saldo se acreditará tras confirmación del administrador.
📎 Adjuntar comprobante
Nuevo favorito
Devolver OP con observaciones
La OP pasará a Pendiente de corrección. El cliente verá las obs. y podrá corregir y reenviar.
Restablecer contraseña de cliente
Asigna una nueva contraseña. Notifica al cliente por correo o teléfono.
Ticket —
Responder
Nuevo ticket de soporte
Programar OP para otra fecha
📅 Si no deseas programar, deja la fecha vacía y la OP se envía hoy.
'; return h; } // Override doPrintContrato to use full contract window.doPrintContrato = function(){ var id = getPrintClient(); if(!id) return; openPrint(buildFullContract(gC(id))); }; // ══ REPORTS WITH CHARTS ══ window.openReportsPanel = function(type){ // Use Chart.js for charts var cid = DB.session.role==='admin'?(document.getElementById('rpt-cl')||{}).value||null:DB.session.clientId; var html = 'Informe Gliica' +'<\/script>' +''; var clients = cid ? [gC(cid)] : DB.clients; var allEntries = DB.entries.filter(function(e){return !cid||e.clientId===cid;}); var allOps = DB.ops.filter(function(o){return !cid||o.clientId===cid;}); // Totals var totalIn = allEntries.filter(function(e){return e.type==='in'&&e.status==='confirmed';}).reduce(function(s,e){return s+e.amount;},0); var totalOut = allEntries.filter(function(e){return e.type==='out'&&!e.isFee&&e.status==='confirmed';}).reduce(function(s,e){return s+e.amount;},0); var totalFees = allEntries.filter(function(e){return e.isFee&&e.status==='confirmed';}).reduce(function(s,e){return s+e.amount;},0); var opsComplete = allOps.filter(function(o){return o.status==='completada';}).length; var opsPending = allOps.filter(function(o){return o.status!=='completada';}).length; // Monthly data for charts var monthly = {}; allOps.forEach(function(op){ var d=op.date||op.createdAt||''; var m=d.substring(0,7)||'Sin fecha'; if(!monthly[m]) monthly[m]={ops:0,vol:0}; monthly[m].ops++;monthly[m].vol+=parseFloat(op.monto||0); }); var months = Object.keys(monthly).sort().slice(-6); html += '
' +'
GLIICA INTERNATIONAL · LIBERTY GLOBAL C.A.
' +'

'+( type==='narrative' ? 'INFORME EJECUTIVO NARRATIVO' : type==='admin' ? 'INFORME CONSOLIDADO ADMINISTRADOR' : 'INFORME DE OPERACIONES EJECUTADAS' )+'

' +'
Generado: '+new Date().toLocaleDateString('es-VE',{day:'2-digit',month:'long',year:'numeric'}) +(cid?' · Cliente: '+gC(cid).empresa:' · Consolidado todos los clientes')+'
'; // KPIs html += '
' +'
FONDOS RECIBIDOS
$'+totalIn.toLocaleString('en',{maximumFractionDigits:0})+'
' +'
OPERADO
$'+totalOut.toLocaleString('en',{maximumFractionDigits:0})+'
' +'
COMISIONES GLIICA
' +'
OPs COMPLETADAS
'+opsComplete+'
' +'
OPs EN PROCESO
'+opsPending+'
' +'
SALDO TOTAL CLIENTES
$'+(clients.reduce(function(s,cl){return s+calcBal(cl.id);},0)).toLocaleString('en',{maximumFractionDigits:0})+'
' +'
'; // OPs table html += '

Detalle de Operaciones

' +''; allOps.slice(0,50).forEach(function(op){ var cl=gC(op.clientId); html+='' +'' +'' +''; }); html += '
Ref.ClienteBeneficiarioTipoFechaMontoCargosEstado
'+op.id+''+cl.empresa.split(' ')[0]+''+op.ben+''+op.type.toUpperCase()+''+op.date+'$'+parseFloat(op.monto||0).toLocaleString('en',{maximumFractionDigits:2})+'$'+parseFloat(op.totalCharges||0).toLocaleString('en',{maximumFractionDigits:2})+''+op.status+'
'; if(type==='narrative'){ html += '

Análisis Narrativo

' +'

Durante el período analizado, Gliica International procesó un total de '+allOps.length+' órdenes de operación para '+(cid?'el cliente '+gC(cid).empresa:DB.clients.length+' clientes')+', con un volumen total operado de $'+totalOut.toLocaleString('en',{maximumFractionDigits:0})+' USD.

' +'

La tasa de ejecución completada fue del '+Math.round(opsComplete/(allOps.length||1)*100)+'%. Los fondos recibidos totalizaron $'+totalIn.toLocaleString('en',{maximumFractionDigits:0})+' USD, generando comisiones por servicios de $'+totalFees.toLocaleString('en',{maximumFractionDigits:0})+' USD (tasa efectiva del '+Math.round(totalFees/(totalIn||1)*100)+' %).

'; // Charts if(months.length > 0){ var vols = months.map(function(m){return monthly[m].vol.toFixed(0);}); var ops_count = months.map(function(m){return monthly[m].ops;}); html += '

Tendencia de Operaciones (últimos 6 meses)

' +'
' +'
' +'window.onload=function(){' +'new Chart(document.getElementById("chart-vol"),{type:"bar",data:{labels:'+JSON.stringify(months)+',datasets:[{label:"Volumen USD",data:'+JSON.stringify(vols)+',backgroundColor:"#1D9E75"}]},options:{plugins:{legend:{display:false}}}});' +'new Chart(document.getElementById("chart-ops"),{type:"line",data:{labels:'+JSON.stringify(months)+',datasets:[{label:"N° OPs",data:'+JSON.stringify(ops_count)+',borderColor:"#D4AF37",backgroundColor:"rgba(212,175,55,.1)",fill:true}]},options:{plugins:{legend:{display:false}}}});' +'}<\/script>'; } } if(type==='admin'||DB.session.role==='admin'){ // Commission income section html += '

Ingresos por Comisiones Gliica

' +''; DB.entries.filter(function(e){return e.isFee&&e.status==='confirmed';}).slice(0,30).forEach(function(e){ html+='' +''; }); html += ''; html += '
FechaClienteTipoBaseComisión
'+e.date+''+gC(e.clientId).empresa.split(' ')[0]+''+e.desc+'$'+e.amount.toLocaleString('en',{maximumFractionDigits:2})+'
TOTAL COMISIONES GLIICA$'+totalFees.toLocaleString('en',{maximumFractionDigits:2})+'
'; // Per client summary html += '

Resumen por Cliente

' +''; DB.clients.forEach(function(cl){ var clOps = DB.ops.filter(function(o){return o.clientId===cl.id&&o.status==='completada';}); var clVol = clOps.reduce(function(s,o){return s+parseFloat(o.monto||0);},0); var clFees = DB.entries.filter(function(e){return e.clientId===cl.id&&e.isFee&&e.status==='confirmed';}).reduce(function(s,e){return s+e.amount;},0); html+='' +'' +'' +''; }); html += '
ClienteContratoSaldo actualOPs completadasVolumen totalComisiones cobradas
'+cl.empresa+''+cl.contrato+'$'+calcBal(cl.id).toLocaleString('en',{maximumFractionDigits:2})+''+clOps.length+'$'+clVol.toLocaleString('en',{maximumFractionDigits:2})+'$'+clFees.toLocaleString('en',{maximumFractionDigits:2})+'
'; } html += '
Gliica International · Liberty Global C.A. · po@gliica.com · www.gliica.com · Generado desde Portal Operativo v6
'; openPrint(html); }; // ══ UPGRADE doReport to add type selection ══ var _origDoReport = window.doReport; window.doReport = function(type){ if(!type) type = 'monthly'; if(type==='narrative'||type==='admin'){ openReportsPanel(type); } else { if(_origDoReport) _origDoReport(type); else openReportsPanel('monthly'); } }; // ══ PENDING FUNDS: enhance renderFondos to show prominent pending section ══ var _origRF = window.renderFondos; window.renderFondos = function(){ if(_origRF) _origRF(); var pending = DB.entries.filter(function(e){return e.status==='pending';}); // Add pending section at top of fondos if exists var kpis = document.getElementById('fondos-kpis'); if(kpis && pending.length>0){ var pEl = document.getElementById('fondos-pending-top'); if(!pEl){ pEl = document.createElement('div'); pEl.id = 'fondos-pending-top'; pEl.style.cssText = 'margin-bottom:12px;border:2px solid #D4AF37;border-radius:10px;overflow:hidden'; kpis.parentNode.insertBefore(pEl, kpis); } pEl.innerHTML = '
⏳ '+pending.length+' SOLICITUD(ES) DE FONDOS PENDIENTE(S) DE ACREDITACIÓN
' + pending.map(function(e){ var cl = gC(e.clientId); return '
' +'
'+cl.empresa+' — $'+e.amount.toLocaleString('en',{maximumFractionDigits:2})+'
' +'
'+e.desc+' · '+e.date+'
' +'' +'
'; }).join(''); } else { var pEl2 = document.getElementById('fondos-pending-top'); if(pEl2) pEl2.remove(); } }; // ══ UNIFIED PRINT FUNCTIONS — pd-* style, same as Estado de Cuenta ══ function pdPrint(html){ var CSS=''; openPrint('' +'' +''+html+''); } function pdClientGrid(cl){ return '
' +'
Empresa contratante
'+cl.empresa+'
' +'
RIF / ID Fiscal
'+cl.rif+'
' +'
Representante legal
'+cl.rep+'
' +'
Cédula / Pasaporte
'+cl.ci+'
' +'
Correo institucional
'+cl.email+'
' +'
Contrato
'+cl.contrato+'
' +'
Vigencia
'+cl.contractStart+' → '+cl.contractEnd+'
' +'
Comisión Gliica
'+((cl.commission||0.029)*100).toFixed(1)+'% sobre fondos recibidos (Cláusula 5ª)
' +'
'; } function pdSignBlock(cl){ var s=DB.settings||{}; var fi=s.firmaGliicaB64?'':''; var si=s.selloGliicaB64?'':''; return '
' +'
'+fi+si +'
SABRINA GUTIERREZ
CI V-7.374.811 · Presidente
Liberty Global C.A. / Liberty Global LLC
El Prestador de Servicios
' +'
' +'
'+cl.rep+'
CI '+cl.ci+' · '+cl.cargo+'
'+cl.empresa+'
El Contratante — Firma y Sello
'; } window.doPrintContrato = function(){ var id=getPrintClient();if(!id)return; var cl=gC(id);var rate=((cl.commission||0.029)*100).toFixed(1); var today=new Date().toLocaleDateString('es',{day:'2-digit',month:'long',year:'numeric'}); var html='
'+pdHdr('Contrato bajo Mandato Mercantil','REF: '+cl.contrato) +'
Contrato de Administración de Pagos y Recursos bajo Mandato Mercantil
' +pdClientGrid(cl) +'
Declaración Preambular · Naturaleza Jurídica
' +'

El presente instrumento es un Contrato de Mandato Mercantil conforme a los Arts. 376-392 del Código de Comercio venezolano. NO realiza captación habitual de depósitos del público ni intermediación financiera (Art. 7 LISB). Los Recursos del Contratante son y permanecen en todo momento de propiedad exclusiva del Contratante.

' +'
Cláusula 5ª · Costos y Tarifas
' +'' +'
Tarifa Gliica acordadaBase de cálculoPlazo ejecución OPs
'+rate+'%Sobre cada depósito recibido — NO sobre cada OP
Ej: Depósito $10.000 → Tarifa $'+((10000*(cl.commission||0.029)).toFixed(2))+' → Neto $'+(10000-(10000*(cl.commission||0.029))).toFixed(2)+'
16 Horas Hábiles Bancarias (≈ 2 días hábiles)
' +'
Obligaciones del Prestador de Servicios
' +'

Ejecutar OPs en 16 HHB · Mantener Registros Auxiliares Individuales separados · NUNCA mezclar recursos del Contratante con fondos propios · Archivo documental 7 años · Reportes semanales de operaciones · No podrán ser embargados ni gravados por acreedores del Prestador.

' +'
Resolución de Controversias (Cláusula 50ª)
' +'

Nivel 1: Negociación directa (30 días) · Nivel 2: Mediación ICC voluntaria (Panamá) · Jurisdicción subsidiaria: Tribunales Mercantiles de Caracas / U.S. District Court S. Florida para LLC.

' +'
DECLARACIÓN FINAL: Las partes suscriben el presente instrumento de forma libre y voluntaria. La firma implica plena aceptación de todas las cláusulas y Anexos. Liberty Global C.A. NO realiza intermediación financiera (Art. 7 LISB). Actúa como mandatario mercantil (Art. 376 C.Com.).
' +'

En Caracas, a los '+today+'

' +pdSignBlock(cl)+pdFooter()+'
'; pdPrint(html); }; window.doPrintAnexoB = function(){ var id=getPrintClient();if(!id)return; var cl=gC(id);var rate=((cl.commission||0.029)*100).toFixed(1); var today=new Date().toLocaleDateString('es',{day:'2-digit',month:'long',year:'numeric'}); var rows=DB.tariffs.map(function(t){return ''+t.name+''+t.value+(t.unit==='%'?'%':' USD')+''+(t.auto?'Automático':'Manual')+''+(t.desc||'')+'';}).join(''); var html='
'+pdHdr('Anexo B · Tarifario de Servicios','REF: '+cl.contrato) +'
Anexo B — Tarifario de Servicios
' +'
' +'
Cliente
'+cl.empresa+'
' +'
Contrato
'+cl.contrato+'
' +'
Comisión Gliica acordada
'+rate+'% sobre cada depósito (NO sobre cada OP)
' +'
Fecha
'+today+'
' +'
' +''+rows+'
ServicioTarifaAplicaciónDescripción
' +'
Los cargos de corresponsales bancarios son fijados por terceros y fuera del control de Gliica (Cláusula 10ª). Las tarifas pueden modificarse con 30 días de notificación previa (Cláusula 6ª). La comisión Gliica se cobra al acreditar cada depósito, nunca sobre OPs individuales.
' +pdSignBlock(cl)+pdFooter()+'
'; pdPrint(html); }; window.doPrintAnexoC = function(){ var id=getPrintClient();if(!id)return; var cl=gC(id); var today=new Date().toLocaleDateString('es',{day:'2-digit',month:'long',year:'numeric'}); var html='
'+pdHdr('Anexo C · Representantes Autorizados','REF: '+cl.contrato) +'
Anexo C — Representantes Autorizados
' +'
' +'
Empresa
'+cl.empresa+'
' +'
RIF
'+cl.rif+'
' +'
Contrato
'+cl.contrato+'
' +'
Fecha
'+today+'
' +'
' +'

Los siguientes representantes están autorizados para emitir, firmar y sellar Órdenes de Operación con plenos efectos vinculantes ante el Prestador de Servicios:

' +'' +'' +'' +'
#Nombre completoC.I. / PasaporteCargoCorreo autorizadoTipo autorización
1'+cl.rep+''+cl.ci+''+cl.cargo+''+cl.email+'Principal — firma individual
2Por designar
3Por designar
' +'
Toda OP debe estar firmada y sellada por el Representante Autorizado. OPs sin firma válida o por canales no autorizados serán rechazadas sin responsabilidad del Prestador (Cláusula 19ª). Canales: po@gliica.com · portal.gliica.com
' +pdSignBlock(cl)+pdFooter()+'
'; pdPrint(html); }; window.doPrintRef = function(){ var id=getPrintClient();if(!id)return; var cl=gC(id);var s=DB.settings||{}; var today=new Date().toLocaleDateString('es',{day:'2-digit',month:'long',year:'numeric'}); var bodyText=s.refText||'Por medio de la presente, Liberty Global C.A. hace constar que la empresa antes identificada mantiene con nosotros una relación comercial activa bajo Contrato de Administración de Pagos y Recursos bajo Mandato Mercantil, con toda responsabilidad y cumplimiento de sus obligaciones contractuales a la fecha de emisión del presente documento.\n\nLa empresa ha demostrado solvencia operativa y compromiso en la ejecución de sus operaciones, razón por la cual la recomendamos ampliamente ante las instituciones, organismos y contrapartes que tengan a bien solicitarlo.\n\nLa presente referencia se emite con vigencia de 30 días hábiles desde su emisión, a solicitud de la parte interesada y sin que ello implique garantía de obligaciones de terceros.'; var bodyParas=bodyText.split('\n').map(function(p){return p?'

'+p+'

':'';}).join(''); var html='
'+pdHdr('Referencia Comercial','RC-'+Math.floor(Math.random()*90000+10000)) +'
Referencia Comercial
' +'
' +'
Razón Social
'+cl.empresa+'
' +'
RIF / ID Fiscal
'+cl.rif+'
' +'
Representante Legal
'+cl.rep+' · CI '+cl.ci+'
' +'
Contrato N°
'+cl.contrato+'
' +'
Vigencia contrato
'+cl.contractStart+' al '+cl.contractEnd+'
' +'
Fecha de emisión
'+today+'
' +'
' +'

A quien corresponda:

' +bodyParas +'
NOTA LEGAL: Liberty Global C.A. NO realiza intermediación financiera (Art. 7 LISB). Actúa como mandatario mercantil (Art. 376 C.Com.). La presente referencia no implica garantía de obligaciones de terceros. Vigencia: 30 días hábiles desde emisión.
' +pdSignBlock(cl)+pdFooter()+'
'; pdPrint(html); }; window.printOP = function(opId){ var op=DB.ops.find(function(o){return o.id===opId;}); if(!op){if(typeof toast==='function')toast('OP no encontrada','err');return;} var cl=gC(op.clientId); var charges=(op.autoCharges||[]).concat(op.manualCharges||[]); var html='
'+pdHdr('Orden de Operación — Anexo D',op.id) +'
Orden de Operación · '+op.id+'
' +'
' +'
Empresa contratante
'+cl.empresa+'
' +'
RIF
'+cl.rif+'
' +'
Representante legal
'+cl.rep+' · CI '+cl.ci+'
' +'
Contrato
'+cl.contrato+'
' +'
' +'
Instrucción de Operación
' +'' +'' +''+(op.docNum?'':'')+'' +(op.banco?'':'') +(op.swift?'':'') +'' +(op.concepto?'':'') +(op.scheduledFor?'':'') +'' +'
Tipo de operación'+op.type.toUpperCase()+' — '+op.subtype+'Fecha'+op.date+'
Beneficiario'+op.ben+'Cédula/RIF'+op.docNum+'
Banco'+op.banco+'N° Cuenta'+(op.cuenta||'—')+'
SWIFT/BIC'+op.swift+'
Monto instruido$'+parseFloat(op.monto||0).toLocaleString('en',{minimumFractionDigits:2})+' '+op.currency+'
Concepto'+op.concepto+'
Fecha programada'+op.scheduledFor+'
Estado'+op.status.toUpperCase()+'
' +(charges.length>0?'
Cargos Aplicables (Anexo B)
' +'' +charges.map(function(ch){return '';}).join('') +'
Descripción del cargoMonto USD
'+ch.desc+'$'+parseFloat(ch.amt||0).toFixed(2)+'
Total cargos corresponsales$'+parseFloat(op.totalCharges||0).toFixed(2)+'
' :'') +'
CONDICIONES: Válida únicamente con firma y sello del Representante Autorizado (Anexo C). Plazo ejecución: 16 Horas Hábiles Bancarias desde recepción válida con fondos suficientes. Liberty Global C.A. actúa como mandatario mercantil (Art. 376 C.Com.). NO realiza intermediación financiera (Art. 7 LISB). Los recursos son propiedad exclusiva del Contratante.
' +pdSignBlock(cl)+pdFooter()+'
'; pdPrint(html); }; window.clientPrintContrato=function(){var cid=DB.session.clientId;if(!cid)return;DB._forcePrintClient=cid;window.doPrintContrato();DB._forcePrintClient=null;}; window.clientPrintAnexoB=function(){var cid=DB.session.clientId;if(!cid)return;DB._forcePrintClient=cid;window.doPrintAnexoB();DB._forcePrintClient=null;}; window.clientPrintAnexoC=function(){var cid=DB.session.clientId;if(!cid)return;DB._forcePrintClient=cid;window.doPrintAnexoC();DB._forcePrintClient=null;}; window.clientPrintRef=function(){var cid=DB.session.clientId;if(!cid)return;DB._forcePrintClient=cid;window.doPrintRef();DB._forcePrintClient=null;}; // ══ ONLINE STATUS — visible indicator for all users ══ var _syncInProgress = false; // Show/hide sync spinner function setSyncStatus(active){ _syncInProgress = active; var spinners = document.querySelectorAll('.sync-spinner'); spinners.forEach(function(s){ s.style.display = active ? 'inline-block' : 'none'; }); var dots = document.querySelectorAll('.online-dot-self'); dots.forEach(function(d){ d.style.background = active ? '#F59E0B' : '#1D9E75'; d.title = active ? 'Actualizando...' : 'En línea'; }); } // Add online indicator to topbars if not already there function initOnlineIndicator(){ document.querySelectorAll('.topbar').forEach(function(tb){ if(tb.querySelector('.online-indicator')) return; var ind = document.createElement('div'); ind.className = 'online-indicator'; ind.style.cssText = 'display:flex;align-items:center;gap:5px;font-size:9px;color:var(--mut);margin-left:6px'; ind.innerHTML = '' +'' +''; tb.appendChild(ind); }); } // Override syncAll to show sync indicator var _origSyncAll3 = window.syncAll || syncAll; syncAll = function(){ setSyncStatus(true); return (_origSyncAll3||function(){return Promise.resolve();})().then(function(r){ setSyncStatus(false); return r; }).catch(function(e){ setSyncStatus(false); throw e; }); }; // Initialize on enter var _origInitAdmin2 = window.initAdmin; window.initAdmin = function(){ if(_origInitAdmin2) _origInitAdmin2(); setTimeout(initOnlineIndicator, 200); }; var _origInitClient2 = window.initClient; window.initClient = function(cid){ if(_origInitClient2) _origInitClient2(cid); setTimeout(initOnlineIndicator, 200); }; // ══ REAL-TIME: submitClientOP forces immediate admin refresh ══ var _origSOPbrand = window.submitClientOP; window.submitClientOP = function(useSched){ if(_origSOPbrand) _origSOPbrand(useSched); // Force immediate sync on both sides if(typeof syncAll === 'function'){ setSyncStatus(true); syncAll().then(function(){ setSyncStatus(false); // Also refresh admin views if logged in if(DB.session && DB.session.role==='admin'){ try{renderAdminOps();renderAdminDash();}catch(e){} } }); } }; // ══ REAL-TIME: confirmEntry forces immediate client refresh ══ var _origCEbrand = window.confirmEntry; window.confirmEntry = function(id){ if(_origCEbrand) _origCEbrand(id); setSyncStatus(true); if(typeof syncAll==='function') syncAll().then(function(){setSyncStatus(false);}); }; // clientPrintContrato → see unified version below // clientPrint functions → see unified version below // ══ FIX: Period/date range toggle in reports ══ document.addEventListener('DOMContentLoaded', function(){ var periodSel = document.getElementById('rpt-period'); if(periodSel){ periodSel.addEventListener('change', function(){ var wrap = document.getElementById('rpt-range-wrap'); if(wrap) wrap.style.display = this.value==='custom' ? 'flex' : 'none'; }); // Populate month options dynamically var now = new Date(); periodSel.innerHTML = ''; for(var i=0;i<12;i++){ var d = new Date(now.getFullYear(), now.getMonth()-i, 1); var val = d.getFullYear()+'-'+(d.getMonth()+1).toString().padStart(2,'0'); var lbl = d.toLocaleDateString('es-VE',{month:'long',year:'numeric'}); periodSel.innerHTML += ''; } periodSel.innerHTML += ''; } }); // ══ FIX: doReport with date range support ══ var _origDoReport2 = window.doReport; window.doReport = function(type){ if(!type) type='monthly'; var period = (document.getElementById('rpt-period')||{}).value||'all'; var from = (document.getElementById('rpt-from')||{}).value||''; var to = (document.getElementById('rpt-to')||{}).value||''; if(type==='narrative'){openReportsPanel('narrative',period,from,to);return;} if(type==='admin'){openReportsPanel('admin',period,from,to);return;} // Fix original report: pass the type so it doesn't default wrong if(_origDoReport2) _origDoReport2(type); }; // ══ FIX: Narrative report - client-facing only, no internal data ══ var _origOpenRP = window.openReportsPanel; window.openReportsPanel = function(type, period, from, to){ var cid = DB.session.role==='admin'?(document.getElementById('rpt-cl')||{}).value||null:DB.session.clientId; // Filter by date range if custom function inRange(dateStr){ if(!dateStr) return true; if(period==='all') return true; // Parse date: handles DD/MM/YYYY, YYYY-MM-DD, YYYY/MM/DD function parseDate(s){ if(!s) return null; var p=s.split(/[-\/]/); if(!p||p.length<3) return null; // DD/MM/YYYY format if(p[0].length<=2&&p[2].length===4) return new Date(p[2],parseInt(p[1])-1,parseInt(p[0])); // YYYY-MM-DD format if(p[0].length===4) return new Date(p[0],parseInt(p[1])-1,parseInt(p[2])); return null; } var d=parseDate(dateStr); if(!d) return true; if(period==='custom'){ if(from){var f=parseDate(from.replace(/-/g,'-'));if(f&&dt)return false;} return true; } // Month filter: period = '2026-05' var parts=period.split('-'); var yr=parseInt(parts[0]),mo=parseInt(parts[1])-1; return d.getFullYear()===yr&&d.getMonth()===mo; } var allOps = DB.ops.filter(function(o){return (!cid||o.clientId===cid)&&inRange(o.date);}); var allEntries = DB.entries.filter(function(e){return (!cid||e.clientId===cid)&&inRange(e.date);}); var totalIn = allEntries.filter(function(e){return e.type==='in'&&e.status==='confirmed';}).reduce(function(s,e){return s+e.amount;},0); var totalOut = allEntries.filter(function(e){return e.type==='out'&&!e.isFee&&e.status==='confirmed';}).reduce(function(s,e){return s+e.amount;},0); var totalFees = allEntries.filter(function(e){return e.isFee&&e.status==='confirmed';}).reduce(function(s,e){return s+e.amount;},0); var opsComplete = allOps.filter(function(o){return o.status==='completada';}).length; var today = new Date().toLocaleDateString('es-VE',{day:'2-digit',month:'long',year:'numeric'}); var periodLabel = period==='all'?'Histórico':period==='custom'?(from+' al '+to):period; // Monthly data for charts var monthly={}; allOps.forEach(function(op){ var d=op.date||''; var m=d.substring(3,10)||d.substring(0,7)||'Sin fecha'; if(!monthly[m]) monthly[m]={ops:0,vol:0}; monthly[m].ops++;monthly[m].vol+=parseFloat(op.monto||0); }); var months=Object.keys(monthly).sort().slice(-6); var html='Informe Gliica' +'<\/script>' +''; html+='
' +'
GLIICA INTERNATIONAL · LIBERTY GLOBAL C.A. · po@gliica.com
' +'

'+(type==='admin'?'INFORME CONSOLIDADO — TODAS LAS EMPRESAS':(cid&&type==='narrative'?'INFORME EJECUTIVO DE OPERACIONES':'INFORME DE OPERACIONES'))+'

' +'
Período: '+periodLabel+' · Generado: '+today +(cid?' · Cliente: '+gC(cid).empresa+'':DB.session.role==='admin'?' · Todos los clientes':'')+'
'; html+='
' +'
FONDOS RECIBIDOS
$'+totalIn.toLocaleString('en',{maximumFractionDigits:0})+'
' +'
VOLUMEN OPERADO
$'+totalOut.toLocaleString('en',{maximumFractionDigits:0})+'
' +(type==='admin'?'
COMISIONES GLIICA
$'+totalFees.toLocaleString('en',{maximumFractionDigits:0})+'
' :'
CARGOS CORRESPONSALES
$'+totalFees.toLocaleString('en',{maximumFractionDigits:0})+'
') +'
OPs COMPLETADAS
'+opsComplete+'
' +'
OPs EN PROCESO
'+(allOps.length-opsComplete)+'
' +'
TASA DE EJECUCIÓN
'+Math.round(opsComplete/(allOps.length||1)*100)+'%
' +'
'; // OP table html+='

Detalle de Operaciones

' +''+(type==='admin'?'':'')+''; allOps.slice(0,100).forEach(function(op){ var cl=gC(op.clientId); html+=''+(type==='admin'?'':'') +'' +'' +'' +''; }); html+='' +''; html+='
ReferenciaClienteBeneficiarioTipoFechaMontoCargos corresponsalEstado
'+op.id+''+cl.empresa.split(' ')[0]+''+op.ben+''+op.type.toUpperCase()+''+op.date+'$'+parseFloat(op.monto||0).toLocaleString('en',{maximumFractionDigits:2})+'$'+parseFloat(op.totalCharges||0).toLocaleString('en',{maximumFractionDigits:2})+''+op.status+'
TOTALES$'+allOps.reduce(function(s,o){return s+parseFloat(o.monto||0);},0).toLocaleString('en',{maximumFractionDigits:2})+'
'; if(type==='narrative'){ html+='

Análisis Ejecutivo del Período

' +'

Durante el período '+periodLabel+', se procesaron '+allOps.length+' órdenes de operación' +(cid?' para '+gC(cid).empresa+'':'')+', con un volumen total de $'+totalOut.toLocaleString('en',{maximumFractionDigits:2})+' USD.

' +'

La tasa de ejecución completada fue del '+Math.round(opsComplete/(allOps.length||1)*100)+'%. Los fondos recibidos en el período totalizaron $'+totalIn.toLocaleString('en',{maximumFractionDigits:2})+' USD.

'; if(months.length>0){ var vols=months.map(function(m){return (monthly[m].vol/1000).toFixed(1);}); var opsc=months.map(function(m){return monthly[m].ops;}); html+='

Tendencia (últimos 6 meses)

' +'
' +'
' +'window.onload=function(){' +'new Chart(document.getElementById("cv"),{type:"bar",data:{labels:'+JSON.stringify(months)+',datasets:[{label:"Volumen (miles USD)",data:'+JSON.stringify(vols)+',backgroundColor:"#1D9E75"}]},options:{responsive:true,plugins:{legend:{position:"top"}}}});' +'new Chart(document.getElementById("co"),{type:"line",data:{labels:'+JSON.stringify(months)+',datasets:[{label:"N° de OPs",data:'+JSON.stringify(opsc)+',borderColor:"#D4AF37",backgroundColor:"rgba(212,175,55,.15)",fill:true,tension:.4}]},options:{responsive:true,plugins:{legend:{position:"top"}}}});' +'}<\/script>'; } } if(type==='admin'){ // Per client summary with commissions html+='

Resumen por Cliente

' +''; DB.clients.forEach(function(cl){ var clOps=allOps.filter(function(o){return o.clientId===cl.id;}); var clVol=clOps.reduce(function(s,o){return s+parseFloat(o.monto||0);},0); var clFees=allEntries.filter(function(e){return e.clientId===cl.id&&e.isFee&&e.status==='confirmed';}).reduce(function(s,e){return s+e.amount;},0); html+='' +'' +'' +'' +''; }); var totalFeesAll=DB.entries.filter(function(e){return e.isFee&&e.status==='confirmed';}).reduce(function(s,e){return s+e.amount;},0); html+='' +'
ClienteContratoSaldo actualOPs períodoVolumenComisión Gliica
'+cl.empresa+''+cl.contrato+'$'+calcBal(cl.id).toLocaleString('en',{maximumFractionDigits:2})+''+clOps.length+'$'+clVol.toLocaleString('en',{maximumFractionDigits:2})+'$'+clFees.toLocaleString('en',{maximumFractionDigits:2})+'
TOTAL INGRESOS GLIICA (PERÍODO)$'+totalFees.toLocaleString('en',{maximumFractionDigits:2})+'
'; html+='

Detalle Comisiones por Operación

' +''; allEntries.filter(function(e){return e.isFee&&e.status==='confirmed';}).forEach(function(e){ html+=''; }); html+='
FechaClienteConceptoComisión
'+e.date+''+gC(e.clientId).empresa.split(' ')[0]+''+e.desc+'$'+e.amount.toLocaleString('en',{maximumFractionDigits:2})+'
'; } html+='
' +'Gliica International · Liberty Global C.A. · po@gliica.com · www.gliica.com · Portal Operativo v6
' +''; openPrint(html); }; // ══ FIX: Custom OP type with field selector ══ window.addOPCat = function(){ var el = document.getElementById('op-cat-modal'); if(!el){ el = document.createElement('div'); el.id = 'op-cat-modal'; el.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:999;display:flex;align-items:center;justify-content:center'; var allFields = ['Beneficiario','Cédula/RIF beneficiario','Banco','N° Cuenta','SWIFT','Monto','Moneda','Concepto','Propósito','Referencia/Período','Notas adicionales']; var checkboxes = allFields.map(function(f,i){ var def = ['Beneficiario','Banco','N° Cuenta','Monto','Concepto'].includes(f); return ''; }).join(''); el.innerHTML = '
' +'
➕ Nuevo tipo de OP
' +'
' +'
' +'
' +'
' +'
Campos del formulario
' +'
'+checkboxes+'
' +'
' +'' +'' +'
'; document.body.appendChild(el); } else { el.style.display='flex'; } }; window.saveOPCatModal = function(){ var n = (document.getElementById('noc-name')||{}).value||''; if(!n){alert('El nombre es obligatorio.');return;} var ico = (document.getElementById('noc-ico')||{}).value||'📋'; var desc = (document.getElementById('noc-desc')||{}).value||''; var fields = []; document.querySelectorAll('#op-cat-modal input[type=checkbox]:checked').forEach(function(cb){fields.push(cb.value);}); DB.opCategories.push({id:'cat-'+Date.now(),name:n,icon:ico,desc:desc,active:true,clientVisible:true,fields:fields}); if(typeof renderOPCats==='function') renderOPCats(); if(typeof log==='function') log('Tipo de OP creado: '+n); if(typeof toast==='function') toast('Tipo de OP creado ✅','ok'); document.getElementById('op-cat-modal').remove(); }; // ══ FIX: Re-print OP button ══ var _origPrintOP = window.printOP; // ══ FIX: Client notifications ══ var _origInitClient = window.initClient; window.initClient = function(cid){ if(_origInitClient) _origInitClient(cid); syncNotifBadge(); }; // Override syncAll to also sync notif badge var _lastSyncAll = syncAll; syncAll = function(){ return _lastSyncAll().then(function(){ if(typeof syncNotifBadge==='function') syncNotifBadge(); }).catch(function(){}); }; // ══ ACCOUNT NUMBER VALIDATION (Venezuela: 20 digits in format 0xxx-xxxx-xx-xxxxxxxxxx) ══ window.validateCuenta = function(inp, prefix){ var val = inp.value.replace(/\D/g,''); var msg = document.getElementById((prefix||'nac')+'-cuenta-msg'); if(!val){if(msg)msg.textContent='';return;} if(val.length===20){ if(msg){msg.textContent='✅ Formato válido (20 dígitos)';msg.style.color='#1D9E75';} inp.style.borderColor='#1D9E75'; } else if(val.length>20){ if(msg){msg.textContent='❌ Demasiados dígitos ('+val.length+'/20)';msg.style.color='#ef4444';} inp.style.borderColor='#ef4444'; } else { if(msg){msg.textContent='⏳ '+val.length+'/20 dígitos';msg.style.color='#F59E0B';} inp.style.borderColor='#F59E0B'; } }; // ══ PROJECTED BALANCE ══ window.calcProjectedBalance = function(clientId){ var cid = clientId || DB.session.clientId; var current = calcBal(cid); var pending = DB.ops.filter(function(o){return o.clientId===cid && ['enviada','en_revision','voucher_cargado'].includes(o.status);}) .reduce(function(s,o){return s+parseFloat(o.monto||0);},0); return {current:current, pending:pending, projected:current-pending}; }; // Override renderClientBalance to add projected balance var _origRCB = window.renderClientBalance; window.renderClientBalance = function(){ if(_origRCB) _origRCB(); var pb = calcProjectedBalance(); var el = document.getElementById('c-projected-bal'); if(el){ el.innerHTML='
' +'
💡 Saldo proyectado
' +'
Saldo actual'+fmtAmt(pb.current)+'
' +'
OPs en proceso-'+fmtAmt(pb.pending)+'
' +'
Saldo estimado disponible'+fmtAmt(pb.projected)+'
' +'
'; } }; // ══ CLIENT FIRMA/SELLO IN DOCUMENTS ══ // Update pdSignBlock to use client's own firma/sello in the signature box window.pdSignBlock = function(cl){ var s = DB.settings||{}; // Gliica signature (left) var fi = s.firmaGliicaB64?'':''; var si = s.selloGliicaB64?'':''; // Client signature (right) - loaded by admin per client var cfi = cl.firmaClienteB64?'':''; var csi = cl.selloClienteB64?'':''; return '
' +'
'+fi+si +'
SABRINA GUTIERREZ
CI V-7.374.811 · Presidente
Liberty Global C.A. / Liberty Global LLC
El Prestador de Servicios
' +'
'+cfi+csi +'
'+cl.rep+'
CI '+cl.ci+' · '+cl.cargo+'
'+cl.empresa+'
El Contratante — Firma y Sello
'; }; // Add client firma/sello upload to Config Gliica (per client) window.initConfigView = function(){ var s = DB.settings||{}; if(s.firmaGliicaB64){var p=document.getElementById('gz-firma-p');if(p){p.src=s.firmaGliicaB64;p.style.display='block';}var z=document.getElementById('gz-firma-z');if(z)z.textContent='✅ Firma cargada';} if(s.selloGliicaB64){var p2=document.getElementById('gz-sello-p');if(p2){p2.src=s.selloGliicaB64;p2.style.display='block';}var z2=document.getElementById('gz-sello-z');if(z2)z2.textContent='✅ Sello cargado';} var rt=document.getElementById('ref-text-custom');if(rt&&s.refText)rt.value=s.refText; var ek=document.getElementById('ejs-key');if(ek&&s.emailjsKey)ek.value=s.emailjsKey; var sel=document.getElementById('print-cl-sel'); if(sel){ sel.innerHTML=''+DB.clients.map(function(cl){return'';}).join(''); } // Add client firma/sello section per client var clSigSec = document.getElementById('cl-sig-section'); if(!clSigSec){ var configCard = document.querySelector('#v-aconfig .card:last-child'); if(configCard){ var div = document.createElement('div');div.className='card';div.style.marginTop='11px'; div.innerHTML='
✍️ Firma y sello del cliente (por contrato)
' +'
Carga aquí la firma y sello del representante autorizado de cada cliente. Se imprimirán automáticamente en todos sus documentos (OPs, contratos, anexos).
' +'
' +'
' +''; div.id='cl-sig-section'; configCard.parentNode.insertBefore(div, configCard.nextSibling); } } }; window.onClSigChange = function(){ var id=(document.getElementById('cl-sig-sel')||{}).value||''; var area=document.getElementById('cl-sig-area'); if(area) area.style.display=id?'block':'none'; if(id){ var cl=gC(id); if(cl.firmaClienteB64){var p=document.getElementById('cl-firma-p');if(p){p.src=cl.firmaClienteB64;p.style.display='block';}var z=document.getElementById('cl-firma-z');if(z)z.textContent='✅ Firma cargada';} if(cl.selloClienteB64){var p2=document.getElementById('cl-sello-p');if(p2){p2.src=cl.selloClienteB64;p2.style.display='block';}var z2=document.getElementById('cl-sello-z');if(z2)z2.textContent='✅ Sello cargado';} } }; window.saveClSig = function(){ var id=(document.getElementById('cl-sig-sel')||{}).value||''; if(!id){if(typeof toast==='function')toast('Selecciona un cliente primero','err');return;} var cl=DB.clients.find(function(x){return x.id===id;}); if(!cl)return; cl.firmaClienteB64=(document.getElementById('cl-firma-b64')||{}).value||cl.firmaClienteB64||''; cl.selloClienteB64=(document.getElementById('cl-sello-b64')||{}).value||cl.selloClienteB64||''; if(typeof toast==='function')toast('Firma y sello del cliente guardados ✅','ok'); api('save_client',Object.assign({},cl)).catch(function(){}); }; // ══ OP STATUS HISTORY ══ // Override status change functions to record history function addStatusHistory(op, newStatus, by, note){ if(!op.statusHistory) op.statusHistory=[]; op.statusHistory.push({status:newStatus, date:new Date().toLocaleDateString('es'), by:by||'admin', note:note||''}); op.status=newStatus; } // Override markComplete to record history var _origMC2 = window.markComplete; window.markComplete = function(){ var op = DB.session.role==='admin' ? DB.ops.find(function(o){return o.id===DB.sideId;}) : null; if(!op){if(_origMC2)_origMC2();return;} // Fetch BCV rate on completion fetch('https://api.frankfurter.app/latest?base=USD') .then(function(r){return r.json();}) .then(function(d){if(d&&d.rates){DB.bcvOnCompletion=DB.bcvOnCompletion||{};DB.bcvOnCompletion[op.id]={eur:d.rates.EUR,date:new Date().toLocaleDateString('es')};} }) .catch(function(){}); addStatusHistory(op,'completada','admin','Operación confirmada y ejecutada por Gliica'); if(_origMC2)_origMC2(); // WhatsApp notification option var cl=gC(op.clientId); if(cl&&cl.tel){ var msg=encodeURIComponent('Hola '+cl.rep+', tu OP '+op.id+' por $'+op.monto+' USD ha sido completada exitosamente. – Gliica International'); var waUrl='https://wa.me/'+cl.tel.replace(/\D/g,'')+('?text='+msg); toast('📲 Notificar por WhatsApp → OP completada','ok'); } }; // Override confirmReturn to record history var _origCR2 = window.confirmReturn; window.confirmReturn = function(){ var op = DB.session.role==='admin' ? DB.ops.find(function(o){return o.id===DB.sideId;}) : null; if(op) addStatusHistory(op,'devuelta','admin','Devuelta con observaciones: '+($('ret-motivo')||{}).value||''); if(_origCR2)_origCR2(); }; // Show status history in side panel window.renderStatusHistory = function(opId){ var op = DB.ops.find(function(o){return o.id===opId;}); if(!op||!op.statusHistory||!op.statusHistory.length) return '
Sin historial
'; return op.statusHistory.map(function(h){ var icon={enviada:'📤',en_revision:'🔍',voucher_cargado:'📎',completada:'✅',devuelta:'↩️',pendiente_correccion:'⚠️',programada:'📅'}[h.status]||'•'; return '
' +''+icon+'' +'
'+h.status.replace('_',' ').toUpperCase()+'
' +'
'+h.date+' · '+h.by+(h.note?' · '+h.note.substring(0,60):'')+'
'; }).join(''); }; // ══ EMAILJS FUNCTIONAL ══ var _emailjsLoaded = false; function loadEmailJS(key){ if(_emailjsLoaded||!key) return; var s=document.createElement('script'); s.src='https://cdn.jsdelivr.net/npm/@emailjs/browser@4/dist/email.min.js'; s.onload=function(){if(window.emailjs)window.emailjs.init(key);_emailjsLoaded=true;}; document.head.appendChild(s); } function sendEmail(to, subject, body, extra){ var s=DB.settings||{}; if(!s.emailjsKey||!s.emailjsService||!s.emailjsTemplate){console.log('EmailJS not configured');return;} loadEmailJS(s.emailjsKey); setTimeout(function(){ if(window.emailjs){ window.emailjs.send(s.emailjsService, s.emailjsTemplate, Object.assign({ to_email:to, subject:subject, message:body, from_name:'Gliica International' }, extra||{})).catch(function(e){console.log('Email error:',e);}); } }, 1000); } // Trigger emails on key events var _origCE2 = window.confirmEntry; window.confirmEntry = function(id){ if(_origCE2) _origCE2(id); var e=DB.entries.find(function(x){return x.id===id;}); if(e){ var cl=gC(e.clientId); sendEmail(cl.email,'Fondos acreditados — Gliica International', 'Estimado(a) '+cl.rep+',\n\nSus fondos por $'+e.amount.toFixed(2)+' USD han sido acreditados a su cuenta en Gliica International.\n\nPuede revisar su saldo en el portal: portal.gliica.com\n\nAtentamente,\nGliica International'); } }; window.testEmailJS = function(){ var s=DB.settings||{}; var st=document.getElementById('ejs-status'); if(!s.emailjsKey){if(st)st.textContent='❌ Configura la API Key primero';return;} loadEmailJS(s.emailjsKey); if(st)st.textContent='Enviando correo de prueba...'; setTimeout(function(){ if(window.emailjs){ window.emailjs.send(s.emailjsService||'', s.emailjsTemplate||'', { to_email:'po@gliica.com',subject:'Test EmailJS — Gliica Portal', message:'Este es un correo de prueba del Portal Operativo Gliica v6.',from_name:'Gliica International' }).then(function(){if(st)st.textContent='✅ Correo enviado correctamente';}) .catch(function(e){if(st)st.textContent='❌ Error: '+e.text;}); } else { if(st)st.textContent='⏳ Cargando EmailJS... intenta en 2 seg.'; } },1500); }; // ══ WHATSAPP NOTIFICATIONS ══ window.sendWhatsApp = function(opId, type){ var op=DB.ops.find(function(o){return o.id===opId;}); if(!op)return; var cl=gC(op.clientId); if(!cl||!cl.tel){toast('El cliente no tiene teléfono registrado','err');return;} var msgs={ enviada:'Hola '+cl.rep+', confirmamos la recepción de tu OP '+op.id+' por $'+op.monto+' USD. La estamos procesando. Tiempo máximo: 16 HHB. – Gliica', completada:'Hola '+cl.rep+', tu OP '+op.id+' por $'+op.monto+' USD ha sido COMPLETADA exitosamente. El pago fue ejecutado. – Gliica', devuelta:'Hola '+cl.rep+', tu OP '+op.id+' necesita correcciones. Revisa el portal para más detalles: portal.gliica.com – Gliica', en_revision:'Hola '+cl.rep+', tu OP '+op.id+' está en revisión. Te notificaremos pronto. – Gliica', }; var msg=msgs[type||op.status]||'Actualización de tu OP '+op.id+' en Gliica International.'; var phone=cl.tel.replace(/\D/g,''); if(!phone.startsWith('58')) phone='58'+phone.replace(/^0/,''); window.open('https://wa.me/'+phone+'?text='+encodeURIComponent(msg),'_blank'); }; // ══ CLIENT ONBOARDING FORM ══ window.openOnboarding = function(){ // Close simple modal if open var simpleModal = document.getElementById('m-new-client'); if(simpleModal) simpleModal.classList.remove('on'); var existing=document.getElementById('onboarding-modal'); if(existing){existing.style.display='flex';return;} var el=document.createElement('div'); el.id='onboarding-modal'; el.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:1000;display:flex;align-items:center;justify-content:center'; el.innerHTML='
' +'
' +'
🏢 Alta de nuevo cliente
Completa todos los datos para crear la cuenta y el contrato
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'
' +'' +'' +'
'; document.body.appendChild(el); // Set default dates var now=new Date(),y=now.getFullYear(),m=now.getMonth(); var s=document.getElementById('ob-start'),e=document.getElementById('ob-end'); if(s)s.value=y+'-'+(m+1).toString().padStart(2,'0')+'-01'; if(e)e.value=(y+1)+'-'+(m+1).toString().padStart(2,'0')+'-01'; }; window.saveOnboarding = function(){ var empresa=($('ob-empresa')||{}).value||'',rif=($('ob-rif')||{}).value||'',rep=($('ob-rep')||{}).value||'',ci=($('ob-ci')||{}).value||'',email=($('ob-email')||{}).value||'',contrato=($('ob-contrato')||{}).value||''; var pass=($('ob-pass')||{}).value||'',start=($('ob-start')||{}).value||'',end=($('ob-end')||{}).value||''; var tc=document.getElementById('ob-tc'); if(!empresa||!rif||!rep||!ci||!email||!contrato||!pass){toast('Completa los campos obligatorios (*)','err');return;} if(!tc||!tc.checked){toast('El cliente debe aceptar los términos y condiciones','err');return;} var cid='c-'+Date.now(); var comm=parseFloat(($('ob-comm')||{}).value||2.9)/100; var newCl={id:cid,name:rep,empresa,rif,email,notifyEmail:email,tel:($('ob-tel')||{}).value||'',dir:($('ob-dir')||{}).value||'',rep,ci,cargo:($('ob-cargo')||{}).value||'Representante',contrato,contractStart:start,contractEnd:end,commission:comm,firmaClienteB64:'',selloClienteB64:'',termsAccepted:true,maxOpAmount:0}; DB.clients.push(newCl); DB.users.push({id:cid+'-usr',user:($('ob-user')||{}).value||email,pass,role:'client',clientId:cid,name:rep}); var bal=parseFloat(($('ob-bal')||{}).value||0); if(bal>0) DB.entries.push({id:'E-init-'+Date.now(),clientId:cid,type:'in',amount:bal,currency:'USD',desc:'Saldo inicial',status:'confirmed',date:new Date().toLocaleDateString('es'),isFee:false,opRef:null}); log('Alta nuevo cliente: '+empresa+' · '+contrato); toast('Cliente '+empresa+' creado ✅','ok'); document.getElementById('onboarding-modal').style.display='none'; renderClients();renderContracts();if(typeof populateSelects==='function')populateSelects();if(typeof initConfigView==='function')initConfigView(); api('save_client',Object.assign({},newCl,{pass})).catch(function(){}); }; // ══ TERMS & CONDITIONS ══ window.showTermsAndConditions = function(onAccept){ var el=document.createElement('div'); el.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:2000;display:flex;align-items:center;justify-content:center'; el.innerHTML='
' +'
📋 Términos y Condiciones del Servicio
' +'
' +'

Gliica International · Liberty Global C.A. — Contrato de Administración de Pagos y Recursos bajo Mandato Mercantil

' +'

Al acceder a este portal y utilizar los servicios de Gliica International, usted declara haber leído, comprendido y aceptado las siguientes condiciones:

' +'

1. NATURALEZA DEL SERVICIO: Liberty Global C.A. actúa como mandatario mercantil (Art. 376 C.Com. venezolano). NO realiza intermediación financiera (Art. 7 LISB). Los recursos administrados son y permanecen en todo momento propiedad exclusiva del Contratante.

' +'

2. COMISIÓN: La tarifa de servicios acordada se aplica EXCLUSIVAMENTE sobre cada depósito o ingreso de fondos recibido. Las Órdenes de Operación individuales NO generan comisión adicional de Gliica.

' +'

3. ÓRDENES DE OPERACIÓN: Toda OP debe estar firmada y sellada por el Representante Autorizado del Anexo C. El plazo de ejecución es de máximo 16 Horas Hábiles Bancarias desde la recepción válida con fondos suficientes.

' +'

4. CONFIDENCIALIDAD: Toda la información compartida es estrictamente confidencial y se tratará conforme a los estándares del RGPD/GDPR como referencia internacional.

' +'

5. RESPONSABILIDAD: Gliica International responde por la correcta custodia y ejecución de instrucciones. La responsabilidad por pérdida directa causada por dolo o culpa grave es integral y sin límite. Para daños distintos a pérdida directa de fondos, la responsabilidad máxima equivale a las tarifas de los 12 meses anteriores.

' +'

6. RESOLUCIÓN DE CONTROVERSIAS: Negociación directa (30 días) → Mediación ICC voluntaria → Tribunales Mercantiles de Caracas (LIBERTY GLOBAL, C.A.).

' +'
' +'' +'
' +'' +'' +'
'; document.body.appendChild(el); }; // ══ ADMIN: Export estado de cuenta to PDF using jsPDF ══ window.exportEstadoPDF = function(){ var cid = (document.getElementById('rpt-cl')||{}).value || (DB.clients[0]||{}).id; if(!cid){toast('Selecciona un cliente primero','err');return;} var cl=gC(cid); // Use print-area approach with Ctrl+P → Save as PDF window.doPrintContrato && window.doPrintContrato.toString(); // Build the estado as a printable page and trigger print (user saves as PDF) pdPrint('
'+pdHdr('Estado de Cuenta',cl.contrato)+buildEstadoHTML(cl)+'
'); setTimeout(function(){toast('💡 Para exportar como PDF: Ctrl+P → Guardar como PDF en el destino','ok');},500); }; function buildEstadoHTML(cl){ var entries=DB.entries.filter(function(e){return e.clientId===cl.id&&e.status==='confirmed';}).sort(function(a,b){return (b.date||'').localeCompare(a.date||'');}); var bal=calcBal(cl.id); return '
Estado de Cuenta · '+cl.empresa+'
' +pdClientGrid(cl) +'' +entries.map(function(e){return '';}).join('') +'
FechaDescripciónCargoAbonoBalance
'+e.date+''+e.desc+''+(e.type==='out'?'$'+e.amount.toFixed(2):'')+''+(e.type==='in'?'$'+e.amount.toFixed(2):'')+'
SALDO DISPONIBLE$'+bal.toFixed(2)+'
' +pdFooter(); } // ══ GLOBAL SEARCH IMPROVEMENTS ══ var _origGlobalSearch = window.globalSearch; window.globalSearch = function(q){ if(!q||q.length<2)return; q=q.toLowerCase().trim(); var results=[]; // Search OPs DB.ops.forEach(function(o){ if(o.id.toLowerCase().includes(q)||o.ben.toLowerCase().includes(q)||(o.concepto||'').toLowerCase().includes(q)||o.banco.toLowerCase().includes(q)){ results.push({type:'op',ref:o.id,desc:o.ben+' · $'+fmtAmt(o.monto)+' · '+o.status,action:"openSide('"+o.id+"')"}); } }); // Search clients DB.clients.forEach(function(cl){ if(cl.empresa.toLowerCase().includes(q)||cl.rif.toLowerCase().includes(q)||cl.rep.toLowerCase().includes(q)||(cl.contrato||'').toLowerCase().includes(q)){ results.push({type:'client',ref:cl.contrato,desc:cl.empresa+' · '+cl.rep,action:"aN('aclientes',null);renderClients();"}); } }); // Search entries/funds DB.entries.forEach(function(e){ if((e.desc||'').toLowerCase().includes(q)){ results.push({type:'entry',ref:e.id,desc:e.desc+' · $'+e.amount.toFixed(2)+' · '+e.status,action:"aN('afondos',null);"}); } }); // Show results var el=document.getElementById('search-results-panel'); if(!el){ el=document.createElement('div');el.id='search-results-panel'; el.style.cssText='position:fixed;top:54px;right:14px;width:340px;max-height:350px;background:#fff;border:1px solid var(--bdr);border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,.15);z-index:400;overflow-y:auto'; document.body.appendChild(el); } if(!results.length){el.innerHTML='
Sin resultados para "'+q+'"
';el.style.display='block';return;} var icons={op:'📋',client:'🏢',entry:'💰'}; el.innerHTML=results.slice(0,12).map(function(r){ return '
' +'
'+icons[r.type]+' '+r.ref+'
' +'
'+r.desc+'
'; }).join(''); el.style.display='block'; }; document.addEventListener('click',function(e){var p=document.getElementById('search-results-panel');if(p&&!p.contains(e.target)&&!e.target.closest('.srch'))p.style.display='none';}); // ══ ADMIN DASHBOARD KPIs with Chart.js ══ window.renderAdminKPIs = function(){ var kpiEl = document.getElementById('admin-kpi-charts'); if(!kpiEl) return; if(kpiEl._chartsBuilt) return; kpiEl._chartsBuilt = true; // Calculate data var totalOps=DB.ops.length; var completed=DB.ops.filter(function(o){return o.status==='completada';}).length; var pending=DB.ops.filter(function(o){return ['enviada','en_revision','voucher_cargado'].includes(o.status);}).length; var totalVol=DB.ops.reduce(function(s,o){return s+parseFloat(o.monto||0);},0); var totalComm=DB.entries.filter(function(e){return e.isFee&&e.status==='confirmed';}).reduce(function(s,e){return s+e.amount;},0); // Monthly volumes var monthly={}; DB.ops.forEach(function(op){var m=(op.date||'').substring(3,10)||'--';if(!monthly[m])monthly[m]=0;monthly[m]+=parseFloat(op.monto||0);}); var months=Object.keys(monthly).sort().slice(-6); // Build charts HTML kpiEl.innerHTML='
' +'
OPs TOTALES
'+totalOps+'
' +'
COMPLETADAS
'+completed+'
' +'
EN PROCESO
'+pending+'
' +'
COMISIONES GLIICA
$'+Math.round(totalComm).toLocaleString('en')+'
' +'
' +(months.length>0?'
' +'
' +'
VOLUMEN MENSUAL (últimos 6 meses)
' +'
' +'
' +'
OPs POR ESTADO
' +'
' +'
':''); // Load Chart.js and render if(!window.Chart){ var s=document.createElement('script'); s.src='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js'; s.onload=function(){renderKPICharts(months,monthly);}; document.head.appendChild(s); } else { renderKPICharts(months,monthly); } }; function renderKPICharts(months,monthly){ var volData=months.map(function(m){return Math.round(monthly[m]);}); var statuses=['completada','enviada','en_revision','devuelta','programada']; var statusLabels=['Completada','Enviada','En revisión','Devuelta','Programada']; var statusCounts=statuses.map(function(s){return DB.ops.filter(function(o){return o.status===s;}).length;}); var c1=document.getElementById('chart-monthly'); var c2=document.getElementById('chart-status'); if(c1&&!c1._chartInstance){ c1._chartInstance=new window.Chart(c1,{type:'bar',data:{labels:months,datasets:[{label:'Vol USD',data:volData,backgroundColor:'#1D9E75',borderRadius:4}]},options:{plugins:{legend:{display:false}},scales:{y:{ticks:{callback:function(v){return '$'+Math.round(v/1000)+'k';}}}}}}); } if(c2&&!c2._chartInstance){ c2._chartInstance=new window.Chart(c2,{type:'doughnut',data:{labels:statusLabels,datasets:[{data:statusCounts,backgroundColor:['#1D9E75','#3B82F6','#F59E0B','#EF4444','#8B5CF6'],borderWidth:2}]},options:{plugins:{legend:{position:'bottom',labels:{font:{size:9}}}}}}); } } // Override renderAdminDash to include KPI charts var _origRAD2 = window.renderAdminDash; window.renderAdminDash = function(){ if(_origRAD2) _origRAD2(); setTimeout(function(){ // Add KPI chart container to admin dashboard at the BOTTOM (charts below KPI cards) var dash=document.getElementById('v-adash'); if(dash&&!document.getElementById('admin-kpi-charts')){ var kpiDiv=document.createElement('div');kpiDiv.id='admin-kpi-charts';kpiDiv.className='card';kpiDiv.style.marginTop='11px'; dash.appendChild(kpiDiv); // append at END } renderAdminKPIs(); },100); }; // ══ ADMIN: Add onboarding button to clients section ══ var _origRenderClients = window.renderClients; window.renderClients = function(){ if(_origRenderClients) _origRenderClients(); // Add onboarding button to new client area if not there var addBtn = document.querySelector('[onclick="showM(\'m-new-client\')"]'); if(addBtn && !document.getElementById('onboarding-btn')){ // Alta completa unified into main Nuevo cliente button } }; // ══ Add WhatsApp button to side panel ══ var _origOpenSide = window.openSide; window.openSide = function(opId){ if(_origOpenSide) _origOpenSide(opId); setTimeout(function(){ var sp=document.getElementById('side-pnl'); if(sp&&!sp.querySelector('#wa-send-btn')){ var op=DB.ops.find(function(o){return o.id===opId;}); if(op){ var cl=gC(op.clientId); if(cl&&cl.tel){ var wa=document.createElement('button'); wa.id='wa-send-btn';wa.className='btn btn-sm';wa.style.cssText='width:100%;margin-top:6px;background:#25D366;color:#fff;border-color:#25D366'; wa.textContent='📲 Notificar por WhatsApp'; wa.onclick=function(){sendWhatsApp(opId);}; var ft=sp.querySelector('.side-ft');if(ft)ft.parentNode.insertBefore(wa,ft); // Also show status history var histDiv=document.createElement('div'); histDiv.style.cssText='margin-top:10px;padding:10px;border:1px solid var(--bdr);border-radius:8px'; histDiv.innerHTML='
Historial de estados
'+renderStatusHistory(opId); if(ft)ft.parentNode.insertBefore(histDiv,ft); } } } },200); }; // ══════════════════════════════════════════════════════ // GLIICA PORTAL — AMPLIACIONES v2 // ══════════════════════════════════════════════════════ // ── 1. CONTRACT EXPIRATION ALERTS ── window.checkContractExpiry = function(){ var today = new Date(); var warnings = []; DB.clients.forEach(function(cl){ if(!cl.contractEnd) return; var end = new Date(cl.contractEnd); var diff = Math.floor((end - today) / (1000*60*60*24)); if(diff <= 30 && diff >= 0){ warnings.push({cl:cl, daysLeft:diff, urgent: diff<=7}); } else if(diff < 0){ warnings.push({cl:cl, daysLeft:diff, expired:true}); } }); if(!warnings.length) return; // Show in admin dashboard var alertsEl = document.getElementById('a-dash-alerts'); if(!alertsEl) return; var old = document.getElementById('expiry-alerts'); if(old) old.remove(); var div = document.createElement('div'); div.id = 'expiry-alerts'; div.innerHTML = warnings.map(function(w){ var color = w.expired ? 'ad' : w.urgent ? 'aw' : 'ai'; var msg = w.expired ? '❌ Contrato de '+w.cl.empresa+' ('+w.cl.contrato+') VENCIDO hace '+Math.abs(w.daysLeft)+' días. Renovar urgente.' : '⏰ Contrato de '+w.cl.empresa+' vence en '+w.daysLeft+' días ('+w.cl.contractEnd+'). '+ ''; return '
'+msg+'
'; }).join(''); alertsEl.insertBefore(div, alertsEl.firstChild); }; window.renewContract = function(clientId){ var cl = DB.clients.find(function(x){return x.id===clientId;}); if(!cl) return; var newEnd = prompt('Nueva fecha de fin de contrato (YYYY-MM-DD):', cl.contractEnd); if(!newEnd) return; cl.contractEnd = newEnd; toast('Contrato de '+cl.empresa+' renovado hasta '+newEnd+' ✅','ok'); checkContractExpiry(); api('save_client', Object.assign({},cl)).catch(function(){}); }; // Run on admin init and on sync var _origInitAdmin3 = window.initAdmin; window.initAdmin = function(){ if(_origInitAdmin3) _origInitAdmin3(); setTimeout(checkContractExpiry, 800); }; // ── 2. MINIMUM BALANCE ALERT (configurable) ── // Add to client config window.checkMinBalance = function(){ var s = DB.settings||{}; var threshold = parseFloat(s.minBalanceAlert||0); if(!threshold) return; DB.clients.forEach(function(cl){ var bal = calcBal(cl.id); if(bal < threshold){ // Alert admin var alertsEl = document.getElementById('a-dash-alerts'); if(alertsEl){ var existing = document.getElementById('lowbal-'+cl.id); if(!existing){ var a = document.createElement('div'); a.id='lowbal-'+cl.id; a.className='alert aw'; a.innerHTML='💰 Saldo bajo: '+cl.empresa+' tiene $'+bal.toFixed(2)+' (mínimo configurado: $'+threshold+')'; alertsEl.appendChild(a); } } // Alert client if(DB.session&&DB.session.clientId===cl.id){ var el=document.getElementById('c-bal-alert'); if(!el){ el=document.createElement('div');el.id='c-bal-alert';el.className='alert aw'; el.style.marginBottom='10px'; el.textContent='⚠️ Tu saldo ($'+bal.toFixed(2)+') está por debajo del mínimo recomendado ($'+threshold+'). Considera cargar fondos.'; var balCard=document.getElementById('c-balance-card'); if(balCard) balCard.parentNode.insertBefore(el,balCard); } } } }); }; // ── 3. OPERATION LIMITS PER CLIENT ── window.checkOpLimit = function(clientId, amount){ var cl = DB.clients.find(function(x){return x.id===clientId;}); if(!cl||!cl.maxOpAmount||cl.maxOpAmount<=0) return {ok:true}; if(amount > cl.maxOpAmount){ return {ok:false, msg:'El monto $'+amount.toFixed(2)+' supera el límite por OP de $'+cl.maxOpAmount+' configurado para '+cl.empresa}; } return {ok:true}; }; // Override submitClientOP to check limit var _origSOPLimit = window.submitClientOP; window.submitClientOP = function(useSched){ var cid = DB.session.clientId; var mEl = document.getElementById(DB.opType==='nac'?'nac-monto':DB.opType==='int'?'int-monto':'otro-monto'); var amount = mEl ? parseFloat(mEl.value)||0 : 0; var check = checkOpLimit(cid, amount); if(!check.ok){ toast(check.msg,'err'); return; } if(_origSOPLimit) _origSOPLimit(useSched); }; // ── 4. MULTIPLE FILE UPLOADS IN OPs ── window.addVoucherFile = function(opId){ var input = document.createElement('input'); input.type='file'; input.accept='image/*,application/pdf'; input.onchange=function(){ if(!input.files[0]) return; var r = new FileReader(); r.onload=function(e){ var op = DB.ops.find(function(o){return o.id===opId;}); if(!op) return; if(!op.documents) op.documents=[]; op.documents.push({name:input.files[0].name,data:e.target.result,date:new Date().toLocaleDateString('es'),by:DB.session.role}); toast('Archivo adjuntado: '+input.files[0].name+' ✅','ok'); if(typeof openSide==='function') openSide(opId); }; r.readAsDataURL(input.files[0]); }; input.click(); }; window.renderOpDocuments = function(opId){ var op = DB.ops.find(function(o){return o.id===opId;}); if(!op||!op.documents||!op.documents.length) return '
Sin archivos adjuntos
'; return op.documents.map(function(d,i){ return '
' +'📎'+d.name+''+d.date+'' +'
'; }).join(''); }; // ── 5. PUSH NOTIFICATIONS (Browser API) ── window.initPushNotifications = function(){ if(!('Notification' in window)) return; if(Notification.permission==='default'){ Notification.requestPermission().then(function(p){ if(p==='granted') toast('Notificaciones del navegador activadas ✅','ok'); }); } }; window.sendPushNotif = function(title, body, icon){ if(!('Notification' in window)||Notification.permission!=='granted') return; try{ new Notification(title, {body:body||'', icon:icon||'', badge:icon||''}); }catch(e){} }; // Send push on OP status change var _origMC3 = window.markComplete; window.markComplete = function(){ if(_origMC3) _origMC3(); var op = DB.ops.find(function(o){return o.id===DB.sideId;}); if(op){ var cl=gC(op.clientId); sendPushNotif('OP Completada', op.id+' · $'+op.monto+' USD para '+op.ben+' — '+cl.empresa); } }; // ── 6. EXPORT TO EXCEL (SheetJS) ── window.exportToExcel = function(type){ type = type||'ops'; function loadXLSX(cb){ if(window.XLSX){ cb(); return; } var s=document.createElement('script'); s.src='https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js'; s.onload=cb; document.head.appendChild(s); } loadXLSX(function(){ var wb = window.XLSX.utils.book_new(); if(type==='ops'||type==='all'){ var opsData = [['Referencia','Cliente','Beneficiario','Tipo','Fecha','Monto USD','Cargos','Estado','Concepto']]; DB.ops.forEach(function(op){ opsData.push([op.id,gC(op.clientId).empresa,op.ben,op.type.toUpperCase(),op.date,parseFloat(op.monto||0),parseFloat(op.totalCharges||0),op.status,op.concepto||'']); }); window.XLSX.utils.book_append_sheet(wb,window.XLSX.utils.aoa_to_sheet(opsData),'OPs'); } if(type==='entries'||type==='all'){ var entData=[['ID','Cliente','Tipo','Monto USD','Descripción','Fecha','Estado','¿Comisión?']]; DB.entries.forEach(function(e){ entData.push([e.id,gC(e.clientId).empresa,e.type,e.amount,e.desc,e.date,e.status,e.isFee?'Sí':'No']); }); window.XLSX.utils.book_append_sheet(wb,window.XLSX.utils.aoa_to_sheet(entData),'Movimientos'); } if(type==='clients'||type==='all'){ var clData=[['ID','Empresa','RIF','Representante','Cédula','Correo','Contrato','Inicio','Fin','Comisión%','Saldo USD']]; DB.clients.forEach(function(cl){ clData.push([cl.id,cl.empresa,cl.rif,cl.rep,cl.ci,cl.email,cl.contrato,cl.contractStart,cl.contractEnd,((cl.commission||0.029)*100).toFixed(1),calcBal(cl.id).toFixed(2)]); }); window.XLSX.utils.book_append_sheet(wb,window.XLSX.utils.aoa_to_sheet(clData),'Clientes'); } var date = new Date().toISOString().split('T')[0]; window.XLSX.writeFile(wb,'Gliica_Export_'+date+'.xlsx'); toast('Excel exportado ✅','ok'); }); }; // ── 7. VES EXCHANGE RATE (manual + auto fetch) ── window.fetchVESRate = function(){ var el=document.getElementById('ves-rate-val'); var src=document.getElementById('ves-rate-src'); if(el) el.textContent='Consultando...'; // Try multiple sources fetch('https://api.exchangerate-api.com/v4/latest/USD') .then(function(r){return r.json();}) .then(function(d){ var ves=d.rates&&d.rates.VES?d.rates.VES:null; if(ves){ if(el) el.textContent='Bs.'+ves.toLocaleString('es',{maximumFractionDigits:2}); if(src) src.textContent='ExchangeRate-API'; DB.settings=DB.settings||{};DB.settings.vesRate=ves;DB.settings.vesRateDate=new Date().toLocaleDateString('es'); } else { if(el) el.textContent='N/D – ingresar manual'; } }).catch(function(){ if(el) el.textContent='Error – ingresar manual'; }); }; window.setVESManual = function(){ var inp=document.getElementById('ves-manual-inp'); var val=parseFloat(inp&&inp.value||0); if(!val) return; DB.settings=DB.settings||{};DB.settings.vesRate=val;DB.settings.vesRateDate=new Date().toLocaleDateString('es'); var el=document.getElementById('ves-rate-val');if(el) el.textContent='Bs.'+val.toLocaleString('es'); var src=document.getElementById('ves-rate-src');if(src) src.textContent='Manual'; toast('Tasa BCV guardada: Bs.'+val+'/USD ✅','ok'); }; // ── 8. UNIFIED STYLES — shared OP row renderer ── window.buildOPRow = function(op, role){ var cl = gC(op.clientId); var badge = ''+(window.SL&&SL[op.status]||op.status)+''; var isReturned = ['devuelta','pendiente_correccion'].includes(op.status); var printBtn = ''; if(role==='admin'){ return '
' +''+op.id+'' +''+cl.empresa.split(' ')[0]+'' +'
'+op.ben+'
'+op.date+(op.banco?' · '+op.banco:'')+'
' +'$'+parseFloat(op.monto||0).toLocaleString('en',{minimumFractionDigits:2})+'' +badge+printBtn+'
'; } else { return '
' +''+op.id+'' +'
'+op.ben+'
'+op.date+(op.banco?' · '+op.banco:'')+'
' +'$'+parseFloat(op.monto||0).toLocaleString('en',{minimumFractionDigits:2})+'' +badge+printBtn+'
'; } }; // ── 9. UNIFIED SETTINGS PANEL (Config Gliica) — add new sections ── var _origInitConfig = window.initConfigView; window.initConfigView = function(){ if(_origInitConfig) _origInitConfig(); // Always populate the client selector window.refreshConfigClients(); // Add VES rate section if not exists var configSect = document.getElementById('ves-rate-card'); if(!configSect){ var configArea = document.querySelector('#v-aconfig'); if(configArea){ var div=document.createElement('div');div.id='ves-rate-card';div.className='card';div.style.marginTop='11px'; div.innerHTML='
💱 Tipo de cambio BCV (VES/USD)
' +'
' +'
Tasa actual
' +'
' +'
' +'' +'
' +'' +'
' +'
La tasa se aplica automáticamente el día que el admin marca una OP como completada.
'; configArea.appendChild(div); } } // Add min balance and op limits section var limitsCard = document.getElementById('limits-config-card'); if(!limitsCard){ var configArea2 = document.querySelector('#v-aconfig'); if(configArea2){ var s=DB.settings||{}; var div2=document.createElement('div');div2.id='limits-config-card';div2.className='card';div2.style.marginTop='11px'; div2.innerHTML='
⚙️ Alertas y Límites globales
' +'
' +'
' +'
' +'
' +''; configArea2.appendChild(div2); } } // Load VES rate display if(DB.settings&&DB.settings.vesRate){ var v=document.getElementById('ves-rate-val');if(v)v.textContent='Bs.'+DB.settings.vesRate.toLocaleString('es'); var vs=document.getElementById('ves-rate-src');if(vs)vs.textContent=DB.settings.vesRateDate||''; } }; // ══ Config Gliica: client selector + print functions ══ window.refreshConfigClients = function(){ var sel=document.getElementById('print-cl-sel'); if(!sel)return; var prev=sel.value; sel.innerHTML=''+DB.clients.map(function(c){return'';}).join(''); if(prev)sel.value=prev; }; // Override doPrintContrato to use full contract window.doPrintContrato=function(){var id=getPrintClient();if(!id)return;openPrint(buildFullContract(gC(id)));}; window.doPrintAnexoB=function(){var id=getPrintClient();if(!id)return;openPrint(buildAnexoB(gC(id)));}; window.doPrintAnexoC=function(){var id=getPrintClient();if(!id)return;openPrint(buildAnexoC(gC(id)));}; window.doPrintRefAdmin=function(){var id=getPrintClient();if(!id)return;var c=gC(id);if(!c)return;printRefAdmin(c);}; window.saveLimitsConfig = function(){ DB.settings=DB.settings||{}; DB.settings.minBalanceAlert=parseFloat(document.getElementById('cfg-min-bal').value||0); DB.settings.globalMaxOp=parseFloat(document.getElementById('cfg-max-op').value||0); toast('Configuración guardada ✅','ok'); checkMinBalance(); }; // ── 10. EXCEL EXPORT BUTTONS in Admin Reports ── var _origRenderReports = window.aN; window.aN = function(id, el){ if(_origRenderReports) _origRenderReports(id, el); if(id==='ainformes'){ setTimeout(function(){ var rptArea=document.querySelector('#v-ainformes'); if(rptArea&&!document.getElementById('excel-export-btns')){ var div=document.createElement('div');div.id='excel-export-btns';div.className='card';div.style.marginTop='11px'; div.innerHTML='
📊 Exportar a Excel
' +'
' +'' +'' +'' +'' +'
'; rptArea.appendChild(div); } if(id==='aconfig') initConfigView(); },100); } }; // ── 11. PUSH NOTIFICATIONS INIT ── document.addEventListener('DOMContentLoaded',function(){ if('Notification' in window && Notification.permission==='default'){ // Don't auto-request, wait for user to click a button } // Check contract expiry on load (admin) setTimeout(function(){ if(DB.session&&DB.session.role==='admin'){ checkContractExpiry(); checkMinBalance(); } },1500); }); // Override aN to trigger balance/expiry checks var _origANb = window.aN; window.aN = function(id, el){ if(_origANb) _origANb(id, el); if(id==='adash'){ setTimeout(function(){checkContractExpiry();checkMinBalance();},200); } if(id==='aconfig'){ setTimeout(function(){initConfigView();},100); } }; // ── 12. ADD PUSH NOTIFICATION & EXCEL BUTTONS to Config ── // Add to existing Config Gliica email section var _origSaveEmailJS = window.saveEmailJS; window.saveEmailJS = function(){ if(_origSaveEmailJS) _origSaveEmailJS(); // Also request push notification permission if('Notification' in window&&Notification.permission!=='granted'){ Notification.requestPermission(); } }; // ── 13. CLIENT: show min balance alert on their dashboard ── var _origRCD2 = window.renderClientDash; window.renderClientDash = function(){ if(_origRCD2) _origRCD2(); checkMinBalance(); }; // ── 14. OP side panel: add multiple files + status history ── var _origOpenSide2 = window.openSide; window.openSide = function(opId){ if(_origOpenSide2) _origOpenSide2(opId); DB.sideId = opId; setTimeout(function(){ var sp=document.getElementById('side-pnl'); if(!sp) return; var op=DB.ops.find(function(o){return o.id===opId;}); if(!op) return; var cl=gC(op.clientId); // Remove old added blocks to re-render ['side-hist-block','side-docs-block','wa-send-btn'].forEach(function(id){ var old=document.getElementById(id);if(old)old.remove(); }); var ft=sp.querySelector('.side-ft'); if(!ft) return; // Status history block var histDiv=document.createElement('div');histDiv.id='side-hist-block'; histDiv.style.cssText='margin:8px 0;padding:10px;background:var(--bg);border:1px solid var(--bdr);border-radius:8px'; histDiv.innerHTML='
📋 Historial de estados
' +(op.statusHistory&&op.statusHistory.length?renderStatusHistory(opId):'
Sin historial registrado
'); ft.parentNode.insertBefore(histDiv,ft); // Multiple docs block var docsDiv=document.createElement('div');docsDiv.id='side-docs-block'; docsDiv.style.cssText='margin:8px 0;padding:10px;background:var(--bg);border:1px solid var(--bdr);border-radius:8px'; docsDiv.innerHTML='
' +'
📎 Archivos adjuntos
' +'
' +renderOpDocuments(opId); ft.parentNode.insertBefore(docsDiv,ft); // WhatsApp button (only if client has phone) if(cl&&cl.tel){ var wa=document.createElement('button');wa.id='wa-send-btn'; wa.className='btn btn-sm';wa.style.cssText='width:100%;margin-top:6px;background:#25D366;color:#fff;border-color:#25D366;font-weight:600'; wa.innerHTML='📲 Notificar por WhatsApp a '+cl.rep.split(' ')[0]; wa.onclick=function(){sendWhatsApp(opId);}; ft.parentNode.insertBefore(wa,ft); } },250); }; })(); 'undefined'=== typeof _trfq || (window._trfq = []);'undefined'=== typeof _trfd && (window._trfd=[]),_trfd.push({'tccl.baseHost':'secureserver.net'},{'ap':'cpsh-oh'},{'server':'p3plzcpnl508539'},{'dcenter':'p3'},{'cp_id':'10914474'},{'cp_cl':'8'}) // Monitoring performance to make your website faster. If you want to opt-out, please contact web hosting support.