Control de acceso · 21 mayo 2026 · branch: feature/core-design-system-refactor · ✓ completo incl. cleanup 7.7

Roles, módulos y permisos.

El modelo de acceso de Enterprise es uniforme: un rol por usuario (a nivel de empresa), módulos activados por hotel como billing gate. El acceso efectivo es siempre: rol.permisos ∩ hotel.módulos_activos.

✓ Completo — todo incluido

Tipos de usuario

El sistema reconoce dos tipos con mecánicas de acceso completamente distintas.

Super Admin

Operador de plataforma — equipo Happnest

  • No pertenece a ninguna empresa
  • Acceso total — cortocircuita la fórmula de permisos
  • Crea y gestiona empresas (/super-admin/companies)
  • Activa/desactiva módulos por hotel
  • Identificado por is_super_admin = true en DB

Company User

Empleado del cliente — hoteles, cadenas...

  • Pertenece a exactamente 1 empresa
  • Tiene 1 rol global (mismo en todos sus hoteles)
  • Se asigna individualmente a los hoteles de la empresa
  • Su acceso efectivo varía hotel a hotel según módulos activos
  • No puede pertenecer a múltiples empresas

Cómo se calcula el acceso

Cada vez que un usuario intenta acceder a un recurso en un hotel, el backend ejecuta resolvePermissions(userId, hotelId). El resultado sigue siempre esta fórmula:

Rol del usuario
role.permissions
Company-wide. El mismo rol aplica en todos los hoteles asignados.
Módulos activos del hotel
hotel.active_modules
JSON boolean por hotel. Solo el super admin puede cambiarlos.
=
Acceso efectivo
resolvedPermissions
Lo que el usuario puede hacer aquí y ahora. Calculado en GET /me/permissions?hotel_id=.
Super Admin cortocircuita la fórmula. Siempre recibe ALL_PERMISSIONS_TRUE sin consultar roles ni módulos activos.
🔑 Capabilities (manage_users, manage_roles, manage_hotel_assignments) no están sujetas al gate de módulos. Si el rol las tiene, funcionan en cualquier hotel asignado.

Catálogo de módulos

Hay 5 módulos en la plataforma. La activación de módulos es estrictamente por hotel y no existen validaciones globales. Cada hotel tiene un JSON assigned_modules (o active_modules) que funciona como el billing gate definitivo. Aunque un rol tenga permisos totales (ej. Superadmin), si el módulo no está en este array para el hotel actual, quedará completamente oculto y desactivado.

🏨
Experiences
experiences
Gestión de experiencias y servicios que ofrece el hotel a sus huéspedes.
● Activo por defecto
Webcheckin
chekinonline
Proceso de check-in online para huéspedes antes de la llegada al hotel.
○ Desactivado
📋
Tareas
tasks
Sistema de gestión de tareas operativas y de mantenimiento del hotel.
○ Desactivado
📧
Email
emailNotifications
Envío de notificaciones automáticas por email a huéspedes y staff.
○ Desactivado
🔔
Push
pushNotifications
Notificaciones push a la aplicación móvil del staff.
○ Desactivado

Roles predefinidos

Cada empresa arranca con 5 roles. Son editables excepto Owner (system role). Un usuario tiene exactamente 1 rol, aplicado en todos sus hoteles asignados. Haz clic en un rol para ver sus permisos en detalle.

Permiso Owner Manager Receptionist Operations Viewer
Módulo · Experiences
Experiences read read
Módulo · Webcheckin
Webcheckin read read
Módulo · Tareas  (solo si tasks activo en el hotel)
Crear tarea
Editar tarea
Eliminar tarea
Asignar tarea
Cambiar estado
Capabilities  (sin gate de módulos — aplican en cualquier hotel asignado)
Gestionar usuarios
Gestionar roles
Asignar hoteles
Acceso total read Solo lectura Sin acceso Capability (sin gate)

CRUD de entidades principales

Operaciones disponibles sobre Hoteles, Usuarios y Experiencias: punto de entrada en UI, método de servicio, campos y notas de comportamiento.

🏨 Hoteles

Servicio: HotelsApiService · Backend: GET|POST|PATCH|DELETE /hotels

