← Back
NovaHair
web

NovaHair

Feb 12, 2025 → Sep 30, 2025

Plataforma de gestión de peluquerías multi-inquilino

NovaHair es una solución monorepo lista para producción y de nivel empresarial para la gestión de salones de belleza. Combina un panel de administración Progressive Web App, un widget de reservas integrable y landings personalizables en un ecosistema cohesivo y tipado.

Construido con React 19, TanStack Router, TanStack Query y siguiendo los principios de Clean Architecture, este proyecto demuestra una gestión avanzada de monorepos, optimización del rendimiento y arquitectura frontend escalable.

Panel de administración con métricas y análisis
Panel de administración con métricas e indicadores en tiempo real.

Descripción del proyecto

NovaHair es una plataforma SaaS multi-inquilino diseñada para optimizar las operaciones de los salones. El sistema consta de tres aplicaciones principales:

    1. Panel de administración - PWA completa para la gestión del salón
    1. Sistema de reservas - Widget integrable para citas de clientes
    1. Landings - Plantillas personalizables para los sitios web del salón

Todas las aplicaciones comparten un sistema de diseño común y lógica de negocio a través de un monorepo de pnpm workspace, asegurando consistencia y reduciendo la duplicación de código en un ~40%.


Logros destacados

  • Tiempos de carga inferiores a un segundo - FCP < 800ms mediante code splitting y SSR
  • 40% menos código - Los paquetes compartidos eliminan la duplicación entre apps
  • Sincronización en tiempo real - Las actualizaciones optimistas con TanStack Query reducen la latencia percibida un 60%
  • Soporte i18n - Localización completa inglés/español con detección automática
  • Capacidades PWA - Panel offline-first con caché de service worker
  • UI accesible - Cumple WCAG 2.1 AA con primitivos de Radix UI
  • Type-safe - TypeScript end-to-end con cobertura del 100% en paquetes compartidos

Arquitectura técnica

Estructura del monorepo

novahair/
├── apps/
│   ├── admin/          # Panel PWA de administración (Puerto 3000)
│   ├── booking/        # Widget de reservas (Puerto 3001)
│   └── landings/       # Plantillas de landing (Puerto 3002)
└── packages/
    ├── ui/             # Librería de componentes compartidos (50+ componentes)
    ├── client/         # Cliente API tipado con React Query
    ├── utils/          # Utilidades y hooks compartidos
    └── book-app/       # Lógica de reservas reutilizable

¿Por qué esta arquitectura?

  • Dependencias centralizadas: Fuente única de verdad para las versiones
  • Hot reload instantáneo: Los cambios en paquetes compartidos se reflejan de inmediato
  • Type safety: TypeScript funciona sin problemas entre los límites de los paquetes
  • Compilaciones optimizadas: El almacenamiento content-addressable de pnpm reduce el tiempo de instalación 2x

Demo en vivo

🔗 novahair.polgubau.com

Interfaz del flujo de reservas
Flujo de reservas en varios pasos con disponibilidad en tiempo real.

Funcionalidades principales

Panel de administración (PWA)

  • Dashboard en tiempo real - Métricas de ingresos, citas y rendimiento del equipo
  • Gestión de equipo - Planificación de turnos con detección de conflictos
  • Sistema de citas - Calendario drag-and-drop con reservas recurrentes
  • Catálogo de servicios - Gestión dinámica de precios y duración
  • Soporte offline - Caché de service worker con sincronización en segundo plano
  • Analíticas - Rangos de fechas personalizados con visualización de tendencias

Widget de reservas

  • Integrable - Funciona como iframe o componente React
  • Flujo multi-paso - Servicio → Personal → Hora → Confirmación
  • Responsivo - Diseño mobile-first con gestos táctiles
  • Multi-inquilino - Un solo despliegue sirve a varios salones
  • Sesiones anónimas - Almacenamiento local para reservas de invitados
  • UI optimista - Respuesta instantánea con rollback en caso de error

Landings

  • Personalizables - Sistema de plantillas para consistencia de marca
  • Rendimiento - Animaciones GSAP con garantía de 60fps
  • Efectos parallax - Scroll suave con Lenis
  • SEO optimizado - SSR con meta tags y sitemap
  • Deep linking - Precarga de reserva desde parámetros de URL

Inmersión técnica
Decisiones de arquitectura y optimizaciones de rendimiento

Stack tecnológico

