iM
iMind
ACCESO RESTRINGIDO

Esta herramienta analiza información sobre personas con cargo público y está restringida a uso autorizado. Ingresa tu correo y la contraseña de acceso.

Tu correo se registra localmente para mantener un control de acceso. Esto es una protección básica; para varios usuarios con contraseñas propias se requiere un sistema de cuentas en el backend.

iMind
INTELIGENCIA POLÍTICA
Fuentes públicas
Los resultados se generan exclusivamente de información pública disponible. Cada dato incluye su fuente. El análisis es automático — los datos son los que generan las fuentes.
Buscar actor político
FUENTES: Gob. estatalesGob. municipales Cámara de DiputadosSenado Congresos localesINE INEGIWikipedia Prensa digitalRedes sociales
🏛️
Ingresa el nombre de un político o funcionario público para obtener su perfil completo con métricas objetivas
Los datos electorales provienen de resultados certificados por el INE. Los índices sociales son de CONEVAL e INEGI. El análisis de problemáticas se basa en fuentes públicas verificadas.
Consultar territorio
🗺️
Selecciona un estado y municipio para obtener el análisis territorial completo
'+ '
'+esc(p.nombre||'Actor Político')+'
'+ '
'+esc(p.cargo_actual||'Cargo no identificado')+'
'+ '
'+esc(p.partido||'Partido no identificado')+' · '+esc(p.territorio||'')+'
'+ '
iMind
STRATEGIC CONSULTING
Inteligencia Política
'+ '
Generado: '+fecha+'
Fuentes públicas verificadas
'+ '
'+ '
EVALUACIÓN GLOBAL · MÉTRICAS OBJETIVAS
'+ '
'+puntaje+'
/100
'+ '
Promedio ponderado de '+Object.keys(MET_LABELS).length+' métricas desde fuentes públicas
'+ '
'+nivel+'   |   Espectro: '+esc(p.posicion_espectro||'N/D')+'
'+ '
Desglose del cálculo del índice
'+ '
'+esc(idx.formula)+'
'+ ''+ ''+ '
Puntaje de desempeño (A–F)'+(idx.puntaje_desempeno!==null?idx.puntaje_desempeno+' / 100':'N/D')+'
Penalización por integridad−'+idx.penalizacion_integridad+' pts
Índice global final'+puntaje+' / 100 · '+nivel+'
'+ '
Métricas de evaluación por dimensión
'+ ''+ ''+ ''+ ''+ ''+metHtml+'
IndicadorValorDescripciónFuente
'+ '
'+ '
✅ Posicionamiento político
'+esc(p.posicion_explicacion||'N/D')+'
'+ '
Predicción de escenarios
'+ '
Riesgo electoral
'+ '
Nivel: '+esc(p.escenario_riesgo||'N/D')+' · Prob: '+esc(p.probabilidad_riesgo||'N/D')+' · Impacto: '+esc(p.impacto_riesgo||'N/D')+'
'+ '
Ventaja electoral
'+ '
'+esc(p.ventaja_electoral||'N/D')+'
'+ (p.sintesis_ia?'
Resumen interpretativo
'+esc(p.sintesis_ia)+'
':'')+ (antHtml?'
Historial político verificado
'+antHtml+'
':'')+ (integHtml2?'
⚠ Antecedentes de integridad — corrupción, delito, violencia y vínculos de riesgo
'+ '
Incluye hechos confirmados y señalamientos no resueltos. No se presume culpabilidad en lo no confirmado; cada elemento indica su estatus real y el efecto exacto en el índice.
'+ integHtml2+'
':'
✓ Sin antecedentes de integridad documentados en las fuentes consultadas
')+ (declHtml?'
Declaraciones públicas relevantes
'+declHtml+'
':'')+ '
'+ '
Riesgos identificados
'+tagsStyle(p.riesgos,'#C62828')+'
'+ '
Oportunidades
'+tagsStyle(p.oportunidades,'#2E7D32')+'
'+ (fofHtml2?'
Fuentes oficiales de gobierno consultadas
'+ ''+ ''+ ''+ ''+fofHtml2+'
FuenteURLInformación obtenida
':'')+'
'+ '
'+ (p.advertencia?'⚠ '+esc(p.advertencia)+'