Operación Entrada UI Método Campos principales Notas
LIST /hotels getHotels(filters?) search status parentId Muestra árbol parent/child. Filtros: activo · inactivo · todos.
DETAIL /hotels/[id] getHotelById(id) Tarjeta con nombre, estado (activo/inactivo), ciudad, país, branding, estadísticas (equipo asignado, hoteles hijos, módulos activos), historial, EditHotelModal. Sync via syncHotelData().
CREATE CreateHotelModal (botón + en lista) createHotel(data) name* description address city country phone email website profileImageUrl coverImageUrl isActive Emite evento hotel-created para sincronizar contexto admin. Solo Owner puede crear.
UPDATE EditHotelModal (ícono editar en lista / detalle) updateHotel(id, data) name description address city country phone email website profileImageUrl coverImageUrl Todos los campos son opcionales (PATCH parcial). Mismo modal que Create.
TOGGLE STATUS Botón en /hotels/[id] updateHotelStatus(id, isActive) isActive Activa o desactiva el hotel. Equivale a updateHotel({ isActive }). Afecta la visibilidad en el selector de hotel.
DELETE Menú ⋯ en lista de hoteles deleteHotel(id) Diálogo de confirmación requerido. Elimina el hotel y sus asignaciones.
ASSIGN USER /hotels/[id]/team · AddUserToHotelModal POST /hotels/:id/users userId* role Asigna un usuario de la empresa al hotel con un rol de tarea específico.
REMOVE USER /hotels/[id]/team · botón quitar (fila) DELETE /hotels/:id/users/:userId Elimina la asignación individual. El usuario sigue existiendo en la empresa. Confirma con window.confirm antes de ejecutar.
BULK REMOVE /hotels/[id]/team · barra de selección DELETE /hotels/:id/users/:userId × N Selecciona N usuarios con checkboxes, llama Promise.allSettled con un DELETE por usuario en paralelo. Limpia selección y recarga el equipo al terminar.
LEGAL TEXTS /hotels/legal-texts legal-texts API hotelId locale content Textos legales multi-idioma por hotel (privacidad, términos, cookies).
👥 Usuarios

Servicio: enterpriseUsersService · Backend: GET|POST|PATCH|DELETE /users

Operación Entrada UI Método Campos principales Notas
LIST /users getUsers() vía useUsersApi search role status hotelId Métricas en cabecera (total, activos, inactivos). Filtros combinables.
CREATE CreateUserModal (botón + en /users) createUser(data) email* password* firstName lastName phone role* hotelIds Soporta asignación a múltiples hoteles en el mismo flujo. Requiere manage_users capability.
UPDATE UserDetailDrawer — tab General updateUser(id, data) displayName firstName lastName phone profileImageUrl PATCH parcial. Solo campos enviados se actualizan.
ACTIVATE / DEACTIVATE UserDetailDrawer — tab General (toggle) updateUser(id, { isActive }) isActive El usuario inactivo no puede hacer login. La cuenta sigue existiendo.
HOTEL ASSIGNMENTS UserDetailDrawer — tab Hoteles POST /hotels/:id/users
DELETE /hotels/:id/users/:uid
hotelIds role Diff inteligente: solo crea/elimina asignaciones que cambiaron. Rol por hotel configurable.
VIEW PERMISSIONS UserDetailDrawer — tab Permisos GET /me/permissions?hotel_id= hotelId Read-only. Muestra permisos efectivos por hotel (rol ∩ módulos activos).
CHANGE PASSWORD UserDetailDrawer — tab Seguridad POST /users/:id/password newPassword* sendEmail UI presente, implementación backend pendiente de completar.
IMPORT (BULK) ImportUsersModal — botón Importar en /users createUser() iterado (secuencial) email* password* nombre* apellido rol* hoteles Lectura desde .xlsx con xlsx. La plantilla incluye 3 hojas (Usuarios, Roles, Reglas). Cabeceras admiten sufijo * y aliases en ES (nombre/firstName, rol/tipo, etc.). Validación fila a fila: email con formato válido, password ≥ 8, rol resuelto contra catálogo backend (getAvailableRoles()) con aliases, dedup de emails dentro del archivo. Roles disponibles se filtran por el rol del usuario actual (no-admin no puede asignar admin). Hoteles separados por coma, resueltos por nombre exacto contra useHotelsApi; nombres no reconocidos se descartan silenciosamente. Tabla previa con estado por fila (válida / error / importado) antes de enviar.
DELETE Menú ⋯ en tabla de usuarios deleteUser(id) Diálogo de confirmación. Elimina usuario y todas sus asignaciones de hotel.
🎟️ Experiencias

