🔐
Oficina IA
Introduce tu contraseña para acceder

Selecciona un proyecto para analizar

\`\`\` ## Canonical y robots \`\`\`html ... \`\`\` Personaliza TODO con los datos reales del negocio.`, blog: `Genera 12 IDEAS DE ARTÍCULOS DE BLOG para posicionar este negocio: NEGOCIO: ${p.descripcion_servicios} ZONA: ${p.seo_zonas} CLIENTE IDEAL: ${p.seo_cliente_ideal} ${document.getElementById('seo-blog-mes')?.value ? 'MES OBJETIVO: '+document.getElementById('seo-blog-mes').value : ''} Para cada artículo incluye: - Título SEO (con keyword) - URL slug sugerida - Keyword principal - Meta description (155 caracteres) - Estructura: H2 y H3 principales - Intención del buscador Mezcla: artículos informativos, comparativos, locales y de servicios específicos. Prioriza keywords con intención comercial en la zona: ${p.seo_zonas}`, competencia: `Analiza la COMPETENCIA SEO para este negocio local: NEGOCIO: ${p.descripcion_servicios} ZONA OBJETIVO: ${p.seo_zonas} KEYWORD PRINCIPAL: ${(p.seo_keywords||'').split(',')[0]?.trim() || p.seo_zonas} ${document.getElementById('seo-comp-extra')?.value ? 'COMPETIDOR A ANALIZAR: '+document.getElementById('seo-comp-extra').value : ''} Incluye: ## 1. Competidores más probables en Google Quiénes aparecerán en los primeros resultados para estas keywords en esta zona. ## 2. Lo que hacen bien Qué estrategias SEO usan los líderes del sector en España. ## 3. Sus puntos débiles Dónde hay oportunidades para superarlos. ## 4. Estrategia de diferenciación Cómo posicionar este negocio por encima de la competencia. ## 5. Quick wins 3 acciones inmediatas para ganar visibilidad frente a ellos.` }; try { const res = await fetch('https://api.groq.com/openai/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer '+KEY }, body: JSON.stringify({ model: 'llama-3.3-70b-versatile', messages: [ { role: 'system', content: SEO_SYSTEM(p) }, { role: 'user', content: prompts[tipo] } ], max_tokens: 2048, temperature: 0.6 }) }); const data = await res.json(); if (data.error) throw new Error(data.error.message); seoOutput(tipo, formatMd(data.choices[0].message.content)); toast('✅ '+{estrategia:'Estrategia',keywords:'Keywords',contenido:'Contenido',meta:'Meta tags',blog:'Ideas blog',competencia:'Análisis'}[tipo]+' generado'); } catch(e) { seoOutput(tipo, `
⚠️ ${e.message}
`); toast('⚠️ Error al generar'); } if (btn) { btn.disabled = false; btn.textContent = {estrategia:'🚀 Generar estrategia completa',keywords:'🔑 Investigar keywords',contenido:'✍️ Generar contenido',meta:'🏷️ Generar meta tags',blog:'📝 Generar ideas',competencia:'🥊 Analizar competencia'}[tipo]; } } // SEO Chat async function seoChatSend() { if (!seoProyecto) { toast('⚠️ Selecciona un proyecto primero'); return; } const input = document.getElementById('seo-chat-input'); const msg = input?.value.trim(); if (!msg) return; input.value = ''; const msgs = document.getElementById('seo-chat-msgs'); msgs.innerHTML += `
${esc(msg)}
`; msgs.innerHTML += `
`; msgs.scrollTop = msgs.scrollHeight; seoChatHistory.push({ role: 'user', content: msg }); try { const res = await fetch('https://api.groq.com/openai/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer '+KEY }, body: JSON.stringify({ model: 'llama-3.3-70b-versatile', messages: [ { role: 'system', content: SEO_SYSTEM(seoProyecto) }, ...seoChatHistory.slice(-10) ], max_tokens: 1500, temperature: 0.7 }) }); const data = await res.json(); if (data.error) throw new Error(data.error.message); const reply = data.choices[0].message.content; seoChatHistory.push({ role: 'assistant', content: reply }); document.getElementById('seo-typing')?.remove(); msgs.innerHTML += `
${formatMd(reply)}
`; msgs.scrollTop = msgs.scrollHeight; } catch(e) { document.getElementById('seo-typing')?.remove(); msgs.innerHTML += `
⚠️ ${e.message}
`; } } // ── BÚSQUEDA GLOBAL ── let searchTimeout = null; function openSearch() { document.getElementById('search-modal').style.display = 'flex'; setTimeout(() => document.getElementById('search-input')?.focus(), 80); } function closeSearch() { document.getElementById('search-modal').style.display = 'none'; document.getElementById('search-input').value = ''; document.getElementById('search-results').innerHTML = '
Escribe para buscar en toda la Oficina
'; } // Keyboard shortcut Cmd/Ctrl+K document.addEventListener('keydown', e => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); openSearch(); } if (e.key === 'Escape') closeSearch(); }); async function doSearch(q) { clearTimeout(searchTimeout); q = q.trim(); if (q.length < 2) { document.getElementById('search-results').innerHTML = '
Escribe al menos 2 caracteres
'; return; } searchTimeout = setTimeout(async () => { try { const [clientes, proyectos, facturas, reportes] = await Promise.all([ api('/api/clientes?q='+encodeURIComponent(q)), api('/api/proyectos'), api('/api/facturas'), api('/api/reportes'), ]); // Filter proyectos and facturas locally const ql = q.toLowerCase(); const projF = proyectos.filter(p => p.nombre?.toLowerCase().includes(ql) || p.dominio?.toLowerCase().includes(ql) || p.descripcion_servicios?.toLowerCase().includes(ql) || p.cliente_nombre?.toLowerCase().includes(ql) || p.seo_zonas?.toLowerCase().includes(ql) || p.seo_keywords?.toLowerCase().includes(ql) ); const facF = facturas.filter(f => f.numero?.toLowerCase().includes(ql) || f.cliente_nombre?.toLowerCase().includes(ql) ); const repF = reportes.filter(r => r.semana?.toLowerCase().includes(ql)); const total = clientes.length + projF.length + facF.length + repF.length; const el = document.getElementById('search-results'); if (!total) { el.innerHTML = `
Sin resultados para "${q}"
`; return; } const section = (icon, label, items, renderFn) => !items.length ? '' : `
${icon} ${label} (${items.length})
${items.map(renderFn).join('')}
`; const row = (ico, title, sub, action) => `
${ico}
${title}
${sub}
`; el.innerHTML = section('👤', 'Clientes', clientes, c => row('👤', c.nombre, `${c.empresa||''}${c.telefono?' · '+c.telefono:''}`, `sw('clientes');setTimeout(()=>editCliente('${c.id}'),400)`)) + section('📁', 'Proyectos', projF, p => row('📁', p.nombre, `${p.cliente_nombre||''} · ${p.dominio||'sin dominio'}`, `sw('proyectos-mgmt');setTimeout(()=>verProyecto('${p.id}'),400)`)) + section('🧾', 'Facturas', facF, f => row('🧾', `Factura ${f.numero}`, `${f.cliente_nombre||'—'} · ${f.total}€ · ${f.estado}`, `sw('pagos')`)) + section('📊', 'Reportes', repF, r => row('📊', r.semana, `${r.ventas} ventas · ${r.ingresos_brutos}€`, `sw('comercial')`)); } catch(e) { document.getElementById('search-results').innerHTML = `
⚠️ ${e.message}
`; } }, 300); } // ── HISTORIAL DE PAGOS ── async function loadPagos() { try { const estadoF = document.getElementById('pagos-filtro-estado')?.value || ''; const clienteF = document.getElementById('pagos-filtro-cliente')?.value || ''; // Load clientes for filter const cls = await api('/api/clientes'); const clSel = document.getElementById('pagos-filtro-cliente'); if (clSel && clSel.options.length <= 1) { cls.forEach(c => { const opt = document.createElement('option'); opt.value = c.id; opt.textContent = c.nombre; clSel.appendChild(opt); }); } let url = '/api/facturas'; const params = []; if (estadoF) params.push('estado='+estadoF); if (clienteF) params.push('cliente_id='+clienteF); if (params.length) url += '?'+params.join('&'); const facturas = await api(url); document.getElementById('pagos-total-count').textContent = facturas.length + ' facturas'; document.getElementById('pagos-año').textContent = new Date().getFullYear(); // KPIs const pagadas = facturas.filter(f=>f.estado==='pagada'); const pendientes = facturas.filter(f=>f.estado==='pendiente'); const totalCobrado = pagadas.reduce((s,f)=>s+(f.total||0),0); const totalPendiente = pendientes.reduce((s,f)=>s+(f.total||0),0); const totalIVA = pagadas.reduce((s,f)=>s+(f.importe_iva||0),0); document.getElementById('pagos-kpis').innerHTML = [ {l:'Total cobrado',v:Math.round(totalCobrado)+'€',s:`${pagadas.length} facturas pagadas`,cls:'f',card:'ok'}, {l:'Pendiente de cobro',v:Math.round(totalPendiente)+'€',s:`${pendientes.length} facturas`,cls:'warn',card:'warn'}, {l:'IVA recaudado',v:Math.round(totalIVA)+'€',s:'De facturas pagadas',cls:'c',card:''}, {l:'Total facturas',v:facturas.length,s:`${pagadas.length} cobradas · ${pendientes.length} pendientes`,cls:'p',card:''}, ].map(k=>`
${k.l}
${k.v}
${k.s}
`).join(''); // Por mes const porMes = {}; facturas.filter(f=>f.estado==='pagada').forEach(f => { const mes = (f.fecha_emision||f.created_at||'').substring(0,7); if (!mes) return; if (!porMes[mes]) porMes[mes] = {total:0,n:0}; porMes[mes].total += f.total||0; porMes[mes].n++; }); const meses = Object.entries(porMes).sort((a,b)=>b[0].localeCompare(a[0])).slice(0,12); const maxMes = Math.max(...meses.map(([,v])=>v.total),1); const mesNombres = ['','Ene','Feb','Mar','Abr','May','Jun','Jul','Ago','Sep','Oct','Nov','Dic']; document.getElementById('pagos-mensual').innerHTML = meses.length ? meses.map(([k,v]) => { const [y,m] = k.split('-'); return `
${mesNombres[+m]} ${y.slice(2)}
${Math.round(v.total).toLocaleString('es')}€
${v.n}f
`; }).join('') : '
Sin facturas pagadas aún
'; // Por cliente const porCliente = {}; facturas.filter(f=>f.estado==='pagada').forEach(f => { const k = f.cliente_nombre||'Sin cliente'; if (!porCliente[k]) porCliente[k] = {total:0,n:0}; porCliente[k].total += f.total||0; porCliente[k].n++; }); const clientes2 = Object.entries(porCliente).sort((a,b)=>b[1].total-a[1].total); const maxCl = Math.max(...clientes2.map(([,v])=>v.total),1); document.getElementById('pagos-clientes').innerHTML = clientes2.length ? clientes2.map(([k,v]) => `
${k}
${Math.round(v.total).toLocaleString('es')}€
` ).join('') : '
Sin datos
'; // Tabla facturas const tbody = document.getElementById('pagos-tbody'); if (!facturas.length) { tbody.innerHTML = 'Sin facturas'; return; } tbody.innerHTML = facturas.map(f => ` ${f.numero} ${f.cliente_nombre||'—'} ${(f.fecha_emision||'').substring(0,10)} ${f.base_imponible?.toFixed(2)}€ ${f.importe_iva?.toFixed(2)}€ ${f.total?.toFixed(2)}€ ${f.estado} ${f.estado==='pendiente'?``:''} `).join(''); } catch(e) { toast('⚠️ '+e.message); } } async function marcarFacturaPagada(id) { try { await api('/api/facturas/'+id, {method:'PUT', body:{estado:'pagada'}}); toast('✅ Factura marcada como cobrada'); loadPagos(); } catch(e) { toast('⚠️ '+e.message); } } // ── PROYECTOS ── async function loadProyectos() { try { const filtro = document.getElementById('proj-filtro')?.value || ''; const rows = await api('/api/proyectos' + (filtro ? '?estado='+filtro : '')); const list = document.getElementById('proj-list'); document.getElementById('nm-proj').textContent = rows.length || ''; if (!list) return; if (!rows.length) { list.innerHTML = '
Sin proyectos aún. Se crean automáticamente cuando un cliente completa el onboarding.
'; return; } list.innerHTML = rows.map(p => { const estadoBadge = {pendiente:'warn', en_progreso:'info', entregado:'ok'}[p.estado] || 'muted'; const estadoLabel = {pendiente:'Pendiente', en_progreso:'En progreso', entregado:'Entregado'}[p.estado] || p.estado; const colores = p.colores ? (() => { try { return JSON.parse(p.colores); } catch { return [p.colores]; }})() : []; return `
${p.nombre} ${estadoLabel} ${p.origen_onboarding ? 'Onboarding' : ''}
👤 ${p.cliente_nombre || '—'}
${p.dominio ? `
🌐 ${p.dominio}
` : ''} ${p.descripcion_servicios ? `
${p.descripcion_servicios.substring(0,120)}${p.descripcion_servicios.length>120?'...':''}
` : ''} ${colores.length ? `
${colores.map(c=>`${c}`).join('')}
` : ''}
${p.num_fotos > 0 ? `📸 ${p.num_fotos} fotos` : ''} ${p.seo_zonas ? `🎯 SEO` : ''}
`; }).join(''); } catch(e) { toast('⚠️ '+e.message); } } async function verProyecto(id) { try { const p = await api('/api/proyectos/'+id); const colores = p.colores ? (() => { try { return JSON.parse(p.colores); } catch { return [p.colores]; }})() : []; const seccion = (titulo, contenido, color='var(--dm)') => `
${titulo}
${contenido}
`; const fotosHTML = p.fotos?.length ? `
${p.fotos.map(f=>` `).join('')}
` : `
Sin fotos subidas
`; const overlay = document.createElement('div'); overlay.className = 'overlay'; overlay.style.zIndex = '150'; overlay.innerHTML = ``; overlay.addEventListener('click', e => { if(e.target===overlay) overlay.remove(); }); document.body.appendChild(overlay); } catch(e) { toast('⚠️ '+e.message); } } async function cambiarEstadoProyecto(id, estado) { try { const p = await api('/api/proyectos/'+id); await api('/api/proyectos/'+id, {method:'PUT', body:{...p, estado}}); toast('✅ Estado actualizado'); loadProyectos(); } catch(e) { toast('⚠️ '+e.message); } } // ── HELPERS ── function gv(id){const el=document.getElementById(id);return el?el.value:'';} function hk(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendMsg();}} function ar(el){el.style.height='auto';el.style.height=Math.min(el.scrollHeight,100)+'px';} function esc(t){return t.replace(/&/g,'&').replace(//g,'>').replace(/\n/g,'
');} function ft(ts){try{return new Date(ts).toLocaleTimeString('es-ES',{hour:'2-digit',minute:'2-digit'});}catch{return '';}} function formatMd(t){return t.replace(/&/g,'&').replace(//g,'>').replace(/\*\*(.*?)\*\*/g,'$1').replace(/\*(.*?)\*/g,'$1').replace(/`(.*?)`/g,'$1').replace(/^#{1,3} (.*$)/gm,'$1').replace(/^- (.*$)/gm,'• $1').replace(/\n/g,'
');} function closeModal(id){document.getElementById(id).style.display='none';} function toast(msg){document.querySelector('.toast')?.remove();const t=document.createElement('div');t.className='toast';t.textContent=msg;document.body.appendChild(t);setTimeout(()=>t.remove(),3200);} window.onload = init;