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'; }