Servicio: enterpriseExperiencesService (wrapper de ExperiencesApiService en @pamaconu/core) · Backend: GET|POST|PATCH|DELETE /experiences

Operación Entrada UI Método Campos principales Notas
LIST /experiences getExperiences(filters?, pagination?) status category hotelId searchQuery Listado paginado con filtros combinables. Categorías derivadas de getExperienceCategories().
DETAIL /experiences/[id] getExperienceById(id) Vista completa con metadata, items, hoteles, métodos de pago y configuración de grupo.
CREATE /experiences/create · ExperienceEditTabs (5 tabs) · TemplateSelector createExperience(data) title.es* place description status provider paymentMethods items[] images[] hotelIds / hotelNames startDate / endDate duration / capacity Multilenguaje (es/en/ca) en title, place, description, included, excluded, programmingOptions. Auto-save por tab vía ExperienceEditTabs.
UPDATE /experiences/[id]/edit updateExperience(id, data) Mismo conjunto que CREATE (PATCH parcial) Reutiliza ExperienceEditTabs. ExperienceUpdateInput = Partial<ExperienceCreateInput>.
CATEGORIES Filtros de lista / TemplateSelector getExperienceCategories() Devuelve catálogo de categorías existentes en la base de datos.
IMPORT (BULK) ImportExperiencesModal — botón Importar en /experiences createExperience() iterado (secuencial) title_es* title_en / title_ca place_es / place_en / place_ca description_es / _en / _ca included_es / _en / _ca excluded_es / _en / _ca programming_options_es / _en / _ca status provider payment_methods group_enabled / group_type / group_min / group_max / group_fixed_count / group_description category / subcategory price_amount / price_currency start_date / end_date duration_minutes / capacity hotel_names Lectura desde .xlsx con xlsx. La plantilla incluye 6 hojas: Experiencias, Reglas, Diccionario columnas, Guía de estados, Hoteles disponibles, Instrucciones IA. Único campo obligatorio: title_es. Cabeceras con * se normalizan. Status acepta canónicos (draft|active|inactive|archived|expired|scheduled) + aliases ES (borrador→draft, activa/publicada→active, caducada→expired, etc.). Status no reconocido marca error y cae a draft. provider ∈ {pamaconu, hotel} (default pamaconu). payment_methods ∈ {room, counter} con aliases ES (habitación→room, recepción→counter). Fechas en YYYY-MM-DD; números deben ser ≥ 0. hotel_names separados por coma, resueltos contra useHotelsApi por nombre exacto; nombres no reconocidos generan error de fila en tiempo de importación (no en preview). Si se informa price_amount, se construye un único items[] con título ES y currency por defecto EUR. Si group_enabled=true, se adjunta groupReservation al payload (campo extendido fuera del tipo canónico). Validador de cabeceras: si falta title_es retorna fila-error con mensaje explicativo.
DELETE Menú ⋯ en tabla de experiencias deleteExperience(id) Diálogo de confirmación. Elimina la experiencia (no aplica soft-delete a fecha actual).

Relación Usuarios ↔ Hoteles

Un usuario puede estar asignado a múltiples hoteles y un hotel puede tener múltiples usuarios. La tabla de unión user_hotels (many-to-many) controla la asignación, el rol dentro del hotel y el estado activo/inactivo del vínculo.

👤 users
  • PK id UUID
  • email
  • user_type
  • is_super_admin
  • display_name
  • is_active
1 → N
user_hotels
  • PK id UUID
  • FK user_id → users
  • FK hotel_id → hotels
  • UQ (user_id, hotel_id)
  • role VARCHAR(50) default 'staff'
  • is_active BOOLEAN
  • created_at / updated_at
N → 1
🏨 hotels
  • PK id UUID
  • name
  • city / country
  • parent_id grupos
  • is_active