CapaTecnologíaPropósito
FrameworkReact 19Últimas funcionalidades (use, optimizaciones del compilador)
RoutingTanStack RouterRutas tipadas con soporte SSR
EstadoTanStack Query v5Estado del servidor con actualizaciones optimistas
BuildVite 7HMR ultrarrápido (<50ms)
Monorepopnpm workspacesGestión eficiente de dependencias
EstilosTailwind CSS 4Utilidades con compilación JIT
Primitivos UIRadix UIComponentes accesibles sin estilos
AnimacionesGSAP + Framer MotionAnimaciones de alto rendimiento
i18ni18nextCambio de idioma en tiempo de ejecución
TestingVitest + Testing LibraryTests unitarios e integración rápidos
LintingBiome100x más rápido que ESLint
Type SafetyTypeScript 5.7Modo estricto sin implicit any
SSRNitroRenderizado universal del servidor

Optimizaciones de rendimiento

1. Estrategia de Code Splitting

// Splitting por ruta con TanStack Router
defaultPreload: "intent" // Precarga al hacer hover/focus
defaultViewTransition: true // Transiciones de vista nativas

Resultado: Bundle inicial reducido de 450KB a 180KB (-60%)

2. Estrategia de caché PWA

// Configuración del service worker
runtimeCaching: [
  {
    urlPattern: /^https:\/\/api\.gerardmartinez\.es\/api\/.*/i,
    handler: "NetworkFirst",
    options: {
      cacheName: "api-cache",
      expiration: { maxAgeSeconds: 86400 }, // 24h
      networkTimeoutSeconds: 10
    }
  }
]

Resultado: 90% de tasa de aciertos en caché para llamadas API, funcionalidad offline

3. Optimizaciones de React Query

// Actualizaciones optimistas para respuesta inmediata en la UI
const { create } = useAppointmentActions(tenantId);

create.mutate(data, {
  onMutate: async (newAppointment) => {
    await queryClient.cancelQueries(['appointments']);
    const previous = queryClient.getQueryData(['appointments']);
    queryClient.setQueryData(['appointments'], old => [...old, newAppointment]);
    return { previous };
  },
  onError: (err, variables, context) => {
    queryClient.setQueryData(['appointments'], context.previous);
  }
});

Resultado: Latencia percibida reducida un 60%

4. Optimización de imágenes

  • Formato WebP con alternativas
  • Lazy loading con Intersection Observer
  • Imágenes responsivas con srcset

Resultado: LCP mejorado de 2,1s a 0,8s


Implementación de Clean Architecture

Cada funcionalidad sigue una arquitectura por capas:

features/dashboard/
├── domain/          # Lógica de negocio (funciones puras)
│   └── metrics.ts
├── infra/           # Dependencias externas
│   ├── metrics-calculator.ts
│   └── date-range-calculator.ts
├── hooks/           # Integración con React
│   └── use-dashboard-metrics.ts
└── ui/              # Componentes de presentación
    ├── dashboard-view.tsx
    └── metric-card.tsx

Beneficios:

  • ✅ Lógica de negocio testeable (100% de cobertura en la capa de dominio)
  • ✅ Fácil intercambio de implementaciones (p.ej., cliente API)
  • ✅ Separación clara de responsabilidades
  • ✅ Reutilizable entre aplicaciones

Cliente API tipado

// Cliente unificado con inferencia de tipos completa
import { client } from '@novahair/client';

const { useAppointments, useAppointmentActions } = client.appointments;

// Completamente tipado, autocompletado en todas partes
const { appointments, isLoading } = useAppointments(tenantId);
const { create, update, delete: deleteAppointment } = useAppointmentActions(tenantId);

// TypeScript conoce la forma exacta de appointment
create.mutate({
  serviceId: string,
  staffId: string,
  customer: { name, email, phone },
  startsAt: Date,
  notes?: string
});

Características:

  • Sin coste en tiempo de ejecución (tipos eliminados en el build)
  • Re-fetch automático en mutaciones
  • Actualizaciones optimistas con rollback
  • Soporte de sesión anónima para reservas de invitados

Beneficios del monorepo

Paquete UI compartido (@novahair/ui)

  • 50+ componentes (Button, Input, Dialog, Table, etc.)
  • Design tokens consistentes
  • Primitivos de Radix UI para accesibilidad
  • Exports tree-shakeable

Paquete Client compartido (@novahair/client)

  • Cliente API tipado
  • Integración con React Query
  • Caché automática e invalidación
  • Cobertura de tests al 100%

Paquete Utils compartido (@novahair/utils)

  • Hooks personalizados (useMobile, useDebounce, etc.)
  • Configuración de i18n
  • Utilidades comunes (cn, formatters, validators)

Impacto:

  • Reducción del 40% en duplicación de código
  • Fuente única de verdad para la lógica de negocio
  • Propagación instantánea de cambios entre apps
  • Estrategia de testing unificada

Internacionalización

// Detección automática de idioma con persistencia en cookie
i18n.use(LanguageDetector).init({
  supportedLngs: ['en', 'es'],
  fallbackLng: 'es',
  detection: {
    order: ['cookie'],
    lookupCookie: 'novahair_i18n',
    caches: ['cookie'],
    cookieMinutes: 525600 // 1 año
  }
});

