feat: implement core frontend architecture with authentication, dashboard, and AI-powered resource booking modules
This commit is contained in:
parent
97349f05be
commit
8063a6c0b6
6
.env
6
.env
@ -1,4 +1,4 @@
|
|||||||
# Kinetix Intranet - Environment Variables Template
|
# App Intranet - Environment Variables Template
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# BACKEND (NestJS) CONFIGURATION
|
# BACKEND (NestJS) CONFIGURATION
|
||||||
@ -7,7 +7,7 @@ PORT=3000
|
|||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
# Database Settings (PostgreSQL / Supabase)
|
# Database Settings (PostgreSQL / Supabase)
|
||||||
DATABASE_URL="postgresql://admin:supersecretpassword123@localhost:5432/kinetix_db?schema=public"
|
DATABASE_URL="postgresql://admin:supersecretpassword123@localhost:5432/App_db?schema=public"
|
||||||
# SUPABASE_URL="https://your-project.supabase.co"
|
# SUPABASE_URL="https://your-project.supabase.co"
|
||||||
# SUPABASE_KEY="your-anon-key"
|
# SUPABASE_KEY="your-anon-key"
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ AD_TENANT_ID="your-azure-tenant-id"
|
|||||||
AD_CLIENT_ID="your-azure-client-id"
|
AD_CLIENT_ID="your-azure-client-id"
|
||||||
AD_CLIENT_SECRET="your-azure-client-secret"
|
AD_CLIENT_SECRET="your-azure-client-secret"
|
||||||
AD_REDIRECT_URI="http://localhost:3000/api/auth/callback"
|
AD_REDIRECT_URI="http://localhost:3000/api/auth/callback"
|
||||||
JWT_SECRET="kinetix-super-jwt-secret-key-2026"
|
JWT_SECRET="App-super-jwt-secret-key-2026"
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# FRONTEND (Angular) CONFIGURATION
|
# FRONTEND (Angular) CONFIGURATION
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Kinetix Intranet - Environment Variables Template
|
# App Intranet - Environment Variables Template
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# BACKEND (NestJS) CONFIGURATION
|
# BACKEND (NestJS) CONFIGURATION
|
||||||
@ -7,7 +7,7 @@ PORT=3000
|
|||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
# Database Settings (PostgreSQL / Supabase)
|
# Database Settings (PostgreSQL / Supabase)
|
||||||
DATABASE_URL="postgresql://admin:supersecretpassword123@localhost:5432/kinetix_db?schema=public"
|
DATABASE_URL="postgresql://admin:supersecretpassword123@localhost:5432/App_db?schema=public"
|
||||||
# SUPABASE_URL="https://your-project.supabase.co"
|
# SUPABASE_URL="https://your-project.supabase.co"
|
||||||
# SUPABASE_KEY="your-anon-key"
|
# SUPABASE_KEY="your-anon-key"
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ AD_TENANT_ID="your-azure-tenant-id"
|
|||||||
AD_CLIENT_ID="your-azure-client-id"
|
AD_CLIENT_ID="your-azure-client-id"
|
||||||
AD_CLIENT_SECRET="your-azure-client-secret"
|
AD_CLIENT_SECRET="your-azure-client-secret"
|
||||||
AD_REDIRECT_URI="http://localhost:3000/api/auth/callback"
|
AD_REDIRECT_URI="http://localhost:3000/api/auth/callback"
|
||||||
JWT_SECRET="kinetix-super-jwt-secret-key-2026"
|
JWT_SECRET="App-super-jwt-secret-key-2026"
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# FRONTEND (Angular) CONFIGURATION
|
# FRONTEND (Angular) CONFIGURATION
|
||||||
|
|||||||
4
AGENT.md
4
AGENT.md
@ -14,7 +14,7 @@
|
|||||||
## 2. Estructura del Monorepo
|
## 2. Estructura del Monorepo
|
||||||
|
|
||||||
```
|
```
|
||||||
Kinetix/
|
App/
|
||||||
│
|
│
|
||||||
├── AGENT.md ← System Prompt principal e instrucciones de onboarding (no borrar)
|
├── AGENT.md ← System Prompt principal e instrucciones de onboarding (no borrar)
|
||||||
├── package.json ← Scripts del monorepo
|
├── package.json ← Scripts del monorepo
|
||||||
@ -77,7 +77,7 @@ Levanta el contenedor de PostgreSQL para desarrollo local:
|
|||||||
```bash
|
```bash
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
Esto creará la base de datos `kinetix_db` expuesta en `localhost:5432` y pgAdmin en `http://localhost:5050` con las credenciales configuradas en el archivo `.env`.
|
Esto creará la base de datos `App_db` expuesta en `localhost:5432` y pgAdmin en `http://localhost:5050` con las credenciales configuradas en el archivo `.env`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Kinetix Intranet - Environment Variables Template
|
# App Intranet - Environment Variables Template
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# BACKEND (NestJS) CONFIGURATION
|
# BACKEND (NestJS) CONFIGURATION
|
||||||
@ -7,7 +7,7 @@ PORT=3000
|
|||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
# Database Settings (PostgreSQL / Supabase)
|
# Database Settings (PostgreSQL / Supabase)
|
||||||
DATABASE_URL="postgresql://admin:supersecretpassword123@localhost:5432/kinetix_db?schema=public"
|
DATABASE_URL="postgresql://admin:supersecretpassword123@localhost:5432/App_db?schema=public"
|
||||||
# SUPABASE_URL="https://your-project.supabase.co"
|
# SUPABASE_URL="https://your-project.supabase.co"
|
||||||
# SUPABASE_KEY="your-anon-key"
|
# SUPABASE_KEY="your-anon-key"
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ AD_TENANT_ID="your-azure-tenant-id"
|
|||||||
AD_CLIENT_ID="your-azure-client-id"
|
AD_CLIENT_ID="your-azure-client-id"
|
||||||
AD_CLIENT_SECRET="your-azure-client-secret"
|
AD_CLIENT_SECRET="your-azure-client-secret"
|
||||||
AD_REDIRECT_URI="http://localhost:3000/api/auth/callback"
|
AD_REDIRECT_URI="http://localhost:3000/api/auth/callback"
|
||||||
JWT_SECRET="kinetix-super-jwt-secret-key-2026"
|
JWT_SECRET="App-super-jwt-secret-key-2026"
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# FRONTEND (Angular) CONFIGURATION
|
# FRONTEND (Angular) CONFIGURATION
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "kinetix-backend",
|
"name": "App-backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
# Propuesta Técnica y Comercial
|
# Propuesta Técnica y Comercial
|
||||||
## Plataforma de Intranet Corporativa · **Kinetix**
|
## Plataforma de Intranet Corporativa · **App**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -12,12 +12,12 @@
|
|||||||
|
|
||||||
## Resumen Ejecutivo
|
## 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.
|
**App** 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.
|
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]
|
> [!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].
|
> App 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].
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -33,17 +33,17 @@ Las empresas modernas operan con herramientas fragmentadas: un sistema para rese
|
|||||||
| Aprobaciones por correo | Sin trazabilidad, cuellos de botella operativos |
|
| Aprobaciones por correo | Sin trazabilidad, cuellos de botella operativos |
|
||||||
| Directorio desactualizado | Información incorrecta, baja colaboración |
|
| Directorio desactualizado | Información incorrecta, baja colaboración |
|
||||||
|
|
||||||
**Kinetix resuelve todos estos problemas en una sola plataforma integrada.**
|
**App resuelve todos estos problemas en una sola plataforma integrada.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. La Solución: Plataforma Kinetix
|
## 2. La Solución: Plataforma App
|
||||||
|
|
||||||
### 2.1 Visión General
|
### 2.1 Visión General
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
│ KINETIX INTRANET │
|
│ App INTRANET │
|
||||||
│ "Un ecosistema. Todos los procesos internos en uno." │
|
│ "Un ecosistema. Todos los procesos internos en uno." │
|
||||||
├──────────────┬──────────────┬──────────────┬────────────────┤
|
├──────────────┬──────────────┬──────────────┬────────────────┤
|
||||||
│ Directorio │ Espacios │ Workflows │ Info Hub CMS │
|
│ Directorio │ Espacios │ Workflows │ Info Hub CMS │
|
||||||
@ -115,14 +115,14 @@ Usuario → App Angular → Azure Entra ID (login)
|
|||||||
Acceso según rol: employee | manager | admin
|
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.
|
**Ninguna contraseña se almacena en App.** La autenticación es 100% delegada a Active Directory de la empresa.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Arquitectura del Sistema
|
## 4. Arquitectura del Sistema
|
||||||
|
|
||||||
```
|
```
|
||||||
kinetix/
|
App/
|
||||||
├── frontend/ ← SPA Angular 18 (Standalone Components + Signals)
|
├── frontend/ ← SPA Angular 18 (Standalone Components + Signals)
|
||||||
│ └── src/app/
|
│ └── src/app/
|
||||||
│ ├── core/ ← Guards SSO, interceptores HTTP, auth services
|
│ ├── core/ ← Guards SSO, interceptores HTTP, auth services
|
||||||
@ -147,7 +147,7 @@ kinetix/
|
|||||||
|
|
||||||
## 5. Diseño e Interfaz de Usuario
|
## 5. Diseño e Interfaz de Usuario
|
||||||
|
|
||||||
Kinetix utiliza el **Executive Suite Design System**, un sistema de diseño corporativo premium desarrollado a medida:
|
App 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`
|
- **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
|
- **Tarjetas Joya (Blackjack System):** Fondos oscuros en Navy, Plum, Viridian, Espresso para los módulos principales
|
||||||
@ -227,7 +227,7 @@ Kinetix utiliza el **Executive Suite Design System**, un sistema de diseño corp
|
|||||||
| Documentación técnica de operación | Runbook de DevOps |
|
| Documentación técnica de operación | Runbook de DevOps |
|
||||||
| Soporte post-lanzamiento (30 días incluidos) | Canal de soporte dedicado |
|
| Soporte post-lanzamiento (30 días incluidos) | Canal de soporte dedicado |
|
||||||
|
|
||||||
**Hito:** 🚀 Go-Live oficial de Kinetix para [Empresa].
|
**Hito:** 🚀 Go-Live oficial de App para [Empresa].
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -235,7 +235,7 @@ Kinetix utiliza el **Executive Suite Design System**, un sistema de diseño corp
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
gantt
|
gantt
|
||||||
title Roadmap de Implementación Kinetix
|
title Roadmap de Implementación App
|
||||||
dateFormat YYYY-MM-DD
|
dateFormat YYYY-MM-DD
|
||||||
section Fase 0 — Diagnóstico
|
section Fase 0 — Diagnóstico
|
||||||
Kickoff & Auditoría AD :2026-07-01, 7d
|
Kickoff & Auditoría AD :2026-07-01, 7d
|
||||||
@ -266,7 +266,7 @@ gantt
|
|||||||
|
|
||||||
| Concepto | Descripción | Estimado |
|
| Concepto | Descripción | Estimado |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **Licencia de Plataforma** | Uso de la plataforma Kinetix base | A negociar |
|
| **Licencia de Plataforma** | Uso de la plataforma App base | A negociar |
|
||||||
| **Implementación e Integración** | Fases 0–1: Setup, SSO, infraestructura | 4–6 semanas |
|
| **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 |
|
| **Desarrollo de Módulos Core** | Fase 2: Directorio, Espacios, Workflow | 6 semanas |
|
||||||
| **Módulos Secundarios** | Fase 3: CMS, Ticketing | 4 semanas |
|
| **Módulos Secundarios** | Fase 3: CMS, Ticketing | 4 semanas |
|
||||||
@ -282,9 +282,9 @@ gantt
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. Por Qué Kinetix sobre Alternativas
|
## 10. Por Qué App sobre Alternativas
|
||||||
|
|
||||||
| Criterio | Kinetix | SharePoint | Confluence | Desarrollo a medida desde cero |
|
| Criterio | App | SharePoint | Confluence | Desarrollo a medida desde cero |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| Integración AD nativa | ✅ | ✅ | ⚠️ Plugin | ✅ |
|
| Integración AD nativa | ✅ | ✅ | ⚠️ Plugin | ✅ |
|
||||||
| Personalizable 100% | ✅ | ⚠️ Limitado | ⚠️ Limitado | ✅ |
|
| Personalizable 100% | ✅ | ⚠️ Limitado | ⚠️ Limitado | ✅ |
|
||||||
@ -321,8 +321,8 @@ gantt
|
|||||||
|
|
||||||
**Jess Miller**
|
**Jess Miller**
|
||||||
Arquitecta de Software Senior · Full-Stack Engineer
|
Arquitecta de Software Senior · Full-Stack Engineer
|
||||||
📧 jess@kinetix.dev
|
📧 jess@App.dev
|
||||||
🔗 github.com/kinetix
|
🔗 github.com/App
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +1,20 @@
|
|||||||
# Walkthrough: Módulo de Autogestión de Espacios y Activos con Asistente IA
|
# 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).
|
Se ha completado la arquitectura e implementación del módulo de gestión de reservas en el frontend de App utilizando Angular 18 (Standalone Components, Signals y enrutamiento perezoso).
|
||||||
|
|
||||||
## Cambios Realizados
|
## Cambios Realizados
|
||||||
|
|
||||||
### Servicio de IA
|
### 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.
|
- [ia-parser.service.ts](file:///d:/Jess/agent/App/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
|
### 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.ts](file:///d:/Jess/agent/App/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).
|
- [calendar-booking.component.html](file:///d:/Jess/agent/App/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.ts](file:///d:/Jess/agent/App/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.
|
- [reserva-form.component.html](file:///d:/Jess/agent/App/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
|
### 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`).
|
- [app.routes.ts](file:///d:/Jess/agent/App/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`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,12 @@ version: '3.8'
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15-alpine
|
image: postgres:15-alpine
|
||||||
container_name: kinetix-postgres
|
container_name: App-postgres
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: admin
|
POSTGRES_USER: admin
|
||||||
POSTGRES_PASSWORD: supersecretpassword123
|
POSTGRES_PASSWORD: supersecretpassword123
|
||||||
POSTGRES_DB: kinetix_db
|
POSTGRES_DB: App_db
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
@ -16,10 +16,10 @@ services:
|
|||||||
|
|
||||||
pgadmin:
|
pgadmin:
|
||||||
image: dpage/pgadmin4
|
image: dpage/pgadmin4
|
||||||
container_name: kinetix-pgadmin
|
container_name: App-pgadmin
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
PGADMIN_DEFAULT_EMAIL: admin@kinetix.com
|
PGADMIN_DEFAULT_EMAIL: admin@App.com
|
||||||
PGADMIN_DEFAULT_PASSWORD: adminpassword123
|
PGADMIN_DEFAULT_PASSWORD: adminpassword123
|
||||||
ports:
|
ports:
|
||||||
- "5050:80"
|
- "5050:80"
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"version": 1,
|
"version": 1,
|
||||||
"newProjectRoot": "projects",
|
"newProjectRoot": "projects",
|
||||||
"projects": {
|
"projects": {
|
||||||
"kinetix-frontend": {
|
"App-frontend": {
|
||||||
"projectType": "application",
|
"projectType": "application",
|
||||||
"schematics": {
|
"schematics": {
|
||||||
"@schematics/angular:component": {
|
"@schematics/angular:component": {
|
||||||
@ -17,7 +17,7 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"builder": "@angular-devkit/build-angular:browser",
|
"builder": "@angular-devkit/build-angular:browser",
|
||||||
"options": {
|
"options": {
|
||||||
"outputPath": "dist/kinetix-frontend",
|
"outputPath": "dist/App-frontend",
|
||||||
"index": "src/index.html",
|
"index": "src/index.html",
|
||||||
"main": "src/main.ts",
|
"main": "src/main.ts",
|
||||||
"polyfills": "zone.js",
|
"polyfills": "zone.js",
|
||||||
@ -55,10 +55,10 @@
|
|||||||
"builder": "@angular-devkit/build-angular:dev-server",
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"browserTarget": "kinetix-frontend:build:production"
|
"browserTarget": "App-frontend:build:production"
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"browserTarget": "kinetix-frontend:build:development"
|
"browserTarget": "App-frontend:build:development"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultConfiguration": "development"
|
"defaultConfiguration": "development"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "kinetix-frontend",
|
"name": "App-frontend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import { Component, inject } from '@angular/core';
|
import { Component, inject } from '@angular/core';
|
||||||
import { RouterOutlet, Router } from '@angular/router';
|
import { RouterOutlet, Router, RouterLink } from '@angular/router';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterOutlet, CommonModule],
|
imports: [RouterOutlet, CommonModule, RouterLink],
|
||||||
styleUrls: ['./app.component.css'],
|
styleUrls: ['./app.component.css'],
|
||||||
template: `
|
template: `
|
||||||
<main class="app-shell" [class.login-mode]="isLoginView()">
|
<main class="app-shell" [class.login-mode]="isLoginView()">
|
||||||
|
|
||||||
<header *ngIf="!isLoginView()" class="app-header">
|
<header *ngIf="!isLoginView()" class="app-header">
|
||||||
<div class="header-brand">
|
<a routerLink="/dashboard" class="header-brand" style="text-decoration: none; cursor: pointer;">
|
||||||
<div class="header-logo">
|
<div class="header-logo">
|
||||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M6 4L13 12L6 20" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M6 4L13 12L6 20" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
@ -20,12 +20,10 @@ import { CommonModule } from '@angular/common';
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-wordmark">
|
<div class="header-wordmark">
|
||||||
<a routerLink="/dashboard">
|
<span class="header-name">App</span>
|
||||||
<span class="header-name">Kinetix</span>
|
|
||||||
<span class="header-sub">Intranet Corporativa</span>
|
<span class="header-sub">Intranet Corporativa</span>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="header-meta">Consorcio · 2026</div>
|
<div class="header-meta">Consorcio · 2026</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@ -34,7 +32,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer *ngIf="!isLoginView()" class="app-footer">
|
<footer *ngIf="!isLoginView()" class="app-footer">
|
||||||
<span>© 2026 Kinetix</span>
|
<span>© 2026 App</span>
|
||||||
<span class="footer-dot"></span>
|
<span class="footer-dot"></span>
|
||||||
<span>Todos los derechos reservados</span>
|
<span>Todos los derechos reservados</span>
|
||||||
<span class="footer-dot"></span>
|
<span class="footer-dot"></span>
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { Router, CanActivateFn } from '@angular/router';
|
|||||||
|
|
||||||
export const authGuard: CanActivateFn = (route, state) => {
|
export const authGuard: CanActivateFn = (route, state) => {
|
||||||
const router = inject(Router);
|
const router = inject(Router);
|
||||||
const token = localStorage.getItem('kinetix_token');
|
const token = localStorage.getItem('App_token');
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -10,13 +10,13 @@
|
|||||||
<div class="login-logo-wrap">
|
<div class="login-logo-wrap">
|
||||||
<!-- K logotipo SVG -->
|
<!-- K logotipo SVG -->
|
||||||
<svg class="login-logo-icon" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg class="login-logo-icon" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M8 6L17 17L8 28" stroke="white" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M8 6L17 17L8 28" stroke="white" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
<path d="M17 6H26" stroke="white" stroke-width="3.5" stroke-linecap="round"/>
|
<path d="M17 6H26" stroke="white" stroke-width="3.5" stroke-linecap="round" />
|
||||||
<path d="M17 28H26" stroke="white" stroke-width="3.5" stroke-linecap="round"/>
|
<path d="M17 28H26" stroke="white" stroke-width="3.5" stroke-linecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<p class="login-eyebrow">Portal Corporativo</p>
|
<p class="login-eyebrow">Portal Corporativo</p>
|
||||||
<h1 class="login-title">KINETIX <span class="login-title-gold">ECO</span></h1>
|
<h1 class="login-title">App <span class="login-title-gold">ECO</span></h1>
|
||||||
<p class="login-sub">Consorcio Empresarial · Intranet 2026</p>
|
<p class="login-sub">Consorcio Empresarial · Intranet 2026</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -28,52 +28,40 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="field-lbl">
|
<label class="field-lbl">
|
||||||
<svg class="field-lbl-icon" viewBox="0 0 16 16" fill="none">
|
<svg class="field-lbl-icon" viewBox="0 0 16 16" fill="none">
|
||||||
<circle cx="8" cy="5" r="3" stroke="currentColor" stroke-width="1.5"/>
|
<circle cx="8" cy="5" r="3" stroke="currentColor" stroke-width="1.5" />
|
||||||
<path d="M2 14c0-3.314 2.686-6 6-6s6 2.686 6 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
<path d="M2 14c0-3.314 2.686-6 6-6s6 2.686 6 6" stroke="currentColor" stroke-width="1.5"
|
||||||
|
stroke-linecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
Usuario corporativo
|
Usuario corporativo
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input id="login-email" type="email" class="field-input" placeholder="nombre@App.com" [value]="email()"
|
||||||
id="login-email"
|
(input)="email.set(getVal($event))" autocomplete="email" />
|
||||||
type="email"
|
|
||||||
class="field-input"
|
|
||||||
placeholder="nombre@kinetix.com"
|
|
||||||
[value]="email()"
|
|
||||||
(input)="email.set(getVal($event))"
|
|
||||||
autocomplete="email"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="field-lbl">
|
<label class="field-lbl">
|
||||||
<svg class="field-lbl-icon" viewBox="0 0 16 16" fill="none">
|
<svg class="field-lbl-icon" viewBox="0 0 16 16" fill="none">
|
||||||
<rect x="3" y="7" width="10" height="8" rx="2" stroke="currentColor" stroke-width="1.5"/>
|
<rect x="3" y="7" width="10" height="8" rx="2" stroke="currentColor" stroke-width="1.5" />
|
||||||
<path d="M5.5 7V5a2.5 2.5 0 015 0v2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
<path d="M5.5 7V5a2.5 2.5 0 015 0v2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||||
<circle cx="8" cy="11" r="1" fill="currentColor"/>
|
<circle cx="8" cy="11" r="1" fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
Contraseña
|
Contraseña
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input id="login-password" type="password" class="field-input" placeholder="••••••••••" [value]="password()"
|
||||||
id="login-password"
|
(input)="password.set(getVal($event))" autocomplete="current-password" />
|
||||||
type="password"
|
|
||||||
class="field-input"
|
|
||||||
placeholder="••••••••••"
|
|
||||||
[value]="password()"
|
|
||||||
(input)="password.set(getVal($event))"
|
|
||||||
autocomplete="current-password"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="btn-login" type="submit" class="btn-cta" [disabled]="loading()">
|
<button id="btn-login" type="submit" class="btn-cta" [disabled]="loading()">
|
||||||
@if (loading()) {
|
@if (loading()) {
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="animation: spin 0.8s linear infinite;">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="animation: spin 0.8s linear infinite;">
|
||||||
<circle cx="12" cy="12" r="10" stroke="rgba(255,255,255,0.3)" stroke-width="3"/>
|
<circle cx="12" cy="12" r="10" stroke="rgba(255,255,255,0.3)" stroke-width="3" />
|
||||||
<path d="M12 2a10 10 0 0110 10" stroke="#fff" stroke-width="3" stroke-linecap="round"/>
|
<path d="M12 2a10 10 0 0110 10" stroke="#fff" stroke-width="3" stroke-linecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
Autenticando...
|
Autenticando...
|
||||||
} @else {
|
} @else {
|
||||||
<svg width="14" height="14" viewBox="0 0 20 20" fill="none">
|
<svg width="14" height="14" viewBox="0 0 20 20" fill="none">
|
||||||
<path d="M10 3v14M3 10l7 7 7-7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="transform:rotate(-90deg);transform-origin:center"/>
|
<path d="M10 3v14M3 10l7 7 7-7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" style="transform:rotate(-90deg);transform-origin:center" />
|
||||||
</svg>
|
</svg>
|
||||||
Ingresar a la Intranet
|
Ingresar a la Intranet
|
||||||
}
|
}
|
||||||
@ -84,8 +72,8 @@
|
|||||||
@if (errorMsg()) {
|
@if (errorMsg()) {
|
||||||
<div class="login-error" role="alert">
|
<div class="login-error" role="alert">
|
||||||
<svg viewBox="0 0 16 16" fill="none">
|
<svg viewBox="0 0 16 16" fill="none">
|
||||||
<circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/>
|
<circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5" />
|
||||||
<path d="M8 5v3M8 10.5h.01" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
<path d="M8 5v3M8 10.5h.01" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
{{ errorMsg() }}
|
{{ errorMsg() }}
|
||||||
</div>
|
</div>
|
||||||
@ -102,25 +90,28 @@
|
|||||||
<button id="btn-sso" type="button" class="btn-sso" (click)="loginSSO()" [disabled]="loading()">
|
<button id="btn-sso" type="button" class="btn-sso" (click)="loginSSO()" [disabled]="loading()">
|
||||||
<!-- Microsoft logo -->
|
<!-- Microsoft logo -->
|
||||||
<svg class="ms-logo" viewBox="0 0 23 23" xmlns="http://www.w3.org/2000/svg">
|
<svg class="ms-logo" viewBox="0 0 23 23" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect x="0" y="0" width="10.5" height="10.5" fill="#f25022"/>
|
<rect x="0" y="0" width="10.5" height="10.5" fill="#f25022" />
|
||||||
<rect x="12.5" y="0" width="10.5" height="10.5" fill="#7fba00"/>
|
<rect x="12.5" y="0" width="10.5" height="10.5" fill="#7fba00" />
|
||||||
<rect x="0" y="12.5" width="10.5" height="10.5" fill="#00a4ef"/>
|
<rect x="0" y="12.5" width="10.5" height="10.5" fill="#00a4ef" />
|
||||||
<rect x="12.5" y="12.5" width="10.5" height="10.5" fill="#ffb900"/>
|
<rect x="12.5" y="12.5" width="10.5" height="10.5" fill="#ffb900" />
|
||||||
</svg>
|
</svg>
|
||||||
<div class="sso-text">
|
<div class="sso-text">
|
||||||
<span class="sso-provider">Microsoft Entra ID</span>
|
<span class="sso-provider">Microsoft Entra ID</span>
|
||||||
<span class="sso-hint">Active Directory SSO · Inicio único</span>
|
<span class="sso-hint">Active Directory SSO · Inicio único</span>
|
||||||
</div>
|
</div>
|
||||||
<svg class="sso-arr" viewBox="0 0 16 16" fill="none">
|
<svg class="sso-arr" viewBox="0 0 16 16" fill="none">
|
||||||
<path d="M3 8h10M9 5l3 3-3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M3 8h10M9 5l3 3-3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Security footer -->
|
<!-- Security footer -->
|
||||||
<div class="login-security">
|
<div class="login-security">
|
||||||
<svg viewBox="0 0 16 16" fill="none">
|
<svg viewBox="0 0 16 16" fill="none">
|
||||||
<path d="M8 1.5L13.5 4v4c0 3-2 5-5.5 6.5C2.5 13 .5 11 .5 8V4L8 1.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/>
|
<path d="M8 1.5L13.5 4v4c0 3-2 5-5.5 6.5C2.5 13 .5 11 .5 8V4L8 1.5z" stroke="currentColor" stroke-width="1.2"
|
||||||
<path d="M5 8l2 2 4-4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
stroke-linejoin="round" />
|
||||||
|
<path d="M5 8l2 2 4-4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
</svg>
|
</svg>
|
||||||
Acceso restringido · SSL/TLS · Políticas Entra ID
|
Acceso restringido · SSL/TLS · Políticas Entra ID
|
||||||
</div>
|
</div>
|
||||||
@ -130,6 +121,8 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
to { transform: rotate(360deg); }
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -39,12 +39,12 @@ export class LoginComponent {
|
|||||||
|
|
||||||
// Simulate authentication delay
|
// Simulate authentication delay
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (em.includes('admin') || em.includes('kinetix') || em.includes('@')) {
|
if (em.includes('admin') || em.includes('App') || em.includes('@')) {
|
||||||
const userName = em.split('@')[0];
|
const userName = em.split('@')[0];
|
||||||
const displayName = userName.charAt(0).toUpperCase() + userName.slice(1);
|
const displayName = userName.charAt(0).toUpperCase() + userName.slice(1);
|
||||||
|
|
||||||
localStorage.setItem('kinetix_token', 'local-mock-jwt-token-998822');
|
localStorage.setItem('App_token', 'local-mock-jwt-token-998822');
|
||||||
localStorage.setItem('kinetix_user', JSON.stringify({
|
localStorage.setItem('App_user', JSON.stringify({
|
||||||
name: displayName,
|
name: displayName,
|
||||||
email: em,
|
email: em,
|
||||||
role: 'Consortium Manager',
|
role: 'Consortium Manager',
|
||||||
@ -63,10 +63,10 @@ export class LoginComponent {
|
|||||||
this.loading.set(true);
|
this.loading.set(true);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
localStorage.setItem('kinetix_token', 'ad-jwt-token-microsoft-entra-id-2026');
|
localStorage.setItem('App_token', 'ad-jwt-token-microsoft-entra-id-2026');
|
||||||
localStorage.setItem('kinetix_user', JSON.stringify({
|
localStorage.setItem('App_user', JSON.stringify({
|
||||||
name: 'Jess Miller',
|
name: 'Jess Miller',
|
||||||
email: 'jess.miller@kinetix-consortium.com',
|
email: 'jess.miller@App-consortium.com',
|
||||||
role: 'Chief Architect & Director',
|
role: 'Chief Architect & Director',
|
||||||
avatar: 'JM'
|
avatar: 'JM'
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -307,7 +307,7 @@ interface UserProfile {
|
|||||||
<span class="text-neon-cyan font-bold">✓</span>
|
<span class="text-neon-cyan font-bold">✓</span>
|
||||||
<div>
|
<div>
|
||||||
<strong class="text-white block text-sm">Social</strong>
|
<strong class="text-white block text-sm">Social</strong>
|
||||||
<span class="text-xs text-slate-400">Posteo automatizado e inmediato en las redes de comunicación interna de Kinetix.</span>
|
<span class="text-xs text-slate-400">Posteo automatizado e inmediato en las redes de comunicación interna de App.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -441,7 +441,7 @@ interface UserProfile {
|
|||||||
<!-- SVG chart visualizer -->
|
<!-- SVG chart visualizer -->
|
||||||
<div class="glass-panel p-6 rounded-2xl lg:col-span-2 flex flex-col gap-4">
|
<div class="glass-panel p-6 rounded-2xl lg:col-span-2 flex flex-col gap-4">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<h3 class="text-sm font-bold uppercase tracking-widest text-slate-400">Proyección de Adopción Kinetix</h3>
|
<h3 class="text-sm font-bold uppercase tracking-widest text-slate-400">Proyección de Adopción App</h3>
|
||||||
<span class="text-xs text-emerald-400 font-bold bg-emerald-500/10 px-2.5 py-0.5 rounded-full">ROI +85%</span>
|
<span class="text-xs text-emerald-400 font-bold bg-emerald-500/10 px-2.5 py-0.5 rounded-full">ROI +85%</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -503,13 +503,13 @@ interface UserProfile {
|
|||||||
<!-- Incident response efficiency comparison (Slide 9) -->
|
<!-- Incident response efficiency comparison (Slide 9) -->
|
||||||
<div class="glass-panel p-6 rounded-2xl flex flex-col gap-4">
|
<div class="glass-panel p-6 rounded-2xl flex flex-col gap-4">
|
||||||
<h3 class="text-sm font-bold uppercase tracking-widest text-slate-400">Eficiencia en Resolución de Incidencias</h3>
|
<h3 class="text-sm font-bold uppercase tracking-widest text-slate-400">Eficiencia en Resolución de Incidencias</h3>
|
||||||
<p class="text-xs text-slate-400">Reducción drástica del tiempo de respuesta mediante el módulo de ticketing inteligente de Kinetix.</p>
|
<p class="text-xs text-slate-400">Reducción drástica del tiempo de respuesta mediante el módulo de ticketing inteligente de App.</p>
|
||||||
|
|
||||||
<!-- Custom styled CSS bar chart -->
|
<!-- Custom styled CSS bar chart -->
|
||||||
<div class="flex flex-col gap-4 mt-2">
|
<div class="flex flex-col gap-4 mt-2">
|
||||||
<!-- Kinetix V2 Bar -->
|
<!-- App V2 Bar -->
|
||||||
<div class="flex flex-col md:flex-row md:items-center gap-2">
|
<div class="flex flex-col md:flex-row md:items-center gap-2">
|
||||||
<span class="w-32 text-xs font-bold text-white">Kinetix V2 (Actual)</span>
|
<span class="w-32 text-xs font-bold text-white">App V2 (Actual)</span>
|
||||||
<div class="flex-grow bg-white/5 rounded-full overflow-hidden h-7 border border-white/5 relative">
|
<div class="flex-grow bg-white/5 rounded-full overflow-hidden h-7 border border-white/5 relative">
|
||||||
<div class="bg-gradient-to-r from-cyan-400 to-indigo-500 h-full rounded-full transition-all duration-1000" style="width: 26%"></div>
|
<div class="bg-gradient-to-r from-cyan-400 to-indigo-500 h-full rounded-full transition-all duration-1000" style="width: 26%"></div>
|
||||||
<span class="absolute right-3 top-1 text-xs font-bold text-cyan-400">12 min</span>
|
<span class="absolute right-3 top-1 text-xs font-bold text-cyan-400">12 min</span>
|
||||||
@ -587,10 +587,10 @@ interface UserProfile {
|
|||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div class="flex justify-between items-center text-xs p-3 rounded-lg bg-white/5 border border-white/5">
|
<div class="flex justify-between items-center text-xs p-3 rounded-lg bg-white/5 border border-white/5">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="font-bold text-white">Lanzamiento Kinetix V2 Intranet</span>
|
<span class="font-bold text-white">Lanzamiento App V2 Intranet</span>
|
||||||
<span class="text-[10px] text-slate-400">Autor: Director de Tecnología (SSO AD)</span>
|
<span class="text-[10px] text-slate-400">Autor: Director de Tecnología (SSO AD)</span>
|
||||||
</div>
|
</div>
|
||||||
<button (click)="approveItem('Lanzamiento Kinetix V2')" class="px-3 py-1.5 rounded-lg bg-purple-600 text-white font-extrabold text-[10px] uppercase hover:bg-purple-500 transition">
|
<button (click)="approveItem('Lanzamiento App V2')" class="px-3 py-1.5 rounded-lg bg-purple-600 text-white font-extrabold text-[10px] uppercase hover:bg-purple-500 transition">
|
||||||
Aprobar y Postear
|
Aprobar y Postear
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -671,7 +671,7 @@ export class DashboardComponent implements OnInit {
|
|||||||
// Sincronized Active Directory Active Profile signal
|
// Sincronized Active Directory Active Profile signal
|
||||||
currentUser = signal<UserProfile>({
|
currentUser = signal<UserProfile>({
|
||||||
name: 'Invitado',
|
name: 'Invitado',
|
||||||
email: 'guest@kinetix.com',
|
email: 'guest@App.com',
|
||||||
role: 'Employee',
|
role: 'Employee',
|
||||||
avatar: 'G'
|
avatar: 'G'
|
||||||
});
|
});
|
||||||
@ -749,7 +749,7 @@ export class DashboardComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
// Load Active Directory profile from local storage
|
// Load Active Directory profile from local storage
|
||||||
const userStr = localStorage.getItem('kinetix_user');
|
const userStr = localStorage.getItem('App_user');
|
||||||
if (userStr) {
|
if (userStr) {
|
||||||
try {
|
try {
|
||||||
const user = JSON.parse(userStr);
|
const user = JSON.parse(userStr);
|
||||||
@ -815,8 +815,8 @@ export class DashboardComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
localStorage.removeItem('kinetix_token');
|
localStorage.removeItem('App_token');
|
||||||
localStorage.removeItem('kinetix_user');
|
localStorage.removeItem('App_user');
|
||||||
this.router.navigate(['/login']);
|
this.router.navigate(['/login']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,71 +25,6 @@ interface Employee {
|
|||||||
template: `
|
template: `
|
||||||
<div class="dir-shell">
|
<div class="dir-shell">
|
||||||
|
|
||||||
<!-- ═══════════════════════════════ SIDEBAR ═══════════════════════════════ -->
|
|
||||||
<aside class="dir-sidebar" [class.dir-sidebar--collapsed]="sidebarCollapsed()">
|
|
||||||
|
|
||||||
<!-- Toggle btn -->
|
|
||||||
<button class="dir-sidebar__toggle" (click)="toggleSidebar()" title="Colapsar menú">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
||||||
<path [attr.d]="sidebarCollapsed() ? 'M6 2l6 6-6 6' : 'M10 2L4 8l6 6'"
|
|
||||||
stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Brand -->
|
|
||||||
<div class="dir-sidebar__brand" (click)="router.navigate(['/dashboard'])">
|
|
||||||
<div class="dir-sidebar__brand-icon">
|
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
|
||||||
<path d="M12 2L22 7L12 12L2 7L12 2Z" stroke="#C9973C" stroke-width="1.8" stroke-linejoin="round"/>
|
|
||||||
<path d="M2 12L12 17L22 12" stroke="#C9973C" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M2 17L12 22L22 17" stroke="#C9973C" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
@if (!sidebarCollapsed()) {
|
|
||||||
<span class="dir-sidebar__brand-name">Kinetix</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Nav label -->
|
|
||||||
@if (!sidebarCollapsed()) {
|
|
||||||
<div class="dir-sidebar__section-label">Panel Kinetix</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- Nav items -->
|
|
||||||
<nav class="dir-sidebar__nav">
|
|
||||||
@for (item of navItems; track item.route) {
|
|
||||||
<a [routerLink]="item.route" class="dir-sidebar__nav-item"
|
|
||||||
[class.dir-sidebar__nav-item--active]="isActiveRoute(item.route)"
|
|
||||||
[title]="item.label">
|
|
||||||
<span class="dir-sidebar__nav-icon" [innerHTML]="item.icon"></span>
|
|
||||||
@if (!sidebarCollapsed()) {
|
|
||||||
<span class="dir-sidebar__nav-label">{{ item.label }}</span>
|
|
||||||
}
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- Footer: User Profile + System Status -->
|
|
||||||
<div class="dir-sidebar__footer">
|
|
||||||
@if (!sidebarCollapsed()) {
|
|
||||||
<div class="dir-sidebar__user">
|
|
||||||
<div class="dir-sidebar__user-avatar">JM</div>
|
|
||||||
<div class="dir-sidebar__user-info">
|
|
||||||
<span class="dir-sidebar__user-name">Jess Miller</span>
|
|
||||||
<span class="dir-sidebar__user-role">Admin · HR</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="dir-sidebar__status">
|
|
||||||
<span class="dir-sidebar__status-dot"></span>
|
|
||||||
Estado API: <strong>ONLINE</strong> | Sesión SSO: <strong>ACTIVE AD</strong>
|
|
||||||
</div>
|
|
||||||
} @else {
|
|
||||||
<div class="dir-sidebar__user-avatar dir-sidebar__user-avatar--sm">JM</div>
|
|
||||||
<div class="dir-sidebar__status-dot dir-sidebar__status-dot--sm"></div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<!-- ═══════════════════════════════ MAIN PANEL ═══════════════════════════════ -->
|
<!-- ═══════════════════════════════ MAIN PANEL ═══════════════════════════════ -->
|
||||||
<main class="dir-main">
|
<main class="dir-main">
|
||||||
|
|
||||||
@ -246,7 +181,6 @@ interface Employee {
|
|||||||
export class DirectorioComponent implements OnInit {
|
export class DirectorioComponent implements OnInit {
|
||||||
readonly router = inject(Router);
|
readonly router = inject(Router);
|
||||||
|
|
||||||
sidebarCollapsed = signal(false);
|
|
||||||
contactToast = signal('');
|
contactToast = signal('');
|
||||||
|
|
||||||
searchQuery = '';
|
searchQuery = '';
|
||||||
@ -255,12 +189,6 @@ export class DirectorioComponent implements OnInit {
|
|||||||
private _searchSignal = signal('');
|
private _searchSignal = signal('');
|
||||||
private _deptSignal = signal('ALL');
|
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<Employee[]>([]);
|
employees = signal<Employee[]>([]);
|
||||||
|
|
||||||
filteredEmployees = computed(() => {
|
filteredEmployees = computed(() => {
|
||||||
@ -282,14 +210,6 @@ export class DirectorioComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit(): void { }
|
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 {
|
onSearch(value: string): void {
|
||||||
this._searchSignal.set(value);
|
this._searchSignal.set(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,10 +27,7 @@
|
|||||||
|
|
||||||
<!-- Celdas -->
|
<!-- Celdas -->
|
||||||
@for (day of calendarDays().slice(0, 35); track day.getTime()) {
|
@for (day of calendarDays().slice(0, 35); track day.getTime()) {
|
||||||
<span
|
<span (click)="selectDay(day)" [class.today]="isToday(day)" [class.other-month]="!isCurrentMonth(day)"
|
||||||
(click)="selectDay(day)"
|
|
||||||
[class.today]="isToday(day)"
|
|
||||||
[class.other-month]="!isCurrentMonth(day)"
|
|
||||||
class="mini-day-cell">
|
class="mini-day-cell">
|
||||||
{{ day.getDate() }}
|
{{ day.getDate() }}
|
||||||
</span>
|
</span>
|
||||||
@ -43,19 +40,23 @@
|
|||||||
<h4 class="filter-title">Categorías</h4>
|
<h4 class="filter-title">Categorías</h4>
|
||||||
<div class="filter-list">
|
<div class="filter-list">
|
||||||
<label class="filter-item">
|
<label class="filter-item">
|
||||||
<input type="radio" name="resType" [checked]="selectedFilter() === 'all'" (change)="setFilter('all')" class="filter-checkbox">
|
<input type="radio" name="resType" [checked]="selectedFilter() === 'all'" (change)="setFilter('all')"
|
||||||
|
class="filter-checkbox">
|
||||||
<span>Ver Todo</span>
|
<span>Ver Todo</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="filter-item">
|
<label class="filter-item">
|
||||||
<input type="radio" name="resType" [checked]="selectedFilter() === 'room'" (change)="setFilter('room')" class="filter-checkbox">
|
<input type="radio" name="resType" [checked]="selectedFilter() === 'room'" (change)="setFilter('room')"
|
||||||
|
class="filter-checkbox">
|
||||||
<span>Salas de Juntas</span>
|
<span>Salas de Juntas</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="filter-item">
|
<label class="filter-item">
|
||||||
<input type="radio" name="resType" [checked]="selectedFilter() === 'desk'" (change)="setFilter('desk')" class="filter-checkbox">
|
<input type="radio" name="resType" [checked]="selectedFilter() === 'desk'" (change)="setFilter('desk')"
|
||||||
|
class="filter-checkbox">
|
||||||
<span>Escritorios</span>
|
<span>Escritorios</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="filter-item">
|
<label class="filter-item">
|
||||||
<input type="radio" name="resType" [checked]="selectedFilter() === 'asset'" (change)="setFilter('asset')" class="filter-checkbox">
|
<input type="radio" name="resType" [checked]="selectedFilter() === 'asset'" (change)="setFilter('asset')"
|
||||||
|
class="filter-checkbox">
|
||||||
<span>Activos</span>
|
<span>Activos</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -67,7 +68,7 @@
|
|||||||
<header class="main-header">
|
<header class="main-header">
|
||||||
<div class="main-header-left">
|
<div class="main-header-left">
|
||||||
<div class="header-title-group">
|
<div class="header-title-group">
|
||||||
<div class="header-app-logo">KINETIX <span>Intranet</span></div>
|
<div class="header-app-logo">App <span>Intranet</span></div>
|
||||||
</div>
|
</div>
|
||||||
<button (click)="today()" class="btn-today">Hoy</button>
|
<button (click)="today()" class="btn-today">Hoy</button>
|
||||||
<button (click)="prev()" class="nav-arrow"><</button>
|
<button (click)="prev()" class="nav-arrow"><</button>
|
||||||
@ -125,11 +126,8 @@
|
|||||||
|
|
||||||
<!-- Eventos -->
|
<!-- Eventos -->
|
||||||
@for (event of getEventsForDay(day); track event.id) {
|
@for (event of getEventsForDay(day); track event.id) {
|
||||||
<div
|
<div (click)="$event.stopPropagation(); selectDay(day)" [style]="getEventStyle(event)"
|
||||||
(click)="$event.stopPropagation(); selectDay(day)"
|
[class]="event.colorClass" class="calendar-event cursor-pointer">
|
||||||
[style]="getEventStyle(event)"
|
|
||||||
[class]="event.colorClass"
|
|
||||||
class="calendar-event cursor-pointer">
|
|
||||||
<span class="event-time">{{ event.start | date:'shortTime' }} - {{ event.end | date:'shortTime' }}</span>
|
<span class="event-time">{{ event.start | date:'shortTime' }} - {{ event.end | date:'shortTime' }}</span>
|
||||||
<strong>{{ event.title }}</strong>
|
<strong>{{ event.title }}</strong>
|
||||||
<span>{{ event.resourceName }}</span>
|
<span>{{ event.resourceName }}</span>
|
||||||
@ -156,29 +154,24 @@
|
|||||||
<!-- Cuadrícula mensual -->
|
<!-- Cuadrícula mensual -->
|
||||||
<div class="grid-month">
|
<div class="grid-month">
|
||||||
@for (day of calendarDays(); track day.getTime()) {
|
@for (day of calendarDays(); track day.getTime()) {
|
||||||
<div
|
<div (click)="selectDay(day)" [class.other-month]="!isCurrentMonth(day)" class="month-day-cell">
|
||||||
(click)="selectDay(day)"
|
|
||||||
[class.other-month]="!isCurrentMonth(day)"
|
|
||||||
class="month-day-cell">
|
|
||||||
|
|
||||||
<div style="display: flex; justify-content: flex-start; align-items: center; margin-bottom: 4px;">
|
<div style="display: flex; justify-content: flex-start; align-items: center; margin-bottom: 4px;">
|
||||||
<span
|
<span [class.today]="isToday(day)" class="month-day-number">
|
||||||
[class.today]="isToday(day)"
|
|
||||||
class="month-day-number">
|
|
||||||
{{ day.getDate() }}
|
{{ day.getDate() }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="flex: 1; display: flex; flex-direction: column; gap: 4px; overflow-y: auto;">
|
<div style="flex: 1; display: flex; flex-direction: column; gap: 4px; overflow-y: auto;">
|
||||||
@for (event of getEventsForDay(day); track event.id) {
|
@for (event of getEventsForDay(day); track event.id) {
|
||||||
<div
|
<div (click)="$event.stopPropagation(); selectDay(day)" [class]="event.colorClass" class="calendar-event"
|
||||||
(click)="$event.stopPropagation(); selectDay(day)"
|
|
||||||
[class]="event.colorClass"
|
|
||||||
class="calendar-event"
|
|
||||||
style="position: static; width: auto; margin: 0; box-shadow: none; border-left-width: 3px;"
|
style="position: static; width: auto; margin: 0; box-shadow: none; border-left-width: 3px;"
|
||||||
[title]="event.title">
|
[title]="event.title">
|
||||||
<div style="font-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ event.title }}</div>
|
<div style="font-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{
|
||||||
<div style="font-size: 9px; opacity: 0.75; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ event.resourceName }}</div>
|
event.title }}</div>
|
||||||
|
<div
|
||||||
|
style="font-size: 9px; opacity: 0.75; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||||
|
{{ event.resourceName }}</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@ -194,7 +187,8 @@
|
|||||||
<!-- Encabezado de la semana -->
|
<!-- Encabezado de la semana -->
|
||||||
<div class="week-header" style="grid-template-cols: 64px 1fr;">
|
<div class="week-header" style="grid-template-cols: 64px 1fr;">
|
||||||
<div class="timezone-label">GMT-04</div>
|
<div class="timezone-label">GMT-04</div>
|
||||||
<div (click)="selectDay(currentDate())" class="week-day-header cursor-pointer hover:bg-slate-50 transition-colors">
|
<div (click)="selectDay(currentDate())"
|
||||||
|
class="week-day-header cursor-pointer hover:bg-slate-50 transition-colors">
|
||||||
<span class="day-name">{{ weekDays[currentDate().getDay()] }}</span>
|
<span class="day-name">{{ weekDays[currentDate().getDay()] }}</span>
|
||||||
<span [class.today]="isToday(currentDate())" class="day-number">
|
<span [class.today]="isToday(currentDate())" class="day-number">
|
||||||
{{ currentDate().getDate() }}
|
{{ currentDate().getDate() }}
|
||||||
@ -224,11 +218,8 @@
|
|||||||
|
|
||||||
<!-- Eventos -->
|
<!-- Eventos -->
|
||||||
@for (event of getEventsForDay(currentDate()); track event.id) {
|
@for (event of getEventsForDay(currentDate()); track event.id) {
|
||||||
<div
|
<div (click)="$event.stopPropagation(); selectDay(currentDate())" [style]="getEventStyle(event)"
|
||||||
(click)="$event.stopPropagation(); selectDay(currentDate())"
|
[class]="event.colorClass" class="calendar-event cursor-pointer">
|
||||||
[style]="getEventStyle(event)"
|
|
||||||
[class]="event.colorClass"
|
|
||||||
class="calendar-event cursor-pointer">
|
|
||||||
<span class="event-time">{{ event.start | date:'shortTime' }} - {{ event.end | date:'shortTime' }}</span>
|
<span class="event-time">{{ event.start | date:'shortTime' }} - {{ event.end | date:'shortTime' }}</span>
|
||||||
<strong>{{ event.title }}</strong>
|
<strong>{{ event.title }}</strong>
|
||||||
<span>{{ event.resourceName }}</span>
|
<span>{{ event.resourceName }}</span>
|
||||||
|
|||||||
@ -13,11 +13,7 @@
|
|||||||
|
|
||||||
<!-- Fila 1: Añadir Título -->
|
<!-- Fila 1: Añadir Título -->
|
||||||
<div class="title-input-container">
|
<div class="title-input-container">
|
||||||
<input
|
<input type="text" formControlName="title" placeholder="Añade un título" class="title-input" />
|
||||||
type="text"
|
|
||||||
formControlName="title"
|
|
||||||
placeholder="Añade un título"
|
|
||||||
class="title-input" />
|
|
||||||
@if (bookingForm.get('title')?.touched && bookingForm.get('title')?.invalid) {
|
@if (bookingForm.get('title')?.touched && bookingForm.get('title')?.invalid) {
|
||||||
<span style="font-size: 10px; color: #EA4335; font-weight: 600; margin-top: 4px; display: block;">
|
<span style="font-size: 10px; color: #EA4335; font-weight: 600; margin-top: 4px; display: block;">
|
||||||
El título es obligatorio.
|
El título es obligatorio.
|
||||||
@ -35,7 +31,8 @@
|
|||||||
|
|
||||||
<!-- Mensaje de éxito de pre-llenado de la IA -->
|
<!-- Mensaje de éxito de pre-llenado de la IA -->
|
||||||
@if (aiSuccessMessage()) {
|
@if (aiSuccessMessage()) {
|
||||||
<div style="padding: 10px 12px; background: rgba(52, 168, 83, 0.08); color: #137333; border: 1.5px solid rgba(52, 168, 83, 0.2); border-radius: 8px; font-size: 12px; display: flex; align-items: center; gap: 8px; font-weight: 600;">
|
<div
|
||||||
|
style="padding: 10px 12px; background: rgba(52, 168, 83, 0.08); color: #137333; border: 1.5px solid rgba(52, 168, 83, 0.2); border-radius: 8px; font-size: 12px; display: flex; align-items: center; gap: 8px; font-weight: 600;">
|
||||||
<span class="material-icons" style="font-size: 16px;">check_circle</span>
|
<span class="material-icons" style="font-size: 16px;">check_circle</span>
|
||||||
<span>{{ aiSuccessMessage() }}</span>
|
<span>{{ aiSuccessMessage() }}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -59,18 +56,14 @@
|
|||||||
<span class="material-icons row-icon" style="color: var(--gold-dark);">smart_toy</span>
|
<span class="material-icons row-icon" style="color: var(--gold-dark);">smart_toy</span>
|
||||||
<div class="row-content">
|
<div class="row-content">
|
||||||
<div class="ai-assistant-container">
|
<div class="ai-assistant-container">
|
||||||
<span class="row-label" style="color: var(--gold-dark); font-weight: bold; display: flex; align-items: center; gap: 4px;">
|
<span class="row-label"
|
||||||
<span>Asistente Inteligente Kinetix</span>
|
style="color: var(--gold-dark); font-weight: bold; display: flex; align-items: center; gap: 4px;">
|
||||||
|
<span>Asistente Inteligente App</span>
|
||||||
</span>
|
</span>
|
||||||
<textarea
|
<textarea #promptInput rows="2"
|
||||||
#promptInput
|
|
||||||
rows="2"
|
|
||||||
placeholder="Escribe: Reserva sala Alpha para 5 personas mañana a las 11:30 am..."
|
placeholder="Escribe: Reserva sala Alpha para 5 personas mañana a las 11:30 am..."
|
||||||
class="ai-assistant-textarea"></textarea>
|
class="ai-assistant-textarea"></textarea>
|
||||||
<button
|
<button type="button" (click)="processAiPrompt(promptInput.value)" [disabled]="isLoadingIa()"
|
||||||
type="button"
|
|
||||||
(click)="processAiPrompt(promptInput.value)"
|
|
||||||
[disabled]="isLoadingIa()"
|
|
||||||
class="ai-assistant-btn">
|
class="ai-assistant-btn">
|
||||||
@if (isLoadingIa()) {
|
@if (isLoadingIa()) {
|
||||||
<span>Procesando...</span>
|
<span>Procesando...</span>
|
||||||
@ -94,12 +87,8 @@
|
|||||||
<option value="desk">Escritorio / Oficina</option>
|
<option value="desk">Escritorio / Oficina</option>
|
||||||
<option value="asset">Activo / Dispositivo</option>
|
<option value="asset">Activo / Dispositivo</option>
|
||||||
</select>
|
</select>
|
||||||
<input
|
<input type="text" formControlName="resourceName" placeholder="Ej: Sala Alpha, Escritorio 4B"
|
||||||
type="text"
|
class="dialog-input" style="flex: 2; min-width: 180px;" />
|
||||||
formControlName="resourceName"
|
|
||||||
placeholder="Ej: Sala Alpha, Escritorio 4B"
|
|
||||||
class="dialog-input"
|
|
||||||
style="flex: 2; min-width: 180px;" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -115,8 +104,10 @@
|
|||||||
<input type="number" formControlName="attendees" class="dialog-input" style="width: 70px;" min="1" />
|
<input type="number" formControlName="attendees" class="dialog-input" style="width: 70px;" min="1" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label style="display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600; cursor: pointer;">
|
<label
|
||||||
<input type="checkbox" formControlName="hasProjector" style="width: 16px; height: 16px; cursor: pointer;" />
|
style="display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600; cursor: pointer;">
|
||||||
|
<input type="checkbox" formControlName="hasProjector"
|
||||||
|
style="width: 16px; height: 16px; cursor: pointer;" />
|
||||||
<span>Requiere Proyector</span>
|
<span>Requiere Proyector</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -128,10 +119,7 @@
|
|||||||
<span class="material-icons row-icon">notes</span>
|
<span class="material-icons row-icon">notes</span>
|
||||||
<div class="row-content">
|
<div class="row-content">
|
||||||
<label class="row-label">Descripción</label>
|
<label class="row-label">Descripción</label>
|
||||||
<textarea
|
<textarea formControlName="notes" rows="3" placeholder="Añadir descripción o especificaciones adicionales..."
|
||||||
formControlName="notes"
|
|
||||||
rows="3"
|
|
||||||
placeholder="Añadir descripción o especificaciones adicionales..."
|
|
||||||
class="dialog-textarea"></textarea>
|
class="dialog-textarea"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -115,7 +115,7 @@ export class IaParserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.title = `Reserva de ${result.resourceType === 'room' ? 'Sala' : result.resourceType === 'desk' ? 'Escritorio' : 'Activo'}`;
|
result.title = `Reserva de ${result.resourceType === 'room' ? 'Sala' : result.resourceType === 'desk' ? 'Escritorio' : 'Activo'}`;
|
||||||
result.notes = `Sugerencia de reserva generada inteligentemente por el Asistente IA Kinetix.`;
|
result.notes = `Sugerencia de reserva generada inteligentemente por el Asistente IA App.`;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,17 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="es">
|
<html lang="es">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Kinetix Intranet</title>
|
<title>App Intranet</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="bg-black text-white antialiased">
|
<body class="bg-black text-white antialiased">
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
/* ============================================================
|
/* ============================================================
|
||||||
KINETIX INTRANET — Executive Suite Design System
|
App INTRANET — Executive Suite Design System
|
||||||
Paleta: Fondo blanco, tarjetas joya sofisticadas
|
Paleta: Fondo blanco, tarjetas joya sofisticadas
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
@ -9,32 +9,47 @@
|
|||||||
/* ── Design Tokens ─────────────────────────────────────── */
|
/* ── Design Tokens ─────────────────────────────────────── */
|
||||||
:root {
|
:root {
|
||||||
/* ── Fondo y superficies (LIGHT MODE) ── */
|
/* ── Fondo y superficies (LIGHT MODE) ── */
|
||||||
--bg-page: #F5F4F1; /* Blanco cálido / ivory */
|
--bg-page: #F5F4F1;
|
||||||
--bg-surface: #FFFFFF; /* Superficie limpia */
|
/* Blanco cálido / ivory */
|
||||||
--bg-surface-2: #F0EEE9; /* Superficies secundarias */
|
--bg-surface: #FFFFFF;
|
||||||
|
/* Superficie limpia */
|
||||||
|
--bg-surface-2: #F0EEE9;
|
||||||
|
/* Superficies secundarias */
|
||||||
|
|
||||||
/* ── Colores joya para tarjetas sofisticadas ── */
|
/* ── Colores joya para tarjetas sofisticadas ── */
|
||||||
--card-navy: #0D1B2A; /* Azul marino profundo */
|
--card-navy: #0D1B2A;
|
||||||
--card-plum: #1A0E2E; /* Ciruela / Violeta oscuro */
|
/* Azul marino profundo */
|
||||||
--card-viridian: #0A2520; /* Verde Viridian / Bosque profundo */
|
--card-plum: #1A0E2E;
|
||||||
--card-espresso: #1C0E08; /* Espresso / Marrón ébano */
|
/* Ciruela / Violeta oscuro */
|
||||||
|
--card-viridian: #0A2520;
|
||||||
|
/* Verde Viridian / Bosque profundo */
|
||||||
|
--card-espresso: #1C0E08;
|
||||||
|
/* Espresso / Marrón ébano */
|
||||||
|
|
||||||
/* ── Acentos elegantes ── */
|
/* ── Acentos elegantes ── */
|
||||||
--gold-luxe: #C9973C; /* Oro antiguo (visible en blanco) */
|
--gold-luxe: #C9973C;
|
||||||
|
/* Oro antiguo (visible en blanco) */
|
||||||
--gold-dark: #A67C28;
|
--gold-dark: #A67C28;
|
||||||
--gold-light: #F0C060; /* Destellos dorados en tarjetas */
|
--gold-light: #F0C060;
|
||||||
--indigo-rich: #3730A3; /* Índigo profundo */
|
/* Destellos dorados en tarjetas */
|
||||||
--sapphire: #1E3A8A; /* Zafiro */
|
--indigo-rich: #3730A3;
|
||||||
--emerald-deep: #065F46; /* Esmeralda profundo */
|
/* Índigo profundo */
|
||||||
|
--sapphire: #1E3A8A;
|
||||||
|
/* Zafiro */
|
||||||
|
--emerald-deep: #065F46;
|
||||||
|
/* Esmeralda profundo */
|
||||||
|
|
||||||
/* ── Neon (login / accents) ── */
|
/* ── Neon (login / accents) ── */
|
||||||
--neon-blue: #0EA5E9;
|
--neon-blue: #0EA5E9;
|
||||||
--neon-cyan: #06B6D4;
|
--neon-cyan: #06B6D4;
|
||||||
|
|
||||||
/* ── Textos (dark on white) ── */
|
/* ── Textos (dark on white) ── */
|
||||||
--text-primary: #0F0D0A; /* Negro cálido */
|
--text-primary: #0F0D0A;
|
||||||
--text-secondary: #44403C; /* Marrón oscuro */
|
/* Negro cálido */
|
||||||
--text-muted: #78716C; /* Gris cálido */
|
--text-secondary: #44403C;
|
||||||
|
/* Marrón oscuro */
|
||||||
|
--text-muted: #78716C;
|
||||||
|
/* Gris cálido */
|
||||||
--text-placeholder: #A8A29E;
|
--text-placeholder: #A8A29E;
|
||||||
|
|
||||||
/* ── Bordes / divisores ── */
|
/* ── Bordes / divisores ── */
|
||||||
@ -55,7 +70,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ── Base ──────────────────────────────────────────────── */
|
/* ── Base ──────────────────────────────────────────────── */
|
||||||
*, *::before, *::after { box-sizing: border-box; }
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@ -74,7 +93,11 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ── Typography ─────────────────────────────────────────── */
|
/* ── Typography ─────────────────────────────────────────── */
|
||||||
h1, h2, h3, h4, .font-display {
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
.font-display {
|
||||||
font-family: 'Inter', 'Outfit', sans-serif;
|
font-family: 'Inter', 'Outfit', sans-serif;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -154,17 +177,31 @@ h1, h2, h3, h4, .font-display {
|
|||||||
.neon-border-gold {
|
.neon-border-gold {
|
||||||
border: 1px solid rgba(201, 151, 60, 0.25);
|
border: 1px solid rgba(201, 151, 60, 0.25);
|
||||||
}
|
}
|
||||||
.neon-border-gold:hover { border-color: var(--gold-luxe); }
|
|
||||||
|
.neon-border-gold:hover {
|
||||||
|
border-color: var(--gold-luxe);
|
||||||
|
}
|
||||||
|
|
||||||
.neon-border-blue {
|
.neon-border-blue {
|
||||||
border: 1px solid rgba(59, 130, 246, 0.20);
|
border: 1px solid rgba(59, 130, 246, 0.20);
|
||||||
}
|
}
|
||||||
.neon-border-blue:hover { border-color: var(--sapphire); }
|
|
||||||
|
.neon-border-blue:hover {
|
||||||
|
border-color: var(--sapphire);
|
||||||
|
}
|
||||||
|
|
||||||
/* Legacy borders */
|
/* Legacy borders */
|
||||||
.neon-border-cyan { border: 1px solid rgba(14, 165, 233, 0.20); }
|
.neon-border-cyan {
|
||||||
.neon-border-magenta { border: 1px solid rgba(240, 12, 147, 0.15); }
|
border: 1px solid rgba(14, 165, 233, 0.20);
|
||||||
.neon-border-purple { border: 1px solid rgba(124, 58, 237, 0.15); }
|
}
|
||||||
|
|
||||||
|
.neon-border-magenta {
|
||||||
|
border: 1px solid rgba(240, 12, 147, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.neon-border-purple {
|
||||||
|
border: 1px solid rgba(124, 58, 237, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Typography Colors ──────────────────────────────────── */
|
/* ── Typography Colors ──────────────────────────────────── */
|
||||||
.text-gold {
|
.text-gold {
|
||||||
@ -205,7 +242,10 @@ h1, h2, h3, h4, .font-display {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-luxe::placeholder { color: var(--text-placeholder); }
|
.input-luxe::placeholder {
|
||||||
|
color: var(--text-placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
.input-luxe:focus {
|
.input-luxe:focus {
|
||||||
border-color: var(--gold-luxe);
|
border-color: var(--gold-luxe);
|
||||||
box-shadow: 0 0 0 3px rgba(201, 151, 60, 0.12);
|
box-shadow: 0 0 0 3px rgba(201, 151, 60, 0.12);
|
||||||
@ -227,7 +267,10 @@ h1, h2, h3, h4, .font-display {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-cyberpunk::placeholder { color: var(--text-placeholder); }
|
.input-cyberpunk::placeholder {
|
||||||
|
color: var(--text-placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
.input-cyberpunk:focus {
|
.input-cyberpunk:focus {
|
||||||
border-color: var(--gold-luxe);
|
border-color: var(--gold-luxe);
|
||||||
box-shadow: 0 0 0 3px rgba(201, 151, 60, 0.10);
|
box-shadow: 0 0 0 3px rgba(201, 151, 60, 0.10);
|
||||||
@ -249,7 +292,10 @@ h1, h2, h3, h4, .font-display {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-dark::placeholder { color: rgba(255, 255, 255, 0.35); }
|
.input-dark::placeholder {
|
||||||
|
color: rgba(255, 255, 255, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
.input-dark:focus {
|
.input-dark:focus {
|
||||||
border-color: rgba(201, 151, 60, 0.55);
|
border-color: rgba(201, 151, 60, 0.55);
|
||||||
box-shadow: 0 0 0 3px rgba(201, 151, 60, 0.12);
|
box-shadow: 0 0 0 3px rgba(201, 151, 60, 0.12);
|
||||||
@ -304,7 +350,10 @@ h1, h2, h3, h4, .font-display {
|
|||||||
box-shadow: 0 4px 16px rgba(201, 151, 60, 0.30);
|
box-shadow: 0 4px 16px rgba(201, 151, 60, 0.30);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-cyan:hover { transform: translateY(-2px); box-shadow: 0 6px 22px rgba(201, 151, 60, 0.45); }
|
.btn-cyan:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 22px rgba(201, 151, 60, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
/* Indigo solid */
|
/* Indigo solid */
|
||||||
.btn-indigo {
|
.btn-indigo {
|
||||||
@ -328,7 +377,9 @@ h1, h2, h3, h4, .font-display {
|
|||||||
box-shadow: 0 4px 16px rgba(55, 48, 163, 0.30);
|
box-shadow: 0 4px 16px rgba(55, 48, 163, 0.30);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-indigo:hover { transform: translateY(-2px); }
|
.btn-indigo:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
/* Ghost on white */
|
/* Ghost on white */
|
||||||
.btn-ghost {
|
.btn-ghost {
|
||||||
@ -373,16 +424,29 @@ h1, h2, h3, h4, .font-display {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.2s;
|
transition: transform 0.2s;
|
||||||
}
|
}
|
||||||
.btn-purple:hover { transform: translateY(-1px); }
|
|
||||||
|
.btn-purple:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Scrollbar ─────────────────────────────────────────── */
|
/* ── Scrollbar ─────────────────────────────────────────── */
|
||||||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
::-webkit-scrollbar {
|
||||||
::-webkit-scrollbar-track { background: var(--bg-surface-2); }
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--bg-surface-2);
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: rgba(15, 13, 10, 0.15);
|
background: rgba(15, 13, 10, 0.15);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
::-webkit-scrollbar-thumb:hover { background: rgba(201, 151, 60, 0.45); }
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(201, 151, 60, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Utilities ─────────────────────────────────────────── */
|
/* ── Utilities ─────────────────────────────────────────── */
|
||||||
.animate-fadeIn {
|
.animate-fadeIn {
|
||||||
@ -390,12 +454,25 @@ h1, h2, h3, h4, .font-display {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from { opacity: 0; transform: translateY(8px); }
|
from {
|
||||||
to { opacity: 1; transform: translateY(0); }
|
opacity: 0;
|
||||||
|
transform: translateY(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sr-only {
|
.sr-only {
|
||||||
position: absolute; width: 1px; height: 1px;
|
position: absolute;
|
||||||
padding: 0; margin: -1px; overflow: hidden;
|
width: 1px;
|
||||||
clip: rect(0,0,0,0); white-space: nowrap; border: 0;
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
}
|
}
|
||||||
12
package-lock.json
generated
12
package-lock.json
generated
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "kinetix-monorepo",
|
"name": "App-monorepo",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "kinetix-monorepo",
|
"name": "App-monorepo",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"frontend",
|
"frontend",
|
||||||
@ -16,7 +16,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"backend": {
|
"backend": {
|
||||||
"name": "kinetix-backend",
|
"name": "App-backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
@ -38,7 +38,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"frontend": {
|
"frontend": {
|
||||||
"name": "kinetix-frontend",
|
"name": "App-frontend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^18.0.0",
|
"@angular/animations": "^18.0.0",
|
||||||
@ -9582,11 +9582,11 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/kinetix-backend": {
|
"node_modules/App-backend": {
|
||||||
"resolved": "backend",
|
"resolved": "backend",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/kinetix-frontend": {
|
"node_modules/App-frontend": {
|
||||||
"resolved": "frontend",
|
"resolved": "frontend",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "kinetix-monorepo",
|
"name": "App-monorepo",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
# setup.ps1
|
# setup.ps1
|
||||||
Write-Output "=== Iniciando configuración de Kinetix Intranet Monorepo ==="
|
Write-Output "=== Iniciando configuración de App Intranet Monorepo ==="
|
||||||
|
|
||||||
# 1. Copiar archivo de entorno si no existe
|
# 1. Copiar archivo de entorno si no existe
|
||||||
if (!(Test-Path ".env")) {
|
if (!(Test-Path ".env")) {
|
||||||
|
|||||||
2
setup.sh
2
setup.sh
@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# setup.sh
|
# setup.sh
|
||||||
echo "=== Iniciando configuración de Kinetix Intranet Monorepo ==="
|
echo "=== Iniciando configuración de App Intranet Monorepo ==="
|
||||||
|
|
||||||
# 1. Copiar archivo de entorno si no existe
|
# 1. Copiar archivo de entorno si no existe
|
||||||
if [ ! -f .env ]; then
|
if [ ! -f .env ]; then
|
||||||
|
|||||||
@ -14,7 +14,7 @@ El proyecto utiliza **PostgreSQL** para persistencia estructurada y transacciona
|
|||||||
## 2. Esquema Conceptual (Prisma Schema Reference)
|
## 2. Esquema Conceptual (Prisma Schema Reference)
|
||||||
|
|
||||||
```prisma
|
```prisma
|
||||||
// Esquema de Base de Datos Kinetix Intranet
|
// Esquema de Base de Datos App Intranet
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
|||||||
@ -31,7 +31,7 @@ El backend utiliza `passport-jwt` o `passport-azure-ad` para descifrar y validar
|
|||||||
|
|
||||||
### Claims esperados en el JWT decodificado:
|
### Claims esperados en el JWT decodificado:
|
||||||
- `sub` (Identificador único de usuario)
|
- `sub` (Identificador único de usuario)
|
||||||
- `email` (Correo electrónico corporativo, ej: `jess@kinetix.com`)
|
- `email` (Correo electrónico corporativo, ej: `jess@App.com`)
|
||||||
- `name` (Nombre del empleado)
|
- `name` (Nombre del empleado)
|
||||||
- `roles` (Roles asignados, ej: `['Employee', 'Manager']`)
|
- `roles` (Roles asignados, ej: `['Employee', 'Manager']`)
|
||||||
- `department` (Departamento de la organización)
|
- `department` (Departamento de la organización)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Estándares - UI/UX Diseño Executive Suite / Kinetix
|
# Estándares - UI/UX Diseño Executive Suite / App
|
||||||
|
|
||||||
Este documento establece la guía de estilos visuales e interactivos de la intranet corporativa Kinetix, adaptada al **Executive Suite Design System**.
|
Este documento establece la guía de estilos visuales e interactivos de la intranet corporativa App, adaptada al **Executive Suite Design System**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user