Restricciones de integridad

UNIQUE
Un usuario solo puede estar asignado una vez a cada hotel. Intentar duplicar el vínculo genera un error 409 del backend.
CASCADE DELETE
Si se elimina un user o un hotel, todas las filas relacionadas en user_hotels se borran automáticamente (ON DELETE CASCADE).
Rol por defecto
Al asignar sin rol explícito el sistema usa 'staff'. Los roles válidos se recuperan con getAvailableRoles().

Flujo de asignación (diff-based)

UserAssignmentsApiService.setUserAssignment() recibe la lista actual y la nueva lista de hoteles, calcula el diff y aplica solo los cambios mínimos.

Paso 1
Recibe previousHotelIds (estado actual) y assignment.hotelIds (estado deseado).
Paso 2 — Diff
Calcula los hoteles a añadir (hotelsToAdd) y los que hay que quitar (hotelsToRemove).
Paso 3 — Assign
Por cada hotel a añadir llama al endpoint de asignación con el rol 'staff' por defecto.
POST /hotels/:id/users
Paso 4 — Remove
Por cada hotel a quitar llama al endpoint de baja del usuario en ese hotel.
DELETE /hotels/:id/users/:userId

Endpoints de asignación

POST /hotels/:id/users DELETE /hotels/:id/users/:userId GET /hotels/me/assigned GET /hotels/:id/users

Interfaz AssignedHotel

Devuelta por getUserAssignedHotels(). Combina datos del hotel con el contexto de la asignación del usuario.

Campo Tipo Descripción
id string UUID del hotel.
name string Nombre del hotel.
address · city · country string? Datos de ubicación del hotel.
status string Estado del hotel: active | inactive.
user_role_in_hotel string? Rol del usuario en este hotel específico (de user_hotels.role).
assignment_is_active boolean? Si el vínculo user↔hotel está activo (user_hotels.is_active).
assigned_modules string[]? Módulos disponibles para el usuario en este hotel.

Puntos de entrada en la UI

UserDetailDrawer
Pestaña Hoteles: muestra los hoteles asignados al usuario. Permite añadir/quitar con checkbox.
/users/[id] → drawer → tab Hotels
Hotel Team
Página de equipo: tabla paginada (PAGE_SIZE 20), filtro por nombre/email y rol, bulk select/remove con checkboxes y barra de acción.
/hotels/[id]/team
AddUserToHotelModal
Modal para asignar un usuario existente a un hotel desde la vista de equipo del hotel.
Hotel detail → Equipo → +
CreateUserModal
Al crear un usuario se puede asignar directamente a uno o varios hoteles vía hotelIds[].
/users → + Nuevo → hotelIds

Team Page — /hotels/[id]/team

Paginación
PAGE_SIZE = 20. El slice visible es filteredUsers.slice((page-1)×20, page×20). El paginador solo aparece si filteredUsers.length > 20. Cambiar el filtro de búsqueda o rol resetea a la página 1 automáticamente.
Bulk select/remove
Columna de checkbox en cada fila + select-all en cabecera (solo página actual). Al seleccionar ≥1 usuario aparece la barra de acción con contador y botón de eliminar. Promise.allSettled lanza todos los DELETE en paralelo; después limpia la selección y recarga.
data-testid públicos
team-row · row-checkbox · select-all-checkbox
bulk-toolbar · btn-bulk-remove · btn-cancel-selection
pagination · page-info · btn-prev-page · btn-next-page
Grupos de hoteles: El campo parent_id en hotels permite modelar jerarquías (cadena → hotel). La lógica de asignación en cascada para grupos (getGroupChildren) aún no está implementada en el backend; cada hotel hijo debe asignarse individualmente.

Módulo de Tareas

Gestión operativa de tareas por hotel (housekeeping, mantenimiento, F&B, recepción…) sobre una capa de gobernanza configurable: catálogo global de carpetas y tipos, activación por hotel, roles que controlan la visibilidad y flujos de estado personalizables por carpeta. El backend ya está migrado de Firestore a PostgreSQL/REST; el módulo es funcional salvo las brechas listadas más abajo.