// Traducciones tipadas
import { t } from 'i18next';
<h1>{t('welcome_message')}</h1> // TypeScript conoce todas las claves

Características:

  • Cambio de idioma en tiempo de ejecución (sin recarga)
  • Fusión de namespaces (comunes + específicos de app)
  • Pluralización e interpolación
  • CLI para extraer claves faltantes

Estrategia de testing

# Tests unitarios para lógica de negocio
pnpm test                    # Ejecutar todos los tests
pnpm coverage                # Generar informe de cobertura

# Tests de integración para funcionalidades
pnpm test --watch            # Modo watch para TDD

Cobertura:

  • Capa de dominio: 100%
  • Capa de infraestructura: 85%
  • Componentes UI: 70%
  • Total: 82%

Herramientas:

  • Vitest (20x más rápido que Jest)
  • Testing Library (tests centrados en el usuario)
  • jsdom (simulación DOM ligera)

Build y despliegue

# Desarrollo
pnpm dev                     # Todas las apps en paralelo
pnpm dev:admin               # App individual

# Producción
pnpm build                   # Builds optimizados
# Salida: dist/ (listo para SSR con Nitro)

Métricas de build:

  • Admin: 220KB comprimido (bundle inicial)
  • Booking: 180KB comprimido
  • Landing: 150KB comprimido
  • Tiempo de build: ~12s (en frío), ~2s (con caché)

Despliegue:

  • Vercel (recomendado) - Sin configuración
  • Servidor Nitro para SSR
  • Despliegues de preview automáticos
  • Edge functions para rutas API

Configuración local

# Clonar repositorio
git clone https://github.com/polgubau/novahair
cd novahair

# Instalar dependencias (se requiere pnpm)
pnpm install

# Iniciar todas las apps
pnpm dev

# O iniciar apps individuales
pnpm dev:admin    # http://localhost:3000
pnpm dev:booking  # http://localhost:3001
pnpm dev:landing  # http://localhost:3002

Requisitos:

  • Node.js ≥ 20.0.0
  • pnpm ≥ 9.0.0

Dashboard de métricas con tendencias de ingresos y citas
Landing page con animaciones parallax de GSAP.

Aprendizajes clave

1. Gestión del monorepo

Gestionar un monorepo con múltiples apps me enseñó la importancia de:

  • Límites de dependencias: Prevenir dependencias circulares entre paquetes
  • Orquestación del build: Asegurar que los paquetes se compilan en el orden correcto
  • Alineación de versiones: Mantener las dependencias compartidas sincronizadas

2. Rendimiento a escala

Optimizar el rendimiento en tres aplicaciones requirió:

  • Code splitting estratégico: Splitting por ruta + por componente
  • Estrategias de caché: Caché multicapa (navegador, service worker, React Query)
  • Análisis de bundle: Auditorías regulares para prevenir el aumento de tamaño

3. Type safety

Lograr type safety de extremo a extremo implicó:

  • Tipos compartidos: Fuente única de verdad para los modelos de dominio
  • Utilidades genéricas: Hooks y funciones reutilizables y tipadas
  • TypeScript estricto: Sin escotillas de escape, cobertura de tipos completa

4. Experiencia del desarrollador

Crear una gran DX requirió:

  • Ciclos de retroalimentación rápidos: HMR en <50ms, tests en <2s
  • Documentación clara: Archivos README para cada paquete
  • Herramientas automatizadas: Biome para linting/formato instantáneo

Desafíos que encontré en este proyecto

NovaHair no fue fácil de implementar; encontré múltiples pasos que requirieron muchas horas de solución, como:

  • Arquitectar sistemas complejos - Un monorepo multi-app con paquetes compartidos fue difícil de configurar, pero la recompensa es increíble
  • Optimizar el rendimiento - Tiempos de carga inferiores a un segundo con caché estratégica y tecnologías frontend modernas
  • Escribir código mantenible - Clean Architecture con 82% de cobertura de tests para garantizar una buena funcionalidad
  • Elegir las herramientas correctas - Stack moderno con tecnologías probadas como React, TypeScript y las soluciones de TanStack
  • Documentar en profundidad - README claros y documentación en línea, ya que el trabajo se dividió entre varios colaboradores
  • Entregar código en producción - Desplegado y funcionando en producción

Licencia

Este proyecto es un producto comercial y no es de código abierto. Todos los derechos reservados por el autor.


Contacto

Construido por:

  • Diseño y desarrollo frontend: Pol Gubau Amores - polgubau.com
  • Base de datos y desarrollo backend: Gerard Martínez Alcocer - gerardmartinez.es

¿Interesado en hablar sobre este proyecto o posibles oportunidades? ¡No dudes en ponerte en contacto!

Enlaces

Proyectos similares

NovaHair

© 2026 Pol Gubau Amores