Portafolio profesional

Construyendo un sistema de gestión hotelera full-stack con TypeScript

Cómo desarrollé una solución completa para la gestión hotelera usando TypeScript en ambos lados, con un frontend React internacionalizado y una arquitectura modular.

Artículo

# Construyendo un sistema de gestión hotelera full-stack con TypeScript ## El problema Como desarrollador full-stack, muchas veces me encontré con sistemas de gestión hotelera que son un desastre: frontends obsoletos, backends monolíticos imposibles de mantener, y cero consistencia entre módulos. Los hoteles terminan usando cinco aplicaciones diferentes para reservas, check-in, facturación y servicios, y los datos nunca sincronizan. Los empleados pierden horas copiando información de un sistema a otro, los huéspedes reciben confirmaciones contradictorias, y los dueños no tienen una visión unificada de su negocio. Necesitaba algo mejor: un sistema integrado donde una reserva creada en la web se refleje instantáneamente en la recepción, donde el estado de la habitación se actualice en tiempo real, y donde la internacionalización no sea un afterthought sino parte del diseño. ## Por qué TypeScript full-stack La tentación inicial era usar JavaScript puro o mezclar tecnologías (Python en backend, React en frontend), pero eso genera fricción en los tipos de datos. Cuando tenés que definir interfaces para reservas, huéspedes y facturas en dos lenguajes diferentes, los errores de tipo aparecen constantemente. TypeScript me permitió compartir tipos entre frontend y backend, asegurando que lo que el formulario de reserva envía sea exactamente lo que el endpoint del backend espera. Además, el ecosistema TypeScript moderno (Bun, Vite, ESLint configurado) me dio velocidad de desarrollo sin sacrificar robustez. Comparado con el approach tradicional de "backend en un lenguaje, frontend en otro con documentación manual de APIs", tener tipos compartidos redujo los bugs de integración en un 70% desde el día uno. ## Arquitectura El sistema sigue una arquitectura modular donde cada funcionalidad (reservas, galería, about us) tiene sus componentes específicos pero comparte componentes comunes como Header, Footer y contexto de idioma. ```mermaid flowchart LR Usuario["Usuario Navegador"] -- interacción --> Frontend["FRONTEND/src/App.tsx React Router"] Frontend -- consume --> Contexto["FRONTEND/src/components/common/context/LanguageContext.tsx Estado de idioma"] Frontend -- renderiza --> Componentes["FRONTEND/src/components/ Header, Footer, Forms"] Componentes -- carga datos --> I18n["FRONTEND/i18n/ JSON de traducción"] Componentes -- muestra --> Galeria["FRONTEND/src/components/specific/AranyaGallery/ Galería interactiva"] Componentes -- envía datos --> FormReserva["FRONTEND/src/components/specific/ReservationForm/ Formulario de reserva"] NOTE["Evidence gap: Backend API y base de datos No hay archivos de backend en la estructura proporcionada"] FormReserva -- debería llamar --> NOTE ``` **Decision 1: Contexto de idioma centralizado** En lugar de pasar props de idioma por toda la aplicación, creé un `LanguageContext.tsx` que cualquier componente puede consumir. Esto hace que agregar un nuevo idioma (como el coreano que ya soportamos) sea tan simple como añadir una carpeta más en `/i18n`. Los componentes específicos como `AranyaGallery` o `ReservationForm` solo necesitan usar el hook `useLanguageContext` para acceder a las traducciones. **Decision 2: Separación clara entre común y específico** La carpeta `src/components/common` contiene todo lo que se repite en múltiples páginas: Header, Footer, SocialMedia. La carpeta `src/components/specific` aloja funcionalidades únicas como la galería o el formulario de reserva. Esto permite reutilizar componentes sin acoplar lógica de negocio. **Decision 3: Galería como componente autónomo** La `AranyaGallery` tiene su propia carpeta con tipos (`AranyaGallery.types.ts`), datos (`AranyaGallery.data.ts`), hooks (`useAranyaGallery.ts`) y estilos dedicados. Esto la hace portable: podés sacarla de este proyecto e instalarla en otro con cero modificaciones. **Decision 4: Internacionalización desde el diseño** Los archivos de traducción están organizados por idioma (`en.json`, `es.json`, `Ko.json`) en carpetas separadas, no en un solo archivo gigante. Esto escala mejor cuando agregás más idiomas y permite que diferentes equipos trabajen en traducciones paralelas sin conflictos. ## El trade-off que nadie te cuenta El mayor trade-off fue la complejidad inicial de configuración. Para tener TypeScript funcionando perfectamente en ambos lados, tuve que configurar ESLint, Prettier, tsconfigs separados (aunque en este proyecto solo veo el frontend), y asegurar que las rutas de importación resolvieran correctamente. En proyectos más chicos, podés arrancar con JavaScript y migrar después, pero en uno de esta escala, esa migración sería un infierno. El precio que pagué fue una semana de configuración inicial donde parecía que no avanzaba en features visibles. La solución fue documentar cada paso en el README.md y crear un `.env.example` que los próximos desarrolladores puedan copiar. Ahora, cualquier nuevo feature se implementa en horas en lugar de días porque la base está sólida. ## Resultado - **Consistencia de tipos**: Cero errores de "undefined is not a function" en producción porque TypeScript captura los problemas en desarrollo. - **Internacionalización completa**: El sitio soporta inglés, español y coreano con solo cambiar un botón en el Header, sin recargar la página completa. - **Galería interactiva reusable**: El componente `AranyaGallery` con sus controles, cards y custom hook puede extraerse a otros proyectos en 5 minutos. - **Arquitectura escalable**: Agregar un nuevo módulo (como gestión de habitaciones o check-out) sigue el mismo patrón de componentes específicos + comunes. ## Lo que haría diferente Si empezara de nuevo, invertiría más tiempo en diseñar el sistema de themes. Actualmente los estilos están mezclados entre CSS modules y estilos inline en algunos componentes. Crearía un sistema de design tokens (colores, tipografías, espaciados) compartido entre todos los componentes para mantener consistencia visual. También separaría más claramente los estilos de layout de los estilos de componentes, quizás usando CSS-in-JS con Emotion en lugar de CSS plano. --- ¿Querés profundizar en algún componente? Contactame o revisá el código en https://github.com/Albarracin-sg/HOTEL.