Estado a 2026-05-28: módulo operativo (lista, detalle, alta, config de carpetas/tipos/roles/flujos, vista housekeeping). El modal de asignación de tareas ya lista los usuarios del hotel (vía EnterpriseUsersService.getAllUsers); pendiente que aproveche el provider por carpeta para filtrar por equipo (ver «Estado actual y pendientes»).

Cómo funciona — del catálogo global a la operativa

1 — Catálogo global
Super admin define carpetas (TaskFolder) y tipos de tarea (TaskType) reutilizables, con icono, color, locales y flags (urgente / cara al cliente / prioridad).
2 — Activación por hotel
Cada hotel activa las carpetas/tipos que usa (initHotelFolders) y puede sobrescribir defaults con custom_config.
POST /task-folders/hotels/:id/init
3 — Roles → acceso
Los roles de tareas (TaskFolderRole) agrupan carpetas y se asignan a usuarios. getMyAccess devuelve las carpetas/tipos visibles para el usuario actual.
GET /task-folders/my-access
4 — Operativa
Se crean tareas (individuales o en bloque por ubicación), se asignan y avanzan por su flujo de estados. Prioridad numérica + flag is_urgent.
5 — Auditoría
Cada cambio genera un TaskEvent (creación, asignación, cambio de estado, comentario, foto), visible en el timeline del detalle.
GET /tasks/:id/events

Rutas y vistas

RutaQué hace
/tasks Lista operativa con toggle Todas / Mis tareas (persistido en localStorage), chips de filtro (status, urgent), badges de alergias, prioridad P1–P5 + urgencia y menú de acciones por fila. Scope multi-hotel vía useHotelScope.
/tasks/[id] Detalle a 2 columnas: edición inline (General · Personas · Fechas), botones de acción según máquina de estados (getAvailableActions), input de comentarios y timeline de eventos.
/tasks/new Wizard de 4 pasos: carpeta → tipo → ubicación → condiciones/resumen. Defaults pre-rellenados (el tipo sobrescribe a la carpeta). Con ubicaciones configuradas usa LocationPicker y permite alta en bloque (una tarea por ubicación).
/tasks/housekeeping Vista móvil de tareas activas agrupadas por planta (acordeones colapsables), botón «avanzar estado» por tarea y filtro «solo urgentes».
/tasks/config Hub con 6 pestañas: Activación Hotel, Carpetas, Tipos de Tarea, Roles, Mi Acceso, Ubicaciones (CRUD + import Excel).
/tasks/config/folders/[id] Editor de carpeta a página completa, 3 pestañas: General · Tipos de Tarea (con overrides por tipo) · Flujo de Estados (diagrama + editor JSON con presets).
/config/tasks/icons Catálogo de iconos permitidos (solo lectura). El catálogo es estático y se gestiona en el código de @pamaconu/core.

Componentes UI

