Sistema de temas
CÉNIT usa CSS custom properties definidas en app/globals.css
para todo el sistema visual. El modo oscuro es el default (no
requiere atributo en <html>). El modo claro se activa con
data-theme="light" desde ThemeToggle + theme-provider.tsx.
Cada organización puede sobreescribir colores primarios (--azul,
--rojo) sin tocar código. Regla absoluta: nunca hardcodear
colores en componentes — siempre via custom property.
Fondos y superficies
Sección titulada «Fondos y superficies»--bg /* fondo principal de la app */--sidebar /* fondo del sidebar */--g1, --g2, --g3 /* fondos de cards / glass / panels */Bordes y texto
Sección titulada «Bordes y texto»--border, --border2--text, --text2, --text3 /* principal / secundario / terciario */Marca (sobreescribibles por org)
Sección titulada «Marca (sobreescribibles por org)»--azul, --azul2 /* azul Nacional (#003082 default) */--rojo /* rojo Nacional (#C8102E default) */Estados semánticos
Sección titulada «Estados semánticos»--ok, --ok-bg, --ok-t, --ok-b /* verde */--warn, --warn-bg, --warn-t, --warn-b /* amarillo */--danger, --danger-bg, --danger-t, --danger-b /* rojo */Cada estado tiene cuatro variantes: el color base, su fondo (-bg),
el color de texto sobre el fondo (-t) y el color de borde (-b).
Modo claro / oscuro
Sección titulada «Modo claro / oscuro»app/globals.css:
- Bloque
:root(sin selector adicional) define las custom properties del modo oscuro. - Bloque
[data-theme="light"]redefine las mismas properties con los valores claros. ThemeProvider(components/theme-provider.tsx) lee la preferencia del usuario (cookie +prefers-color-scheme) y setea el atributo en<html>antes del hydrate para evitar flash.ThemeTogglepermite cambiar manualmente.
Branding por organización
Sección titulada «Branding por organización»Cada org tiene en organizations:
primary_color→ mapea a--azul.secondary_color→ mapea a--rojo.logo_url→ consumido por componentes de header / footer / emails.
El Shell del dashboard inyecta un <style dangerouslySetInnerHTML>
con las overrides de la org actual:
<style dangerouslySetInnerHTML={{ __html: `:root { --azul: ${org.primary_color}; --rojo: ${org.secondary_color}; }`}} />Esto significa que un componente que usa var(--azul) adopta
automáticamente el color del club sin re-render condicional.
Validación: primary_color y secondary_color se sanitizan en el
form de settings (regex hex #RRGGBB). No se permite javascript: ni
expresiones CSS complejas.
Reglas de uso
Sección titulada «Reglas de uso»<div className="bg-[var(--g1)] border border-[var(--border)] text-[var(--text)]" /><div className="bg-[#0F1E35] border-[#1A2A42] text-white" />La segunda forma rompe modo claro y branding por org. Hay ESLint rule pendiente para enforzar — por ahora es disciplina de review.
Tailwind v4
Sección titulada «Tailwind v4»Tailwind v4 con @theme inline permite mapear las CSS custom
properties a tokens de Tailwind:
@theme inline { --color-bg: var(--bg); --color-azul: var(--azul);}Eso habilita bg-bg o text-azul como utilities, sin perder el
sistema de temas runtime.
Integraciones
Sección titulada «Integraciones»- Multi-tenant — el branding por
org se persiste en
organizations.primary_color/secondary_color. - Plans y billing —
custom_brandingestá enproyenterprise. Hoy sin gate: esencial también puede usarlo. Deuda técnica conocida. - PWA —
app/manifest.tsusa el color#070D1Ccomo fondo de splash en modo oscuro.
Limitaciones / roadmap
Sección titulada «Limitaciones / roadmap»- Falta gate de
custom_branding—season_comparisonsycustom_brandingson las dos features sin guard que esencial puede usar gratis. ~2-3 h. - Sin theme builder UI — el admin de la org elige solo dos colores. Para clubes con paletas complejas se requiere intervención manual.
- Modo claro menos pulido — el producto se diseñó dark-first. Algunos componentes legacy pueden tener contraste subóptimo en light. Auditar caso por caso al recibir feedback.
- ESLint rule para enforzar no-hardcoded colors — pendiente.