Files
soft_usite/resources/views/portal/dashboard.html
T
Lizandro GuarnizoandClaude Opus 5 af71b66bb8 feat(umind): logo propio, plantillas de agente por rubro y página promocional
Logo. Hasta ahora no había: era el texto "uM" en un cuadrado, con tamaño y
esquinas distintas en cada pantalla. Ahora hay un mark — la "u" del nombre con
el punto de pulso integrado — probado a 16px, 20px y 32px sobre claro y oscuro,
que es donde se cae la mayoría de los logos. Reemplaza el texto en el portal,
el header, el sidebar del Studio y la promo, y va también de favicon.

Plantillas por rubro. Un agente recién creado no sabía nada, y escribir el
conocimiento desde cero frente a un campo en blanco es donde la mayoría
abandona. Al crear uno se puede elegir un punto de partida —tienda,
restaurante, consultorio, servicios, peluquería o la base genérica— y arranca
con sus notas ya escritas, listas para editar.

Los valores de ejemplo van entre corchetes a propósito, para que se vea que hay
que reemplazarlos: una nota que parezca dato real termina en boca del asistente.
Un test lo verifica nota por nota. Y la plantilla de salud abre con el límite —
el asistente no da consejo médico ni interpreta síntomas — porque es el único
rubro donde una respuesta inventada hace daño de verdad.

El catálogo vive en código, no en base: es contenido editorial que mejoramos
nosotros, no configuración que cada instalación toca por su lado. Las notas se
cargan en segundo plano porque cada una necesita sus embeddings, y hacer
esperar el alta sería castigar justo al que eligió plantilla.

Con las opciones nuevas el formulario pasaba de largo la pantalla y el botón de
guardar quedaba abajo del borde: el modal ahora tiene su propio scroll.

Y la página promocional de uMind queda servida en /umind.html.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 18:21:46 -05:00

260 lines
14 KiB
HTML