Bloques reutilizables en packages/enterprise/src/components/tasks/. Las páginas en app/tasks/** los componen; los iconos viven en @pamaconu/core.

ComponenteQué hace
TaskStatusBadge Pildora con color/label del estado actual. Resuelve i18n contra el flujo activo de la carpeta cuando existe; cae al enum canónico si no.
TaskUrgencyIndicator Indicador «Urgente» (pulso + icono) para tareas con is_urgent=true. Variante compacta sin label para listas densas.
TaskActionsMenu Menú contextual por fila (≡): acciones según el estado actual (start, pause, resume, complete, verify, cancel) calculadas con getAvailableActions + abrir AssignTaskModal.
AssignTaskModal Modal para asignar una tarea concreta a un usuario, con búsqueda. Carga el listado del hotel vía EnterpriseUsersService.getAllUsers y muestra el actual asignado marcado.
AssignUsersToRoleModal Modal usado en /tasks/config · pestaña Roles: asigna usuarios del hotel a un TaskFolderRole. Sí lista usuarios (diferente provider que AssignTaskModal).
LocationPicker Selector jerárquico de ubicaciones (planta → habitación / zona). Usado en el wizard de alta (paso 3) y en el detalle. Soporta multi-selección para alta en bloque.
ShiftHandoverModal Traspaso de turno: agrupa las tareas activas del usuario para entregarlas a otro responsable en un solo paso.
MyTasksWidget Tarjeta de dashboard «Mis Tareas Pendientes»: 5 más recientes + total, badges de alergias, completar rápido y deep-link a /tasks?view=mine. Etiquetas de estado alineadas al enum real (pending/assigned/in_progress/paused/completed/verified/cancelled).
TaskIcon · TaskInstanceIcon Wrappers que resuelven iconos por id. TaskIcon delega en CoreIcon con un mapeo TaskIconId → CoreIconId; TaskInstanceIcon elige icono por carpeta/tipo en listas.
TemplateIconSelector Picker visual del catálogo de iconos al editar carpetas/tipos en /tasks/config.
StatusFlowDiagram Diagrama visual del flujo de estados de una carpeta (folders/StatusFlowDiagram.tsx): borde discontinuo = inicial, borde grueso = terminal, flechas = transiciones permitidas.

Modelo de datos (backend PostgreSQL · snake_case)

Tipos activos en @pamaconu/core (tasks.service.ts). Texto multilingüe en campos *_ml resueltos con getLocalizedText(ml, locale).

EntidadRepresentaCampos clave
Task Tarea operativa. id, hotel_id, template_id?, group_id, title, location_id/room_number, priority, is_urgent, status, assigned_to, guest_allergies[], timestamps de ciclo de vida (started_at, completed_at, verified_at…).
TaskGroup / TaskTemplate Catálogo legacy de agrupación y plantillas. code, name_ml, icon, color, display_order, is_active; template añade default_priority, estimated_duration_minutes.
TaskFolder Capa de gobernanza (más reciente que groups): organiza tipos. name_ml, icon, color, is_urgent, is_client_facing, is_priority, recommended_duration_minutes, created_by (null = global).
TaskType Tipo de tarea reutilizable (N:M con carpetas). code, name_ml, icon, color, active_locales.
TaskFolderRole Rol que da acceso a un conjunto de carpetas. code, name_ml, is_system, folders[]; el modelo core añade visibility_scope (own/group/hotel/chain).
Location Ubicación del hotel (hotel_locations). Referenciada por Task.location_id; resuelve location_code, location_name_ml.
TaskEvent Historial de auditoría. event_type, user_id, previous_status/new_status, comment, photo_url.

Flujo de estados

Conjunto de estados del backend activo:

pending assigned in_progress paused completed verified · terminal: verified / cancelled

Además, cada carpeta puede definir un flujo personalizable (StatusFlowConfig): initialStatus + states[] con key, label_ml, color, terminal y transitions_to[] (transiciones permitidas). Se edita en JSON con validación y tres presets: Básico, Con revisión y Kanban; se visualiza con StatusFlowDiagram (inicial = borde discontinuo, terminal = borde grueso, flechas = transiciones). Los cambios de estado se aplican vía POST /tasks/:id/status.

API — dos servicios

TasksApiAdapter → /api/v1/tasks
Operativa y catálogo legacy. Auth Bearer (localStorage) + cookie, con auto-refresh en 401.
TaskFoldersApiService → /api/v1/task-folders
Gobernanza (carpetas, tipos, roles, acceso). Auth solo cookie; redirige a /login en 401.
GET /tasks POST /tasks PATCH /tasks/:id POST /tasks/:id/assign POST /tasks/:id/status POST /tasks/:id/comment GET /tasks/:id/events POST /tasks/bulk-by-locations GET /task-folders/folders PUT /task-folders/folders/:id/status-flow GET /task-folders/my-access POST /task-folders/hotels/:id/init

Estado actual y pendientes

✅ Hecho y testeado
  • Lista (Todas / Mis tareas), detalle con timeline + comentarios, alta por wizard, vista housekeeping.
  • Config completa: carpetas, tipos, roles, ubicaciones, flujos de estado por carpeta.
  • Migración Firestore → PostgreSQL/REST funcionalmente completa.
  • Cobertura: 16 ficheros de test · 194 casos · 100% pasando + 7 specs e2e Playwright. Desglose abajo.
FicheroTipoCasosQué cubre
TaskDetailPage.test.tsxComponente47Edición inline, acciones por estado, comentarios, timeline.
TaskDetailPage.helpers.test.tsUnidad28Helpers de la página de detalle (formateo, transiciones, permisos).
role-badge.test.tsUnidad19Render de badges de rol y colores derivados.
MultilingualEditor.test.tsxComponente15Editor de campos *_ml (locale switcher, validación, idiomas disponibles = activos de plataforma).
status-transition.test.tsUnidad12Validez de transiciones contra el flujo activo.
TaskRolesTab.refactored.test.tsxComponente11Pestaña de roles en /tasks/config (CRUD + asignación).
HotelTeamPage.test.tsxComponente9Vista de equipo por hotel.
FolderEditPage.test.tsxComponente9Editor de carpeta a página completa (General · Tipos · Flujo).
status-flow.test.tsUnidad8StatusFlowConfig: validación, presets, terminal/initial.
AssignTaskModal.test.tsxComponente10Modal de asignación: render, búsqueda, listar usuarios reales, marcar al asignado, confirmar asignación, fallback de error del provider.
MyTasksWidget.test.tsxComponente7Etiquetas por estado del enum real, ocultar terminales (completed/cancelled), regresión contra blocked/review.
getUsersByRole.controller.test.tsUnidad5Controller backend para el provider de usuarios por rol (lo usa AssignUsersToRoleModal; pendiente que AssignTaskModal lo aproveche para filtrar por carpeta).
folder-type-defaults.test.tsUnidad5Defaults heredados de carpeta a tipo.
MyTasksFilter.test.tsxComponente4Toggle Todas / Mis tareas + chips de filtro.
TaskIcon.test.tsxComponente3Delega en CoreIcon (no fallback «?»), propaga id y className.
StatusFlowDiagram.test.tsxComponente2Render del diagrama (nodos, transiciones, estilos terminal/initial).

E2E Playwrightpackages/enterprise/e2e/specs/tasks/ (7 specs + helpers.ts): my-tasks.spec.ts, task-detail.spec.ts, assign-task.spec.ts, status-flow.spec.ts, folder-editing.spec.ts, role-management.spec.ts, hotel-team.spec.ts.

Huecos visibles: ningún test cubre directamente el wizard de alta de tareas (/tasks/new) ni la vista /tasks/housekeeping (los checkboxes de selección siguen inertes).

✅ Resuelto en 2026-05-28
  • Asignación de tareas: AssignTaskModal ahora carga el listado de usuarios del hotel vía EnterpriseUsersService.getAllUsers({ limit: 200 }), mapea User → RoleUserSummary y permite asignar. (components/tasks/AssignTaskModal.tsx; 10 casos en AssignTaskModal.test.tsx)
  • TaskIcon renderiza iconos reales: el wrapper de enterprise delega en CoreIcon con un mapeo local TaskIconId → CoreIconId (mismo que el de core/TaskIcon.tsx); sin más fallback «?». (components/tasks/TaskIcon.tsx; 3 casos en TaskIcon.test.tsx)
  • MyTasksWidget usa el enum real (pending, assigned, in_progress, paused, completed, verified, cancelled); ya no hay claves blocked/review ni fallthrough al label crudo. (components/tasks/MyTasksWidget.tsx; 7 casos nuevos en MyTasksWidget.test.tsx)
  • Limpieza: eliminados app/tasks/page.tsx.firestore-backup, .server-backup, hooks/useTasksApi.ts.old, types/tasks/task-types.ts y sus tres tests acoplados (task-domain-utils, workflow-transition-helpers, task-defaults-merge).
🔴 Brechas en el código (a corregir)
  • Housekeeping — selección en bloque inerte: los checkboxes de selección existen pero no hay acción masiva que los consuma. (app/tasks/housekeeping/page.tsx)
  • Dos clientes API con estrategias de auth diferentes: TasksApiAdapter usa el ApiClient de @pamaconu/core (Bearer en header si hay token + cookie credentials:'include' + auto-refresh con refreshToken en 401); TaskFoldersApiService usa el ApiClient local de enterprise (solo cookie; redirige a /login?error=session_expired en 401, sin refresh). Conviene unificar. (puerto de fallback ya alineado a :4001 en ambos)
  • Código duplicado: la lógica de edición de carpeta está copiada entre folders/[id]/page.tsx y TaskFolderEditModal.tsx.
  • AssignTaskModal · folderId sin efecto: el prop sigue declarado pero no filtra usuarios por acceso a la carpeta — siempre se listan todos los del hotel. Pendiente añadir un endpoint users-by-folder o consolidar contra getUsersByRole.
🧹 Limpieza de deuda técnica
  • Marcar como obsoletos los docs antiguos (TASKS_MODULE_DESIGN.md, TASKS_MODULE_IMPLEMENTATION_PLAN.md «3%», ENTERPRISE_TASKS_ANALYSIS.md Firestore).
  • Contradicción docs⇄código: el EPIC afirma haber añadido UserTaskRolesTab a UserDetailDrawer, pero ese componente/test no existen (eliminados en el rewrite de control de acceso).
🗺️ Roadmap (planificado en docs, no implementado)
  • Subtareas, dependencias y adjuntos.
  • Notificaciones (email/push) y panel de analítica / export (PDF/Excel).
  • Tablero Kanban con drag & drop; SLA por severidad.
  • Permisos granulares de roles aplicados en UI; i18n del catálogo de iconos.

Mapa de funcionalidades

Todas las secciones del paquete @happnest/enterprise con sus rutas, cobertura de tests y estado actual.

🔐 Auth & Setup
completo
  • /login
  • /logout
  • /setup  (wizard inicial)
  • /select-hotel
📊 Dashboard
completo
  • /dashboard
👥 Usuarios
completo
  • /users  (lista, métricas, filtros)

CreateUserModal · ImportUsersModal · UserDetailDrawer (5 tabs) · UsersTable · UsersFilters · UsersMetrics

🛡️ Roles
completo
  • /settings/roles  (lista)
  • /settings/roles/[id]  (role builder)

5 roles predefinidos · edición in-place · Owner bloqueado (system role)

🏨 Hoteles
completo
  • /hotels  (lista · cards / tabla · filtros)
  • /hotels/[id]  (detalle · stats · branding · EditModal)
  • /hotels/[id]/team  (equipo · paginación · bulk remove)
  • /hotels/legal-texts

CreateHotelModal · EditHotelModal · HotelVisualCard · AddUserToHotelModal · HotelTeamPage (PAGE_SIZE 20, bulk select/remove)

🏖️ Experiences
completo
  • /experiences  (lista)
  • /experiences/create
  • /experiences/[id]
  • /experiences/[id]/edit

Wizard multi-paso · Excel import · TemplateSelector

Tasks
casi completo
  • /tasks  (lista · Todas / Mis tareas)
  • /tasks/[id]  (detalle · timeline · comentarios)
  • /tasks/new  (wizard 4 pasos)
  • /tasks/housekeeping  (por planta)
  • /tasks/config  (6 pestañas)
  • /tasks/config/folders/[id]  (editor de carpeta)
  • /config/tasks/icons  (catálogo de iconos)

Backend migrado a PostgreSQL/REST. Gobernanza por carpetas + roles + flujos de estado configurables. Detalle completo en Módulo Tareas →

🛎️ Checkins
completo
  • /checkins  (lista, grupos)
  • /checkins/[id]  (detalle)
📬 Requests
completo
  • /requests  (lista)
  • /requests/[id]  (detalle)
📅 Reservations
completo
  • /reservations  (lista, timeline)
🔔 Notificaciones
completo
  • /notifications  (inbox)
👤 Perfil
completo
  • /profile
🎨 Branding
completo
  • /branding  (system branding)

SystemBrandingInjector · ClientHotelBranding · runtime CSS injection

Super Admin
completo
  • /super-admin/companies
  • /super-admin/hotels/[id]/modules
🌍 Internacionalización
completo
  • /settings/languages  (activación)
  • pnpm run i18n:sync  (script)

Soporte para múltiples idiomas (es, en, fr, de, it, pt, ca). Sincronización automática de diccionarios en local.

🧹 Cleanup 7.7
completo
  • Migration 040 — drop users.role VARCHAR
  • UserTaskRolesTab.tsx eliminado
  • setup.controller.ts usa user_type
Resumen: 16 secciones completas 35+ rutas de página 80 archivos de test todo completo