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>
260 lines
14 KiB
HTML
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">→</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>
|