<!-- Portal Dashboard -->
<div x-data="portalDashboard()" x-init="init()">
<!-- Saludo -->
<div class="mb-8">
<h1 class="text-2xl font-bold text-slate-800">
Hola, {{ if .portalUser }}{{ .portalUser.Nombre }}{{ end }} 👋
</h1>
<p class="text-slate-500 text-sm mt-1">
{{ if .isPartner }}
Estás viendo todos los proyectos de tus clientes.
{{ else }}
Aquí puedes ver el avance de tus proyectos.
{{ end }}
</p>
</div>
<!-- Un fallo al cargar tiene que verse como un fallo, no como "no tenés
proyectos": son dos situaciones muy distintas para quien mira. -->
{{ if .error }}
<div class="bg-red-50 border border-red-200 text-red-700 rounded-xl p-4 text-sm mb-6">
{{ .error }}
</div>
{{ end }}
<!-- uMind: atajo si ya lo tiene, oferta si no. El dueño ya entró y ya
confía; es el punto de contacto más barato que existe. -->
{{ if .tieneUmind }}
<a href="/portal/studio" class="flex items-center gap-3 bg-white border border-slate-200 hover:border-[#8eb02f] rounded-2xl p-4 mb-6 transition-colors group">
<svg viewBox="0 0 96 96" class="w-10 h-10 flex-shrink-0" aria-hidden="true"><rect width="96" height="96" rx="22" fill="#8eb02f"/><path d="M32,42 V58 A14,14 0 0 0 60,58 V42" fill="none" stroke="#fff" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M60,58 V64" fill="none" stroke="#fff" stroke-width="10" stroke-linecap="round"/><circle cx="60" cy="28" r="7" fill="#fff"/></svg>
<div class="min-w-0 flex-1">
<p class="font-semibold text-slate-800 text-sm">uMind Studio</p>
<p class="text-slate-500 text-xs mt-0.5">Configurá tus asistentes, revisá conversaciones y consumo.</p>
</div>
<span class="text-slate-300 group-hover:text-[#8eb02f] transition-colors">&rarr;</span>
</a>
{{ else }}
<div class="rounded-2xl p-5 mb-6 border border-slate-200 bg-gradient-to-br from-[#8eb02f]/8 to-transparent">
<div class="flex items-start gap-3">
<svg viewBox="0 0 96 96" class="w-10 h-10 flex-shrink-0" aria-hidden="true"><rect width="96" height="96" rx="22" fill="#8eb02f"/><path d="M32,42 V58 A14,14 0 0 0 60,58 V42" fill="none" stroke="#fff" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/><path d="M60,58 V64" fill="none" stroke="#fff" stroke-width="10" stroke-linecap="round"/><circle cx="60" cy="28" r="7" fill="#fff"/></svg>
<div class="min-w-0">
<p class="font-semibold text-slate-800 text-sm">Tu WhatsApp puede responder solo</p>
<p class="text-slate-600 text-sm mt-1">
Un asistente que contesta con la información de tu negocio — precios, horarios, servicios.
Entiende las <strong>notas de voz</strong> y lee las <strong>fotos</strong> que te mandan tus clientes.
</p>
<a href="mailto:{{ if .portalUser }}{{ end }}soporte@u-site.app?subject=Quiero%20activar%20uMind"
class="inline-block mt-3 text-sm font-semibold text-white rounded-lg px-4 py-2" style="background:#8eb02f">
Quiero activarlo
</a>
</div>
</div>
</div>
{{ end }}
<!-- Proyectos cards -->
{{ if .isPartner }}
<!-- Agrupado por cliente -->
{{ range .grupos }}
<div class="mb-8">
<h2 class="text-sm font-bold text-slate-500 uppercase tracking-wide mb-3">
{{ if .Cliente.Empresa }}{{ .Cliente.Empresa }}{{ else }}{{ .Cliente.Nombre }}{{ end }}
</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{{ range .Proyectos }}
<a href="/portal/proyecto/{{ .Slug }}" class="bg-white rounded-2xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow p-5 block group">
<div class="flex items-center gap-3 mb-3">
<span class="w-3 h-3 rounded-full" style="background:{{ .Color }}"></span>
<span class="font-semibold text-slate-800 group-hover:text-[#8eb02f] transition-colors text-sm">{{ .Nombre }}</span>
</div>
<p class="text-xs text-slate-500 mb-3 line-clamp-2">{{ .Descripcion }}</p>
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-medium text-slate-500">Progreso</span>
<span class="text-xs font-bold text-slate-700">{{ .Progreso }}%</span>
</div>
<div class="w-full bg-slate-100 rounded-full h-2">
<div class="h-2 rounded-full" style="background:{{ .Color }};width:{{ .Progreso }}%"></div>
</div>
<div class="mt-3 flex justify-between items-center">
<span class="text-xs px-2 py-0.5 rounded-full font-semibold" style="background:#f1f5f9;color:#475569">{{ .Estado }}</span>
<span class="text-xs text-slate-400">Ver detalle →</span>
</div>
</a>
{{ end }}
</div>
</div>
{{ end }}
{{ else }}
<!-- Vista simple para cliente -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{{ range .proyectos }}
<a href="/portal/proyecto/{{ .Slug }}" class="bg-white rounded-2xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow p-5 block group">
<div class="flex items-center gap-3 mb-3">
<span class="w-3 h-3 rounded-full" style="background:{{ .Color }}"></span>
<span class="font-semibold text-slate-800 group-hover:text-[#8eb02f] transition-colors text-sm">{{ .Nombre }}</span>
</div>
<p class="text-xs text-slate-500 mb-3 line-clamp-2">{{ .Descripcion }}</p>
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-medium text-slate-500">Progreso</span>
<span class="text-xs font-bold text-slate-700">{{ .Progreso }}%</span>
</div>
<div class="w-full bg-slate-100 rounded-full h-2">
<div class="h-2 rounded-full" style="background:{{ .Color }};width:{{ .Progreso }}%"></div>
</div>
<div class="mt-3 flex justify-between items-center">
<span class="text-xs px-2 py-0.5 rounded-full font-semibold" style="background:#f1f5f9;color:#475569">{{ .Estado }}</span>
<span class="text-xs text-slate-400">Ver detalle →</span>
</div>
</a>
{{ end }}
</div>
{{ if not .proyectos }}
<div class="text-center py-20 text-slate-400">
<svg class="w-12 h-12 mx-auto mb-3 opacity-40" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z"/></svg>
<p>Aún no tienes proyectos asignados.</p>
</div>
{{ end }}
{{ end }}
</div>
{{ if .isPartner }}
<!-- ─── Sección Partner: Recursos y Comunicados ─────────────────────────────── -->
<div x-data="partnerHub()" x-init="init()" class="mt-10">
<div class="flex items-center gap-3 mb-5">
<span class="w-8 h-8 rounded-lg bg-[#8eb02f] text-white flex items-center justify-center text-sm font-bold">P</span>
<div>
<h2 class="text-base font-bold text-slate-800">Zona Partner</h2>
<p class="text-xs text-slate-400">Recursos y comunicados exclusivos para ti</p>
</div>
</div>
<!-- Tabs -->
<div class="flex border-b border-slate-200 mb-5 gap-0">
<button @click="tab='comunicados'" class="px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors"
:class="tab==='comunicados' ? 'border-[#8eb02f] text-[#8eb02f]' : 'border-transparent text-slate-500 hover:text-slate-700'">
📢 Comunicados
<span x-show="comunicados.length > 0" class="ml-1 bg-slate-100 text-slate-500 text-xs px-1.5 py-0.5 rounded-full" x-text="comunicados.length"></span>
</button>
<button @click="tab='recursos'" class="px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors"
:class="tab==='recursos' ? 'border-[#8eb02f] text-[#8eb02f]' : 'border-transparent text-slate-500 hover:text-slate-700'">
📁 Recursos
<span x-show="recursos.length > 0" class="ml-1 bg-slate-100 text-slate-500 text-xs px-1.5 py-0.5 rounded-full" x-text="recursos.length"></span>
</button>
</div>
<!-- Comunicados -->
<div x-show="tab==='comunicados'" x-cloak>
<div class="space-y-4">
<template x-for="com in comunicados" :key="com.ID">
<div class="bg-white border border-slate-200 rounded-xl p-5">
<div class="flex items-start justify-between gap-3">
<div class="flex-1">
<h3 class="font-semibold text-slate-800 text-sm mb-1" x-text="com.titulo"></h3>
<p class="text-sm text-slate-600 whitespace-pre-line leading-relaxed" x-text="com.contenido"></p>
<p class="text-xs text-slate-400 mt-2" x-text="formatDate(com.CreatedAt)"></p>
</div>
<template x-if="com.archivo">
<a :href="`/portal/partner-comunicados/${com.ID}/download`" target="_blank"
class="flex-shrink-0 inline-flex items-center gap-1.5 text-xs text-[#8eb02f] hover:text-[#6d8c24] border border-[#8eb02f] px-2.5 py-1.5 rounded-lg hover:bg-[#f6ffe0] transition-colors">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>
Adjunto
</a>
</template>
</div>
</div>
</template>
<p x-show="comunicados.length === 0 && !loading" class="text-slate-400 text-sm text-center py-10">Sin comunicados por ahora.</p>
</div>
</div>
<!-- Recursos -->
<div x-show="tab==='recursos'" x-cloak>
<!-- Filtro por categoría -->
<div class="flex flex-wrap gap-2 mb-4">
<template x-for="cat in ['todos','general','tecnico','comercial','marketing']" :key="cat">
<button @click="filtro=cat" class="text-xs px-3 py-1 rounded-full border font-medium transition-colors capitalize"
:class="filtro===cat ? 'bg-[#8eb02f] text-white border-[#8eb02f]' : 'border-slate-200 text-slate-500 hover:border-slate-400'" x-text="cat">
</button>
</template>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
<template x-for="r in recursosFiltrados" :key="r.ID">
<div class="bg-white border border-slate-200 rounded-xl p-4 flex flex-col gap-2">
<div class="flex items-start justify-between gap-2">
<div class="flex-1">
<div class="flex items-center gap-2 mb-1">
<span class="text-xs px-2 py-0.5 rounded-full font-medium capitalize" :class="catClass(r.categoria)" x-text="r.categoria"></span>
</div>
<h4 class="font-semibold text-slate-800 text-sm" x-text="r.nombre"></h4>
<p class="text-xs text-slate-500 mt-0.5 line-clamp-2" x-text="r.descripcion"></p>
</div>
</div>
<template x-if="r.archivo">
<a :href="`/portal/partner-recursos/${r.ID}/download`" target="_blank"
class="mt-auto inline-flex items-center gap-2 text-xs font-medium text-white bg-[#8eb02f] hover:bg-[#6d8c24] px-3 py-1.5 rounded-lg transition-colors">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>
Descargar
</a>
</template>
<template x-if="r.enlace">
<a :href="r.enlace" target="_blank" rel="noopener noreferrer"
class="inline-flex items-center gap-2 text-xs font-medium text-[#8eb02f] border border-[#8eb02f] hover:bg-[#f0f6e4] px-3 py-1.5 rounded-lg transition-colors">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg>
Ver enlace
</a>
</template>
<template x-if="!r.archivo && !r.enlace">
<span class="text-xs text-slate-400 italic">Sin archivo adjunto</span>
</template>
</div>
</template>
<p x-show="recursosFiltrados.length === 0 && !loading" class="sm:col-span-2 lg:col-span-3 text-slate-400 text-sm text-center py-10">Sin recursos disponibles.</p>
</div>
</div>
</div>
{{ end }}
<script>
function partnerHub() {
return {
tab: 'comunicados',
filtro: 'todos',
recursos: [],
comunicados: [],
loading: false,
async init() {
this.loading = true;
try {
const r = await axios.get('/portal/partner-recursos');
this.recursos = r.data.recursos || [];
this.comunicados = r.data.comunicados || [];
} catch(e) {} finally { this.loading = false; }
},
get recursosFiltrados() {
if (this.filtro === 'todos') return this.recursos;
return this.recursos.filter(r => r.categoria === this.filtro);
},
catClass(cat) {
return {
general: 'bg-slate-100 text-slate-600',
tecnico: 'bg-blue-100 text-blue-700',
comercial: 'bg-yellow-100 text-yellow-700',
marketing: 'bg-pink-100 text-pink-700',
}[cat] || 'bg-slate-100 text-slate-600';
},
formatDate(d) {
if (!d) return '';
return new Date(d).toLocaleDateString('es-CO', { day:'2-digit', month:'short', year:'numeric' });
},
}
}
</script>