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.

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:
-
- Panel de administración - PWA completa para la gestión del salón
-
- Sistema de reservas - Widget integrable para citas de clientes
-
- 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

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
| Capa | Tecnología | Propósito |
|---|---|---|
| Framework | React 19 | Últimas funcionalidades (use, optimizaciones del compilador) |
| Routing | TanStack Router | Rutas tipadas con soporte SSR |
| Estado | TanStack Query v5 | Estado del servidor con actualizaciones optimistas |
| Build | Vite 7 | HMR ultrarrápido (<50ms) |
| Monorepo | pnpm workspaces | Gestión eficiente de dependencias |
| Estilos | Tailwind CSS 4 | Utilidades con compilación JIT |
| Primitivos UI | Radix UI | Componentes accesibles sin estilos |
| Animaciones | GSAP + Framer Motion | Animaciones de alto rendimiento |
| i18n | i18next | Cambio de idioma en tiempo de ejecución |
| Testing | Vitest + Testing Library | Tests unitarios e integración rápidos |
| Linting | Biome | 100x más rápido que ESLint |
| Type Safety | TypeScript 5.7 | Modo estricto sin implicit any |
| SSR | Nitro | Renderizado 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 nativasResultado: 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.tsxBeneficios:
- ✅ 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 clavesCaracterí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 TDDCobertura:
- 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:3002Requisitos:
- Node.js ≥ 20.0.0
- pnpm ≥ 9.0.0

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