Sistema de Fichaje para Hostelería: API Propia, App Móvil y 4 Idiomas

Resumen
Control Horario Onya nace de una necesidad real vivida trabajando en un hotel en Suiza. El objetivo fue sustituir las hojas de papel por un sistema fiable: una API propia en FastAPI que guarda cada fichaje como un intervalo, una app móvil para fichar durante el turno y una web para que administración revise registros, apruebe correcciones y exporte informes, sin perder la trazabilidad.
El Problema
En un hotel de Suiza el control horario seguía dependiendo de hojas en papel. Eso servía para salir del paso, pero fallaba en lo importante: descansos poco claros, turnos partidos difíciles de revisar, olvidos corregidos a mano y poca visibilidad sobre las horas reales, las extras o el déficit. El problema no era crear un formulario digital, sino convertir una rutina diaria en un sistema fiable para empleados y administración.
La Solución
Diseñé un sistema completo de control horario para hostelería: un backend propio en FastAPI con SQLAlchemy, Alembic y JWT, y dos clientes que consumen la misma API: una app móvil en React Native y una SPA web. Cubre el ciclo completo —fichaje de entrada, salida y descansos, calendario, dashboard de horas, solicitudes de corrección con aprobación y panel de administración por rol— y centraliza el cálculo horario para que trabajador y administración vean siempre los mismos números.
Herramientas
Qué utilicé y por qué
Elegí herramientas que me permitieran construir un backend propio sólido y consumirlo desde dos clientes (móvil y web) con una interfaz usable por perfiles no técnicos.
FastAPI y SQLAlchemy
Backend propio que concentra la lógica de negocio, la autenticación y la persistencia. Cada fichaje se guarda como un intervalo (trabajo o descanso) con inicio y fin.
Alembic
Migraciones versionadas para evolucionar el esquema (añadir columnas o tablas) sin perder datos entre versiones.
React Native
App móvil con React Navigation y AsyncStorage para fichar rápido durante el turno y consultar horas. Sesión persistida en el dispositivo y consumo de la API con axios.
SPA web
Frontend web ligero para administración: panel de trabajadores, panel de admin y módulos comunes de autenticación, interfaz e internacionalización.
JWT y bcrypt
Login con token JWT (python-jose), contraseñas hasheadas con bcrypt (Passlib) y una dependencia que valida el rol en cada ruta protegida.
Internacionalización
Interfaz en español, inglés, alemán y húngaro, pensada para equipos de hotel con trabajadores de distintos países.
Funcionalidades
Qué incluye el producto
El objetivo no era hacer una demo de fichaje, sino cubrir el ciclo completo que aparece en un hotel: registrar, revisar, corregir, calcular y administrar.
Acceso por credenciales y rol
Login con usuario y contraseña, token JWT y rol (trabajador o admin) que decide qué rutas de la API y qué pantallas ve cada perfil.
Fichaje guiado
La API expone /work/in, /work/out, /break/in y /break/out. Al fichar la salida, busca el último intervalo abierto y le pone fin: es imposible tener dos jornadas abiertas a la vez.
Descansos y turnos partidos
Cada intervalo se modela por separado (trabajo o descanso), de modo que un turno partido o varios descansos se calculan sin ambigüedad, no con una resta simple.
Dashboard y resumen de horas
El endpoint /history/summary agrega las horas por día para alimentar el calendario y las tablas. El trabajador ve sus horas reales, sus descansos y sus extras.
Solicitudes de corrección trazables
Si alguien olvida fichar, no se edita el historial a escondidas: crea una solicitud con el horario propuesto. Al aprobarla, se reemplazan los intervalos del día y queda registrada en el log de auditoría.
Administración y exportación
El admin gestiona usuarios (CRUD), aprueba o rechaza correcciones y descarga informes en CSV generados con pandas.
Proceso
Cómo lo construí
El proyecto salió de observar una fricción real y convertirla en producto. Primero entendí el flujo manual, después lo traduje a una API, unas pantallas y unas reglas de negocio.
Observé el proceso en papel
Partí del uso real: trabajadores apuntando horas, responsables revisando hojas y dudas frecuentes sobre descansos, turnos partidos u horas extra.
Definí usuarios y permisos
Separé trabajador y administrador para que cada perfil viera solo lo que necesita: fichar y consultar, o revisar correcciones y gestionar usuarios.
Diseñé el modelo de datos
Llevé el problema a tablas relacionales con SQLAlchemy: users, time_logs, correction_requests y audit_logs, con estados, relaciones e índices.
Construí la API en FastAPI
Endpoints de fichaje, resumen y correcciones para el trabajador; CRUD de usuarios, aprobación de correcciones y export para el admin; todo protegido por JWT.
Centralicé el cálculo horario
Modelé cada fichaje como un intervalo. Así el dashboard, el calendario y el panel de admin parten de la misma fuente y nunca dan resultados distintos.
Conecté dos clientes
Una app móvil en React Native para fichar y una SPA web para administrar, ambas consumiendo la misma API con la sesión guardada en el dispositivo o el navegador.
Dudas y decisiones
Problemas que tuve que resolver
Las partes difíciles no estuvieron solo en la interfaz, sino en convertir reglas humanas y excepciones del día a día en una lógica consistente.
No todos los días son iguales
Había que soportar turno simple, turno partido, descansos explícitos y jornadas que todavía no están cerradas (sin end_at).
Fechas y zonas horarias
Agrupar por día usando la fecha local fue clave. Un fichaje guardado en UTC no siempre debe agruparse por el día UTC, especialmente en una app usada en distintos husos.
Correcciones sin perder trazabilidad
La duda era si permitir editar los fichajes directamente. Opté por solicitudes con estado y aprobación, que guardan el antes y el después y dejan rastro en la auditoría.
Simplicidad para el empleado
La app tenía que funcionar para personas que no quieren aprender un sistema nuevo. Por eso el fichaje muestra pocas acciones, pero bien elegidas según el último registro del día.
Una sola fuente de verdad, dos clientes
Móvil y web no podían divergir. Toda la lógica vive en la API; los clientes solo presentan y envían, lo que evita cálculos duplicados que se contradigan.
Equipo internacional
El hotel podía tener trabajadores de varios países, así que añadí cuatro idiomas (español, inglés, alemán y húngaro) desde el principio.
Arquitectura
Cómo está construido
La arquitectura separa la API (lógica y datos) de los dos clientes que la consumen, para que cada parte evolucione sin mezclar responsabilidades.
app/ contiene el backend FastAPI: main.py (rutas), models.py (SQLAlchemy), schemas.py (Pydantic), auth.py (JWT y roles), database.py y config.py.
La autenticación usa JWT (python-jose) y bcrypt (Passlib); una dependencia valida el token y el rol en cada petición protegida.
PostgreSQL (o SQLite en local) guarda usuarios, fichajes como intervalos, solicitudes de corrección y auditoría; Alembic gestiona las migraciones.
El cliente móvil en React Native (React Navigation + AsyncStorage) consume la API con axios y conserva la sesión en el dispositivo.
La SPA web sirve la administración: panel de trabajadores, panel de admin y módulos comunes de autenticación, interfaz e i18n.
El cálculo horario vive en el backend, de modo que móvil y web muestran siempre los mismos números.
Modelo de datos
Cuatro tablas para un ciclo completo
El modelo es deliberadamente pequeño, pero cubre fichaje, corrección y auditoría sin ambigüedades.
usersEmpleados con sus credenciales, rol (trabajador o admin) e idioma preferido de la interfaz.
time_logsCada fila es un intervalo de tiempo (trabajo o descanso) con inicio y fin. Es la base de todo el cálculo de horas.
correction_requestsSolicitud de cambio de horario que guarda el antes y el después en JSON, con su estado y la razón del administrador.
audit_logsRegistro de las acciones administrativas (aprobaciones, cambios) para que nada quede sin rastro.
Contrato de API
La API que comparten móvil y web
Toda la lógica vive en la API; los dos clientes solo presentan datos y envían acciones. Estos son los endpoints clave.
/work/inInicia la jornada: crea un intervalo de trabajo si no hay ninguno abierto.
auth: JWT (trabajador)
/work/outCierra la jornada: pone end_at al último intervalo abierto.
auth: JWT (trabajador)
/history/summaryResumen de horas por día para el calendario y las tablas.
auth: JWT
/requestsCrea una solicitud de corrección con el horario propuesto (before/after).
auth: JWT (trabajador)
/export/csvDescarga el informe en CSV generado con pandas.
auth: JWT (admin)
Stack Tecnológico
Tecnologías utilizadas
¿Tienes un proyecto similar?
Puedo ayudarte a diseñar, construir y desplegar productos digitales claros, seguros y preparados para operar en producción.