':'')+'Este análisis fue generado automáticamente a partir de fuentes públicas verificables. No representa opinión editorial de iMind Strategic Consulting. Los datos están sujetos a la disponibilidad de información pública al momento de la consulta.
'+ '
iMind Strategic Consulting · www.imind.com.mx · Inteligencia PolíticaInformación de fuentes públicas · '+fecha+'
'+ '
'; var win = window.open('','_blank','width=860,height=700'); if (win) { win.document.write(html); win.document.close(); win.onload = function(){ win.focus(); win.print(); }; } toast('Ficha técnica lista — usa Imprimir → Guardar como PDF'); } // ══════════════════════════════════════════ // MÓDULO 1 — MODAL RED DE RELACIONES // ══════════════════════════════════════════ var redNodes = [], redEdges = []; var rCvs, rCtx, rCamX=0, rCamY=0, rCamZ=1; var rDrag=false, rDragStart={x:0,y:0}, rDragNode=null; var mPosSelected = ''; function abrirRed() { var p = _lastProfile; if (!p) { toast('Primero busca un actor político','r'); return; } document.getElementById('modal-actor-name').textContent = 'Red de relaciones de: ' + (p.nombre||'Actor analizado'); document.getElementById('modal-red').classList.add('open'); // Reinicia la red cada vez que se abre, para no mezclar datos de un actor // analizado previamente. _redActorActual rastrea a quién pertenece la red actual. if (window._redActorActual !== p.nombre) { redNodes = []; redEdges = []; window._redActorActual = p.nombre; } // Si no hay nodo central, crear uno if (redNodes.length === 0) { var main = { id: 'main', nombre: p.nombre||'Actor', cargo: p.cargo_actual||'', posicion: 'center', influencia: 10, r: 36, x: 0, y: 0, isMain: true }; redNodes.push(main); // Pre-cargar aliados y opositores del análisis var cp = p.circulo_poder||{}; (cp.aliados||[]).slice(0,3).forEach(function(a,i){ var ang = -Math.PI/2 + i*(Math.PI/(Math.max(3,(cp.aliados||[]).length))); redNodes.push({ id:uid(), nombre:a.nombre, cargo:a.rol||'', posicion:'aliado', influencia:6, r:22, x:200*Math.cos(ang), y:200*Math.sin(ang), isMain:false }); redEdges.push({ from:'main', to:redNodes[redNodes.length-1].id, tipo:'alianza' }); }); (cp.opositores||[]).slice(0,3).forEach(function(a,i){ var ang = Math.PI/2 + i*(Math.PI/(Math.max(3,(cp.opositores||[]).length))); redNodes.push({ id:uid(), nombre:a.nombre, cargo:a.rol||'', posicion:'opositor', influencia:6, r:22, x:200*Math.cos(ang), y:200*Math.sin(ang), isMain:false }); redEdges.push({ from:'main', to:redNodes[redNodes.length-1].id, tipo:'conflicto' }); }); } setTimeout(initRedCanvas, 80); renderMActorList(); } function cerrarModal() { document.getElementById('modal-red').classList.remove('open'); } function initRedCanvas() { rCvs = document.getElementById('rel-canvas'); if (!rCvs) return; rCtx = rCvs.getContext('2d'); rCvs.width = rCvs.parentElement.clientWidth; rCvs.height = rCvs.parentElement.clientHeight; rCamX = rCvs.width/2; rCamY = rCvs.height/2; rCamZ = 0.9; rCvs.removeEventListener('mousedown', rMD); rCvs.removeEventListener('mousemove', rMM); rCvs.removeEventListener('mouseup', rMU); rCvs.removeEventListener('wheel', rWheel); rCvs.addEventListener('mousedown', rMD); rCvs.addEventListener('mousemove', rMM); rCvs.addEventListener('mouseup', rMU); rCvs.addEventListener('wheel', rWheel, {passive:false}); rLoop(); } function rMD(e){ var r=rCvs.getBoundingClientRect(),mx=e.clientX-r.left,my=e.clientY-r.top,wx=(mx-rCamX)/rCamZ,wy=(my-rCamY)/rCamZ,n=redNodes.find(function(n){return(wx-n.x)*(wx-n.x)+(wy-n.y)*(wy-n.y)<=(n.r+6)*(n.r+6);}); if(n){rDragNode=n;rDrag=true;}else{rDrag=true;rDragStart={x:mx-rCamX,y:my-rCamY};rDragNode=null;rCvs.classList.add('grabbing');} } function rMM(e){ if(!rDrag)return;var r=rCvs.getBoundingClientRect(),mx=e.clientX-r.left,my=e.clientY-r.top;if(rDragNode){rDragNode.x=(mx-rCamX)/rCamZ;rDragNode.y=(my-rCamY)/rCamZ;}else{rCamX=mx-rDragStart.x;rCamY=my-rDragStart.y;} } function rMU(){ rDrag=false;rDragNode=null;rCvs.classList.remove('grabbing'); } function rWheel(e){ e.preventDefault();var r=rCvs.getBoundingClientRect(),mx=e.clientX-r.left,my=e.clientY-r.top,d=e.deltaY<0?1.12:.9,wx=(mx-rCamX)/rCamZ,wy=(my-rCamY)/rCamZ;rCamZ=Math.max(.18,Math.min(4,rCamZ*d));rCamX=mx-wx*rCamZ;rCamY=my-wy*rCamZ; } function rLoop() { requestAnimationFrame(rLoop); if(!rCvs||!rCtx)return; rCtx.clearRect(0,0,rCvs.width,rCvs.height); rCtx.save(); rCtx.translate(rCamX,rCamY); rCtx.scale(rCamZ,rCamZ); // Edges redEdges.forEach(function(e){ var fr=redNodes.find(function(n){return n.id===e.from;}),to=redNodes.find(function(n){return n.id===e.to;}); if(!fr||!to)return; var col=RC[e.tipo]||'#0F4C5C'; var dx=to.x-fr.x,dy=to.y-fr.y,dist=Math.sqrt(dx*dx+dy*dy)||1; rCtx.save();rCtx.strokeStyle=col;rCtx.lineWidth=2/rCamZ;rCtx.globalAlpha=.6; if(e.tipo==='conflicto')rCtx.setLineDash([6/rCamZ,3/rCamZ]);else rCtx.setLineDash([]); rCtx.beginPath();rCtx.moveTo(fr.x+dx/dist*fr.r,fr.y+dy/dist*fr.r);rCtx.lineTo(to.x-dx/dist*to.r,to.y-dy/dist*to.r);rCtx.stroke(); // Arrow rCtx.setLineDash([]);rCtx.globalAlpha=.85; var ang=Math.atan2(to.y-fr.y,to.x-fr.x),al=9/rCamZ; var ex=to.x-dx/dist*to.r,ey=to.y-dy/dist*to.r; rCtx.beginPath();rCtx.moveTo(ex,ey);rCtx.lineTo(ex-al*Math.cos(ang-.45),ey-al*Math.sin(ang-.45));rCtx.lineTo(ex-al*Math.cos(ang+.45),ey-al*Math.sin(ang+.45));rCtx.closePath();rCtx.fillStyle=col;rCtx.fill(); // Label rCtx.globalAlpha=.7;rCtx.font=(9/rCamZ)+'px monospace';rCtx.fillStyle=col;rCtx.textAlign='center'; rCtx.fillText(e.tipo,(fr.x+to.x)/2,(fr.y+to.y)/2-5/rCamZ); rCtx.restore(); }); // Nodes redNodes.forEach(function(n){ var col = n.isMain ? '#0F4C5C' : (PC[n.posicion]||'#0F4C5C'); var r = n.r; rCtx.save(); if(n.isMain){rCtx.shadowColor='#0F4C5C';rCtx.shadowBlur=16/rCamZ;} var g=rCtx.createRadialGradient(n.x-r*.25,n.y-r*.25,0,n.x,n.y,r); g.addColorStop(0,hexA(col,.38));g.addColorStop(1,hexA(col,.12)); rCtx.beginPath();rCtx.arc(n.x,n.y,r,0,Math.PI*2);rCtx.fillStyle=g;rCtx.fill(); rCtx.strokeStyle=col;rCtx.lineWidth=(n.isMain?2.5:1.5)/rCamZ;rCtx.globalAlpha=.85;rCtx.stroke(); rCtx.shadowBlur=0;rCtx.globalAlpha=1; var ini=n.nombre.split(' ').map(function(w){return w[0];}).join('').slice(0,2).toUpperCase(); rCtx.font='bold '+(Math.max(9,r*.6)/rCamZ)+'px Arial,sans-serif';rCtx.fillStyle=col;rCtx.textAlign='center';rCtx.textBaseline='middle';rCtx.fillText(ini,n.x,n.y); rCtx.font=(9.5/rCamZ)+'px Arial,sans-serif';rCtx.fillStyle='#2C2C2A';rCtx.textBaseline='top'; rCtx.shadowColor='rgba(255,255,255,.9)';rCtx.shadowBlur=5/rCamZ; var lbl=n.nombre.length>18?n.nombre.slice(0,16)+'…':n.nombre; rCtx.fillText(lbl,n.x,n.y+r+6/rCamZ); rCtx.restore(); }); rCtx.restore(); } function mZoomIn(){rCamZ=Math.min(4,rCamZ*1.18);} function mZoomOut(){rCamZ=Math.max(.18,rCamZ/1.18);} function mReset(){rCamX=rCvs?rCvs.width/2:300;rCamY=rCvs?rCvs.height/2:250;rCamZ=.9;} function mReorg(){ var main=redNodes.find(function(n){return n.isMain;}); if(!main)return; var rest=redNodes.filter(function(n){return !n.isMain;}); rest.forEach(function(n,i){var ang=(i/Math.max(rest.length,1))*Math.PI*2-Math.PI/2;n.x=main.x+200*Math.cos(ang);n.y=main.y+200*Math.sin(ang);}); } function mSelPos(pos, btn) { mPosSelected = pos; document.querySelectorAll('.m-pos-btn').forEach(function(b){ b.classList.remove('sel'); b.style.background='#fff'; b.style.borderWidth='1px'; }); btn.classList.add('sel'); btn.style.background = hexA(PC[pos]||'#0F4C5C',.13); btn.style.borderWidth = '2px'; } function agregarNodo() { var n = document.getElementById('m-nombre').value.trim(); if (!n) { toast('Ingresa el nombre del actor','r'); return; } var inf = parseInt(document.getElementById('m-inf').value)||5; var main = redNodes.find(function(n){return n.isMain;}); var ang = redNodes.length * 0.8; var newN = { id:uid(), nombre:n, cargo:document.getElementById('m-cargo').value.trim(), posicion:mPosSelected, influencia:inf, r:16+inf*2, x:(main?main.x:0)+180*Math.cos(ang), y:(main?main.y:0)+180*Math.sin(ang), isMain:false }; redNodes.push(newN); if (main) redEdges.push({ from:main.id, to:newN.id, tipo:document.getElementById('m-vinculo').value, fuente:document.getElementById('m-fuente').value.trim() }); ['m-nombre','m-cargo','m-fuente'].forEach(function(id){document.getElementById(id).value='';}); document.getElementById('m-inf').value='5'; mPosSelected=''; document.querySelectorAll('.m-pos-btn').forEach(function(b){b.classList.remove('sel');b.style.background='#fff';b.style.borderWidth='1px';}); renderMActorList(); toast('Actor agregado a la red'); } function renderMActorList() { var list = document.getElementById('m-actor-list'); if (!list) return; list.innerHTML = ''; redNodes.forEach(function(n) { var c = n.isMain ? '#0F4C5C' : (PC[n.posicion]||'#0F4C5C'); var ini = n.nombre.split(' ').map(function(w){return w[0];}).join('').slice(0,2).toUpperCase(); var d = document.createElement('div'); d.className='m-actor-item'; d.innerHTML='
'+ini+'
'+ ''+esc(n.nombre)+''+ ''+esc(n.isMain?'★ Principal':(PL[n.posicion]||'—'))+''; list.appendChild(d); }); } function exportarRed() { var d = { nodes:redNodes, edges:redEdges, exportado:new Date().toISOString() }; var b=new Blob([JSON.stringify(d,null,2)],{type:'application/json'}); var u=URL.createObjectURL(b),a=document.createElement('a'); a.href=u;a.download='imind_red_relaciones.json';a.click();URL.revokeObjectURL(u); toast('Red exportada como JSON'); } // ══════════════════════════════════════════ // MÓDULO 2 — TERRITORIAL // ══════════════════════════════════════════ async function analizarTerritorio() { var estado = document.getElementById('ter-estado').value; var municipio = document.getElementById('ter-municipio').value.trim(); var tipo = document.getElementById('ter-tipo').value; if (!estado && !municipio) { toast('Selecciona al menos un estado o escribe un municipio','r'); return; } var ter = [municipio, estado].filter(Boolean).join(', '); var result = document.getElementById('ter-result'); result.innerHTML = '
Consultando datos oficiales de "'+esc(ter)+'"...
'; var backendUrl = window.IMIND_BACKEND_URL || ''; // ── PASO 1: datos oficiales gratis (INEGI) — siempre se intenta, sin IA, sin costo ── var datosInegi = null; var datosINE = null; if (backendUrl && estado) { try { var respInegi = await fetch(backendUrl + '/proxy/datos-inegi/' + encodeURIComponent(estado)); if (respInegi.ok) datosInegi = await respInegi.json(); } catch(e) { console.warn('INEGI no disponible:', e.message); } } // ── PASO 1B: datos oficiales gratis (INE/DERFE) — Padrón Electoral y Lista Nominal ── if (backendUrl && estado) { try { var municipioINE = limpiarMunicipioParaINE(municipio); var urlINE = municipioINE ? backendUrl + '/proxy/ine-pdln/secciones/' + encodeURIComponent(estado) + '/' + encodeURIComponent(municipioINE) : backendUrl + '/proxy/ine-pdln/municipios/' + encodeURIComponent(estado); var respINE = await fetch(urlINE); if (respINE.ok) datosINE = await respINE.json(); } catch(e) { console.warn('INE PDLN no disponible:', e.message); } } // Renderiza de inmediato lo que sí tenemos (gratis), antes de intentar el análisis con IA renderDatosOficiales(datosInegi, datosINE, ter); // ── PASO 2: análisis interpretativo con IA — opcional, puede no estar disponible ── var prompt = buildTerPrompt(ter, tipo); try { if (!backendUrl) throw new Error('backend no configurado'); var resp = await fetch(backendUrl + '/proxy/analizar-territorio', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ territorio: ter, tipo: tipo, prompt: prompt }) }); if (!resp.ok) throw new Error('backend respondió ' + resp.status); var t = await resp.json(); renderTerritorio(t, true); // true = agregar después de los datos oficiales, no reemplazar } catch(e) { console.warn('Análisis con IA no disponible:', e.message); var terLower = ter.toLowerCase(); var esEjemploCancun = terLower.includes('cancún') || terLower.includes('cancun') || terLower.includes('benito juárez') || terLower.includes('benito juarez') || (terLower.includes('quintana roo') && !municipio); if (esEjemploCancun) { renderTerritorio(getDemoTerritorio(ter), true); } else { document.getElementById('ter-result-ia').innerHTML = '
'+ '
Análisis interpretativo no disponible
'+ '
El análisis narrativo adicional (con IA) no respondió en este momento. Los datos oficiales mostrados arriba son reales y no dependen de este servicio.
'+ ''+ '
'; } } } // Genera una gráfica de barras simple en SVG, sin dependencias externas. // Recibe la serie en orden cronológico ascendente (año más antiguo primero). function renderGraficaBarras(serie, color) { if (!serie || serie.length === 0) return ''; var w = 600, h = 180, padBottom = 24, padLeft = 4, padRight = 4, padTop = 10; var maxVal = Math.max.apply(null, serie.map(function(s){ return s.valor; })); var barW = (w - padLeft - padRight) / serie.length; var bars = serie.map(function(s, i) { var barH = maxVal > 0 ? ((s.valor / maxVal) * (h - padBottom - padTop)) : 0; var x = padLeft + i * barW; var y = h - padBottom - barH; return ''+ ''+esc(s.anio)+': '+s.valor.toLocaleString('es-MX')+''+ ''+esc(s.anio)+''; }).join(''); return ''+bars+''; } function formatearNumeroINE(n) { var num = Number(n || 0); return num.toLocaleString('es-MX'); } function renderTablaINE(datosINE) { if (!datosINE || !Array.isArray(datosINE.datos) || datosINE.datos.length === 0) { return '
🗳️
No se encontraron datos del INE para esta consulta. Verifica que el backend tenga cargado el Excel PDLN y que el nombre del municipio coincida con el oficial.
'; } var filas = datosINE.datos || []; var nivel = datosINE.nivel || ''; var totalPadron = filas.reduce(function(s, r){ return s + Number(r.padron_total || 0); }, 0); var totalLN = filas.reduce(function(s, r){ return s + Number(r.lista_nominal_total || 0); }, 0); var dif = totalPadron - totalLN; var pct = totalPadron > 0 ? ((totalLN / totalPadron) * 100).toFixed(2) : '0.00'; var titulo = nivel === 'seccion' ? 'Secciones electorales' : nivel === 'municipio' ? 'Municipios' : 'Estados'; var top = filas.slice(0, 12); var rows = top.map(function(r){ var nombre = nivel === 'seccion' ? ('Sección ' + esc(r.seccion || 'N/D')) : esc(r.municipio || r.estado || 'N/D'); var p = Number(r.padron_total || 0); var ln = Number(r.lista_nominal_total || 0); var porcentaje = p > 0 ? ((ln / p) * 100).toFixed(2) : '0.00'; return ''+ ''+nombre+''+ ''+formatearNumeroINE(p)+''+ ''+formatearNumeroINE(ln)+''+ ''+porcentaje+'%'+ ''; }).join(''); return '
'+ '
Padrón electoral
'+formatearNumeroINE(totalPadron)+'
Suma de '+filas.length+' '+titulo.toLowerCase()+'
'+ '
Lista nominal
'+formatearNumeroINE(totalLN)+'
INE / DERFE
'+ '
Cobertura LN/Padrón
'+pct+'%
Diferencia: '+formatearNumeroINE(dif)+'
'+ '
'+ '
'+ '
Ranking INE — '+esc(titulo)+' con mayor lista nominal
'+ ''+ ''+ ''+ ''+ ''+ ''+ ''+rows+'
TerritorioPadrónLista nominalLN/Padrón
'+ '
Fuente: '+esc(datosINE.fuente || 'INE / DERFE - Padrón Electoral y Lista Nominal')+' · Corte cargado en backend
'+ '
'; } function getPropValor(obj, keys) { obj = obj || {}; for (var i=0;i
Mapa electoral INE
La cartografía electoral ya está preparada para Quintana Roo. Para otros estados se debe cargar su Base Geográfica Digital del INE.
'; } return '
'+ '
'+ '
Mapa electoral INE · Quintana Roo
Secciones, municipios y lista nominal cruzada con cartografía oficial.
'+ '
'+ ''+ ''+ '
'+ '
'+ '
'+ '
Sección/Municipio consultadoMayor lista nominal
Fuente: INE / DERFE + Base Geográfica Digital INE. La capa se muestra solo para Quintana Roo en esta versión.
'+ '
'; } window.__imindDatosINEActuales = null; window.__imindMapaElectoral = null; window.__imindLayerElectoral = null; async function iniciarMapaElectoralINE(datosINE) { window.__imindDatosINEActuales = datosINE; if (!datosINE || !Array.isArray(datosINE.datos) || datosINE.datos.length === 0) return; var filas = datosINE.datos || []; var esQroo = filas.some(function(r){ return normGeoTxt(r.estado) === 'QUINTANA ROO' || String(r.clave_estado) === '23'; }); if (!esQroo || !document.getElementById('mapa-electoral-ine')) return; setTimeout(function(){ cargarMapaElectoralINE(datosINE.nivel === 'municipio' ? 'municipios' : 'secciones'); }, 80); } async function cargarMapaElectoralINE(tipo) { var cont = document.getElementById('mapa-electoral-ine'); var datosINE = window.__imindDatosINEActuales; if (!cont || !datosINE) return; if (!window.L) { cont.innerHTML = '
🗺️
No se pudo cargar Leaflet. Revisa conexión a internet del navegador.
'; return; } cont.innerHTML = ''; if (window.__imindMapaElectoral) { try { window.__imindMapaElectoral.remove(); } catch(e){} } var map = L.map('mapa-electoral-ine', { scrollWheelZoom: false }).setView([19.58, -88.05], 8); window.__imindMapaElectoral = map; L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, attribution: '© OpenStreetMap | INE / DERFE' }).addTo(map); var baseUrl = (window.IMIND_BACKEND_URL || '').replace(/\/$/, ''); var url = baseUrl + '/geo/qroo/' + (tipo === 'municipios' ? 'qroo_municipios.geojson' : 'qroo_secciones.geojson'); var resp = await fetch(url); if (!resp.ok) throw new Error('No se pudo cargar cartografía: '+resp.status); var geo = await resp.json(); var filas = datosINE.datos || []; var nivel = datosINE.nivel || ''; var secciones = new Set(filas.map(function(r){ return String(r.seccion || '').trim(); }).filter(Boolean)); var municipios = new Set(filas.map(function(r){ return normGeoTxt(r.municipio); }).filter(Boolean)); if (tipo === 'secciones' && secciones.size > 0) { geo.features = (geo.features || []).filter(function(f){ var p = f.properties || {}; var sec = String(getPropValor(p, ['SECCION','seccion','SECC','SECCION_','CVE_SECC','CLAVE_SECCION'])).trim(); return secciones.has(sec); }); } if (tipo === 'municipios' && municipios.size > 0 && nivel !== 'municipio') { geo.features = (geo.features || []).filter(function(f){ var p = f.properties || {}; var mun = normGeoTxt(getPropValor(p, ['MUNICIPIO','municipio','NOMBRE','NOM_MUN','NOMGEO','NOM_MPIO'])); return municipios.has(mun); }); } var maxLN = Math.max.apply(null, filas.map(function(r){ return Number(r.lista_nominal_total || 0); }).concat([1])); function datoParaFeature(feature) { var p = feature.properties || {}; if (tipo === 'secciones') { var sec = String(getPropValor(p, ['SECCION','seccion','SECC','SECCION_','CVE_SECC','CLAVE_SECCION'])).trim(); return filas.find(function(r){ return String(r.seccion || '').trim() === sec; }) || {}; } var mun = normGeoTxt(getPropValor(p, ['MUNICIPIO','municipio','NOMBRE','NOM_MUN','NOMGEO','NOM_MPIO'])); return filas.find(function(r){ return normGeoTxt(r.municipio) === mun; }) || {}; } var layer = L.geoJSON(geo, { style: function(feature) { var d = datoParaFeature(feature); var ln = Number(d.lista_nominal_total || 0); var peso = maxLN ? Math.max(.18, Math.min(.85, ln / maxLN)) : .35; return { color: '#0F4C5C', weight: tipo === 'municipios' ? 1.4 : .65, opacity: .85, fillColor: ln >= maxLN * .75 ? '#2E7D32' : '#0F4C5C', fillOpacity: peso }; }, onEachFeature: function(feature, lyr) { var p = feature.properties || {}; var d = datoParaFeature(feature); var nombre = tipo === 'secciones' ? 'Sección ' + esc(String(getPropValor(p, ['SECCION','seccion','SECC','SECCION_','CVE_SECC','CLAVE_SECCION']) || d.seccion || 'N/D')) : esc(String(getPropValor(p, ['MUNICIPIO','municipio','NOMBRE','NOM_MUN','NOMGEO','NOM_MPIO']) || d.municipio || 'Municipio')); var popup = '
'+nombre+'
'+ 'Padrón: '+formatearNumeroINE(d.padron_total || 0)+'
'+ 'Lista nominal: '+formatearNumeroINE(d.lista_nominal_total || 0)+'
'+ 'LN/Padrón: '+esc(d.porcentaje_lista || 'N/D')+'%
'; lyr.bindPopup(popup); } }).addTo(map); window.__imindLayerElectoral = layer; try { map.fitBounds(layer.getBounds(), { padding: [20, 20] }); } catch(e) { map.setView([19.58, -88.05], 8); } } // Muestra los datos oficiales gratuitos (INEGI) — esto NUNCA depende de IA ni tiene costo. function renderDatosOficiales(datosInegi, datosINE, ter) { var result = document.getElementById('ter-result'); var html = '
'; html += '
Datos oficiales — '+esc(ter)+' GRATIS · SIEMPRE DISPONIBLE
'; html += '
'; html += '
INE / DERFE — Padrón Electoral y Lista Nominal
'; html += renderMapaElectoralCard(datosINE); html += renderTablaINE(datosINE); html += '
'; var ind = datosInegi && datosInegi.indicadores; var popTotal = ind && ind.poblacion_total && !ind.poblacion_total.error ? ind.poblacion_total.serie : null; var popH = ind && ind.poblacion_hombres && !ind.poblacion_hombres.error ? ind.poblacion_hombres.serie : null; var popM = ind && ind.poblacion_mujeres && !ind.poblacion_mujeres.error ? ind.poblacion_mujeres.serie : null; var vivienda = ind && ind.viviendas && !ind.viviendas.error ? ind.viviendas.serie : null; if (popTotal) { var ultimo = popTotal[0]; var penultimo = popTotal[1]; var variacion = penultimo ? (((ultimo.valor - penultimo.valor) / penultimo.valor) * 100).toFixed(1) : null; // ── Tarjetas resumen: población total, hombres, mujeres, viviendas ── html += '
'; html += '
Población total
'+ultimo.valor.toLocaleString('es-MX')+'
Censo '+esc(ultimo.anio)+'
'; if (popH && popM) { var h = popH[0].valor, m = popM[0].valor, tot = h + m; var pctH = ((h/tot)*100).toFixed(1), pctM = ((m/tot)*100).toFixed(1); html += '
Hombres / Mujeres
'+ '
'+h.toLocaleString('es-MX')+' ('+pctH+'%)
'+ '
'+m.toLocaleString('es-MX')+' ('+pctM+'%)
'+ '
'+ '
'; } if (vivienda) { html += '
Viviendas habitadas
'+vivienda[0].valor.toLocaleString('es-MX')+'
Censo '+esc(vivienda[0].anio)+'
'; } html += '
'; if (variacion !== null) { html += '
Respecto al censo '+esc(penultimo.anio)+' ('+penultimo.valor.toLocaleString('es-MX')+'): '+(variacion>=0?'+':'')+variacion+'%
'; } // ── Gráfica de barras SVG: evolución histórica de población ── html += '
'; html += '
Evolución de la población (serie histórica)
'; html += renderGraficaBarras(popTotal.slice().reverse(), 'var(--vino)'); html += '
Fuente: '+esc(datosInegi.fuente)+' · Dato verificable, sin interpretación de IA
'; html += '
'; html += '
Ver tabla completa de todos los indicadores'; html += ''; popTotal.forEach(function(s){ var h = popH ? popH.find(function(x){return x.anio===s.anio;}) : null; var m = popM ? popM.find(function(x){return x.anio===s.anio;}) : null; var v = vivienda ? vivienda.find(function(x){return x.anio===s.anio;}) : null; html += ''+ ''+ ''+ ''+ ''; }); html += '
AñoPoblación totalHombresMujeresViviendas
'+esc(s.anio)+''+s.valor.toLocaleString('es-MX')+''+(h?h.valor.toLocaleString('es-MX'):'N/D')+''+(m?m.valor.toLocaleString('es-MX'):'N/D')+''+(v?v.valor.toLocaleString('es-MX'):'N/D')+'
'; } else { html += '
No se pudieron obtener datos del INEGI para este estado en este momento. Si seleccionaste solo un municipio sin estado, selecciona también el estado correspondiente.
'; } html += '
'; result.innerHTML = html; } function renderTerritorio(t, agregar) { var pc = pcolor(t.color_politico||''); var is = t.indices||{}; // Historial electoral var histHtml = (t.historial_electoral||[]).map(function(e){ var bars = (e.partidos||[]).slice(0,4).map(function(p){ return '
'+ '
'+esc(p.nombre)+''+p.pct+'%
'+ '
'; }).join(''); return '
'+ '
'+esc(e.eleccion)+''+ 'Participación: '+e.participacion+'% · '+esc(e.fuente||'INE')+'
'+ '
'+bars+'
'; }).join(''); // Tendencia SVG var tendSVG = ''; var hist = t.historial_electoral||[]; if (hist.length >= 2) { var pcts = hist.map(function(e){ return (e.partidos&&e.partidos[0]&&e.partidos[0].pct)||0; }).reverse(); var labels = hist.map(function(e){ return (e.eleccion||'').match(/\d{4}/)?.[0]||''; }).reverse(); var W=300,H=80,pad=22; var mn=Math.min.apply(null,pcts)-8, mx=Math.max.apply(null,pcts)+8; var sx=function(i){return pad+i*(W-pad*2)/(pcts.length-1);}; var sy=function(v){return H-pad-(v-mn)/(mx-mn)*(H-pad*2);}; var pts=pcts.map(function(v,i){return sx(i)+','+sy(v);}).join(' '); var dots=pcts.map(function(v,i){return ''+v+'%'+labels[i]+'';}).join(''); tendSVG='
'+dots+'
'; } // Problemáticas var probHtml = (t.problematicas||[]).map(function(pr){ var sev = pr.severidad; var scol = sev==='alta'?'var(--red)':sev==='media'?'var(--amber)':'var(--green)'; var sbord = sev==='alta'?'var(--red-b)':sev==='media'?'var(--amber-b)':'var(--green-b)'; var sbg = sev==='alta'?'var(--red-bg)':sev==='media'?'var(--amber-bg)':'var(--green-bg)'; return '
'+ '
'+esc(pr.tema)+'
'+ '
'+esc(pr.descripcion||'')+'
'+ '
'+sev+' impacto'+ ''+esc(pr.fuente||'')+'
'; }).join(''); // Actores clave var actHtml = (t.actores_clave||[]).map(function(a){ var ac = pcolor(a.partido||''); var ai = (a.nombre||'').split(' ').map(function(w){return w[0];}).join('').slice(0,2).toUpperCase(); return '
'+ '
'+ai+'
'+ '
'+esc(a.nombre)+'
'+esc(a.cargo||'')+' · '+esc(a.partido||'')+'
'; }).join(''); // Index metric function meterRow(label, val, suffix) { if (val === undefined || val === null) return '
'+label+'N/D
'; var pct=Math.min(parseFloat(val)||0,100); var col=pct>60?'var(--red)':pct>35?'var(--amber)':'var(--green)'; return '
'+label+''+val+(suffix||'')+'
'; } var tituloSeccion = agregar ? '
Análisis interpretativo adicional CON IA · LIMITADO
' : ''; var targetEl = agregar ? document.getElementById('ter-result-ia') : document.getElementById('ter-result'); if (!targetEl) return; // por si la sección de IA aún no existe en el DOM targetEl.innerHTML = tituloSeccion + `
Territorio
${esc(t.territorio||'')}
Análisis: ${new Date().toLocaleDateString('es-MX')}
Población estimada
${esc(t.poblacion||'N/D')}
INEGI
Lista nominal
${esc(t.lista_nominal||'N/D')}
INE
🏛 Control actual: ${esc(t.color_politico||'No determinado')}
Contexto territorial
${esc(t.descripcion||'')}
Tendencia electoral
${esc(t.tendencia_electoral||'')}
${tendSVG}
Índices sociales
Marginación ${esc(is.marginacion||'N/D')}
${meterRow('Pobreza',is.pobreza_pct,'%')} ${meterRow('Rezago educativo',is.rezago_edu_pct,'%')} ${meterRow('Sin acceso a salud',is.sin_salud_pct,'%')}
${esc(is.fuente||'')}
${actHtml ? `
Actores clave
${actHtml}
` : ''}
Historial electoral — INE resultados certificados
${histHtml||'
Sin datos históricos disponibles
'}
Problemáticas clave del territorio
${probHtml||'
Sin problemáticas identificadas
'}
${(t.agenda_local||[]).length ? `
Agenda local
${(t.agenda_local||[]).map(function(x){return ''+esc(x)+'';}).join('')}
` : ''} ${(t.tendencias_sociales||[]).length ? `
Tendencias sociales
${(t.tendencias_sociales||[]).map(function(x){return '
📈 '+esc(x)+'
';}).join('')}
` : ''}
${(t.riesgos_electorales||[]).length ? `
⚠ Riesgos electorales
${(t.riesgos_electorales||[]).map(function(x){return ''+esc(x)+'';}).join('')}
` : ''} ${(t.oportunidades_electorales||[]).length ? `
✅ Oportunidades
${(t.oportunidades_electorales||[]).map(function(x){return ''+esc(x)+'';}).join('')}
` : ''}
${t.advertencia ? `
${esc(t.advertencia)}
` : ''}
Fuentes: ${(t.fuentes||[]).join(' · ')}
`; } // ══════════════════════════════════════════ // ACCESO RESTRINGIDO — login simple con contraseña fija // Esto NO es un sistema de cuentas real: es una barrera básica para que no // cualquiera con el enlace entre, y un registro local de qué correos han // entrado. La contraseña vive aquí en el código del front-end, así que // cualquiera con acceso al código fuente puede verla — es una protección // contra acceso casual, no contra alguien técnico con intención de saltarla. // Para control de acceso real (varios usuarios, contraseñas propias, // revocación de acceso) se necesita un sistema de cuentas en el backend. // ══════════════════════════════════════════ var IMIND_PASSWORD = 'imind2026'; // ← cambia esta contraseña por la que tú definas async function intentarAcceso() { var email = document.getElementById('login-email').value.trim(); var pass = document.getElementById('login-pass').value; var errEl = document.getElementById('login-error'); errEl.style.display = 'none'; if (!email || !email.includes('@')) { errEl.textContent = 'Ingresa un correo válido.'; errEl.style.display = 'block'; return; } if (pass !== IMIND_PASSWORD) { errEl.textContent = 'Contraseña incorrecta.'; errEl.style.display = 'block'; return; } // Registra el acceso (correo + fecha) en almacenamiento local del artifact. try { var key = 'acceso:' + email.toLowerCase(); var registro = { email: email, primer_acceso: null, ultimo_acceso: new Date().toISOString(), veces: 1 }; try { var existente = await window.storage.get(key); if (existente && existente.value) { var prev = JSON.parse(existente.value); registro.primer_acceso = prev.primer_acceso; registro.veces = (prev.veces || 1) + 1; } else { registro.primer_acceso = registro.ultimo_acceso; } } catch(e) { registro.primer_acceso = registro.ultimo_acceso; } await window.storage.set(key, JSON.stringify(registro), true); // shared=true: registro visible para quien administre } catch(e) { console.warn('No se pudo registrar el acceso:', e.message); } document.getElementById('login-screen').style.display = 'none'; document.getElementById('app-content').style.display = 'block'; }