From 38c623722a299b890c858e8f2ba4418f38f65519 Mon Sep 17 00:00:00 2001 From: Jessica Orozco Date: Tue, 16 Jun 2026 14:17:55 -0400 Subject: [PATCH] feat: implement Kinetix reservation module with Angular 18, AI-assisted form filling, and executive design system --- .gitignore | 3 + doc/presentacion_kinetix.html | 1495 +++++++++++++++++ doc/propuesta_kinetix.md | 329 ++++ doc/walkthrough.md | 29 + frontend/angular.json | 12 +- .../kinetix-frontend/3rdpartylicenses.txt | 259 --- frontend/dist/kinetix-frontend/index.html | 13 - .../polyfills.db3ad031cea79ddb.js | 1 - frontend/src/app/app.component.ts | 6 +- frontend/src/app/app.config.ts | 4 +- frontend/src/app/app.routes.ts | 5 + .../features/dashboard/dashboard.component.ts | 215 +-- .../directorio/directorio.component.css | 696 ++++++++ .../directorio/directorio.component.ts | 317 ++++ .../calendar-booking.component.css | 511 ++++++ .../calendar-booking.component.html | 376 +++-- .../calendar-booking.component.ts | 65 +- .../reserva-form/reserva-form.component.css | 272 +++ .../reserva-form/reserva-form.component.html | 284 ++-- .../reserva-form/reserva-form.component.ts | 2 +- frontend/src/index.html | 1 + 21 files changed, 4094 insertions(+), 801 deletions(-) create mode 100644 doc/presentacion_kinetix.html create mode 100644 doc/propuesta_kinetix.md create mode 100644 doc/walkthrough.md delete mode 100644 frontend/dist/kinetix-frontend/3rdpartylicenses.txt delete mode 100644 frontend/dist/kinetix-frontend/index.html delete mode 100644 frontend/dist/kinetix-frontend/polyfills.db3ad031cea79ddb.js create mode 100644 frontend/src/app/features/directorio/directorio.component.css create mode 100644 frontend/src/app/features/directorio/directorio.component.ts create mode 100644 frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.css create mode 100644 frontend/src/app/features/reservas/reserva-form/reserva-form.component.css diff --git a/.gitignore b/.gitignore index bf64d24..b9cf172 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ dist /frontend/.angular/cache /.vscode + +# Engram AI agent memory +.engram/ diff --git a/doc/presentacion_kinetix.html b/doc/presentacion_kinetix.html new file mode 100644 index 0000000..db010b7 --- /dev/null +++ b/doc/presentacion_kinetix.html @@ -0,0 +1,1495 @@ + + + + + + Propuesta Corporativa 2026 — Plataforma de Intranet + + + + + + + + + + + + +
+
+ + +
+
+
+ + + + + +
+
+

Intranet Corporativa

+

Plataforma de Nueva Generación · Propuesta Técnica y Comercial

+
+

+ + Preparado por: Jess Miller · Arquitecta de Software Senior · Junio 2026 +

+
+ + +
+
01 / 19
+
Resumen Ejecutivo
+

Una plataforma integrada y lista para producción

+
+
+
5
+
Módulos core
+
Directorio · Espacios · Workflow · CMS · Tickets
+
+
+
SSO
+
Azure Entra ID
+
Autenticación nativa de nivel empresarial
+
+
+
100%
+
Código Abierto
+
Angular 18 · NestJS 10 · PostgreSQL 15
+
+
+
+

+ LA PLATAFORMA NO ES UN PROTOTIPO. Se presenta como una solución central de intranet corporativa completamente funcional en desarrollo activo, con frontends y backends estables. Consolida procesos dispersos, garantiza seguridad empresarial y otorga propiedad total del código al cliente sin dependencias ni vendor lock-in. +

+
+
+ + +
+
02 / 19
+
El Diagnóstico
+

El impacto de la fragmentación de herramientas

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Síntoma OperativoImpacto en el Negocio
Fragmentación de herramientasPérdida de tiempo, ineficiencia operativa y errores de coordinación interdepartamental.
Sin integración con Active DirectoryOnboarding y offboarding lentos, gestión manual de accesos y serios riesgos de seguridad.
Reservas de salas sin control centralizadoConflictos de disponibilidad, overbooking y salas de reuniones subutilizadas.
Aprobaciones de contenidos por correo/chatsFalta absoluta de trazabilidad, cuellos de botella operativos y pérdida de historial.
Directorio de personal desactualizadoDificultad para localizar habilidades internas, baja colaboración y silos de información.
+
+ + +
+
03 / 19
+
La Propuesta
+

Un ecosistema. Todos los procesos internos en uno.

+ +
+
+
+ 🏢 +

Directorio (IAM)

+
+
+ 📅 +

Espacios & IA

+
+
+ ⚙️ +

Aprobaciones BPM

+
+
+ 📰 +

Info Hub CMS

+
+
+ 🎫 +

Ticketing SLA

+
+
+ +
+
🔑 Autenticación SSO: Microsoft Azure Entra ID / Tokens JWT
+
🐘 Persistencia: PostgreSQL (ACID) & Prisma ORM
+
🐳 Infraestructura: Docker / Cloud Ready
+
+
+ +

+ La solución consolida la seguridad y la experiencia del usuario bajo un mismo diseño unificado y coherente. +

+
+ + +
+
04 / 19
+
Módulos de la Solución
+

Módulo 1: Directorio Corporativo (IAM / HR)

+
+
+
+

👥 Sincronización Azure Entra ID

+

Lectura directa de usuarios, departamentos y jerarquías sin replicar datos sensibles de manera local.

+
+
+

🔍 Búsqueda Reactiva en Tiempo Real

+

Búsqueda instantánea integrada con Angular Signals. Filtros por rol, departamento o habilidades específicas de manera fluida.

+
+
+ +
+
+

🆔 Perfiles de Colaborador Detallados

+

Muestra ID del directorio (AD-XXXX), tarifa por hora, badge de calificación y datos de contacto integrados.

+
+
+

⚡ Interfaz Ergónomica e Inmersiva

+

Tarjetas de personal dinámicas con micro-interacciones de hover y navegación rápida a información de contacto.

+
+
+
+
+ + +
+
05 / 19
+
Módulos de la Solución
+

Módulo 2: Autogestión de Espacios y Reservas (PMS)

+
+
+
+

📅 Calendario Interactivo

+

Vistas completas de Mes, Semana y Día. Clic en cualquier bloque de tiempo disponible redirige al formulario con parámetros precargados.

+
+
+

🛡️ Anti-Overbooking Garantizado

+

Control transaccional estricto en base de datos PostgreSQL, asegurando consistencia absoluta sin duplicidad de reservas.

+
+
+

⚙️ Filtros por Tipo de Activo

+

Filtrado de recursos en tiempo real: salas de reuniones, equipos tecnológicos, vehículos y áreas comunes.

+
+
+ +
+
🤖 ASISTENTE DE IA INTEGRADO (NLP)
+

+ El usuario puede agendar recursos usando lenguaje natural de forma intuitiva: +

+
+ Entrada libre:
+ "Reservar la sala Londres para mañana a las 10 am para 8 personas"
+
+ Variables detectadas por IA:
+ • Espacio: London Hall (ID: SALA-02)
+ • Fecha: 2026-06-17 (Calculado)
+ • Hora: 10:00 AM - 11:00 AM
+ • Participantes: 8 colaboradores +
+
+
+
+ + +
+
06 / 19
+
Módulos de la Solución
+

Módulo 3: Workflow de Aprobación y Publicación (BPM)

+
+
+
+

⚙️ Motor de Reglas Jerárquicas

+

Flujo estructurado multinivel basado en roles de Active Directory. Asigna aprobadores de manera automática según el departamento.

+
+
+

📈 Trazabilidad de Estados

+

Control exacto del estado de cada solicitud (Borrador → En Aprobación → Aprobado → Publicado) con auditoría histórica detallada.

+
+
+ +
+
+

🔔 Notificaciones y Alertas Reactivas

+

Integración directa con el sistema de mensajería para avisos de solicitudes pendientes, garantizando flujos de trabajo sin demoras.

+
+
+

📰 Integración con Canales CMS

+

Una vez aprobado el contenido corporativo, el motor realiza la publicación automática en los tableros del Info Hub de la empresa.

+
+
+
+
+ + +
+
07 / 19
+
Módulos de la Solución
+

Módulos 4 & 5: Info Hub CMS y Ticketing

+
+
+ 📰 +

Módulo 4: Info Hub / CMS Corporativo

+

Gestión y publicación de información interna en un portal centralizado de noticias corporativas.

+
    +
  • Editor WYSIWYG de contenidos enriquecidos
  • +
  • Categorías y taxonomías customizables
  • +
  • Programación de comunicados a futuro
  • +
  • Publicación segmentada por departamento o rol
  • +
+
+ +
+ 🎫 +

Módulo 5: Soporte Técnico e Incidencias

+

Mesa de ayuda integrada para gestionar requerimientos de soporte interno técnico, operacional y de RR.HH.

+
    +
  • Creación de solicitudes con prioridades claras
  • +
  • Asignación automatizada según especialidad del agente
  • +
  • Control de Tiempos de Respuesta y SLAs por ticket
  • +
  • Dashboard de resolución para coordinadores de mesa
  • +
+
+
+
+ + +
+
08 / 19
+
Arquitectura de Software
+

Stack Tecnológico Moderno y Open Source

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CapaTecnología UtilizadaVersiónJustificación y Beneficio
FrontendAngular (Standalone Components & Signals)18.0Framework enterprise mantenido por Google, alta reactividad y orden.
BackendNestJS (REST API)10.0Arquitectura modular sólida basada en TypeScript, fácil mantenimiento.
AutenticaciónAzure Entra ID + JWT (SSO)EstándarIntegración corporativa directa con el Active Directory de la empresa.
Base de DatosPostgreSQL + Prisma ORM15 / v5Garantiza transacciones ACID indispensables para evitar overbooking de salas.
InfraestructuraDocker + Docker Composev3.8Asegura portabilidad absoluta y despliegue rápido en local o cloud.
+
+ + +
+
09 / 19
+
Seguridad Corporativa
+

Arquitectura SSO: Cero almacenamiento de credenciales

+ +
+
+

1. Usuario

+

Accede a la plataforma web desde el navegador.

+
+
+
+

2. Azure Entra ID

+

Autenticación delegada (OAuth2) con credenciales corporativas.

+
+
+
+

3. Token JWT

+

Retorna token con identidad y pertenencia a grupos de AD.

+
+
+
+

4. NestJS API

+

Decodifica y valida firma del JWT por cada petición HTTP.

+
+
+
+

5. Autorización

+

Acceso concedido según rol: employee, manager o admin.

+
+
+ +
+
+

+ OWASP Top 10 Compliance: Toda la transmisión de datos corre sobre HTTPS y los permisos expiran automáticamente mediante tokens JWT firmados. +

+
+
+

+ Control Centralizado: Si se da de baja un colaborador en el AD corporativo de la empresa, pierde acceso de forma inmediata y automática a la intranet. +

+
+
+
+ + +
+
10 / 19
+
Ingeniería de Software
+

Arquitectura de Repositorio Estructurado

+ +
+ proyecto-intranet/
+ ├── frontend/← Aplicación Angular 18 (Standalone Components + Signals)
+ │ └── src/app/
+ │ ├── core/← Guard SSO, interceptores HTTP, auth services y manejo de tokens
+ │ ├── shared/← Componentes del Design System y pipes comunes
+ │ └── features/← Módulos funcionales (directorio, calendar-booking, etc.)
+ ├── backend/← REST API en NestJS (TypeScript modular estructurado)
+ │ └── src/
+ │ ├── auth/← Módulo Passport con validación JWT de Azure Entra ID
+ │ ├── directory/ / spaces/ / workflow/← Módulos con lógica empresarial
+ ├── standards/← Documentación de calidad de código y especificaciones de diseño
+ ├── docker-compose.yml← Orquestación local: base de datos PostgreSQL + administradores
+ └── setup.ps1 / .sh← Scripts de automatización y despliegue rápido para desarrollo +
+ +

+ → Código limpio, modular y documentado que permite la adición de nuevos desarrolladores de forma ágil. +

+
+ + +
+
11 / 19
+
Experiencia de Usuario
+

Executive Suite Design System

+

+ Diseñado bajo directrices premium que mezclan sobriedad corporativa y tecnología de vanguardia. +

+ +
+
+

✨ Tipografía Limpia

+

Inter para titulares de gran peso, brindando jerarquía y firmeza, complementada con Outfit para lecturas extensas fluidas.

+
+
+

🖼️ Card System Joya

+

Glows de fondo en colores oscuros pero ricos (Navy, Plum, Viridian, Espresso). Estructura elegante sin bordes cargados.

+
+
+

🔮 Micro-animaciones

+

Efectos de hover sutiles en tarjetas y botones, feedback inmediato al clickear y transiciones fluidas de página.

+
+
+ +
+
+
+

