Alinear panel y auth con la identidad B2B "Architectural Warmth"

Sustituye la paleta negra/azul B2C del panel del reformista por el verde
de marca, neutros cálidos y titulares en Instrument Serif de la landing B2B.
Añade tokens --color-primary-*, --color-stone-50 y --font-display al @theme.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Carlos Narro
2026-06-01 20:01:57 +02:00
parent 15f2d67970
commit bf9e72064b
15 changed files with 54 additions and 45 deletions

View File

@@ -44,6 +44,15 @@
/* Fonts */
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-display: 'Instrument Serif', Georgia, 'Times New Roman', serif;
/* Paleta de marca B2B "Architectural Warmth" — panel y área autenticada */
--color-primary-50: #f4f8f5;
--color-primary-100: #e8f0eb;
--color-primary-500: #4d8a6d;
--color-primary-700: #2f5c46;
--color-primary-900: #1f3a2e;
--color-stone-50: #f7f8f7;
/* Transitions */
--transition-fast: 150ms ease;

View File

@@ -16,7 +16,7 @@ export default function LoginPage() {
>
<form action={formAction} className="flex flex-col gap-5">
<div>
<h1 className="text-2xl font-black tracking-tight text-black">Entra en tu panel</h1>
<h1 className="font-display text-3xl tracking-tight text-black">Entra en tu panel</h1>
<p className="mt-1 text-sm text-gray-500">Gestiona tus leads y tu funnel.</p>
</div>
@@ -27,7 +27,7 @@ export default function LoginPage() {
type="email"
required
autoComplete="email"
className="rounded-lg border border-gray-300 px-3 py-2.5 focus:border-gray-900 focus:outline-none focus:ring-1 focus:ring-gray-900"
className="rounded-lg border border-gray-300 px-3 py-2.5 focus:border-primary-700 focus:outline-none focus:ring-1 focus:ring-primary-700"
/>
</label>
<label className="flex flex-col gap-1 text-sm">
@@ -37,19 +37,19 @@ export default function LoginPage() {
type="password"
required
autoComplete="current-password"
className="rounded-lg border border-gray-300 px-3 py-2.5 focus:border-gray-900 focus:outline-none focus:ring-1 focus:ring-gray-900"
className="rounded-lg border border-gray-300 px-3 py-2.5 focus:border-primary-700 focus:outline-none focus:ring-1 focus:ring-primary-700"
/>
</label>
{error && <p className="text-sm text-red-600">{error}</p>}
<button type="submit" disabled={pending} className="btn btn-primary w-full disabled:opacity-60">
<button type="submit" disabled={pending} className="btn bg-primary-700 text-white hover:bg-primary-900 w-full disabled:opacity-60">
{pending ? 'Entrando…' : 'Entrar'}
</button>
<p className="text-center text-sm text-gray-500">
¿No tienes cuenta?{' '}
<Link href="/signup" className="font-semibold text-black hover:underline">
<Link href="/signup" className="font-semibold text-primary-700 hover:underline">
Empieza gratis
</Link>
</p>

View File

@@ -47,7 +47,7 @@ export default async function LeadDetailPage({ params }: { params: Promise<{ id:
return (
<div className="flex flex-col gap-6">
<Link href="/panel" className="text-sm text-gray-500 hover:text-black w-fit">
<Link href="/panel" className="text-sm text-gray-500 hover:text-primary-700 w-fit">
Volver a leads
</Link>
@@ -55,7 +55,7 @@ export default async function LeadDetailPage({ params }: { params: Promise<{ id:
<div className="bg-white border border-gray-200 rounded-xl p-5 flex flex-col gap-4">
<div className="flex flex-wrap items-start justify-between gap-4">
<div className="flex flex-col gap-1">
<h1 className="text-2xl font-black tracking-tight text-black">{lead.nombre}</h1>
<h1 className="font-display text-3xl tracking-tight text-black">{lead.nombre}</h1>
<p className="text-sm text-gray-500">
{lead.tipoReforma ? TIPO_LABEL[lead.tipoReforma] : 'Reforma'} ·{' '}
{lead.provincia ?? '—'} · entró {formatFecha(lead.createdAt)}
@@ -80,7 +80,7 @@ export default async function LeadDetailPage({ params }: { params: Promise<{ id:
<p className="text-sm text-gray-500">
Solicitada el {formatFecha(lead.testimonioSolicitadoAt)}. Comparte este enlace con el
cliente para que deje su opinión. Cuando la envíe, la verás en{' '}
<Link href="/panel/opiniones" className="text-black underline underline-offset-2">
<Link href="/panel/opiniones" className="text-primary-700 underline underline-offset-2">
Opiniones
</Link>{' '}
para aprobarla.
@@ -96,7 +96,7 @@ export default async function LeadDetailPage({ params }: { params: Promise<{ id:
<form action={solicitarOpinion.bind(null, lead.id)}>
<button
type="submit"
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-black text-white text-sm font-semibold w-fit hover:bg-gray-800"
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-primary-700 text-white text-sm font-semibold w-fit hover:bg-primary-900"
>
Solicitar opinión al cliente
</button>
@@ -254,7 +254,7 @@ export default async function LeadDetailPage({ params }: { params: Promise<{ id:
<div className="flex flex-wrap items-center gap-3">
<a
href={`/panel/${lead.id}/presupuesto?download=1`}
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-black text-white text-sm font-semibold w-fit hover:bg-gray-800"
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-primary-700 text-white text-sm font-semibold w-fit hover:bg-primary-900"
>
Descargar PDF
</a>
@@ -262,7 +262,7 @@ export default async function LeadDetailPage({ params }: { params: Promise<{ id:
href={`/panel/${lead.id}/presupuesto`}
target="_blank"
rel="noopener"
className="text-sm font-medium text-gray-500 hover:text-black"
className="text-sm font-medium text-gray-500 hover:text-primary-700"
>
Ver en el navegador
</a>
@@ -320,7 +320,7 @@ export default async function LeadDetailPage({ params }: { params: Promise<{ id:
<form action={recalcularPresupuesto.bind(null, lead.id)}>
<button
type="submit"
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-black text-white text-sm font-semibold w-fit hover:bg-gray-800"
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-primary-700 text-white text-sm font-semibold w-fit hover:bg-primary-900"
>
Recalcular desde el catálogo
</button>

View File

@@ -18,7 +18,7 @@ export default async function EmpresaPage() {
return (
<div className="space-y-10 max-w-2xl">
<div>
<h1 className="text-2xl font-black tracking-tight text-black">Datos de empresa</h1>
<h1 className="font-display text-3xl tracking-tight text-black">Datos de empresa</h1>
<p className="text-sm text-gray-500 mt-1">
Estos datos y el logo aparecen en la cabecera de los presupuestos en PDF que recibe el
cliente. Manténlos al día.
@@ -62,7 +62,7 @@ export default async function EmpresaPage() {
href={funnelUrl}
target="_blank"
rel="noopener noreferrer"
className="text-black underline underline-offset-2 break-all"
className="text-primary-700 underline underline-offset-2 break-all"
>
{funnelUrl}
</a>
@@ -159,7 +159,7 @@ export default async function EmpresaPage() {
type="checkbox"
name="aboutEnabled"
defaultChecked={perfil.aboutEnabled}
className="w-4 h-4 accent-black"
className="w-4 h-4 accent-[#2f5c46]"
/>
<span className="text-gray-700">Mostrar el bloque Quiénes somos en mi funnel</span>
</label>
@@ -185,7 +185,7 @@ export default async function EmpresaPage() {
/>
</label>
<button className="md:col-span-2 justify-self-start inline-flex items-center justify-center rounded-lg bg-black px-4 py-2 text-sm font-semibold text-white transition hover:bg-gray-900">
<button className="md:col-span-2 justify-self-start inline-flex items-center justify-center rounded-lg bg-primary-700 px-4 py-2 text-sm font-semibold text-white transition hover:bg-primary-900">
Guardar datos
</button>
</form>

View File

@@ -11,7 +11,7 @@ export default async function GaleriaPage() {
return (
<div className="space-y-8 max-w-3xl">
<div>
<h1 className="text-2xl font-black tracking-tight text-black">Galería de trabajos</h1>
<h1 className="font-display text-3xl tracking-tight text-black">Galería de trabajos</h1>
<p className="text-sm text-gray-500 mt-1">
Sube fotos de reformas que ya has hecho. Aparecen en tu funnel para dar confianza al
cliente antes de pedir presupuesto.

View File

@@ -27,14 +27,14 @@ export default async function PanelLayout({ children }: { children: React.ReactN
const nombreEmpresa = tenant?.nombreEmpresa ?? 'Reformix';
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-stone-50">
<header className="sticky top-0 z-20 bg-white border-b border-gray-200">
<div className="relative max-w-6xl mx-auto px-6 h-16 flex items-center justify-between">
<Link href="/panel" className="flex items-center gap-2 min-w-0">
<span className="inline-flex shrink-0 items-center justify-center w-8 h-8 rounded-lg bg-black text-white font-black italic text-lg leading-none">
<span className="inline-flex shrink-0 items-center justify-center w-8 h-8 rounded-lg bg-primary-700 text-white font-black italic text-lg leading-none">
R
</span>
<span className="font-black tracking-tight text-black">Reformix</span>
<span className="font-bold tracking-tight text-black">Reformix</span>
<span className="hidden sm:inline text-gray-300">/</span>
<span className="hidden sm:inline text-sm font-medium text-gray-600 truncate">
{nombreEmpresa}

View File

@@ -33,7 +33,7 @@ export default async function OpinionesPage() {
return (
<div className="space-y-8 max-w-3xl">
<div>
<h1 className="text-2xl font-black tracking-tight text-black">Opiniones</h1>
<h1 className="font-display text-3xl tracking-tight text-black">Opiniones</h1>
<p className="text-sm text-gray-500 mt-1">
Las opiniones que te dejan tus clientes. Aprueba las que quieras mostrar en tu funnel; solo
las publicadas aparecen en tu página.

View File

@@ -50,7 +50,7 @@ export default async function PanelPage({
return (
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-1">
<h1 className="text-2xl font-black tracking-tight text-black">Leads</h1>
<h1 className="font-display text-3xl tracking-tight text-black">Leads</h1>
<p className="text-sm text-gray-500">
{resumen.total} leads en total · {resumen.porEstado['nuevo'] ?? 0} sin contactar
</p>
@@ -79,7 +79,7 @@ export default async function PanelPage({
href={f.value === 'todos' ? '/panel' : `/panel?estado=${f.value}`}
className={`px-3 py-1.5 rounded-full text-sm font-medium border transition-colors ${
active
? 'bg-black text-white border-black'
? 'bg-primary-700 text-white border-primary-700'
: 'bg-white text-gray-600 border-gray-200 hover:border-gray-400'
}`}
>

View File

@@ -28,7 +28,7 @@ export default async function PreciosPage() {
return (
<div className="space-y-10">
<div>
<h1 className="text-2xl font-black tracking-tight text-black">Tabla de precios</h1>
<h1 className="font-display text-3xl tracking-tight text-black">Tabla de precios</h1>
<p className="text-sm text-gray-500 mt-1">
Define los precios unitarios y la mano de obra. El motor calcula el presupuesto a
partir de estos valores y las medidas del lead.
@@ -73,7 +73,7 @@ export default async function PreciosPage() {
</span>
</span>
</label>
<button className="self-start bg-black text-white rounded-lg px-4 py-2 text-sm font-medium">
<button className="self-start bg-primary-700 text-white rounded-lg px-4 py-2 text-sm font-medium">
Guardar preferencia
</button>
</form>
@@ -112,7 +112,7 @@ export default async function PreciosPage() {
/>
</label>
))}
<button className="col-span-2 md:col-span-5 justify-self-start bg-black text-white rounded-lg px-4 py-2 text-sm font-medium">
<button className="col-span-2 md:col-span-5 justify-self-start bg-primary-700 text-white rounded-lg px-4 py-2 text-sm font-medium">
Guardar configuración
</button>
</form>
@@ -212,7 +212,7 @@ export default async function PreciosPage() {
<label className="col-span-2 flex items-center gap-2 text-gray-500 sm:col-auto">
<input type="checkbox" name="esDefault" /> Marcar como default
</label>
<button className="col-span-2 bg-black text-white rounded-lg px-3 py-2 font-medium sm:col-auto sm:py-1.5">
<button className="col-span-2 bg-primary-700 text-white rounded-lg px-3 py-2 font-medium sm:col-auto sm:py-1.5">
Añadir
</button>
</form>
@@ -234,7 +234,7 @@ export default async function PreciosPage() {
placeholder="categoria,nombre,calidad,precio,unidad,descriptor_render,sku"
className="w-full border border-gray-300 rounded-lg px-3 py-2 font-mono text-xs"
/>
<button className="mt-2 bg-black text-white rounded-lg px-4 py-2 text-sm font-medium">
<button className="mt-2 bg-primary-700 text-white rounded-lg px-4 py-2 text-sm font-medium">
Importar
</button>
</form>

View File

@@ -6,7 +6,7 @@ import { signup } from './actions';
import AuthShell from '@/components/auth/AuthShell';
const inputClass =
'rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:border-gray-900 focus:outline-none focus:ring-1 focus:ring-gray-900';
'rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:border-primary-700 focus:outline-none focus:ring-1 focus:ring-primary-700';
export default function SignupPage() {
const [error, formAction, pending] = useActionState(signup, null);
@@ -19,7 +19,7 @@ export default function SignupPage() {
>
<form action={formAction} className="flex flex-col gap-4">
<div>
<h1 className="text-2xl font-black tracking-tight text-black">Empieza gratis 14 días</h1>
<h1 className="font-display text-3xl tracking-tight text-black">Empieza gratis 14 días</h1>
<p className="mt-1 text-sm text-gray-500">
Sin tarjeta. Configura tu catálogo y recibe leads.
</p>
@@ -51,19 +51,19 @@ export default function SignupPage() {
className={inputClass}
/>
<label className="flex items-center gap-2 text-xs text-gray-500">
<input name="optInMarketing" type="checkbox" className="h-4 w-4 accent-black" /> Quiero
<input name="optInMarketing" type="checkbox" className="h-4 w-4 accent-[#2f5c46]" /> Quiero
recibir novedades de Reformix
</label>
{error && <p className="text-sm text-red-600">{error}</p>}
<button type="submit" disabled={pending} className="btn btn-primary w-full disabled:opacity-60">
<button type="submit" disabled={pending} className="btn bg-primary-700 text-white hover:bg-primary-900 w-full disabled:opacity-60">
{pending ? 'Creando cuenta…' : 'Crear cuenta'}
</button>
<p className="text-center text-sm text-gray-500">
¿Ya tienes cuenta?{' '}
<Link href="/login" className="font-semibold text-black hover:underline">
<Link href="/login" className="font-semibold text-primary-700 hover:underline">
Entrar
</Link>
</p>

View File

@@ -116,13 +116,13 @@ export default function AppNav({ links }: { links: readonly AppNavLink[] }) {
<Link
key={l.href}
href={l.href}
className={active === l.href ? 'text-black font-semibold' : 'text-gray-500 hover:text-black'}
className={active === l.href ? 'text-primary-700 font-semibold' : 'text-gray-500 hover:text-primary-700'}
>
{l.label}
</Link>
))}
<form action="/logout" method="post">
<button type="submit" className="text-gray-500 hover:text-black">
<button type="submit" className="text-gray-500 hover:text-primary-700">
Salir
</button>
</form>
@@ -147,11 +147,11 @@ export default function AppNav({ links }: { links: readonly AppNavLink[] }) {
aria-current={isActive ? 'page' : undefined}
className={
'relative flex flex-col items-center justify-center gap-1 pt-2.5 pb-2 text-[11px] font-medium transition-colors ' +
(isActive ? 'text-black' : 'text-gray-400 hover:text-gray-600')
(isActive ? 'text-primary-700' : 'text-gray-400 hover:text-gray-600')
}
>
{isActive && (
<span className="absolute top-0 h-0.5 w-8 rounded-full bg-black" aria-hidden="true" />
<span className="absolute top-0 h-0.5 w-8 rounded-full bg-primary-700" aria-hidden="true" />
)}
<Icon name={l.icon} />
<span className="leading-none">{l.label}</span>

View File

@@ -18,7 +18,7 @@ export default function AuthShell({
{/* Panel del formulario */}
<div className="flex flex-col px-6 py-8 sm:px-10 lg:px-16">
<Link href="/" className="inline-flex items-center gap-2 self-start">
<span className="inline-flex h-9 w-9 items-center justify-center rounded-lg bg-black text-lg font-black italic leading-none text-white">
<span className="inline-flex h-9 w-9 items-center justify-center rounded-lg bg-primary-700 text-lg font-black italic leading-none text-white">
R
</span>
<span className="text-lg font-extrabold tracking-tight text-black">Reformix</span>

View File

@@ -37,7 +37,7 @@ export default function GaleriaUploader({
name="foto"
accept="image/png,image/jpeg,image/webp"
disabled={lleno || pending}
className="text-sm file:mr-2 file:rounded-lg file:border-0 file:bg-black file:text-white file:px-3 file:py-1.5 file:text-sm file:font-medium disabled:opacity-50"
className="text-sm file:mr-2 file:rounded-lg file:border-0 file:bg-primary-700 file:text-white file:px-3 file:py-1.5 file:text-sm file:font-medium disabled:opacity-50"
/>
<input
type="text"
@@ -51,7 +51,7 @@ export default function GaleriaUploader({
<button
type="submit"
disabled={lleno || pending}
className="inline-flex items-center justify-center rounded-lg bg-black px-4 py-2 text-sm font-semibold text-white transition hover:bg-gray-900 disabled:opacity-50"
className="inline-flex items-center justify-center rounded-lg bg-primary-700 px-4 py-2 text-sm font-semibold text-white transition hover:bg-primary-900 disabled:opacity-50"
>
{pending ? 'Subiendo…' : 'Añadir foto'}
</button>

View File

@@ -117,7 +117,7 @@ function ToggleVista({ vista, onChange }: { vista: Vista; onChange: (v: Vista) =
aria-pressed={activo}
className={
'inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-semibold transition-colors ' +
(activo ? 'bg-black text-white' : 'text-gray-500 hover:text-black')
(activo ? 'bg-primary-700 text-white' : 'text-gray-500 hover:text-primary-700')
}
>
<svg
@@ -148,7 +148,7 @@ function Tarjeta({ l }: { l: PanelLead }) {
className="group flex flex-col gap-3 rounded-xl border border-gray-200 bg-white p-4 transition hover:border-gray-400 hover:shadow-sm"
>
<div className="flex items-start gap-3">
<span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-gray-900 text-sm font-bold text-white">
<span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-primary-700 text-sm font-bold text-white">
{iniciales(l.nombre)}
</span>
<div className="min-w-0 flex-1">

View File

@@ -40,7 +40,7 @@ export default function ThemePicker({
className={
'cursor-pointer rounded-xl border p-3 transition ' +
(activo
? 'border-gray-900 ring-1 ring-gray-900'
? 'border-primary-700 ring-1 ring-primary-700'
: 'border-gray-200 hover:border-gray-400')
}
>
@@ -83,7 +83,7 @@ export default function ThemePicker({
name="usarColor"
checked={usarColor}
onChange={(e) => setUsarColor(e.target.checked)}
className="h-4 w-4 accent-black"
className="h-4 w-4 accent-[#2f5c46]"
/>
<span className="text-sm font-medium text-gray-700">
Usar un color de marca personalizado
@@ -140,7 +140,7 @@ export default function ThemePicker({
<button
type="submit"
disabled={pending}
className="inline-flex items-center justify-center rounded-lg bg-black px-4 py-2 text-sm font-semibold text-white transition hover:bg-gray-900 disabled:opacity-50"
className="inline-flex items-center justify-center rounded-lg bg-primary-700 px-4 py-2 text-sm font-semibold text-white transition hover:bg-primary-900 disabled:opacity-50"
>
{pending ? 'Guardando…' : 'Guardar tema'}
</button>