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.
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 = trueen 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:
GET /me/permissions?hotel_id=.ALL_PERMISSIONS_TRUE sin consultar roles ni módulos activos.
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.
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 | ✓ | — | — | — | — |
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.
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). |
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/usersDELETE /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. |
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.
- PK id UUID
- user_type
- is_super_admin
- display_name
- is_active
- 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
- PK id UUID
- name
- city / country
- parent_id grupos
- is_active
Restricciones de integridad
user o un hotel, todas las filas relacionadas en user_hotels se borran automáticamente (ON DELETE CASCADE).'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.
previousHotelIds (estado actual) y assignment.hotelIds (estado deseado).hotelsToAdd) y los que hay que quitar (hotelsToRemove).'staff' por defecto.Endpoints de asignación
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
hotelIds[].Team Page — /hotels/[id]/team
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.Promise.allSettled lanza todos los DELETE en paralelo; después limpia la selección y recarga.team-row · row-checkbox · select-all-checkboxbulk-toolbar · btn-bulk-remove · btn-cancel-selectionpagination · page-info · btn-prev-page · btn-next-page
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.
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
TaskFolder) y tipos de tarea (TaskType) reutilizables, con icono, color, locales y flags (urgente / cara al cliente / prioridad).initHotelFolders) y puede sobrescribir defaults con custom_config.TaskFolderRole) agrupan carpetas y se asignan a usuarios. getMyAccess devuelve las carpetas/tipos visibles para el usuario actual.is_urgent.TaskEvent (creación, asignación, cambio de estado, comentario, foto), visible en el timeline del detalle.Rutas y vistas
| Ruta | Qué 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.
| Componente | Qué 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).
| Entidad | Representa | Campos 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
/api/v1/tasks/api/v1/task-folders/login en 401.Estado actual y pendientes
- 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.
| Fichero | Tipo | Casos | Qué cubre |
|---|---|---|---|
| TaskDetailPage.test.tsx | Componente | 47 | Edición inline, acciones por estado, comentarios, timeline. |
| TaskDetailPage.helpers.test.ts | Unidad | 28 | Helpers de la página de detalle (formateo, transiciones, permisos). |
| role-badge.test.ts | Unidad | 19 | Render de badges de rol y colores derivados. |
| MultilingualEditor.test.tsx | Componente | 15 | Editor de campos *_ml (locale switcher, validación, idiomas disponibles = activos de plataforma). |
| status-transition.test.ts | Unidad | 12 | Validez de transiciones contra el flujo activo. |
| TaskRolesTab.refactored.test.tsx | Componente | 11 | Pestaña de roles en /tasks/config (CRUD + asignación). |
| HotelTeamPage.test.tsx | Componente | 9 | Vista de equipo por hotel. |
| FolderEditPage.test.tsx | Componente | 9 | Editor de carpeta a página completa (General · Tipos · Flujo). |
| status-flow.test.ts | Unidad | 8 | StatusFlowConfig: validación, presets, terminal/initial. |
| AssignTaskModal.test.tsx | Componente | 10 | Modal de asignación: render, búsqueda, listar usuarios reales, marcar al asignado, confirmar asignación, fallback de error del provider. |
| MyTasksWidget.test.tsx | Componente | 7 | Etiquetas por estado del enum real, ocultar terminales (completed/cancelled), regresión contra blocked/review. |
| getUsersByRole.controller.test.ts | Unidad | 5 | Controller backend para el provider de usuarios por rol (lo usa AssignUsersToRoleModal; pendiente que AssignTaskModal lo aproveche para filtrar por carpeta). |
| folder-type-defaults.test.ts | Unidad | 5 | Defaults heredados de carpeta a tipo. |
| MyTasksFilter.test.tsx | Componente | 4 | Toggle Todas / Mis tareas + chips de filtro. |
| TaskIcon.test.tsx | Componente | 3 | Delega en CoreIcon (no fallback «?»), propaga id y className. |
| StatusFlowDiagram.test.tsx | Componente | 2 | Render del diagrama (nodos, transiciones, estilos terminal/initial). |
E2E Playwright — packages/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).
- Asignación de tareas:
AssignTaskModalahora carga el listado de usuarios del hotel víaEnterpriseUsersService.getAllUsers({ limit: 200 }), mapeaUser → RoleUserSummaryy permite asignar. (components/tasks/AssignTaskModal.tsx; 10 casos enAssignTaskModal.test.tsx) - TaskIcon renderiza iconos reales: el wrapper de enterprise delega en
CoreIconcon un mapeo localTaskIconId → CoreIconId(mismo que el decore/TaskIcon.tsx); sin más fallback «?». (components/tasks/TaskIcon.tsx; 3 casos enTaskIcon.test.tsx) - MyTasksWidget usa el enum real (
pending,assigned,in_progress,paused,completed,verified,cancelled); ya no hay clavesblocked/reviewni fallthrough al label crudo. (components/tasks/MyTasksWidget.tsx; 7 casos nuevos enMyTasksWidget.test.tsx) - Limpieza: eliminados
app/tasks/page.tsx.firestore-backup,.server-backup,hooks/useTasksApi.ts.old,types/tasks/task-types.tsy sus tres tests acoplados (task-domain-utils,workflow-transition-helpers,task-defaults-merge).
- 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:
TasksApiAdapterusa elApiClientde@pamaconu/core(Bearer en header si hay token + cookiecredentials:'include'+ auto-refresh conrefreshTokenen 401);TaskFoldersApiServiceusa elApiClientlocal de enterprise (solo cookie; redirige a/login?error=session_expireden 401, sin refresh). Conviene unificar. (puerto de fallback ya alineado a:4001en ambos) - Código duplicado: la lógica de edición de carpeta está copiada entre
folders/[id]/page.tsxyTaskFolderEditModal.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.
- Marcar como obsoletos los docs antiguos (
TASKS_MODULE_DESIGN.md,TASKS_MODULE_IMPLEMENTATION_PLAN.md«3%»,ENTERPRISE_TASKS_ANALYSIS.mdFirestore). - Contradicción docs⇄código: el EPIC afirma haber añadido
UserTaskRolesTabaUserDetailDrawer, pero ese componente/test no existen (eliminados en el rewrite de control de acceso).
- 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.
- /login
- /logout
- /setup (wizard inicial)
- /select-hotel
- /dashboard
- /users (lista, métricas, filtros)
CreateUserModal · ImportUsersModal · UserDetailDrawer (5 tabs) · UsersTable · UsersFilters · UsersMetrics
- /settings/roles (lista)
- /settings/roles/[id] (role builder)
5 roles predefinidos · edición in-place · Owner bloqueado (system role)
- /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 (lista)
- /experiences/create
- /experiences/[id]
- /experiences/[id]/edit
Wizard multi-paso · Excel import · TemplateSelector
- /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 (lista, grupos)
- /checkins/[id] (detalle)
- /requests (lista)
- /requests/[id] (detalle)
- /reservations (lista, timeline)
- /notifications (inbox)
- /profile
- /branding (system branding)
SystemBrandingInjector · ClientHotelBranding · runtime CSS injection
- /super-admin/companies
- /super-admin/hotels/[id]/modules
- /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.
- Migration 040 — drop
users.roleVARCHAR UserTaskRolesTab.tsxeliminadosetup.controller.tsusauser_type