Ivory (#F5F4F1)

+

Color primario para contraste de textos limpios y de alta legibilidad.

+
+
+
+

Gold (#C9973C)

+

Dorado corporativo para estados activos, botones primarios y tags.

+
+
+
+

Navy (#0D1B2A)

+

Color de fondo para el Directorio IAM y elementos de seguridad.

+
+
+
+

Plum (#1A0E2E)

+

Fondo sofisticado reservado para calendario, reservas e IA.

+
+
+
+

Viridian (#0A2520)

+

Tonalidad verde para el motor de reglas y aprobaciones BPM.

+
+
+
+ + +
+
12 / 19
+
Estado del Proyecto
+

Módulos desarrollados y nivel de avance

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Módulo o ComponenteAvanceDetalles Técnicos Implementados
Autenticación SSO corporativo✅ ImplementadoMódulo Auth JWT en NestJS, interceptores en Angular y Guards SSO.
Dashboard & Shell✅ ImplementadoSidebar interactivo colapsable, métricas globales, navegación.
Directorio de Personal✅ ImplementadoBúsqueda reactiva con Signals, filtros por departamento, tarjetas de AD.
Calendario de Reservas✅ ImplementadoCalendario reactivo en frontend con vistas de Mes, Semana y Día.
Formulario de Reservas + IA✅ ImplementadoParser NLP configurado con mock local y listado de consumo HTTP.
Workflow de Aprobación BPM🔄 En DesarrolloEstructuración del motor de jerarquías y vistas de historial de cambios.
Info Hub CMS / Ticketing SLA📋 PlanificadoDiseño de tablas de base de datos definidos en esquemas de Prisma.
+
+ + +
+
13 / 19
+
Roadmap de Entrega
+

Fase 0: Diagnóstico & Fase 1: Setup e Integración

+ +
+ +
+
+ Fase 0 — Diagnóstico y NDA (Semanas 1–2) +
+
+

📋 Kickoff e Inventario Técnico

+

Alineación de objetivos con el departamento de IT y negocio. Definición de ambientes de desarrollo y sandbox.

+
+
+

🔍 Auditoría de Active Directory

+

Revisión de grupos, jerarquías de aprobadores y políticas de acceso a la tenant del cliente.

+
+
+ + +
+
+ Fase 1 — Setup y Conexión SSO (Semanas 3–4) +
+
+

🛡️ Integración Microsoft Entra ID

+

Conexión directa de SSO vía OAuth2 en entorno de Staging. Traspaso de token JWT con claims asignados.

+
+
+

🐘 Despliegue de DB de Producción

+

Instalación de base de datos PostgreSQL, ejecución de esquemas Prisma y pruebas de concurrencia base.

+
+
+
+ +
+ 🔑 HITO CLAVE FASE 1: Inicio de sesión exitoso de usuarios del cliente en ambiente de Staging usando sus credenciales corporativas reales. +
+
+ + +
+
14 / 19
+
Roadmap de Entrega
+

Fases 2, 3 & 4: Módulos Core, Secundarios y Go-Live

+ +
+ +
+ Fase 2 (Sem 5–10) +

Módulos Core

+

Desarrollo final de los módulos esenciales:

+
    +
  • Directorio IAM en Staging
  • +
  • Reservas e IA integradas
  • +
  • Motor de Workflow BPM
  • +
  • Piloto con 50 usuarios
  • +
+
+ + +
+ Fase 3 (Sem 11–14) +

Módulos Secundarios

+

Construcción de soporte e información:

+
    +
  • Info Hub CMS con editor
  • +
  • Ticketing y asignación SLA
  • +
  • Pruebas de aceptación globales (UAT)
  • +
  • Congelamiento de código
  • +
+
+ + +
+ Fase 4 (Sem 15–16) +

Go-Live & Traspaso

+

Despliegue y estabilización:

+
    +
  • Paso a Producción Cloud
  • +
  • Capacitación de usuarios
  • +
  • Traspaso de Runbooks y código
  • +
  • 30 días de garantía incluidos
  • +
+
+
+ +
+
🚀 HITO CLAVE FASE 4: Plataforma 100% en vivo para todos los colaboradores de la empresa.
+
+
+ + +
+
15 / 19
+
Roadmap de Entrega
+

Cronograma General de Trabajo (16 Semanas)

+ +
+ +
+
Fase 0: Diagnóstico
+
+
Diagnóstico & AD Audit
+
+
+ +
+
Fase 1: Setup & SSO
+
+
SSO Azure Staging
+
+
+ +
+
Fase 2: Módulos Core
+
+
Dir + Espacios IA + BPM + Piloto
+
+
+ +
+
Fase 3: Módulos Secundarios
+
+
CMS + Tickets + UAT General
+
+
+ +
+
Fase 4: Lanzamiento
+
+
Go-Live & Traspaso
+
+
+ + +
+ Mes 1 (Sem 1-4) + Mes 2 (Sem 5-8) + Mes 3 (Sem 9-12) + Mes 4 (Sem 13-16) +
+
+ +

+ Cada hito requiere aprobación y pruebas UAT de cara al cliente antes del paso a la siguiente fase del proyecto. +

+
+ + +
+
16 / 19
+
Presupuesto y Adquisición
+

Esquema de Presupuesto y Modalidades de Contratación

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Concepto de TrabajoDescripción del Entregable
Licenciamiento baseEntrega de la plataforma e infraestructura base preconfigurada.
Integración SSO e InfraFases 0 y 1: Configuración de Azure Entra ID, base de datos PostgreSQL.
Desarrollo Core & BPMFase 2: Conexión de Directorio, Calendario + IA, workflow BPM.
Desarrollo CMS & TicketsFase 3: Info Hub, Ticketing y SLA. Pruebas UAT.
Go-Live & GarantíaDespliegue, manuales, capacitación y 30 días de garantía inicial.
+
+ + +
+
+ Modalidades de Adquisición +
+
+

🔑 Proyecto Llave en Mano

+

Precio y plazos fijos por la entrega del alcance completo de las 16 semanas. Ideal para presupuestos definidos.

+
+
+

⏳ Tiempo y Materiales (T&M)

+

Facturación mensual por sprints según requerimientos variables y flexibilidad de prioridades.

+
+
+

☁️ SaaS Mensual (OpEx)

+

Suscripción mensual recurrente con hosting, soporte continuo y actualizaciones evolutivas incluidas.

+
+
+
+
+ + +
+
17 / 19
+
Análisis del Mercado
+

Por qué nuestra solución frente a las alternativas

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CriterioNuestra PlataformaSharePointConfluenceDesarrollo desde 0
Integración AD Nativa✅ Sí (SSO)✅ Sí⚠️ Vía Plugin✅ Sí
Personalización UI/UX✅ 100% Libre⚠️ Limitada⚠️ Limitada✅ 100% Libre
Asistente IA NLP✅ Integrado❌ No❌ No⚠️ Costo Extra
Plazos de Entrega✅ 16 Semanas3 - 12 meses2 - 6 meses12 - 18 meses
Propiedad de Código✅ Del Cliente❌ Microsoft❌ Atlassian✅ Del Cliente
Vendor Lock-in✅ Cero (Open Source)❌ Alto❌ Alto✅ Cero
+
+ + +
+
18 / 19
+
Transparencia
+

Garantías y Respaldos del Proyecto

+ +
+
+

📦 Propiedad Intelectual Total

+

El cliente es dueño absoluto del código fuente, base de datos y documentación técnica al cierre del proyecto. Sin regalías.

+
+
+

🔓 Cero Dependencias

+

Uso de stack open source estándar (Angular, NestJS, PostgreSQL). Cualquier equipo de desarrollo puede asumir y escalar el sistema en el futuro.

+
+
+

📈 Escalabilidad Enterprise

+

Arquitectura desacoplada en backend y frontend, preparada para soportar 10,000+ usuarios concurrentes sin degradar velocidad.

+
+
+

🛡️ Seguridad by Design

+

Cumplimiento estricto de lineamientos OWASP Top 10, cifrado SSL/TLS obligatorio y validación de sesiones delegadas en el SSO.

+
+
+

📄 Soporte y Operatividad

+

30 días de soporte técnico reactivo de primer nivel tras el Go-Live, garantizando una transición operativa suave.

+
+
+

⚙️ Documentación Exhaustiva

+

Traspaso completo de guías de desarrollo, runbooks de DevOps, esquemas de bases de datos y manuales de usuario.

+
+
+
+ + +
+
Propuesta de Intranet · 2026
+

+ Unificando procesos para
potenciar tu organización. +

+ +
+
+ PRÓXIMOS PASOS + 1. Demo en vivo de la plataforma actual (30 min)
+ 2. Workshop técnico sobre Active Directory (2h)
+ 3. Propuesta comercial formal ajustada a usuarios
+ 4. Firma de acuerdo NDA y Contrato de Desarrollo
+ 5. Kick-off de Fase 0 +
+
+ +
+ +
+ Jess Miller — Arquitecta de Software Senior & Full-Stack Engineer + 📧 jess@intranet.dev  |  🔗 github.com/intranet-platform + + Para descargar o imprimir a PDF: Ctrl+P (Chrome) → Guardar como PDF → Habilitar "Gráficos de fondo" e imprimir horizontal. + +
+
+ +
+
+ + + + + + diff --git a/doc/propuesta_kinetix.md b/doc/propuesta_kinetix.md new file mode 100644 index 0000000..2278406 --- /dev/null +++ b/doc/propuesta_kinetix.md @@ -0,0 +1,329 @@ +# Propuesta Técnica y Comercial +## Plataforma de Intranet Corporativa · **Kinetix** + +--- + +> **Preparado por:** Jess Miller · Arquitecta de Software Senior +> **Fecha:** Junio 2026 +> **Versión:** 1.0 — Confidencial +> **Para:** [Nombre de la Empresa Objetivo] + +--- + +## Resumen Ejecutivo + +**Kinetix** es una plataforma de intranet corporativa de nueva generación, diseñada para consolidar en un único ecosistema digital todos los procesos internos de una organización empresarial moderna: gestión de identidades, reserva de espacios, aprobación de contenidos, directorio de personal y ticketing de incidencias. + +La plataforma está operativa, con frontend y backend en producción local, y lista para su despliegue en infraestructura cloud o on-premise. Está construida sobre un stack tecnológico de primer nivel —**Angular 18, NestJS 10 y PostgreSQL 15**— con autenticación nativa vía **Microsoft Azure Entra ID (Active Directory)**, lo que garantiza integración inmediata con el ecosistema Microsoft de cualquier empresa. + +> [!IMPORTANT] +> Kinetix no es un prototipo. Es un producto en desarrollo activo con arquitectura escalable, código limpio y documentación técnica estructurada. Esta propuesta presenta su adopción como solución central de intranet para [Empresa]. + +--- + +## 1. Problema que Resuelve + +Las empresas modernas operan con herramientas fragmentadas: un sistema para reservas, otro para el directorio, formularios manuales para workflows de aprobación y correos internos para incidencias. Este modelo genera: + +| Problema | Impacto | +|---|---| +| Fragmentación de herramientas | Pérdida de tiempo y errores de coordinación | +| Sin integración con Active Directory | Onboarding lento, acceso manual, riesgos de seguridad | +| Reservas de salas sin control centralizado | Conflictos de disponibilidad, overbooking | +| Aprobaciones por correo | Sin trazabilidad, cuellos de botella operativos | +| Directorio desactualizado | Información incorrecta, baja colaboración | + +**Kinetix resuelve todos estos problemas en una sola plataforma integrada.** + +--- + +## 2. La Solución: Plataforma Kinetix + +### 2.1 Visión General + +``` +┌─────────────────────────────────────────────────────────────┐ +│ KINETIX INTRANET │ +│ "Un ecosistema. Todos los procesos internos en uno." │ +├──────────────┬──────────────┬──────────────┬────────────────┤ +│ Directorio │ Espacios │ Workflows │ Info Hub CMS │ +│ Corporativo │ & Reservas │ de Aprobación│ + Ticketing │ +│ (Entra ID) │ (Calendar) │ (BPM Multi- │ (SLA/Tickets) │ +│ │ + IA Parser │ nivel) │ │ +└──────────────┴──────────────┴──────────────┴────────────────┘ + ↑ ↑ ↑ + SSO / JWT PostgreSQL DB Azure Entra ID +``` + +### 2.2 Módulos Implementados + +#### 🏢 Módulo 1 — Directorio Corporativo (IAM/HR) +Sincronizado con **Microsoft Entra ID (Azure AD)**. Muestra el organigrama real de la empresa con perfiles dinámicos: nombre, rol, departamento, skills, métricas y contacto directo. + +- Búsqueda en tiempo real por nombre, rol o habilidad +- Filtrado por departamento +- ID de Active Directory (AD-XXXX) en cada perfil +- Badge de calificación y tarifa por hora +- Diseño de tarjetas con micro-animaciones y hover + +#### 📅 Módulo 2 — Autogestión de Espacios y Activos (PMS) +Sistema completo de reservas con **calendario dinámico** con vistas Mes/Semana/Día. + +- Clic en cualquier slot del calendario → redirige a formulario de reserva +- Filtros en tiempo real por tipo de recurso (salas, equipos, vehículos) +- **Asistente IA de lenguaje natural**: el usuario escribe "Reservar sala para el jueves 5 personas" y el sistema auto-completa el formulario usando un parser de IA +- Confirmación y notificación a calendario de Active Directory + +#### ⚙️ Módulo 3 — Workflow de Aprobación y Publicación (BPM) +Motor de reglas multinivel integrado con SSO y Active Directory para jerarquías de aprobación de contenidos institucionales. + +- Creación → Validación → Publicación automática +- Estados de aprobación trazables +- Publicación en canales de comunicación interna + +#### 📰 Módulo 4 — Info Hub / CMS Corporativo +Gestión de contenidos corporativos: artículos, comunicados y publicaciones institucionales con editor integrado. + +#### 🎫 Módulo 5 — Ticketing e Incidencias +Creación, flujo y asignación de tickets internos con gestión de SLAs y prioridades de soporte técnico y operativo. + +--- + +## 3. Stack Tecnológico + +| Capa | Tecnología | Versión | Justificación | +|---|---|---|---| +| **Frontend** | Angular | 18.0 | Framework enterprise estándar de Google. Signals natively reactive. | +| **Backend** | NestJS | 10.0 | Framework TypeScript para APIs REST escalables y modulares | +| **Autenticación** | Azure Entra ID + JWT | — | SSO corporativo con Passport.js. Sin gestión de contraseñas propias | +| **Base de Datos** | PostgreSQL | 15 | Transaccional, ACID-compliant. Crítico para reservas sin overbooking | +| **ORM** | Prisma | — | Migraciones tipeadas, esquema declarativo | +| **Contenedores** | Docker + Docker Compose | — | Portabilidad total entre entornos | +| **Estilos** | TailwindCSS + Vanilla CSS | — | Sistema de diseño Executive Suite personalizado | +| **IA Parser** | API de IA (NLP) | — | Procesamiento de lenguaje natural para reservas conversacionales | + +### 3.1 Arquitectura de Seguridad SSO + +``` +Usuario → App Angular → Azure Entra ID (login) + ↓ + Token JWT (con roles AD) + ↓ + Backend NestJS valida token + con passport-azure-ad / passport-jwt + ↓ + Acceso según rol: employee | manager | admin +``` + +**Ninguna contraseña se almacena en Kinetix.** La autenticación es 100% delegada a Active Directory de la empresa. + +--- + +## 4. Arquitectura del Sistema + +``` +kinetix/ +├── frontend/ ← SPA Angular 18 (Standalone Components + Signals) +│ └── src/app/ +│ ├── core/ ← Guards SSO, interceptores HTTP, auth services +│ ├── shared/ ← Componentes reutilizables del Design System +│ └── features/ ← Módulos por dominio (reservas, directorio, etc.) +│ +├── backend/ ← API REST NestJS (TypeScript modular) +│ └── src/ +│ ├── auth/ ← Módulo SSO Active Directory / JWT +│ ├── cms/ ← Gestión de contenidos +│ ├── directory/ ← Directorio corporativo +│ ├── workflow/ ← Motor de aprobaciones +│ ├── spaces/ ← Reservas de espacios (con anti-overbooking) +│ └── ticketing/ ← Sistema de incidencias +│ +├── standards/ ← Documentación técnica y estándares del proyecto +├── docker-compose.yml ← Infraestructura local: PostgreSQL + pgAdmin +└── setup.ps1 / .sh ← Scripts de onboarding para nuevos devs +``` + +--- + +## 5. Diseño e Interfaz de Usuario + +Kinetix utiliza el **Executive Suite Design System**, un sistema de diseño corporativo premium desarrollado a medida: + +- **Paleta:** Ivory `#F5F4F1` + Negro sofisticado `#0F0D0A` + Dorado bronce `#C9973C` +- **Tarjetas Joya (Blackjack System):** Fondos oscuros en Navy, Plum, Viridian, Espresso para los módulos principales +- **Tipografía:** Inter (titulares) + Outfit (cuerpo) +- **Glassmorphism y micro-animaciones** para interactividad premium +- **Responsive design** completo: mobile, tablet, desktop +- **Sidebar colapsable** con estado de sistema en tiempo real + +> El diseño fue validado contra estándares documentados en `standards/04-ui-ux-neon.md`. + +--- + +## 6. Estado Actual del Proyecto + +| Módulo | Estado | Detalle | +|---|---|---| +| Autenticación SSO | ✅ Implementado | JWT + Guards + Interceptores HTTP | +| Dashboard principal | ✅ Implementado | KPIs, cards de módulos, navegación | +| Directorio de Personal | ✅ Implementado | Búsqueda reactiva, filtros, tarjetas AD | +| Calendario de Reservas | ✅ Implementado | Vistas Mes/Semana/Día, filtros dinámicos | +| Formulario de Reserva + IA | ✅ Implementado | Parser NLP con mock local y API IA | +| Workflow de Aprobación | 🔄 En desarrollo | Motor de reglas, vistas de simulación | +| Info Hub / CMS | 📋 Planificado | Editor WYSIWYG, taxonomías | +| Ticketing | 📋 Planificado | SLA, asignación, prioridades | +| Base de datos PostgreSQL | ✅ Configurado | Docker Compose, schema documentado | +| Despliegue cloud | 📋 Pendiente | Azure / AWS / on-premise | + +--- + +## 7. Plan de Acción (Roadmap de Entrega) + +### Fase 0 — Diagnóstico e Integración *(Semanas 1–2)* + +| Actividad | Responsable | Entregable | +|---|---|---| +| Reunión de kickoff con equipo técnico | Arquitecta + IT Cliente | Acta de requerimientos | +| Auditoría del Active Directory existente | Arquitecta + Admin AD | Mapeo de grupos, roles y UOs | +| Definición de ambientes (dev / staging / prod) | DevOps | Documento de infraestructura | +| Firma de NDA y contrato de desarrollo | Legal | Contrato ejecutado | + +### Fase 1 — Setup e Integración SSO *(Semanas 3–4)* + +| Actividad | Entregable | +|---|---| +| Configurar Azure Entra ID con la tenant del cliente | App registrada en Azure Portal | +| Integrar OAuth2 + JWT con roles reales de AD | Auth funcional en entorno del cliente | +| Onboarding técnico al equipo de IT interno | Workshop de 4 horas | +| Setup de base de datos PostgreSQL en producción | DB productiva con migraciones aplicadas | + +**Hito:** ✅ Login con credenciales reales de la empresa funcionando. + +### Fase 2 — Módulos Core *(Semanas 5–10)* + +| Semana | Módulo | Entregable | +|---|---|---| +| 5–6 | Directorio Corporativo | Perfiles sincronizados con AD real | +| 7–8 | Gestión de Espacios + IA | Calendario + reservas en producción | +| 9–10 | Workflow de Aprobación | Motor de reglas con notificaciones | + +**Hito:** ✅ Piloto con 50 usuarios internos en entorno staging. + +### Fase 3 — Módulos Secundarios *(Semanas 11–14)* + +| Semana | Módulo | Entregable | +|---|---|---| +| 11–12 | Info Hub / CMS | Editor de artículos operativo | +| 13–14 | Ticketing e Incidencias | Sistema de tickets con SLA | + +**Hito:** ✅ Plataforma completa en staging. UAT (User Acceptance Testing). + +### Fase 4 — Lanzamiento *(Semanas 15–16)* + +| Actividad | Entregable | +|---|---| +| Despliegue en producción cloud/on-premise | Plataforma 100% en vivo | +| Capacitación a usuarios finales (por roles) | Manual de usuario + sesiones | +| Documentación técnica de operación | Runbook de DevOps | +| Soporte post-lanzamiento (30 días incluidos) | Canal de soporte dedicado | + +**Hito:** 🚀 Go-Live oficial de Kinetix para [Empresa]. + +--- + +## 8. Diagrama de Fases + +```mermaid +gantt + title Roadmap de Implementación Kinetix + dateFormat YYYY-MM-DD + section Fase 0 — Diagnóstico + Kickoff & Auditoría AD :2026-07-01, 7d + Definición de Ambientes :2026-07-07, 7d + section Fase 1 — SSO & Setup + Azure Entra ID Integration :2026-07-14, 7d + PostgreSQL Producción :2026-07-21, 7d + section Fase 2 — Core + Directorio Corporativo :2026-07-28, 14d + Gestión de Espacios + IA :2026-08-11, 14d + Workflow de Aprobación :2026-08-25, 14d + section Fase 3 — Secundarios + Info Hub / CMS :2026-09-08, 14d + Ticketing & Incidencias :2026-09-22, 14d + section Fase 4 — Lanzamiento + Despliegue Producción :2026-10-06, 7d + Capacitación & Go-Live :2026-10-13, 7d +``` + +--- + +## 9. Propuesta Económica + +### Modelo de Precios Sugerido + +> [!NOTE] +> Los siguientes valores son referenciales y ajustables según el tamaño de la organización, cantidad de usuarios y requerimientos adicionales de personalización. + +| Concepto | Descripción | Estimado | +|---|---|---| +| **Licencia de Plataforma** | Uso de la plataforma Kinetix base | A negociar | +| **Implementación e Integración** | Fases 0–1: Setup, SSO, infraestructura | 4–6 semanas | +| **Desarrollo de Módulos Core** | Fase 2: Directorio, Espacios, Workflow | 6 semanas | +| **Módulos Secundarios** | Fase 3: CMS, Ticketing | 4 semanas | +| **Soporte Post-Lanzamiento** | 30 días incluidos + contratos de mantenimiento opcionales | Incluido | + +#### Modalidades de Contratación + +| Modalidad | Descripción | Recomendado para | +|---|---|---| +| **Llave en mano** | Precio fijo por entrega completa | Empresas con presupuesto definido | +| **Tiempo y materiales** | Pago por hora/sprint según avance | Proyectos con alcance variable | +| **SaaS Mensual** | Hosting + mantenimiento + actualizaciones | Empresas que prefieren OpEx | + +--- + +## 10. Por Qué Kinetix sobre Alternativas + +| Criterio | Kinetix | SharePoint | Confluence | Desarrollo a medida desde cero | +|---|---|---|---|---| +| Integración AD nativa | ✅ | ✅ | ⚠️ Plugin | ✅ | +| Personalizable 100% | ✅ | ⚠️ Limitado | ⚠️ Limitado | ✅ | +| Stack moderno (Angular + NestJS) | ✅ | ❌ | ❌ | ✅ | +| Asistente IA integrado | ✅ | ❌ | ❌ | Solo si se desarrolla | +| Tiempo de implementación | **~4 meses** | 3–12 meses | 2–6 meses | 12–18 meses | +| Costo de implementación | **Medio** | Alto | Medio | Muy alto | +| Curva de aprendizaje usuarios | Baja | Alta | Media | Variable | + +--- + +## 11. Garantías y Compromisos + +- ✅ **Código fuente entregado:** La empresa es propietaria del código al finalizar el proyecto +- ✅ **Documentación técnica completa:** Estándares, esquemas DB, guías de API +- ✅ **Sin vendor lock-in:** Stack 100% open source (Angular, NestJS, PostgreSQL) +- ✅ **Seguridad by design:** OWASP Top 10, JWT validation, HTTPS obligatorio +- ✅ **Escalabilidad:** Arquitectura modular preparada para 10,000+ usuarios +- ✅ **Soporte 30 días post-lanzamiento** incluido en el contrato base + +--- + +## 12. Próximos Pasos + +1. **Reunión de presentación** (30–45 min): Demo en vivo de la plataforma actual +2. **Workshop técnico** (2h): Revisión de requerimientos específicos con IT y RR.HH. +3. **Propuesta económica formal** ajustada al volumen de usuarios y módulos requeridos +4. **Firma de NDA** para compartir acceso al repositorio y documentación interna +5. **Kick-off** de Fase 0 + +--- + +## Contacto + +**Jess Miller** +Arquitecta de Software Senior · Full-Stack Engineer +📧 jess@kinetix.dev +🔗 github.com/kinetix + +--- + +*Documento confidencial. Preparado exclusivamente para [Nombre de la Empresa]. Prohibida su reproducción parcial o total sin autorización expresa.* diff --git a/doc/walkthrough.md b/doc/walkthrough.md new file mode 100644 index 0000000..77fc232 --- /dev/null +++ b/doc/walkthrough.md @@ -0,0 +1,29 @@ +# Walkthrough: Módulo de Autogestión de Espacios y Activos con Asistente IA + +Se ha completado la arquitectura e implementación del módulo de gestión de reservas en el frontend de Kinetix utilizando Angular 18 (Standalone Components, Signals y enrutamiento perezoso). + +## Cambios Realizados + +### Servicio de IA +- [ia-parser.service.ts](file:///d:/Jess/agent/Kinetix/frontend/src/app/features/reservas/services/ia-parser.service.ts): Servicio de comunicación NLP/IA con parseo heurístico inteligente como fallback de desarrollo para auto-llenar los formularios mediante prompts en lenguaje natural. + +### Componentes de Presentación e Interfaz +- [calendar-booking.component.ts](file:///d:/Jess/agent/Kinetix/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.ts): Vista del calendario interactivo a pantalla completa con soporte de vistas y filtrado reactivo de salas, escritorios y activos. +- [calendar-booking.component.html](file:///d:/Jess/agent/Kinetix/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.html): Estructura visual responsiva estilizada con el **Executive Suite Design System** (paleta Ivory y tarjetas joya de alta gama). +- [reserva-form.component.ts](file:///d:/Jess/agent/Kinetix/frontend/src/app/features/reservas/reserva-form/reserva-form.component.ts): Lógica del formulario de reserva que escucha `QueryParams` para pre-seleccionar fecha/tipo de recurso y gestiona el binding con el Asistente IA. +- [reserva-form.component.html](file:///d:/Jess/agent/Kinetix/frontend/src/app/features/reservas/reserva-form/reserva-form.component.html): Diseño elegante de dos paneles que destaca la interfaz del Asistente de IA y provee inputs completamente accesibles. + +### Enrutamiento +- [app.routes.ts](file:///d:/Jess/agent/Kinetix/frontend/src/app/app.routes.ts): Agregado el cargado perezoso (Lazy Loading) para las rutas principales del módulo de reservas (`/reservas/calendario` y `/reservas/nuevo`). + +--- + +## Verificación de Compilación + +La compilación se completó exitosamente sin errores: +```bash +npx ng build --configuration development +``` +Generó los siguientes fragmentos perezosos sin comprometer el tamaño inicial del bundle principal: +- `features-reservas-reserva-form-reserva-form-component` (407.74 kB) +- `features-reservas-calendar-booking-calendar-booking-component` (27.11 kB) diff --git a/frontend/angular.json b/frontend/angular.json index b6ff011..9541579 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -34,15 +34,11 @@ "configurations": { "production": { "budgets": [ - { - "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" - }, { "type": "anyComponentStyle", - "maximumWarning": "6kb", - "maximumError": "8kb" + "baseline": "5kb", + "maximumWarning": "15kb", + "maximumError": "20kb" } ], "outputHashing": "all" @@ -73,4 +69,4 @@ "cli": { "analytics": "fc6d1cf8-556d-44ba-883b-4df33dd8aff3" } -} +} \ No newline at end of file diff --git a/frontend/dist/kinetix-frontend/3rdpartylicenses.txt b/frontend/dist/kinetix-frontend/3rdpartylicenses.txt deleted file mode 100644 index fa316c1..0000000 --- a/frontend/dist/kinetix-frontend/3rdpartylicenses.txt +++ /dev/null @@ -1,259 +0,0 @@ -@angular/common -MIT - -@angular/core -MIT - -@angular/forms -MIT - -@angular/platform-browser -MIT - -@angular/router -MIT - -rxjs -Apache-2.0 - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - -tslib -0BSD -Copyright (c) Microsoft Corporation. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - -zone.js -MIT -The MIT License - -Copyright (c) 2010-2024 Google LLC. https://angular.io/license - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/frontend/dist/kinetix-frontend/index.html b/frontend/dist/kinetix-frontend/index.html deleted file mode 100644 index a8fd441..0000000 --- a/frontend/dist/kinetix-frontend/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - Kinetix Intranet - - - - - - - - diff --git a/frontend/dist/kinetix-frontend/polyfills.db3ad031cea79ddb.js b/frontend/dist/kinetix-frontend/polyfills.db3ad031cea79ddb.js deleted file mode 100644 index bcd78a3..0000000 --- a/frontend/dist/kinetix-frontend/polyfills.db3ad031cea79ddb.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkkinetix_frontend=self.webpackChunkkinetix_frontend||[]).push([[461],{853:()=>{const te=globalThis;function ee(e){return(te.__Zone_symbol_prefix||"__zone_symbol__")+e}const ke=Object.getOwnPropertyDescriptor,Ne=Object.defineProperty,Le=Object.getPrototypeOf,_t=Object.create,Et=Array.prototype.slice,Ie="addEventListener",Me="removeEventListener",Ze=ee(Ie),Ae=ee(Me),ae="true",le="false",ve=ee("");function je(e,r){return Zone.current.wrap(e,r)}function He(e,r,c,t,i){return Zone.current.scheduleMacroTask(e,r,c,t,i)}const j=ee,we=typeof window<"u",Te=we?window:void 0,$=we&&Te||globalThis,Tt="removeAttribute";function xe(e,r){for(let c=e.length-1;c>=0;c--)"function"==typeof e[c]&&(e[c]=je(e[c],r+"_"+c));return e}function We(e){return!e||!1!==e.writable&&!("function"==typeof e.get&&typeof e.set>"u")}const qe=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,Re=!("nw"in $)&&typeof $.process<"u"&&"[object process]"===$.process.toString(),Ve=!Re&&!qe&&!(!we||!Te.HTMLElement),Xe=typeof $.process<"u"&&"[object process]"===$.process.toString()&&!qe&&!(!we||!Te.HTMLElement),Ce={},yt=j("enable_beforeunload"),Ye=function(e){if(!(e=e||$.event))return;let r=Ce[e.type];r||(r=Ce[e.type]=j("ON_PROPERTY"+e.type));const c=this||e.target||$,t=c[r];let i;return Ve&&c===Te&&"error"===e.type?(i=t&&t.call(this,e.message,e.filename,e.lineno,e.colno,e.error),!0===i&&e.preventDefault()):(i=t&&t.apply(this,arguments),"beforeunload"===e.type&&$[yt]&&"string"==typeof i?e.returnValue=i:null!=i&&!i&&e.preventDefault()),i};function $e(e,r,c){let t=ke(e,r);if(!t&&c&&ke(c,r)&&(t={enumerable:!0,configurable:!0}),!t||!t.configurable)return;const i=j("on"+r+"patched");if(e.hasOwnProperty(i)&&e[i])return;delete t.writable,delete t.value;const u=t.get,E=t.set,T=r.slice(2);let m=Ce[T];m||(m=Ce[T]=j("ON_PROPERTY"+T)),t.set=function(D){let d=this;!d&&e===$&&(d=$),d&&("function"==typeof d[m]&&d.removeEventListener(T,Ye),E&&E.call(d,null),d[m]=D,"function"==typeof D&&d.addEventListener(T,Ye,!1))},t.get=function(){let D=this;if(!D&&e===$&&(D=$),!D)return null;const d=D[m];if(d)return d;if(u){let w=u.call(this);if(w)return t.set.call(this,w),"function"==typeof D[Tt]&&D.removeAttribute(r),w}return null},Ne(e,r,t),e[i]=!0}function Je(e,r,c){if(r)for(let t=0;tfunction(E,T){const m=c(E,T);return m.cbIdx>=0&&"function"==typeof T[m.cbIdx]?He(m.name,T[m.cbIdx],m,i):u.apply(E,T)})}function fe(e,r){e[j("OriginalDelegate")]=r}let Ke=!1,Ge=!1;function kt(){if(Ke)return Ge;Ke=!0;try{const e=Te.navigator.userAgent;(-1!==e.indexOf("MSIE ")||-1!==e.indexOf("Trident/")||-1!==e.indexOf("Edge/"))&&(Ge=!0)}catch{}return Ge}function Qe(e){return"function"==typeof e}function et(e){return"number"==typeof e}let ge=!1;if(typeof window<"u")try{const e=Object.defineProperty({},"passive",{get:function(){ge=!0}});window.addEventListener("test",e,e),window.removeEventListener("test",e,e)}catch{ge=!1}const vt={useG:!0},ne={},tt={},nt=new RegExp("^"+ve+"(\\w+)(true|false)$"),rt=j("propagationStopped");function ot(e,r){const c=(r?r(e):e)+le,t=(r?r(e):e)+ae,i=ve+c,u=ve+t;ne[e]={},ne[e][le]=i,ne[e][ae]=u}function bt(e,r,c,t){const i=t&&t.add||Ie,u=t&&t.rm||Me,E=t&&t.listeners||"eventListeners",T=t&&t.rmAll||"removeAllListeners",m=j(i),D="."+i+":",d="prependListener",w="."+d+":",Z=function(k,h,H){if(k.isRemoved)return;const V=k.callback;let Y;"object"==typeof V&&V.handleEvent&&(k.callback=g=>V.handleEvent(g),k.originalDelegate=V);try{k.invoke(k,h,[H])}catch(g){Y=g}const G=k.options;return G&&"object"==typeof G&&G.once&&h[u].call(h,H.type,k.originalDelegate?k.originalDelegate:k.callback,G),Y};function x(k,h,H){if(!(h=h||e.event))return;const V=k||h.target||e,Y=V[ne[h.type][H?ae:le]];if(Y){const G=[];if(1===Y.length){const g=Z(Y[0],V,h);g&&G.push(g)}else{const g=Y.slice();for(let z=0;z{throw z})}}}const U=function(k){return x(this,k,!1)},J=function(k){return x(this,k,!0)};function K(k,h){if(!k)return!1;let H=!0;h&&void 0!==h.useG&&(H=h.useG);const V=h&&h.vh;let Y=!0;h&&void 0!==h.chkDup&&(Y=h.chkDup);let G=!1;h&&void 0!==h.rt&&(G=h.rt);let g=k;for(;g&&!g.hasOwnProperty(i);)g=Le(g);if(!g&&k[i]&&(g=k),!g||g[m])return!1;const z=h&&h.eventNameToString,O={},R=g[m]=g[i],b=g[j(u)]=g[u],S=g[j(E)]=g[E],Q=g[j(T)]=g[T];let W;h&&h.prepend&&(W=g[j(h.prepend)]=g[h.prepend]);const q=H?function(s){if(!O.isExisting)return R.call(O.target,O.eventName,O.capture?J:U,O.options)}:function(s){return R.call(O.target,O.eventName,s.invoke,O.options)},A=H?function(s){if(!s.isRemoved){const l=ne[s.eventName];let v;l&&(v=l[s.capture?ae:le]);const C=v&&s.target[v];if(C)for(let y=0;yse.zone.cancelTask(se);s.call(me,"abort",ce,{once:!0}),se.removeAbortListener=()=>me.removeEventListener("abort",ce)}return O.target=null,Pe&&(Pe.taskData=null),lt&&(O.options.once=!0),!ge&&"boolean"==typeof se.options||(se.options=ie),se.target=I,se.capture=Be,se.eventName=M,B&&(se.originalDelegate=F),L?pe.unshift(se):pe.push(se),y?I:void 0}};return g[i]=a(R,D,q,A,G),W&&(g[d]=a(W,w,function(s){return W.call(O.target,O.eventName,s.invoke,O.options)},A,G,!0)),g[u]=function(){const s=this||e;let l=arguments[0];h&&h.transferEventName&&(l=h.transferEventName(l));const v=arguments[2],C=!!v&&("boolean"==typeof v||v.capture),y=arguments[1];if(!y)return b.apply(this,arguments);if(V&&!V(b,y,s,arguments))return;const L=ne[l];let I;L&&(I=L[C?ae:le]);const M=I&&s[I];if(M)for(let F=0;Ffunction(i,u){i[rt]=!0,t&&t.apply(i,u)})}const De=j("zoneTask");function ye(e,r,c,t){let i=null,u=null;c+=t;const E={};function T(D){const d=D.data;d.args[0]=function(){return D.invoke.apply(this,arguments)};const w=i.apply(e,d.args);return et(w)?d.handleId=w:(d.handle=w,d.isRefreshable=Qe(w.refresh)),D}function m(D){const{handle:d,handleId:w}=D.data;return u.call(e,d??w)}i=ue(e,r+=t,D=>function(d,w){if(Qe(w[0])){const Z={isRefreshable:!1,isPeriodic:"Interval"===t,delay:"Timeout"===t||"Interval"===t?w[1]||0:void 0,args:w},x=w[0];w[0]=function(){try{return x.apply(this,arguments)}finally{const{handle:H,handleId:V,isPeriodic:Y,isRefreshable:G}=Z;!Y&&!G&&(V?delete E[V]:H&&(H[De]=null))}};const U=He(r,w[0],Z,T,m);if(!U)return U;const{handleId:J,handle:K,isRefreshable:X,isPeriodic:k}=U.data;if(J)E[J]=U;else if(K&&(K[De]=U,X&&!k)){const h=K.refresh;K.refresh=function(){const{zone:H,state:V}=U;return"notScheduled"===V?(U._state="scheduled",H._updateTaskCount(U,1)):"running"===V&&(U._state="scheduling"),h.call(this)}}return K??J??U}return D.apply(e,w)}),u=ue(e,c,D=>function(d,w){const Z=w[0];let x;et(Z)?(x=E[Z],delete E[Z]):(x=Z?.[De],x?Z[De]=null:x=Z),x?.type?x.cancelFn&&x.zone.cancelTask(x):D.apply(e,w)})}function it(e,r,c){if(!c||0===c.length)return r;const t=c.filter(u=>u.target===e);if(!t||0===t.length)return r;const i=t[0].ignoreProperties;return r.filter(u=>-1===i.indexOf(u))}function ct(e,r,c,t){e&&Je(e,it(e,r,c),t)}function Fe(e){return Object.getOwnPropertyNames(e).filter(r=>r.startsWith("on")&&r.length>2).map(r=>r.substring(2))}function It(e,r,c,t,i){const u=Zone.__symbol__(t);if(r[u])return;const E=r[u]=r[t];r[t]=function(T,m,D){return m&&m.prototype&&i.forEach(function(d){const w=`${c}.${t}::`+d,Z=m.prototype;try{if(Z.hasOwnProperty(d)){const x=e.ObjectGetOwnPropertyDescriptor(Z,d);x&&x.value?(x.value=e.wrapWithCurrentZone(x.value,w),e._redefineProperty(m.prototype,d,x)):Z[d]&&(Z[d]=e.wrapWithCurrentZone(Z[d],w))}else Z[d]&&(Z[d]=e.wrapWithCurrentZone(Z[d],w))}catch{}}),E.call(r,T,m,D)},e.attachOriginToPatched(r[t],E)}const at=function Oe(){const e=globalThis,r=!0===e[ee("forceDuplicateZoneCheck")];if(e.Zone&&(r||"function"!=typeof e.Zone.__symbol__))throw new Error("Zone already loaded.");return e.Zone??=function ze(){const e=te.performance;function r(N){e&&e.mark&&e.mark(N)}function c(N,_){e&&e.measure&&e.measure(N,_)}r("Zone");let t=(()=>{class N{static{this.__symbol__=ee}static assertZonePatched(){if(te.Promise!==O.ZoneAwarePromise)throw new Error("Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten.\nMost likely cause is that a Promise polyfill has been loaded after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. If you must load one, do so before loading zone.js.)")}static get root(){let n=N.current;for(;n.parent;)n=n.parent;return n}static get current(){return b.zone}static get currentTask(){return S}static __load_patch(n,o,p=!1){if(O.hasOwnProperty(n)){const P=!0===te[ee("forceDuplicateZoneCheck")];if(!p&&P)throw Error("Already loaded patch: "+n)}else if(!te["__Zone_disable_"+n]){const P="Zone:"+n;r(P),O[n]=o(te,N,R),c(P,P)}}get parent(){return this._parent}get name(){return this._name}constructor(n,o){this._parent=n,this._name=o?o.name||"unnamed":"",this._properties=o&&o.properties||{},this._zoneDelegate=new u(this,this._parent&&this._parent._zoneDelegate,o)}get(n){const o=this.getZoneWith(n);if(o)return o._properties[n]}getZoneWith(n){let o=this;for(;o;){if(o._properties.hasOwnProperty(n))return o;o=o._parent}return null}fork(n){if(!n)throw new Error("ZoneSpec required!");return this._zoneDelegate.fork(this,n)}wrap(n,o){if("function"!=typeof n)throw new Error("Expecting function got: "+n);const p=this._zoneDelegate.intercept(this,n,o),P=this;return function(){return P.runGuarded(p,this,arguments,o)}}run(n,o,p,P){b={parent:b,zone:this};try{return this._zoneDelegate.invoke(this,n,o,p,P)}finally{b=b.parent}}runGuarded(n,o=null,p,P){b={parent:b,zone:this};try{try{return this._zoneDelegate.invoke(this,n,o,p,P)}catch(q){if(this._zoneDelegate.handleError(this,q))throw q}}finally{b=b.parent}}runTask(n,o,p){if(n.zone!=this)throw new Error("A task can only be run in the zone of creation! (Creation: "+(n.zone||K).name+"; Execution: "+this.name+")");const P=n,{type:q,data:{isPeriodic:A=!1,isRefreshable:_e=!1}={}}=n;if(n.state===X&&(q===z||q===g))return;const he=n.state!=H;he&&P._transitionTo(H,h);const de=S;S=P,b={parent:b,zone:this};try{q==g&&n.data&&!A&&!_e&&(n.cancelFn=void 0);try{return this._zoneDelegate.invokeTask(this,P,o,p)}catch(oe){if(this._zoneDelegate.handleError(this,oe))throw oe}}finally{const oe=n.state;if(oe!==X&&oe!==Y)if(q==z||A||_e&&oe===k)he&&P._transitionTo(h,H,k);else{const f=P._zoneDelegates;this._updateTaskCount(P,-1),he&&P._transitionTo(X,H,X),_e&&(P._zoneDelegates=f)}b=b.parent,S=de}}scheduleTask(n){if(n.zone&&n.zone!==this){let p=this;for(;p;){if(p===n.zone)throw Error(`can not reschedule task to ${this.name} which is descendants of the original zone ${n.zone.name}`);p=p.parent}}n._transitionTo(k,X);const o=[];n._zoneDelegates=o,n._zone=this;try{n=this._zoneDelegate.scheduleTask(this,n)}catch(p){throw n._transitionTo(Y,k,X),this._zoneDelegate.handleError(this,p),p}return n._zoneDelegates===o&&this._updateTaskCount(n,1),n.state==k&&n._transitionTo(h,k),n}scheduleMicroTask(n,o,p,P){return this.scheduleTask(new E(G,n,o,p,P,void 0))}scheduleMacroTask(n,o,p,P,q){return this.scheduleTask(new E(g,n,o,p,P,q))}scheduleEventTask(n,o,p,P,q){return this.scheduleTask(new E(z,n,o,p,P,q))}cancelTask(n){if(n.zone!=this)throw new Error("A task can only be cancelled in the zone of creation! (Creation: "+(n.zone||K).name+"; Execution: "+this.name+")");if(n.state===h||n.state===H){n._transitionTo(V,h,H);try{this._zoneDelegate.cancelTask(this,n)}catch(o){throw n._transitionTo(Y,V),this._zoneDelegate.handleError(this,o),o}return this._updateTaskCount(n,-1),n._transitionTo(X,V),n.runCount=-1,n}}_updateTaskCount(n,o){const p=n._zoneDelegates;-1==o&&(n._zoneDelegates=null);for(let P=0;PN.hasTask(n,o),onScheduleTask:(N,_,n,o)=>N.scheduleTask(n,o),onInvokeTask:(N,_,n,o,p,P)=>N.invokeTask(n,o,p,P),onCancelTask:(N,_,n,o)=>N.cancelTask(n,o)};class u{get zone(){return this._zone}constructor(_,n,o){this._taskCounts={microTask:0,macroTask:0,eventTask:0},this._zone=_,this._parentDelegate=n,this._forkZS=o&&(o&&o.onFork?o:n._forkZS),this._forkDlgt=o&&(o.onFork?n:n._forkDlgt),this._forkCurrZone=o&&(o.onFork?this._zone:n._forkCurrZone),this._interceptZS=o&&(o.onIntercept?o:n._interceptZS),this._interceptDlgt=o&&(o.onIntercept?n:n._interceptDlgt),this._interceptCurrZone=o&&(o.onIntercept?this._zone:n._interceptCurrZone),this._invokeZS=o&&(o.onInvoke?o:n._invokeZS),this._invokeDlgt=o&&(o.onInvoke?n:n._invokeDlgt),this._invokeCurrZone=o&&(o.onInvoke?this._zone:n._invokeCurrZone),this._handleErrorZS=o&&(o.onHandleError?o:n._handleErrorZS),this._handleErrorDlgt=o&&(o.onHandleError?n:n._handleErrorDlgt),this._handleErrorCurrZone=o&&(o.onHandleError?this._zone:n._handleErrorCurrZone),this._scheduleTaskZS=o&&(o.onScheduleTask?o:n._scheduleTaskZS),this._scheduleTaskDlgt=o&&(o.onScheduleTask?n:n._scheduleTaskDlgt),this._scheduleTaskCurrZone=o&&(o.onScheduleTask?this._zone:n._scheduleTaskCurrZone),this._invokeTaskZS=o&&(o.onInvokeTask?o:n._invokeTaskZS),this._invokeTaskDlgt=o&&(o.onInvokeTask?n:n._invokeTaskDlgt),this._invokeTaskCurrZone=o&&(o.onInvokeTask?this._zone:n._invokeTaskCurrZone),this._cancelTaskZS=o&&(o.onCancelTask?o:n._cancelTaskZS),this._cancelTaskDlgt=o&&(o.onCancelTask?n:n._cancelTaskDlgt),this._cancelTaskCurrZone=o&&(o.onCancelTask?this._zone:n._cancelTaskCurrZone),this._hasTaskZS=null,this._hasTaskDlgt=null,this._hasTaskDlgtOwner=null,this._hasTaskCurrZone=null;const p=o&&o.onHasTask;(p||n&&n._hasTaskZS)&&(this._hasTaskZS=p?o:i,this._hasTaskDlgt=n,this._hasTaskDlgtOwner=this,this._hasTaskCurrZone=this._zone,o.onScheduleTask||(this._scheduleTaskZS=i,this._scheduleTaskDlgt=n,this._scheduleTaskCurrZone=this._zone),o.onInvokeTask||(this._invokeTaskZS=i,this._invokeTaskDlgt=n,this._invokeTaskCurrZone=this._zone),o.onCancelTask||(this._cancelTaskZS=i,this._cancelTaskDlgt=n,this._cancelTaskCurrZone=this._zone))}fork(_,n){return this._forkZS?this._forkZS.onFork(this._forkDlgt,this.zone,_,n):new t(_,n)}intercept(_,n,o){return this._interceptZS?this._interceptZS.onIntercept(this._interceptDlgt,this._interceptCurrZone,_,n,o):n}invoke(_,n,o,p,P){return this._invokeZS?this._invokeZS.onInvoke(this._invokeDlgt,this._invokeCurrZone,_,n,o,p,P):n.apply(o,p)}handleError(_,n){return!this._handleErrorZS||this._handleErrorZS.onHandleError(this._handleErrorDlgt,this._handleErrorCurrZone,_,n)}scheduleTask(_,n){let o=n;if(this._scheduleTaskZS)this._hasTaskZS&&o._zoneDelegates.push(this._hasTaskDlgtOwner),o=this._scheduleTaskZS.onScheduleTask(this._scheduleTaskDlgt,this._scheduleTaskCurrZone,_,n),o||(o=n);else if(n.scheduleFn)n.scheduleFn(n);else{if(n.type!=G)throw new Error("Task is missing scheduleFn.");U(n)}return o}invokeTask(_,n,o,p){return this._invokeTaskZS?this._invokeTaskZS.onInvokeTask(this._invokeTaskDlgt,this._invokeTaskCurrZone,_,n,o,p):n.callback.apply(o,p)}cancelTask(_,n){let o;if(this._cancelTaskZS)o=this._cancelTaskZS.onCancelTask(this._cancelTaskDlgt,this._cancelTaskCurrZone,_,n);else{if(!n.cancelFn)throw Error("Task is not cancelable");o=n.cancelFn(n)}return o}hasTask(_,n){try{this._hasTaskZS&&this._hasTaskZS.onHasTask(this._hasTaskDlgt,this._hasTaskCurrZone,_,n)}catch(o){this.handleError(_,o)}}_updateTaskCount(_,n){const o=this._taskCounts,p=o[_],P=o[_]=p+n;if(P<0)throw new Error("More tasks executed then were scheduled.");0!=p&&0!=P||this.hasTask(this._zone,{microTask:o.microTask>0,macroTask:o.macroTask>0,eventTask:o.eventTask>0,change:_})}}class E{constructor(_,n,o,p,P,q){if(this._zone=null,this.runCount=0,this._zoneDelegates=null,this._state="notScheduled",this.type=_,this.source=n,this.data=p,this.scheduleFn=P,this.cancelFn=q,!o)throw new Error("callback is not defined");this.callback=o;const A=this;this.invoke=_===z&&p&&p.useG?E.invokeTask:function(){return E.invokeTask.call(te,A,this,arguments)}}static invokeTask(_,n,o){_||(_=this),Q++;try{return _.runCount++,_.zone.runTask(_,n,o)}finally{1==Q&&J(),Q--}}get zone(){return this._zone}get state(){return this._state}cancelScheduleRequest(){this._transitionTo(X,k)}_transitionTo(_,n,o){if(this._state!==n&&this._state!==o)throw new Error(`${this.type} '${this.source}': can not transition to '${_}', expecting state '${n}'${o?" or '"+o+"'":""}, was '${this._state}'.`);this._state=_,_==X&&(this._zoneDelegates=null)}toString(){return this.data&&typeof this.data.handleId<"u"?this.data.handleId.toString():Object.prototype.toString.call(this)}toJSON(){return{type:this.type,state:this.state,source:this.source,zone:this.zone.name,runCount:this.runCount}}}const T=ee("setTimeout"),m=ee("Promise"),D=ee("then");let Z,d=[],w=!1;function x(N){if(Z||te[m]&&(Z=te[m].resolve(0)),Z){let _=Z[D];_||(_=Z.then),_.call(Z,N)}else te[T](N,0)}function U(N){0===Q&&0===d.length&&x(J),N&&d.push(N)}function J(){if(!w){for(w=!0;d.length;){const N=d;d=[];for(let _=0;_b,onUnhandledError:W,microtaskDrainDone:W,scheduleMicroTask:U,showUncaughtError:()=>!t[ee("ignoreConsoleErrorUncaughtError")],patchEventTarget:()=>[],patchOnProperties:W,patchMethod:()=>W,bindArguments:()=>[],patchThen:()=>W,patchMacroTask:()=>W,patchEventPrototype:()=>W,isIEOrEdge:()=>!1,getGlobalObjects:()=>{},ObjectDefineProperty:()=>W,ObjectGetOwnPropertyDescriptor:()=>{},ObjectCreate:()=>{},ArraySlice:()=>[],patchClass:()=>W,wrapWithCurrentZone:()=>W,filterProperties:()=>[],attachOriginToPatched:()=>W,_redefineProperty:()=>W,patchCallbacks:()=>W,nativeScheduleMicroTask:x};let b={parent:null,zone:new t(null,null)},S=null,Q=0;function W(){}return c("Zone","Zone"),t}(),e.Zone}();(function Zt(e){(function Nt(e){e.__load_patch("ZoneAwarePromise",(r,c,t)=>{const i=Object.getOwnPropertyDescriptor,u=Object.defineProperty,T=t.symbol,m=[],D=!1!==r[T("DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION")],d=T("Promise"),w=T("then"),Z="__creationTrace__";t.onUnhandledError=f=>{if(t.showUncaughtError()){const a=f&&f.rejection;a?console.error("Unhandled Promise rejection:",a instanceof Error?a.message:a,"; Zone:",f.zone.name,"; Task:",f.task&&f.task.source,"; Value:",a,a instanceof Error?a.stack:void 0):console.error(f)}},t.microtaskDrainDone=()=>{for(;m.length;){const f=m.shift();try{f.zone.runGuarded(()=>{throw f.throwOriginal?f.rejection:f})}catch(a){U(a)}}};const x=T("unhandledPromiseRejectionHandler");function U(f){t.onUnhandledError(f);try{const a=c[x];"function"==typeof a&&a.call(this,f)}catch{}}function J(f){return f&&f.then}function K(f){return f}function X(f){return A.reject(f)}const k=T("state"),h=T("value"),H=T("finally"),V=T("parentPromiseValue"),Y=T("parentPromiseState"),G="Promise.then",g=null,z=!0,O=!1,R=0;function b(f,a){return s=>{try{N(f,a,s)}catch(l){N(f,!1,l)}}}const S=function(){let f=!1;return function(s){return function(){f||(f=!0,s.apply(null,arguments))}}},Q="Promise resolved with itself",W=T("currentTaskTrace");function N(f,a,s){const l=S();if(f===s)throw new TypeError(Q);if(f[k]===g){let v=null;try{("object"==typeof s||"function"==typeof s)&&(v=s&&s.then)}catch(C){return l(()=>{N(f,!1,C)})(),f}if(a!==O&&s instanceof A&&s.hasOwnProperty(k)&&s.hasOwnProperty(h)&&s[k]!==g)n(s),N(f,s[k],s[h]);else if(a!==O&&"function"==typeof v)try{v.call(s,l(b(f,a)),l(b(f,!1)))}catch(C){l(()=>{N(f,!1,C)})()}else{f[k]=a;const C=f[h];if(f[h]=s,f[H]===H&&a===z&&(f[k]=f[Y],f[h]=f[V]),a===O&&s instanceof Error){const y=c.currentTask&&c.currentTask.data&&c.currentTask.data[Z];y&&u(s,W,{configurable:!0,enumerable:!1,writable:!0,value:y})}for(let y=0;y{try{const L=f[h],I=!!s&&H===s[H];I&&(s[V]=L,s[Y]=C);const M=a.run(y,void 0,I&&y!==X&&y!==K?[]:[L]);N(s,!0,M)}catch(L){N(s,!1,L)}},s)}const P=function(){},q=r.AggregateError;class A{static toString(){return"function ZoneAwarePromise() { [native code] }"}static resolve(a){return a instanceof A?a:N(new this(null),z,a)}static reject(a){return N(new this(null),O,a)}static withResolvers(){const a={};return a.promise=new A((s,l)=>{a.resolve=s,a.reject=l}),a}static any(a){if(!a||"function"!=typeof a[Symbol.iterator])return Promise.reject(new q([],"All promises were rejected"));const s=[];let l=0;try{for(let y of a)l++,s.push(A.resolve(y))}catch{return Promise.reject(new q([],"All promises were rejected"))}if(0===l)return Promise.reject(new q([],"All promises were rejected"));let v=!1;const C=[];return new A((y,L)=>{for(let I=0;I{v||(v=!0,y(M))},M=>{C.push(M),l--,0===l&&(v=!0,L(new q(C,"All promises were rejected")))})})}static race(a){let s,l,v=new this((L,I)=>{s=L,l=I});function C(L){s(L)}function y(L){l(L)}for(let L of a)J(L)||(L=this.resolve(L)),L.then(C,y);return v}static all(a){return A.allWithCallback(a)}static allSettled(a){return(this&&this.prototype instanceof A?this:A).allWithCallback(a,{thenCallback:l=>({status:"fulfilled",value:l}),errorCallback:l=>({status:"rejected",reason:l})})}static allWithCallback(a,s){let l,v,C=new this((M,F)=>{l=M,v=F}),y=2,L=0;const I=[];for(let M of a){J(M)||(M=this.resolve(M));const F=L;try{M.then(B=>{I[F]=s?s.thenCallback(B):B,y--,0===y&&l(I)},B=>{s?(I[F]=s.errorCallback(B),y--,0===y&&l(I)):v(B)})}catch(B){v(B)}y++,L++}return y-=2,0===y&&l(I),C}constructor(a){const s=this;if(!(s instanceof A))throw new Error("Must be an instanceof Promise.");s[k]=g,s[h]=[];try{const l=S();a&&a(l(b(s,z)),l(b(s,O)))}catch(l){N(s,!1,l)}}get[Symbol.toStringTag](){return"Promise"}get[Symbol.species](){return A}then(a,s){let l=this.constructor?.[Symbol.species];(!l||"function"!=typeof l)&&(l=this.constructor||A);const v=new l(P),C=c.current;return this[k]==g?this[h].push(C,v,a,s):o(this,C,v,a,s),v}catch(a){return this.then(null,a)}finally(a){let s=this.constructor?.[Symbol.species];(!s||"function"!=typeof s)&&(s=A);const l=new s(P);l[H]=H;const v=c.current;return this[k]==g?this[h].push(v,l,a,a):o(this,v,l,a,a),l}}A.resolve=A.resolve,A.reject=A.reject,A.race=A.race,A.all=A.all;const _e=r[d]=r.Promise;r.Promise=A;const he=T("thenPatched");function de(f){const a=f.prototype,s=i(a,"then");if(s&&(!1===s.writable||!s.configurable))return;const l=a.then;a[w]=l,f.prototype.then=function(v,C){return new A((L,I)=>{l.call(this,L,I)}).then(v,C)},f[he]=!0}return t.patchThen=de,_e&&(de(_e),ue(r,"fetch",f=>function oe(f){return function(a,s){let l=f.apply(a,s);if(l instanceof A)return l;let v=l.constructor;return v[he]||de(v),l}}(f))),Promise[c.__symbol__("uncaughtPromiseErrors")]=m,A})})(e),function Lt(e){e.__load_patch("toString",r=>{const c=Function.prototype.toString,t=j("OriginalDelegate"),i=j("Promise"),u=j("Error"),E=function(){if("function"==typeof this){const d=this[t];if(d)return"function"==typeof d?c.call(d):Object.prototype.toString.call(d);if(this===Promise){const w=r[i];if(w)return c.call(w)}if(this===Error){const w=r[u];if(w)return c.call(w)}}return c.call(this)};E[t]=c,Function.prototype.toString=E;const T=Object.prototype.toString;Object.prototype.toString=function(){return"function"==typeof Promise&&this instanceof Promise?"[object Promise]":T.call(this)}})}(e),function Mt(e){e.__load_patch("util",(r,c,t)=>{const i=Fe(r);t.patchOnProperties=Je,t.patchMethod=ue,t.bindArguments=xe,t.patchMacroTask=mt;const u=c.__symbol__("BLACK_LISTED_EVENTS"),E=c.__symbol__("UNPATCHED_EVENTS");r[E]&&(r[u]=r[E]),r[u]&&(c[u]=c[E]=r[u]),t.patchEventPrototype=Pt,t.patchEventTarget=bt,t.isIEOrEdge=kt,t.ObjectDefineProperty=Ne,t.ObjectGetOwnPropertyDescriptor=ke,t.ObjectCreate=_t,t.ArraySlice=Et,t.patchClass=be,t.wrapWithCurrentZone=je,t.filterProperties=it,t.attachOriginToPatched=fe,t._redefineProperty=Object.defineProperty,t.patchCallbacks=It,t.getGlobalObjects=()=>({globalSources:tt,zoneSymbolEventNames:ne,eventNames:i,isBrowser:Ve,isMix:Xe,isNode:Re,TRUE_STR:ae,FALSE_STR:le,ZONE_SYMBOL_PREFIX:ve,ADD_EVENT_LISTENER_STR:Ie,REMOVE_EVENT_LISTENER_STR:Me})})}(e)})(at),function Ot(e){e.__load_patch("legacy",r=>{const c=r[e.__symbol__("legacyPatch")];c&&c()}),e.__load_patch("timers",r=>{const c="set",t="clear";ye(r,c,t,"Timeout"),ye(r,c,t,"Interval"),ye(r,c,t,"Immediate")}),e.__load_patch("requestAnimationFrame",r=>{ye(r,"request","cancel","AnimationFrame"),ye(r,"mozRequest","mozCancel","AnimationFrame"),ye(r,"webkitRequest","webkitCancel","AnimationFrame")}),e.__load_patch("blocking",(r,c)=>{const t=["alert","prompt","confirm"];for(let i=0;ifunction(D,d){return c.current.run(E,r,d,m)})}),e.__load_patch("EventTarget",(r,c,t)=>{(function Dt(e,r){r.patchEventPrototype(e,r)})(r,t),function Ct(e,r){if(Zone[r.symbol("patchEventTarget")])return;const{eventNames:c,zoneSymbolEventNames:t,TRUE_STR:i,FALSE_STR:u,ZONE_SYMBOL_PREFIX:E}=r.getGlobalObjects();for(let m=0;m{be("MutationObserver"),be("WebKitMutationObserver")}),e.__load_patch("IntersectionObserver",(r,c,t)=>{be("IntersectionObserver")}),e.__load_patch("FileReader",(r,c,t)=>{be("FileReader")}),e.__load_patch("on_property",(r,c,t)=>{!function St(e,r){if(Re&&!Xe||Zone[e.symbol("patchEvents")])return;const c=r.__Zone_ignore_on_properties;let t=[];if(Ve){const i=window;t=t.concat(["Document","SVGElement","Element","HTMLElement","HTMLBodyElement","HTMLMediaElement","HTMLFrameSetElement","HTMLFrameElement","HTMLIFrameElement","HTMLMarqueeElement","Worker"]);const u=function pt(){try{const e=Te.navigator.userAgent;if(-1!==e.indexOf("MSIE ")||-1!==e.indexOf("Trident/"))return!0}catch{}return!1}()?[{target:i,ignoreProperties:["error"]}]:[];ct(i,Fe(i),c&&c.concat(u),Le(i))}t=t.concat(["XMLHttpRequest","XMLHttpRequestEventTarget","IDBIndex","IDBRequest","IDBOpenDBRequest","IDBDatabase","IDBTransaction","IDBCursor","WebSocket"]);for(let i=0;i{!function Rt(e,r){const{isBrowser:c,isMix:t}=r.getGlobalObjects();(c||t)&&e.customElements&&"customElements"in e&&r.patchCallbacks(r,e.customElements,"customElements","define",["connectedCallback","disconnectedCallback","adoptedCallback","attributeChangedCallback","formAssociatedCallback","formDisabledCallback","formResetCallback","formStateRestoreCallback"])}(r,t)}),e.__load_patch("XHR",(r,c)=>{!function D(d){const w=d.XMLHttpRequest;if(!w)return;const Z=w.prototype;let U=Z[Ze],J=Z[Ae];if(!U){const R=d.XMLHttpRequestEventTarget;if(R){const b=R.prototype;U=b[Ze],J=b[Ae]}}const K="readystatechange",X="scheduled";function k(R){const b=R.data,S=b.target;S[E]=!1,S[m]=!1;const Q=S[u];U||(U=S[Ze],J=S[Ae]),Q&&J.call(S,K,Q);const W=S[u]=()=>{if(S.readyState===S.DONE)if(!b.aborted&&S[E]&&R.state===X){const _=S[c.__symbol__("loadfalse")];if(0!==S.status&&_&&_.length>0){const n=R.invoke;R.invoke=function(){const o=S[c.__symbol__("loadfalse")];for(let p=0;pfunction(R,b){return R[i]=0==b[2],R[T]=b[1],V.apply(R,b)}),G=j("fetchTaskAborting"),g=j("fetchTaskScheduling"),z=ue(Z,"send",()=>function(R,b){if(!0===c.current[g]||R[i])return z.apply(R,b);{const S={target:R,url:R[T],isPeriodic:!1,args:b,aborted:!1},Q=He("XMLHttpRequest.send",h,S,k,H);R&&!0===R[m]&&!S.aborted&&Q.state===X&&Q.invoke()}}),O=ue(Z,"abort",()=>function(R,b){const S=function x(R){return R[t]}(R);if(S&&"string"==typeof S.type){if(null==S.cancelFn||S.data&&S.data.aborted)return;S.zone.cancelTask(S)}else if(!0===c.current[G])return O.apply(R,b)})}(r);const t=j("xhrTask"),i=j("xhrSync"),u=j("xhrListener"),E=j("xhrScheduled"),T=j("xhrURL"),m=j("xhrErrorBeforeScheduled")}),e.__load_patch("geolocation",r=>{r.navigator&&r.navigator.geolocation&&function gt(e,r){const c=e.constructor.name;for(let t=0;t{const m=function(){return T.apply(this,xe(arguments,c+"."+i))};return fe(m,T),m})(u)}}}(r.navigator.geolocation,["getCurrentPosition","watchPosition"])}),e.__load_patch("PromiseRejectionEvent",(r,c)=>{function t(i){return function(u){st(r,i).forEach(T=>{const m=r.PromiseRejectionEvent;if(m){const D=new m(i,{promise:u.promise,reason:u.rejection});T.invoke(D)}})}}r.PromiseRejectionEvent&&(c[j("unhandledPromiseRejectionHandler")]=t("unhandledrejection"),c[j("rejectionHandledHandler")]=t("rejectionhandled"))}),e.__load_patch("queueMicrotask",(r,c,t)=>{!function wt(e,r){r.patchMethod(e,"queueMicrotask",c=>function(t,i){Zone.current.scheduleMicroTask("queueMicrotask",i[0])})}(r,t)})}(at)}},te=>{te(te.s=853)}]); \ No newline at end of file diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 899148c..e5c209b 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -20,8 +20,10 @@ import { CommonModule } from '@angular/common';
- Kinetix - Intranet Corporativa + + Kinetix + Intranet Corporativa +
Consorcio · 2026
diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts index 336f76b..32e4e5c 100644 --- a/frontend/src/app/app.config.ts +++ b/frontend/src/app/app.config.ts @@ -1,10 +1,12 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; +import { provideHttpClient, withFetch } from '@angular/common/http'; import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), - provideRouter(routes) + provideRouter(routes), + provideHttpClient(withFetch()) ] }; diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index a9de31f..ad3ccc3 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -16,6 +16,11 @@ export const routes: Routes = [ canActivate: [authGuard], loadComponent: () => import('./features/dashboard/dashboard.component').then(m => m.DashboardComponent) }, + { + path: 'directorio', + canActivate: [authGuard], + loadComponent: () => import('./features/directorio/directorio.component').then(m => m.DirectorioComponent) + }, { path: 'reservas', canActivate: [authGuard], diff --git a/frontend/src/app/features/dashboard/dashboard.component.ts b/frontend/src/app/features/dashboard/dashboard.component.ts index e01c0b9..de8458f 100644 --- a/frontend/src/app/features/dashboard/dashboard.component.ts +++ b/frontend/src/app/features/dashboard/dashboard.component.ts @@ -97,7 +97,7 @@ interface UserProfile { -
@@ -137,7 +137,7 @@ interface UserProfile {
-
@@ -616,203 +616,30 @@ interface UserProfile {
} - - -
- -
-
- - Portal de Consorcio · Intranet 2026 -

- Consorcio Corporativo Kinetix -

-

- Plataforma unificada de administración y autogestión de recursos del consorcio. Control centralizado, monitoreo en tiempo real y acceso seguro vía Active Directory. -

-
- - -
-
- + + + +
- -
-
Panel Kinetix
- - - - - - - - - - - - -
- - -
-
- Estado API: - - ONLINE - -
-
- Sesión SSO: - ACTIVE AD -
-
- Sincronización: - Entra ID (AD) -
-
- `, @@ -968,6 +795,14 @@ export class DashboardComponent implements OnInit { }, 1000); } + navigateToCalendario(): void { + this.router.navigate(['/reservas/calendario']); + } + + navigateToDirectorio(): void { + this.router.navigate(['/directorio']); + } + bookSpace() { this.bookingSuccess.set(true); } diff --git a/frontend/src/app/features/directorio/directorio.component.css b/frontend/src/app/features/directorio/directorio.component.css new file mode 100644 index 0000000..75442f1 --- /dev/null +++ b/frontend/src/app/features/directorio/directorio.component.css @@ -0,0 +1,696 @@ +/* Layout General y Elementos Comunes */ +.dir-shell { + display: flex; + min-height: 100vh; + background: #F5F4F1; + font-family: 'Inter', 'Outfit', system-ui, sans-serif; +} + +.dir-sidebar, +.dir-main { + min-height: 100vh; +} + +.dir-sidebar { + width: 228px; + background: #0F0D0A; + display: flex; + flex-direction: column; + padding: 24px 0 20px; + transition: width 0.25s cubic-bezier(0.4, 0, 0.2, 1); + position: sticky; + top: 0; + z-index: 40; + flex-shrink: 0; +} + +.dir-sidebar--collapsed { + width: 64px; +} + +.dir-main { + flex: 1; + padding: 36px 40px; + overflow-y: auto; + min-width: 0; +} + +/* Componentes Flex Comunes */ +.dir-sidebar__brand, +.dir-sidebar__brand-icon, +.dir-sidebar__nav-item, +.dir-sidebar__user, +.dir-header__title-block, +.dir-header__badge, +.dir-filters, +.dir-card__avatar-row, +.dir-card__avatar, +.dir-card__rating, +.dir-card__identity, +.dir-card__metrics, +.dir-card__cta, +.dir-empty, +.dir-toast, +.dir-sidebar__toggle { + display: flex; + align-items: center; +} + +/* Elementos Interactivos y Botones */ +.dir-sidebar__toggle, +.dir-filters__input, +.dir-filters__select, +.dir-filters__reset, +.dir-card__ad-id, +.dir-card__rating, +.dir-card__tag, +.dir-empty button { + border: 1px solid #E8E4DC; +} + +.dir-sidebar__toggle, +.dir-sidebar__nav-item, +.dir-filters__input, +.dir-filters__reset, +.dir-card, +.dir-card__cta, +.dir-empty button { + transition: all 0.15s; +} + +.dir-sidebar__toggle { + position: absolute; + top: 20px; + right: -13px; + width: 26px; + height: 26px; + border-radius: 50%; + background: #FFF; + color: #78716C; + justify-content: center; + cursor: pointer; + box-shadow: 0 2px 8px rgba(15, 13, 10, 0.12); + z-index: 10; +} + +.dir-sidebar__toggle:hover { + background: #C9973C; + color: #FFF; + border-color: #C9973C; +} + +/* Tipografía y Textos */ +.dir-sidebar__brand-name, +.dir-card__name, +.dir-card__metric-value { + font-weight: 800; + color: #0F0D0A; + letter-spacing: -0.01em; +} + +.dir-sidebar__brand { + gap: 10px; + padding: 0 18px 24px; + cursor: pointer; + overflow: hidden; +} + +.dir-sidebar__brand-icon { + width: 36px; + height: 36px; + background: rgba(201, 151, 60, 0.1); + border-radius: 10px; + justify-content: center; + flex-shrink: 0; +} + +.dir-sidebar__brand-name { + font-size: 15px; + color: #FFF; + white-space: nowrap; +} + +.dir-sidebar__section-label { + font-size: 9px; + font-weight: 700; + letter-spacing: 0.18em; + text-transform: uppercase; + color: rgba(255, 255, 255, 0.28); + padding: 0 18px 10px; +} + +.dir-sidebar__nav { + display: flex; + flex-direction: column; + gap: 2px; + padding: 0 10px; + flex: 1; + overflow: hidden; +} + +.dir-sidebar__nav-item { + gap: 10px; + padding: 10px; + border-radius: 10px; + text-decoration: none; + color: rgba(255, 255, 255, 0.48); + font-size: 13px; + font-weight: 500; + white-space: nowrap; + overflow: hidden; +} + +.dir-sidebar__nav-item:hover { + background: rgba(255, 255, 255, 0.06); + color: rgba(255, 255, 255, 0.88); +} + +.dir-sidebar__nav-item--active { + background: rgba(201, 151, 60, 0.12); + color: #C9973C; + font-weight: 600; +} + +.dir-sidebar__nav-icon { + width: 20px; + height: 20px; + justify-content: center; + flex-shrink: 0; + font-size: 15px; +} + +/* Sidebar Footer */ +.dir-sidebar__footer { + padding: 16px 14px 0; + border-top: 1px solid rgba(255, 255, 255, 0.06); + margin-top: 16px; + display: flex; + flex-direction: column; + gap: 10px; + overflow: hidden; +} + +.dir-sidebar__user { + gap: 10px; +} + +.dir-sidebar__user-avatar { + width: 34px; + height: 34px; + border-radius: 10px; + background: linear-gradient(135deg, #C9973C, #A67C28); + color: #FFF; + font-size: 11px; + font-weight: 800; + justify-content: center; + flex-shrink: 0; +} + +.dir-sidebar__user-avatar--sm { + width: 30px; + height: 30px; + margin: 0 auto; +} + +.dir-sidebar__user-info { + display: flex; + flex-direction: column; +} + +.dir-sidebar__user-name { + font-size: 12px; + font-weight: 700; + color: #FFF; + white-space: nowrap; +} + +.dir-sidebar__user-role { + font-size: 10px; + color: rgba(255, 255, 255, 0.36); + white-space: nowrap; +} + +.dir-sidebar__status { + font-size: 9.5px; + color: rgba(255, 255, 255, 0.3); + line-height: 1.5; + white-space: nowrap; +} + +.dir-sidebar__status strong { + color: rgba(255, 255, 255, 0.55); +} + +.dir-sidebar__status-dot { + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + background: #34D399; + margin-right: 5px; + animation: pulse-dot 2s infinite; +} + +.dir-sidebar__status-dot--sm { + width: 8px; + height: 8px; + margin: 4px auto 0; +} + +@keyframes pulse-dot { + + 0%, + 100% { + opacity: 1 + } + + 50% { + opacity: .4 + } +} + +/* Header Principal */ +.dir-header { + display: flex; + justify-content: space-between; + margin-bottom: 28px; + gap: 16px; + flex-wrap: wrap; +} + +.dir-header__title-block { + gap: 14px; + flex-wrap: wrap; +} + +.dir-header__title { + font-size: 26px; + font-weight: 900; + color: #0F0D0A; + letter-spacing: -0.02em; + margin: 0; + line-height: 1.15; +} + +.dir-header__badge { + gap: 7px; + background: #FFF; + border-radius: 9999px; + padding: 5px 12px; + font-size: 11px; + font-weight: 600; + color: #57534E; +} + +.dir-header__badge-dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: #34D399; + animation: pulse-dot 2s infinite; +} + +.dir-header__count { + font-size: 12px; + font-weight: 600; + color: #A8A29E; + align-self: center; +} + +/* Filtros y Buscador */ +.dir-filters { + gap: 10px; + flex-wrap: wrap; + margin-bottom: 14px; +} + +.dir-filters__search { + position: relative; + flex: 1; + min-width: 240px; + max-width: 440px; +} + +.dir-filters__search-icon { + position: absolute; + left: 14px; + top: 50%; + transform: translateY(-50%); + pointer-events: none; +} + +.dir-filters__input { + width: 100%; + padding: 11px 40px; + border-radius: 14px; + background: #FFF; + font-size: 13.5px; + color: #0F0D0A; + outline: none; + box-shadow: 0 1px 3px rgba(15, 13, 10, 0.04); + font-family: inherit; +} + +.dir-filters__input::placeholder { + color: #A8A29E; +} + +.dir-filters__input:focus { + border-color: #C9973C; + box-shadow: 0 0 0 3px rgba(201, 151, 60, 0.12); +} + +.dir-filters__clear { + position: absolute; + right: 12px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: #A8A29E; + cursor: pointer; + font-size: 13px; + padding: 2px 4px; + border-radius: 4px; +} + +.dir-filters__clear:hover { + color: #0F0D0A; +} + +.dir-filters__select-wrap { + position: relative; + flex-shrink: 0; +} + +.dir-filters__select { + appearance: none; + padding: 11px 36px 11px 16px; + border-radius: 14px; + background: #FFF; + font-size: 13px; + color: #0F0D0A; + cursor: pointer; + outline: none; + font-family: inherit; + box-shadow: 0 1px 3px rgba(15, 13, 10, 0.04); +} + +.dir-filters__select:focus { + border-color: #C9973C; +} + +.dir-filters__select-arrow { + position: absolute; + right: 12px; + top: 50%; + transform: translateY(-50%); + pointer-events: none; +} + +.dir-filters__reset { + padding: 10px 16px; + border-radius: 14px; + background: transparent; + font-size: 12.5px; + font-weight: 600; + color: #78716C; + cursor: pointer; + font-family: inherit; +} + +.dir-filters__reset:hover { + border-color: #C9973C; + color: #C9973C; + background: rgba(201, 151, 60, 0.05); +} + +.dir-results-summary { + font-size: 12px; + color: #A8A29E; + margin: 0 0 22px; +} + +.dir-results-summary strong { + color: #57534E; +} + +/* Grid y Tarjetas */ +.dir-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + gap: 20px; +} + +.dir-card { + position: relative; + background: #FFF; + border-radius: 20px; + padding: 24px; + border: 1px solid rgba(15, 13, 10, 0.07); + box-shadow: 0 1px 4px rgba(15, 13, 10, 0.05); + gap: 16px; + flex-direction: column; + cursor: default; + transition: box-shadow 0.22s cubic-bezier(0.4, 0, 0.2, 1), transform 0.22s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.22s; +} + +.dir-card:hover { + box-shadow: 0 8px 28px rgba(15, 13, 10, 0.1); + transform: translateY(-2px); + border-color: rgba(201, 151, 60, 0.25); +} + +.dir-card__ad-id { + position: absolute; + top: 16px; + right: 16px; + font-family: 'SF Mono', 'Fira Code', monospace; + font-size: 10px; + font-weight: 600; + color: #A8A29E; + background: #FAF9F6; + padding: 3px 8px; + border-radius: 6px; + letter-spacing: 0.04em; +} + +.dir-card__avatar-row { + gap: 14px; +} + +.dir-card__avatar { + width: 56px; + height: 56px; + border-radius: 50%; + justify-content: center; + font-size: 17px; + font-weight: 800; + color: #FFF; + letter-spacing: -0.02em; + flex-shrink: 0; +} + +.dir-card__rating { + gap: 4px; + background: #FAF9F6; + border-radius: 9999px; + padding: 4px 10px; + font-size: 12px; + font-weight: 700; + color: #0F0D0A; +} + +.dir-card__identity { + gap: 3px; +} + +.dir-card__name { + font-size: 17px; + margin: 0; + line-height: 1.2; +} + +.dir-card__role { + font-size: 12.5px; + font-weight: 600; + color: #C9973C; +} + +.dir-card__dept { + font-size: 11px; + font-weight: 500; + color: #A8A29E; +} + +.dir-card__tags { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.dir-card__tag { + font-size: 11px; + font-weight: 500; + color: #57534E; + background: #F5F4F1; + border-radius: 9999px; + padding: 3px 10px; +} + +.dir-card__divider { + height: 1px; + background: #F0EEE9; +} + +.dir-card__metric { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; + text-align: center; +} + +.dir-card__metric-label { + font-size: 9.5px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.1em; + color: #A8A29E; +} + +.dir-card__metric-value { + font-size: 17px; + font-variant-numeric: tabular-nums; +} + +.dir-card__metric-sep { + width: 1px; + height: 36px; + background: #F0EEE9; +} + +.dir-card__cta { + width: 100%; + padding: 11px 0; + border-radius: 12px; + border: 1.5px solid #0F0D0A; + background: transparent; + color: #0F0D0A; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.04em; + cursor: pointer; + justify-content: center; + gap: 7px; + margin-top: auto; +} + +.dir-card__cta:hover { + background: #0F0D0A; + color: #FFF; +} + +/* Estados Vacíos y Toasts */ +.dir-empty { + grid-column: 1 / -1; + flex-direction: column; + justify-content: center; + gap: 14px; + padding: 60px 20px; + text-align: center; + color: #A8A29E; +} + +.dir-empty p { + font-size: 14px; + margin: 0; +} + +.dir-empty button { + padding: 8px 20px; + border-radius: 10px; + background: transparent; + font-size: 13px; + font-weight: 600; + color: #78716C; + cursor: pointer; + font-family: inherit; +} + +.dir-empty button:hover { + border-color: #C9973C; + color: #C9973C; +} + +.dir-toast { + position: fixed; + bottom: 28px; + right: 28px; + background: #FFF; + border: 1px solid #D1FAE5; + box-shadow: 0 8px 24px rgba(15, 13, 10, 0.1); + border-radius: 14px; + padding: 13px 20px; + font-size: 13px; + font-weight: 600; + color: #065F46; + gap: 9px; + z-index: 999; + animation: slide-toast 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +@keyframes slide-toast { + from { + opacity: 0; + transform: translateY(12px) + } + + to { + opacity: 1; + transform: translateY(0) + } +} + +/* Media Queries Responsivas */ +@media (max-width: 900px) { + .dir-sidebar { + width: 64px; + } + + .dir-sidebar__brand-name, + .dir-sidebar__section-label, + .dir-sidebar__nav-label, + .dir-sidebar__user-info, + .dir-sidebar__status, + .dir-sidebar__toggle { + display: none; + } + + .dir-sidebar__user { + justify-content: center; + } + + .dir-main { + padding: 24px 18px; + } +} + +@media (max-width: 600px) { + .dir-sidebar { + display: none; + } + + .dir-header { + flex-direction: column; + align-items: flex-start; + } + + .dir-main { + padding: 20px 14px; + } + + .dir-grid { + grid-template-columns: 1fr; + } +} \ No newline at end of file diff --git a/frontend/src/app/features/directorio/directorio.component.ts b/frontend/src/app/features/directorio/directorio.component.ts new file mode 100644 index 0000000..ad5ebbd --- /dev/null +++ b/frontend/src/app/features/directorio/directorio.component.ts @@ -0,0 +1,317 @@ +import { Component, signal, computed, inject, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { Router, RouterModule } from '@angular/router'; + +interface Employee { + id: number; + adId: string; + name: string; + initials: string; + role: string; + department: string; + rating: number; + clients: number; + rate: number; + tags: string[]; + email: string; + avatarColor: string; +} + +@Component({ + selector: 'app-directorio', + standalone: true, + imports: [CommonModule, FormsModule, RouterModule], + template: ` +
+ + + + + +
+ + +
+
+

Directorio de Personal

+
+ + Sincronizado con Active Directory +
+
+ {{ filteredEmployees().length }} empleados +
+ + +
+ + + + +
+ + + + +
+ + + @if (selectedDept !== 'ALL' || searchQuery) { + + } +
+ + +

+ Mostrando {{ filteredEmployees().length }} de {{ employees().length }} empleados registrados +

+ + +
+ @for (emp of filteredEmployees(); track emp.id) { +
+ + +
{{ emp.adId }}
+ + +
+
+ {{ emp.initials }} +
+
+ + + + {{ emp.rating }} +
+
+ + +
+

{{ emp.name }}

+ {{ emp.role }} + {{ emp.department }} +
+ + +
+ @for (tag of emp.tags; track tag) { + {{ tag }} + } +
+ + +
+ + +
+
+ Clientes + {{ emp.clients }} +
+
+
+ Tarifa/Hora + \${{ emp.rate }} +
+
+ + + + +
+ } + + + @if (filteredEmployees().length === 0) { +
+ + + + +

Sin resultados para "{{ searchQuery }}"

+ +
+ } +
+ +
+
+ + + @if (contactToast()) { +
+ + + + Mensaje enviado a {{ contactToast() }} +
+ } + `, + styleUrls: ['./directorio.component.css'] +}) +export class DirectorioComponent implements OnInit { + readonly router = inject(Router); + + sidebarCollapsed = signal(false); + contactToast = signal(''); + + searchQuery = ''; + selectedDept = 'ALL'; + + private _searchSignal = signal(''); + private _deptSignal = signal('ALL'); + + navItems = [ + { label: 'Panel Principal', route: '/dashboard', icon: '⚡' }, + { label: 'Directorio AD', route: '/directorio', icon: '👤' }, + { label: 'Gestión de Espacios', route: '/reservas/calendario', icon: '📅' }, + ]; + + employees = signal([]); + + filteredEmployees = computed(() => { + const q = this._searchSignal().toLowerCase().trim(); + const dept = this._deptSignal(); + + return this.employees().filter(emp => { + const matchesSearch = !q || + emp.name.toLowerCase().includes(q) || + emp.role.toLowerCase().includes(q) || + emp.tags.some(t => t.toLowerCase().includes(q)); + + const matchesDept = dept === 'ALL' || + emp.department === dept; + + return matchesSearch && matchesDept; + }); + }); + + ngOnInit(): void { } + + toggleSidebar(): void { + this.sidebarCollapsed.update(v => !v); + } + + isActiveRoute(route: string): boolean { + return this.router.url === route || this.router.url.startsWith(route + '/'); + } + + onSearch(value: string): void { + this._searchSignal.set(value); + } + + onDeptChange(value: string): void { + this._deptSignal.set(value); + } + + clearSearch(): void { + this.searchQuery = ''; + this._searchSignal.set(''); + } + + resetFilters(): void { + this.searchQuery = ''; + this.selectedDept = 'ALL'; + this._searchSignal.set(''); + this._deptSignal.set('ALL'); + } + + contact(emp: Employee): void { + this.contactToast.set(emp.name); + setTimeout(() => this.contactToast.set(''), 3500); + } +} diff --git a/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.css b/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.css new file mode 100644 index 0000000..f2a00dc --- /dev/null +++ b/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.css @@ -0,0 +1,511 @@ +/* Layout Principal y Contenedores */ +.calendar-layout { + display: flex; + width: 100%; + height: calc(100vh - 160px); + background-color: var(--bg-surface, #FFF); + border: 1px solid var(--border-light, rgba(15, 13, 10, 0.08)); + border-radius: 20px; + overflow: hidden; + box-shadow: 0 4px 20px rgba(15, 13, 10, 0.04); +} + +.calendar-sidebar, +.calendar-view-container { + overflow-y: auto; +} + +.calendar-sidebar { + width: 280px; + background: #FFF; + border-right: 1px solid var(--border-light, rgba(15, 13, 10, 0.08)); + padding: 24px 16px; + display: flex; + flex-direction: column; + gap: 24px; + box-sizing: border-box; + flex-shrink: 0; +} + +.calendar-main { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + background-color: #FFF; +} + +.calendar-view-container { + flex: 1; + display: flex; + flex-direction: column; + background: #FFF; +} + +/* Botones y Elementos Interactivos */ +.btn-create, +.btn-today, +.view-select { + border: 1px solid var(--border-medium, rgba(15, 13, 10, 0.14)); + background: #FFF; + font-weight: 600; + cursor: pointer; +} + +.btn-create { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 12px; + color: var(--text-primary, #0F0D0A); + padding: 12px 24px; + border-radius: 24px; + font-family: 'Inter', sans-serif; + font-size: 14px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 4px 12px rgba(0, 0, 0, 0.05); + transition: box-shadow 0.2s, background-color 0.2s; + width: 100%; + box-sizing: border-box; +} + +.btn-create:hover { + background-color: #F8F9FA; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12); +} + +.btn-create .plus-icon { + font-size: 24px; + font-weight: 300; + color: var(--gold-luxe, #C9973C); + line-height: 1; +} + +.btn-today, +.view-select { + padding: 6px 16px; + border-radius: 18px; + font-size: 12px; +} + +.btn-today:hover, +.hour-slot-cell:hover, +.btn-create:hover { + background-color: #F8F9FA; +} + +.nav-arrow { + width: 28px; + height: 28px; + border-radius: 50%; + border: 1px solid var(--border-medium, rgba(15, 13, 10, 0.08)); + background: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 13px; + font-weight: bold; + transition: background-color 0.2s; +} + +.nav-arrow:hover, +.mini-day-cell:hover, +.month-day-cell:hover { + background-color: var(--bg-page, #F5F4F1); +} + +.view-select { + outline: none; +} + +/* Mini Calendario de la Barra Lateral */ +.mini-calendar, +.filter-section, +.filter-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.mini-calendar, +.filter-section { + gap: 12px; +} + +.mini-calendar { + gap: 8px; +} + +.mini-calendar-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 8px; +} + +.mini-calendar-title { + font-size: 13px; + font-weight: 600; + color: var(--text-secondary, #44403C); + text-transform: capitalize; +} + +.mini-calendar-nav { + display: flex; + gap: 4px; +} + +.mini-calendar-grid, +.grid-month { + display: grid; + grid-template-cols: repeat(7, 1fr); +} + +.mini-calendar-grid { + text-align: center; + row-gap: 4px; +} + +.mini-day-name, +.day-name-cell { + font-weight: 700; + text-transform: uppercase; + color: var(--text-muted, #78716C); +} + +.mini-day-name { + font-size: 10px; + padding-bottom: 4px; +} + +.mini-day-cell { + font-size: 11px; + height: 28px; + width: 28px; + margin: 0 auto; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: 50%; + color: var(--text-primary); + box-sizing: border-box; +} + +.mini-day-cell.other-month { + color: var(--text-placeholder, #A8A29E); +} + +.mini-day-cell.today, +.day-number.today, +.month-day-number.today { + background-color: var(--gold-luxe, #C9973C); + color: #FFF !important; + font-weight: bold; +} + +/* Filtros */ +.filter-title { + font-size: 10px; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 0.8px; + color: var(--text-muted, #78716C); + margin: 0; +} + +.filter-item { + display: flex; + align-items: center; + gap: 10px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + user-select: none; +} + +.filter-checkbox { + width: 16px; + height: 16px; + border-radius: 4px; + cursor: pointer; + accent-color: var(--gold-luxe, #C9973C); +} + +/* Header Principal */ +.main-header { + height: 64px; + background: var(--bg-surface, #FFF); + border-bottom: 1px solid var(--border-light, rgba(0, 0, 0, 0.08)); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 24px; + flex-shrink: 0; +} + +.main-header-left, +.header-title-group, +.header-app-logo, +.main-header-right { + display: flex; + align-items: center; + gap: 16px; +} + +.header-title-group { + gap: 12px; +} + +.header-app-logo { + gap: 8px; + font-family: 'Inter', sans-serif; + font-weight: 900; + font-size: 16px; + letter-spacing: 1px; + color: var(--text-primary); + text-transform: uppercase; +} + +.header-app-logo span { + color: var(--gold-luxe); + font-weight: 400; + text-transform: none; +} + +.current-date-label { + font-size: 18px; + font-weight: 700; + text-transform: capitalize; + color: var(--text-primary, #0F0D0A); +} + +/* Vista Semanal */ +.week-view { + display: flex; + flex-direction: column; + min-width: 800px; + flex: 1; +} + +.week-header, +.week-grid-body { + display: grid; + grid-template-cols: 64px repeat(7, 1fr); +} + +.week-header { + border-bottom: 1px solid var(--border-light, rgba(0, 0, 0, 0.08)); + position: sticky; + top: 0; + background: #FFF; + z-index: 10; +} + +.timezone-label { + display: flex; + align-items: flex-end; + justify-content: center; + font-size: 9px; + color: var(--text-muted, #78716C); + padding-bottom: 8px; + border-right: 1px solid var(--border-light, rgba(0, 0, 0, 0.08)); +} + +.week-day-header, +.day-column { + border-right: 1px solid var(--border-light, rgba(0, 0, 0, 0.08)); +} + +.week-day-header { + display: flex; + flex-direction: column; + align-items: center; + padding: 10px 0; +} + +.week-day-header:last-child, +.day-column:last-child, +.day-name-cell:last-child { + border-right: none; +} + +.day-name { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + color: var(--text-muted, #78716C); + margin-bottom: 4px; +} + +.day-number { + font-size: 20px; + font-weight: 500; + width: 34px; + height: 34px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + color: var(--text-primary); +} + +.week-grid-body { + position: relative; + flex: 1; +} + +.hour-labels { + display: flex; + flex-direction: column; + border-right: 1px solid var(--border-light, rgba(0, 0, 0, 0.08)); +} + +.hour-label-cell { + height: 60px; + display: flex; + align-items: flex-start; + justify-content: flex-end; + padding-right: 8px; + font-size: 10px; + color: var(--text-muted, #78716C); + transform: translateY(-6px); +} + +.day-column { + position: relative; + background-color: #FFF; +} + +.hour-slot-cell { + height: 60px; + border-bottom: 1px solid var(--border-light, rgba(15, 13, 10, 0.05)); + box-sizing: border-box; +} + +/* Eventos e Indicadores */ +.calendar-event { + position: absolute; + left: 4px; + right: 4px; + border-radius: 4px; + padding: 6px 8px; + font-size: 11px; + font-weight: 500; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); + border-left: 4px solid; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: flex; + flex-direction: column; + gap: 2px; +} + +.calendar-event .event-time { + font-size: 9px; + font-weight: 600; + opacity: 0.8; +} + +.current-time-indicator { + position: absolute; + left: 0; + right: 0; + height: 2px; + background-color: #EA4335; + z-index: 5; +} + +.current-time-indicator::before { + content: ''; + position: absolute; + left: -6px; + top: -5px; + width: 12px; + height: 12px; + border-radius: 50%; + background-color: #EA4335; +} + +/* Vista Mensal */ +.day-names-row { + display: grid; + grid-template-cols: repeat(7, 1fr); + background: var(--bg-page, #F0EEE9); + border-bottom: 1px solid var(--border-light, rgba(15, 13, 10, 0.08)); + width: 100%; +} + +.day-name-cell { + padding: 12px 0; + text-align: center; + font-size: 11px; + letter-spacing: 0.5px; + border-right: 1px solid var(--border-light, rgba(0, 0, 0, 0.05)); +} + +.grid-month { + grid-template-rows: repeat(6, 1fr); + flex: 1; + width: 100%; + height: 100%; + min-height: 500px; + border-bottom: 1px solid var(--border-light, rgba(15, 13, 10, 0.08)); + box-sizing: border-box; +} + +.month-day-cell { + padding: 8px; + display: flex; + flex-direction: column; + gap: 4px; + border-right: 1px solid var(--border-light, rgba(15, 13, 10, 0.08)); + border-bottom: 1px solid var(--border-light, rgba(15, 13, 10, 0.08)); + cursor: pointer; + background-color: #FFF; + transition: background-color 0.2s; + box-sizing: border-box; + min-height: 80px; +} + +.month-day-cell.other-month { + background-color: #FAF9F6; +} + +.month-day-number { + font-size: 11px; + font-weight: 600; + color: var(--text-secondary, #44403C); + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; +} + +/* Media Queries */ +@media (max-width: 992px) { + .calendar-layout { + flex-direction: column; + height: auto; + overflow: visible; + } + + .calendar-sidebar { + width: 100% !important; + border-right: none !important; + border-bottom: 1px solid var(--border-light, rgba(0, 0, 0, 0.08)); + padding: 24px; + } + + .calendar-main { + height: auto; + overflow: visible; + } + + .calendar-view-container { + overflow-x: auto; + } +} \ No newline at end of file diff --git a/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.html b/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.html index c4c0908..6a53758 100644 --- a/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.html +++ b/frontend/src/app/features/reservas/calendar-booking/calendar-booking.component.html @@ -1,177 +1,243 @@ -
- -