fix: tema claro blanco, logo 4Ventures y menú original

Fondo blanco en la app; menú con labels/iconos originales; logo en sidebar y login.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4Ventures
2026-09-24 09:53:36 +02:00
parent 6d1602bd68
commit 068faa3d25
33 changed files with 642 additions and 633 deletions

View File

@@ -33,7 +33,7 @@ export default async function AdTrackerPage() {
if (ctx.portfolioWide) {
return (
<p className="text-sm text-gray-500 border border-gray-800 rounded-xl p-6 bg-[#111827]">
<p className="text-sm text-gray-500 border border-gray-200 rounded-xl p-6 bg-white">
Selecciona un cliente en Contexto para abrir su Ad Tracker (privado por
tenant).
</p>

View File

@@ -82,41 +82,41 @@ export default async function AgencyPage() {
{/* Global Agency KPI Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-[#111827] border border-gray-800 rounded-xl p-5">
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="flex items-center justify-between mb-3">
<span className="text-xs text-gray-400 font-medium">Clientes / Tenants</span>
<Building2 className="w-4 h-4 text-brand-400" />
</div>
<p className="text-2xl font-extrabold text-white">{tenants.length}</p>
<p className="text-2xl font-extrabold text-gray-900">{tenants.length}</p>
<p className="text-[11px] text-emerald-400 mt-1">100% aislados vía RLS</p>
</div>
<div className="bg-[#111827] border border-gray-800 rounded-xl p-5">
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="flex items-center justify-between mb-3">
<span className="text-xs text-gray-400 font-medium">Inversión Gestionada</span>
<DollarSign className="w-4 h-4 text-blue-400" />
</div>
<p className="text-2xl font-extrabold text-white">
<p className="text-2xl font-extrabold text-gray-900">
{totalSpend.toLocaleString('es-ES', { minimumFractionDigits: 2 })} €
</p>
<p className="text-[11px] text-gray-400 mt-1">Vía Windsor.ai pipeline</p>
</div>
<div className="bg-[#111827] border border-gray-800 rounded-xl p-5">
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="flex items-center justify-between mb-3">
<span className="text-xs text-gray-400 font-medium">Leads Captados</span>
<Users className="w-4 h-4 text-purple-400" />
</div>
<p className="text-2xl font-extrabold text-white">{totalLeads}</p>
<p className="text-2xl font-extrabold text-gray-900">{totalLeads}</p>
<p className="text-[11px] text-gray-400 mt-1">{totalWonDeals} cerrados ganados</p>
</div>
<div className="bg-[#111827] border border-gray-800 rounded-xl p-5">
<div className="bg-white border border-gray-200 rounded-xl p-5">
<div className="flex items-center justify-between mb-3">
<span className="text-xs text-gray-400 font-medium">ROAS Blended Agencia</span>
<TrendingUp className="w-4 h-4 text-emerald-400" />
</div>
<p className="text-2xl font-extrabold text-white">{blendedRoas}x</p>
<p className="text-2xl font-extrabold text-gray-900">{blendedRoas}x</p>
<p className="text-[11px] text-emerald-400 mt-1">
{totalRevenue.toLocaleString('es-ES', { minimumFractionDigits: 2 })} € generados
</p>

View File

@@ -19,19 +19,19 @@ function FormattedMessage({ content }: { content: string }) {
const matchStr = match[0];
if (matchStr.startsWith('**') && matchStr.endsWith('**')) {
parts.push(
<strong key={match.index} className="font-bold text-white tracking-wide">
<strong key={match.index} className="font-bold text-gray-900 tracking-wide">
{matchStr.slice(2, -2)}
</strong>
);
} else if (matchStr.startsWith('*') && matchStr.endsWith('*')) {
parts.push(
<em key={match.index} className="italic text-gray-300">
<em key={match.index} className="italic text-gray-600">
{matchStr.slice(1, -1)}
</em>
);
} else if (matchStr.startsWith('`') && matchStr.endsWith('`')) {
parts.push(
<code key={match.index} className="bg-gray-900/80 text-brand-400 font-mono text-[11px] px-1.5 py-0.5 rounded border border-gray-700/60">
<code key={match.index} className="bg-gray-50 text-brand-400 font-mono text-[11px] px-1.5 py-0.5 rounded border border-gray-200">
{matchStr.slice(1, -1)}
</code>
);
@@ -85,7 +85,7 @@ function FormattedMessage({ content }: { content: string }) {
if (isQuote) {
return (
<blockquote key={lineIdx} className="border-l-2 border-brand-500/80 pl-3 italic text-gray-300 my-1 bg-gray-850/40 py-1 rounded-r">
<blockquote key={lineIdx} className="border-l-2 border-brand-500/80 pl-3 italic text-gray-600 my-1 bg-gray-850/40 py-1 rounded-r">
{renderInline(displayText)}
</blockquote>
);
@@ -164,15 +164,15 @@ export default function AsistentePage() {
};
return (
<div className="flex flex-col h-[calc(100vh-10rem)] bg-[#111827] border border-gray-800 rounded-xl overflow-hidden shadow-sm">
<div className="flex flex-col h-[calc(100vh-10rem)] bg-white border border-gray-200 rounded-xl overflow-hidden shadow-sm">
{/* Header */}
<div className="p-4 border-b border-gray-800 flex items-center justify-between bg-[#0D121F]">
<div className="p-4 border-b border-gray-200 flex items-center justify-between bg-white">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-brand-500/20 text-brand-500 flex items-center justify-center">
<Bot className="w-5 h-5" />
</div>
<div>
<h3 className="font-bold text-sm text-white">Asistente IA Financiero & KPIs</h3>
<h3 className="font-bold text-sm text-gray-900">Asistente IA Financiero & KPIs</h3>
<p className="text-[11px] text-gray-400">Temperatura 0.0 · Verificación Numérica Estricta · Supabase RPC</p>
</div>
</div>
@@ -189,7 +189,7 @@ export default function AsistentePage() {
className={`max-w-2xl px-5 py-3.5 rounded-2xl ${
m.role === 'user'
? 'bg-gradient-to-r from-brand-500 to-amber-600 text-white font-medium shadow-md shadow-brand-500/10'
: 'bg-[#1a2333] text-gray-200 border border-gray-800/80 shadow-lg shadow-black/20'
: 'bg-[#1a2333] text-gray-800 border border-gray-200 shadow-lg shadow-black/20'
}`}
>
<FormattedMessage content={m.content} />
@@ -199,7 +199,7 @@ export default function AsistentePage() {
{loading && (
<div className="flex justify-start">
<div className="bg-[#1a2333] text-gray-300 px-5 py-3 rounded-2xl text-sm border border-gray-800/80 flex items-center gap-2">
<div className="bg-[#1a2333] text-gray-600 px-5 py-3 rounded-2xl text-sm border border-gray-200 flex items-center gap-2">
<Sparkles className="w-4 h-4 animate-spin text-brand-500" />
Calculando métricas sin alucinaciones...
</div>
@@ -209,7 +209,7 @@ export default function AsistentePage() {
</div>
{/* Quick Prompts Chips */}
<div className="px-6 py-2.5 bg-[#0D121F]/80 border-t border-gray-800/80 flex gap-2 overflow-x-auto text-xs text-gray-300">
<div className="px-6 py-2.5 bg-white/80 border-t border-gray-200 flex gap-2 overflow-x-auto text-xs text-gray-600">
<button
onClick={() => sendMessage('¿Qué puedo preguntar?')}
className="px-3 py-1.5 rounded-full bg-brand-500/20 text-brand-400 hover:bg-brand-500/30 whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-brand-500/40"
@@ -219,35 +219,35 @@ export default function AsistentePage() {
</button>
<button
onClick={() => sendMessage('¿Qué pasa si invierto 1.000 € en Meta?')}
className="px-3 py-1.5 rounded-full bg-gray-800 hover:bg-gray-700 hover:text-white whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-700/50"
className="px-3 py-1.5 rounded-full bg-gray-100 hover:bg-gray-700 hover:text-gray-900 whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-200/50"
>
<DollarSign className="w-3.5 h-3.5 text-brand-400" />
🔮 Simular 1.000 € en Meta
</button>
<button
onClick={() => sendMessage('¿Cómo va el cumplimiento de objetivos de este mes?')}
className="px-3 py-1.5 rounded-full bg-gray-800 hover:bg-gray-700 hover:text-white whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-700/50"
className="px-3 py-1.5 rounded-full bg-gray-100 hover:bg-gray-700 hover:text-gray-900 whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-200/50"
>
<Target className="w-3.5 h-3.5 text-emerald-400" />
🎯 Objetivos de este mes
</button>
<button
onClick={() => sendMessage('Muéstrame el embudo comercial')}
className="px-3 py-1.5 rounded-full bg-gray-800 hover:bg-gray-700 hover:text-white whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-700/50"
className="px-3 py-1.5 rounded-full bg-gray-100 hover:bg-gray-700 hover:text-gray-900 whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-200/50"
>
<Filter className="w-3.5 h-3.5 text-blue-400" />
🔄 Embudo comercial
</button>
<button
onClick={() => sendMessage('¿Cuál es el CAC y ROAS por canal de los últimos 30 días?')}
className="px-3 py-1.5 rounded-full bg-gray-800 hover:bg-gray-700 hover:text-white whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-700/50"
className="px-3 py-1.5 rounded-full bg-gray-100 hover:bg-gray-700 hover:text-gray-900 whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-200/50"
>
<TrendingUp className="w-3.5 h-3.5 text-purple-400" />
📊 CAC & ROAS por canal
</button>
<button
onClick={() => sendMessage('Explícame la metodología 4Ventures y cómo calculáis el CAC de cohorte')}
className="px-3 py-1.5 rounded-full bg-gray-800 hover:bg-gray-700 hover:text-white whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-700/50"
className="px-3 py-1.5 rounded-full bg-gray-100 hover:bg-gray-700 hover:text-gray-900 whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-200/50"
>
<BookOpen className="w-3.5 h-3.5 text-amber-400" />
📖 Metodología y atribución
@@ -255,7 +255,7 @@ export default function AsistentePage() {
</div>
{/* Input Box */}
<div className="p-4 border-t border-gray-800 bg-[#0D121F]">
<div className="p-4 border-t border-gray-200 bg-white">
<form
onSubmit={(e) => {
e.preventDefault();
@@ -268,7 +268,7 @@ export default function AsistentePage() {
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Pregunta sobre inversión, metas, CAC, o escribe «¿Qué puedo preguntar?»..."
className="flex-1 bg-[#111827] border border-gray-700 rounded-xl px-4 py-2.5 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-brand-500 transition-all"
className="flex-1 bg-white border border-gray-200 rounded-xl px-4 py-2.5 text-sm text-gray-900 placeholder-gray-500 focus:outline-none focus:border-brand-500 transition-all"
/>
<button
type="submit"

View File

@@ -74,7 +74,7 @@ function platformMeta(p: string) {
function DualBadge({ dual }: { dual?: Dual }) {
if (!dual) {
return (
<span className="text-[10px] uppercase tracking-wider px-2 py-0.5 rounded-full border border-gray-700 text-gray-500">
<span className="text-[10px] uppercase tracking-wider px-2 py-0.5 rounded-full border border-gray-200 text-gray-500">
Sin checks
</span>
);
@@ -245,7 +245,7 @@ export default function CalendarClient({
{calendarTitle} · privado (sin publicación pública) · doble check
agencia/cliente
</p>
<div className="flex gap-1 rounded-lg border border-gray-800 p-0.5 bg-black/20">
<div className="flex gap-1 rounded-lg border border-gray-200 p-0.5 bg-gray-50">
{(
[
['month', 'Mes'],
@@ -267,12 +267,12 @@ export default function CalendarClient({
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3.5">
<div className="bg-[#111827] border border-gray-800 rounded-xl p-4">
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="flex items-center justify-between text-gray-400 text-xs mb-1">
<span>Total piezas</span>
<CalendarDays className="w-4 h-4 text-brand-400" />
</div>
<p className="text-2xl font-extrabold text-white">{totalPosts}</p>
<p className="text-2xl font-extrabold text-gray-900">{totalPosts}</p>
</div>
{platforms
.filter((p) => p !== 'all')
@@ -281,16 +281,16 @@ export default function CalendarClient({
const meta = platformMeta(p);
const n = initialPosts.filter((x) => x.platform === p).length;
return (
<div key={p} className="bg-[#111827] border border-gray-800 rounded-xl p-4">
<div key={p} className="bg-white border border-gray-200 rounded-xl p-4">
<div className="flex items-center justify-between text-gray-400 text-xs mb-1">
<span>{meta.label}</span>
<meta.Icon className="w-4 h-4" />
</div>
<p className="text-2xl font-extrabold text-white">{n}</p>
<p className="text-2xl font-extrabold text-gray-900">{n}</p>
</div>
);
})}
<div className="bg-[#111827] border border-gray-800 rounded-xl p-4">
<div className="bg-white border border-gray-200 rounded-xl p-4">
<div className="flex items-center justify-between text-gray-400 text-xs mb-1">
<span>Doble OK</span>
<CheckCircle2 className="w-4 h-4 text-emerald-400" />
@@ -303,7 +303,7 @@ export default function CalendarClient({
</div>
{/* Unified + per-network tabs */}
<div className="bg-[#111827] border border-gray-800 rounded-xl p-4 flex flex-col md:flex-row md:items-center justify-between gap-4">
<div className="bg-white border border-gray-200 rounded-xl p-4 flex flex-col md:flex-row md:items-center justify-between gap-4">
<div className="flex items-center gap-1.5 overflow-x-auto pb-1 md:pb-0">
{platforms.map((p) => {
const count =
@@ -324,7 +324,7 @@ export default function CalendarClient({
: p === 'instagram'
? 'bg-gradient-to-r from-pink-500 to-purple-600 text-white'
: `${meta?.color || 'bg-gray-700'} text-white`
: 'bg-gray-800/60 text-gray-300 hover:text-white'
: 'bg-gray-100 text-gray-600 hover:text-white'
}`}
>
{meta && <meta.Icon className="w-3.5 h-3.5" />}
@@ -338,7 +338,7 @@ export default function CalendarClient({
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value as typeof statusFilter)}
className="bg-gray-900 border border-gray-700 text-xs text-white rounded-lg px-2.5 py-1.5 focus:outline-none focus:border-brand-500"
className="bg-gray-900 border border-gray-200 text-xs text-gray-900 rounded-lg px-2.5 py-1.5 focus:outline-none focus:border-brand-500"
>
<option value="all">Todos</option>
<option value="approved">Aprobados</option>
@@ -356,7 +356,7 @@ export default function CalendarClient({
byDay.map(([day, posts]) => (
<div
key={day}
className="rounded-xl border border-gray-800 bg-[#111827] p-4"
className="rounded-xl border border-gray-200 bg-white p-4"
>
<p className="text-xs font-semibold text-brand-400 font-mono mb-3">
{new Date(day).toLocaleDateString('es-ES', {
@@ -400,10 +400,10 @@ export default function CalendarClient({
{selectedPost && (
<div className="fixed inset-0 z-50 bg-black/75 backdrop-blur-sm flex items-center justify-center p-4">
<div className="bg-[#0D121F] border border-gray-800 rounded-2xl w-full max-w-2xl max-h-[90vh] overflow-hidden shadow-2xl flex flex-col">
<div className="p-5 border-b border-gray-800 flex items-center justify-between bg-[#111827]">
<div className="bg-white border border-gray-200 rounded-2xl w-full max-w-2xl max-h-[90vh] overflow-hidden shadow-2xl flex flex-col">
<div className="p-5 border-b border-gray-200 flex items-center justify-between bg-white">
<div>
<h3 className="text-base font-bold text-white">{selectedPost.title}</h3>
<h3 className="text-base font-bold text-gray-900">{selectedPost.title}</h3>
<p className="text-xs text-gray-400 font-mono mt-0.5">
{selectedPost.platform} · {selectedPost.date} · {selectedPost.time}h
</p>
@@ -411,13 +411,13 @@ export default function CalendarClient({
<button
type="button"
onClick={() => setSelectedPost(null)}
className="text-gray-400 hover:text-white p-1 rounded-lg hover:bg-gray-800"
className="text-gray-400 hover:text-gray-900 p-1 rounded-lg hover:bg-gray-50"
>
<X className="w-5 h-5" />
</button>
</div>
<div className="p-6 overflow-y-auto space-y-4 text-sm text-gray-200">
<div className="p-6 overflow-y-auto space-y-4 text-sm text-gray-800">
<div className="flex flex-wrap items-center gap-2">
<DualBadge dual={dualMap[selectedPost.id]} />
<span className="text-[10px] text-gray-500">
@@ -437,8 +437,8 @@ export default function CalendarClient({
</Block>
{canCollaborate && (
<div className="rounded-xl border border-gray-800 bg-black/20 p-4 space-y-3">
<div className="flex items-center gap-2 text-xs font-semibold text-white">
<div className="rounded-xl border border-gray-200 bg-gray-50 p-4 space-y-3">
<div className="flex items-center gap-2 text-xs font-semibold text-gray-900">
<MessageSquare className="w-3.5 h-3.5 text-sky-400" />
Comentarios
</div>
@@ -449,7 +449,7 @@ export default function CalendarClient({
{comments.map((c) => (
<li
key={c.id}
className="text-xs border border-gray-800 rounded-lg px-2.5 py-2"
className="text-xs border border-gray-200 rounded-lg px-2.5 py-2"
>
<div className="flex justify-between gap-2 text-[10px] text-gray-500 mb-1">
<span>
@@ -465,7 +465,7 @@ export default function CalendarClient({
})}
</span>
</div>
<p className="text-gray-200 whitespace-pre-wrap">{c.body}</p>
<p className="text-gray-800 whitespace-pre-wrap">{c.body}</p>
</li>
))}
</ul>
@@ -474,7 +474,7 @@ export default function CalendarClient({
value={commentText}
onChange={(e) => setCommentText(e.target.value)}
placeholder="Escribe un comentario…"
className="flex-1 rounded-lg bg-gray-900 border border-gray-700 px-3 py-2 text-xs text-white placeholder:text-gray-600 focus:outline-none focus:border-brand-500/50"
className="flex-1 rounded-lg bg-gray-900 border border-gray-200 px-3 py-2 text-xs text-gray-900 placeholder:text-gray-600 focus:outline-none focus:border-brand-500/50"
/>
<button
type="button"
@@ -492,11 +492,11 @@ export default function CalendarClient({
)}
</div>
<div className="p-4 border-t border-gray-800 bg-[#111827] flex flex-wrap items-center justify-between gap-2">
<div className="p-4 border-t border-gray-200 bg-white flex flex-wrap items-center justify-between gap-2">
<button
type="button"
onClick={() => copyFullCopy(selectedPost)}
className="px-3 py-2 rounded-xl bg-gray-800 hover:bg-gray-700 text-gray-200 text-xs font-semibold inline-flex items-center gap-1.5"
className="px-3 py-2 rounded-xl bg-gray-100 hover:bg-gray-700 text-gray-800 text-xs font-semibold inline-flex items-center gap-1.5"
>
{copied ? <Check className="w-3.5 h-3.5 text-emerald-300" /> : <Copy className="w-3.5 h-3.5" />}
Copiar copy
@@ -508,7 +508,7 @@ export default function CalendarClient({
onClick={() => approve(myApproved ? 'revoke' : 'approve')}
className={`px-4 py-2 rounded-xl text-xs font-semibold inline-flex items-center gap-1.5 ${
myApproved
? 'bg-gray-800 text-gray-300 border border-gray-700'
? 'bg-gray-100 text-gray-600 border border-gray-200'
: 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/40'
}`}
>
@@ -526,9 +526,9 @@ export default function CalendarClient({
function EmptyState() {
return (
<div className="col-span-full bg-[#111827] border border-gray-800 rounded-xl p-12 text-center text-gray-400">
<div className="col-span-full bg-white border border-gray-200 rounded-xl p-12 text-center text-gray-400">
<CalendarDays className="w-10 h-10 text-gray-600 mx-auto mb-3" />
<p className="text-base font-semibold text-white">Sin piezas con este filtro.</p>
<p className="text-base font-semibold text-gray-900">Sin piezas con este filtro.</p>
</div>
);
}
@@ -548,7 +548,7 @@ function Block({
{label}
</label>
<div
className={`p-3.5 rounded-xl bg-gray-900 border border-gray-800 whitespace-pre-wrap ${className}`}
className={`p-3.5 rounded-xl bg-gray-900 border border-gray-200 whitespace-pre-wrap ${className}`}
>
{children}
</div>
@@ -569,7 +569,7 @@ function PostCard({
}) {
const meta = platformMeta(post.platform);
return (
<div className="bg-[#0D121F] border border-gray-800 hover:border-gray-700 rounded-xl p-4 flex flex-col justify-between">
<div className="bg-white border border-gray-200 hover:border-gray-200 rounded-xl p-4 flex flex-col justify-between">
<div>
<div className="flex items-center justify-between mb-2 gap-2">
<span
@@ -586,10 +586,10 @@ function PostCard({
<p className="text-[11px] text-brand-400 font-mono mb-1">
{post.time ? `${post.time}h` : '—'} · {post.format}
</p>
<h4 className="font-bold text-white text-sm mb-1.5 line-clamp-2">{post.title}</h4>
<h4 className="font-bold text-gray-900 text-sm mb-1.5 line-clamp-2">{post.title}</h4>
<p className="text-xs text-gray-400 line-clamp-2 italic">«{post.hook}»</p>
</div>
<div className="pt-3 mt-3 border-t border-gray-800/80 flex items-center justify-between">
<div className="pt-3 mt-3 border-t border-gray-200 flex items-center justify-between">
<button
type="button"
onClick={onOpen}
@@ -600,7 +600,7 @@ function PostCard({
<button
type="button"
onClick={onCopy}
className="p-1.5 rounded-md hover:bg-gray-800 text-gray-400"
className="p-1.5 rounded-md hover:bg-gray-50 text-gray-400"
title="Copiar"
>
<Copy className="w-3.5 h-3.5" />

View File

@@ -45,7 +45,7 @@ export default async function CalendarioPage({
if (ctx.portfolioWide) {
return (
<p className="text-sm text-gray-500 border border-gray-800 rounded-xl p-6 bg-[#111827]">
<p className="text-sm text-gray-500 border border-gray-200 rounded-xl p-6 bg-white">
Selecciona un cliente en Contexto para ver su hub RRSS (privado).
</p>
);
@@ -107,7 +107,7 @@ export default async function CalendarioPage({
<p className="text-[11px] font-bold tracking-[0.16em] uppercase text-sky-400 mb-2">
RRSS · privado
</p>
<h2 className="text-3xl font-extrabold text-white tracking-tight">
<h2 className="text-3xl font-extrabold text-gray-900 tracking-tight">
{ctx.label}
</h2>
<p className="text-sm text-gray-400 mt-2 max-w-xl">
@@ -118,13 +118,13 @@ export default async function CalendarioPage({
<div className="flex flex-wrap gap-3">
<Link
href="/dashboard/calendario?view=archive"
className="inline-flex items-center justify-center min-h-[52px] px-6 rounded-xl bg-[#16204A] text-white text-sm font-bold hover:bg-[#1a2a63]"
className="inline-flex items-center justify-center min-h-[52px] px-6 rounded-xl bg-[#16204A] text-gray-900 text-sm font-bold hover:bg-[#1a2a63]"
>
<Instagram className="w-4 h-4 mr-2" /> IG / FB
</Link>
<Link
href="/dashboard/calendario?view=month&month=2026-10&tab=linkedin"
className="inline-flex items-center justify-center min-h-[52px] px-6 rounded-xl bg-[#2071CF] text-white text-sm font-bold hover:bg-[#1a5fb8]"
className="inline-flex items-center justify-center min-h-[52px] px-6 rounded-xl bg-[#2071CF] text-gray-900 text-sm font-bold hover:bg-[#1a5fb8]"
>
<Linkedin className="w-4 h-4 mr-2" /> LinkedIn
</Link>

View File

@@ -52,12 +52,12 @@ function ProgressBar({
return (
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-300 font-medium">{label}</span>
<span className="text-white font-semibold tabular-nums">
<span className="text-gray-600 font-medium">{label}</span>
<span className="text-gray-900 font-semibold tabular-nums">
{p}%
</span>
</div>
<div className="h-2 bg-gray-800 rounded-full overflow-hidden">
<div className="h-2 bg-gray-100 rounded-full overflow-hidden">
<div className={`h-full ${bar}`} style={{ width: `${p}%` }} />
</div>
<p className="text-xs text-gray-500 tabular-nums">
@@ -195,7 +195,7 @@ export default async function EstrategiaPage() {
) : (
<div className="grid gap-4 lg:grid-cols-2">
{/* Presupuesto */}
<section className="bg-[#111827] border border-gray-800 rounded-xl p-6 space-y-5">
<section className="bg-white border border-gray-200 rounded-xl p-6 space-y-5">
<div className="flex items-center gap-2 text-amber-300">
<Wallet className="w-4 h-4" />
<h3 className="text-sm font-bold uppercase tracking-wider">
@@ -214,22 +214,22 @@ export default async function EstrategiaPage() {
tone="amber"
/>
) : (
<div className="rounded-lg border border-dashed border-gray-700 px-3 py-4 text-xs text-gray-500">
<div className="rounded-lg border border-dashed border-gray-200 px-3 py-4 text-xs text-gray-500">
Sin <code className="text-gray-400">spend_target</code> para este
mes en <code className="text-gray-400">targets</code>. Gasto
actual: {spendActual.toLocaleString('es-ES')} €.
</div>
)}
<div className="grid grid-cols-2 gap-3 pt-1">
<div className="rounded-lg bg-black/25 border border-gray-800 px-3 py-2">
<div className="rounded-lg bg-gray-50 border border-gray-200 px-3 py-2">
<p className="text-[10px] uppercase text-gray-500">Ppto</p>
<p className="text-lg font-bold text-white tabular-nums">
<p className="text-lg font-bold text-gray-900 tabular-nums">
{spendTarget.toLocaleString('es-ES')} €
</p>
</div>
<div className="rounded-lg bg-black/25 border border-gray-800 px-3 py-2">
<div className="rounded-lg bg-gray-50 border border-gray-200 px-3 py-2">
<p className="text-[10px] uppercase text-gray-500">Gastado</p>
<p className="text-lg font-bold text-white tabular-nums">
<p className="text-lg font-bold text-gray-900 tabular-nums">
{spendActual.toLocaleString('es-ES')} €
</p>
</div>
@@ -237,7 +237,7 @@ export default async function EstrategiaPage() {
</section>
{/* Consecución */}
<section className="bg-[#111827] border border-gray-800 rounded-xl p-6 space-y-5">
<section className="bg-white border border-gray-200 rounded-xl p-6 space-y-5">
<div className="flex items-center gap-2 text-emerald-300">
<Trophy className="w-4 h-4" />
<h3 className="text-sm font-bold uppercase tracking-wider">

View File

@@ -82,11 +82,11 @@ export default async function CanalDashPage({
<div>
<Link
href="/dashboard/kpis"
className="inline-flex items-center gap-1.5 text-xs text-gray-400 hover:text-white mb-3"
className="inline-flex items-center gap-1.5 text-xs text-gray-400 hover:text-gray-900 mb-3"
>
<ArrowLeft className="w-3.5 h-3.5" /> Volver a métricas
</Link>
<h2 className="text-2xl font-bold text-white capitalize">
<h2 className="text-2xl font-bold text-gray-900 capitalize">
Canal · {channel}
</h2>
<p className="text-sm text-gray-400 mt-1">
@@ -103,18 +103,18 @@ export default async function CanalDashPage({
].map((s) => (
<div
key={s.label}
className="rounded-xl border border-gray-800 bg-[#111827] px-4 py-3"
className="rounded-xl border border-gray-200 bg-white px-4 py-3"
>
<p className="text-[10px] uppercase tracking-wider text-gray-500">
{s.label}
</p>
<p className="text-xl font-bold text-white mt-1">{s.value}</p>
<p className="text-xl font-bold text-gray-900 mt-1">{s.value}</p>
</div>
))}
</div>
<div className="rounded-xl border border-gray-800 bg-[#111827] p-5">
<h3 className="text-sm font-semibold text-white mb-3">
<div className="rounded-xl border border-gray-200 bg-white p-5">
<h3 className="text-sm font-semibold text-gray-900 mb-3">
Semanas ({points.length})
</h3>
{points.length === 0 ? (
@@ -124,10 +124,10 @@ export default async function CanalDashPage({
{points.map((p) => (
<li
key={p.week}
className="flex flex-wrap items-center justify-between gap-2 text-xs border border-gray-800 rounded-lg px-3 py-2"
className="flex flex-wrap items-center justify-between gap-2 text-xs border border-gray-200 rounded-lg px-3 py-2"
>
<span className="font-mono text-brand-400">{p.week}</span>
<span className="text-gray-300">
<span className="text-gray-600">
{formatEur(p.spend)} · {formatNum(p.leads)} leads · CPL{' '}
{p.cpl ? `${formatNum(p.cpl)} €` : '—'}
</span>

View File

@@ -3,39 +3,34 @@
@tailwind utilities;
:root {
--background: #0b0f19;
--foreground: #f3f4f6;
--card: #111827;
--sidebar: #0d121f;
--border: rgba(255, 255, 255, 0.06);
--background: #ffffff;
--foreground: #111827;
--card: #ffffff;
--sidebar: #ffffff;
--border: #e5e7eb;
--brand: #e58a3c;
--muted: #9ca3af;
--muted: #6b7280;
}
html {
color-scheme: dark;
color-scheme: light;
}
body {
background-color: var(--background);
color: var(--foreground);
font-family: var(--font-sans), ui-sans-serif, system-ui, sans-serif;
background-image:
radial-gradient(ellipse 80% 50% at 10% -10%, rgba(229, 138, 60, 0.12), transparent 55%),
radial-gradient(ellipse 60% 40% at 100% 0%, rgba(56, 189, 248, 0.06), transparent 50%);
background-attachment: fixed;
min-height: 100vh;
}
/* Scrollbars sobrios */
* {
scrollbar-width: thin;
scrollbar-color: #1f2937 transparent;
scrollbar-color: #d1d5db transparent;
}
::selection {
background: rgba(229, 138, 60, 0.35);
color: #fff;
background: rgba(229, 138, 60, 0.25);
color: #111827;
}
@layer utilities {

View File

@@ -27,8 +27,8 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<html lang="es" className={`dark ${sans.variable}`}>
<body className={`${sans.className} bg-background text-gray-100 min-h-screen antialiased`}>
<html lang="es" className={sans.variable}>
<body className={`${sans.className} bg-background text-gray-900 min-h-screen antialiased`}>
{children}
</body>
</html>

View File

@@ -1,6 +1,7 @@
'use client';
import React, { useState, Suspense } from 'react';
import Image from 'next/image';
import { useRouter, useSearchParams } from 'next/navigation';
import { createClient } from '@/lib/supabase/client';
import { Mail, Lock, Eye, EyeOff, ShieldCheck, ArrowRight, Loader2 } from 'lucide-react';
@@ -39,20 +40,24 @@ function LoginForm() {
router.refresh();
}
} catch (err: any) {
setErrorMsg(err?.message || 'Error de conexión con el servidor de autenticación.');
setErrorMsg(
err?.message || 'Error de conexión con el servidor de autenticación.'
);
setLoading(false);
}
};
return (
<div className="bg-[#0D121F] border border-gray-800/80 rounded-2xl p-8 shadow-2xl shadow-black/50 backdrop-blur-sm">
<div className="bg-white border border-gray-200 rounded-2xl p-8 shadow-lg shadow-gray-200/80">
<div className="mb-6">
<h2 className="text-lg font-semibold text-white">Iniciar Sesión</h2>
<p className="text-xs text-gray-400 mt-1">Introduce tus credenciales corporativas para acceder a tu portal.</p>
<h2 className="text-lg font-semibold text-gray-900">Iniciar Sesión</h2>
<p className="text-xs text-gray-500 mt-1">
Introduce tus credenciales corporativas para acceder a tu portal.
</p>
</div>
{errorMsg && (
<div className="mb-6 p-3.5 rounded-xl bg-red-500/10 border border-red-500/30 text-red-400 text-xs flex items-center gap-2">
<div className="mb-6 p-3.5 rounded-xl bg-red-50 border border-red-200 text-red-600 text-xs flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-red-500 shrink-0" />
<span>{errorMsg}</span>
</div>
@@ -60,7 +65,7 @@ function LoginForm() {
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-xs font-medium text-gray-300 mb-1.5">
<label className="block text-xs font-medium text-gray-700 mb-1.5">
Correo Electrónico
</label>
<div className="relative">
@@ -73,13 +78,13 @@ function LoginForm() {
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="ejemplo@cliente.com"
className="w-full pl-10 pr-4 py-2.5 bg-gray-900/80 border border-gray-800 rounded-xl text-sm text-white placeholder-gray-500 focus:outline-none focus:border-brand-500 focus:ring-1 focus:ring-brand-500 transition-all"
className="w-full pl-10 pr-4 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-sm text-gray-900 placeholder-gray-400 focus:outline-none focus:border-brand-500 focus:ring-1 focus:ring-brand-500 transition-all"
/>
</div>
</div>
<div>
<label className="block text-xs font-medium text-gray-300 mb-1.5">
<label className="block text-xs font-medium text-gray-700 mb-1.5">
Contraseña
</label>
<div className="relative">
@@ -92,14 +97,18 @@ function LoginForm() {
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••••••"
className="w-full pl-10 pr-10 py-2.5 bg-gray-900/80 border border-gray-800 rounded-xl text-sm text-white placeholder-gray-500 focus:outline-none focus:border-brand-500 focus:ring-1 focus:ring-brand-500 transition-all"
className="w-full pl-10 pr-10 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-sm text-gray-900 placeholder-gray-400 focus:outline-none focus:border-brand-500 focus:ring-1 focus:ring-brand-500 transition-all"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute inset-y-0 right-0 pr-3.5 flex items-center text-gray-400 hover:text-gray-200 transition-colors"
className="absolute inset-y-0 right-0 pr-3.5 flex items-center text-gray-400 hover:text-gray-700 transition-colors"
>
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
{showPassword ? (
<EyeOff className="w-4 h-4" />
) : (
<Eye className="w-4 h-4" />
)}
</button>
</div>
</div>
@@ -128,33 +137,40 @@ function LoginForm() {
export default function LoginPage() {
return (
<div className="min-h-screen bg-[#0B0F19] text-gray-100 flex flex-col justify-center items-center px-4 relative overflow-hidden">
{/* Background Glow */}
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[350px] bg-gradient-to-tr from-brand-500/10 via-amber-500/5 to-purple-600/10 blur-[120px] rounded-full pointer-events-none" />
<div className="min-h-screen bg-white text-gray-900 flex flex-col justify-center items-center px-4 relative overflow-hidden">
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[350px] bg-gradient-to-tr from-brand-500/10 via-amber-500/5 to-orange-400/10 blur-[120px] rounded-full pointer-events-none" />
<div className="w-full max-w-md relative z-10">
{/* Header Branding */}
<div className="text-center mb-8">
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-gradient-to-br from-brand-500 to-amber-600 text-white font-bold text-xl shadow-xl shadow-brand-500/20 mb-4 border border-brand-400/30">
4V
</div>
<h1 className="text-2xl font-bold tracking-tight text-white">4VENTURES</h1>
<p className="text-sm text-gray-400 mt-1 font-medium">Centro de Mando del Cliente</p>
<Image
src="/logo-4ventures.png"
alt="4Ventures"
width={72}
height={72}
className="mx-auto rounded-2xl shadow-lg shadow-brand-500/15 mb-4"
priority
/>
<h1 className="text-2xl font-bold tracking-tight text-gray-900">
4VENTURES
</h1>
<p className="text-sm text-gray-500 mt-1 font-medium">
Centro de Mando del Cliente
</p>
</div>
{/* Login Box wrapped in Suspense for useSearchParams */}
<Suspense fallback={
<div className="bg-[#0D121F] border border-gray-800/80 rounded-2xl p-8 flex justify-center items-center h-64">
<Loader2 className="w-6 h-6 animate-spin text-brand-500" />
</div>
}>
<Suspense
fallback={
<div className="bg-white border border-gray-200 rounded-2xl p-8 flex justify-center items-center h-64">
<Loader2 className="w-6 h-6 animate-spin text-brand-500" />
</div>
}
>
<LoginForm />
</Suspense>
{/* Security Footer */}
<div className="mt-8 flex items-center justify-center gap-4 text-[11px] text-gray-500">
<div className="flex items-center gap-1.5">
<ShieldCheck className="w-3.5 h-3.5 text-emerald-400" />
<ShieldCheck className="w-3.5 h-3.5 text-emerald-500" />
<span>Aislamiento RLS multi-tenant</span>
</div>
<span>·</span>