npm package: @mp-front/common
Description: Una librería integral de TypeScript para aplicaciones frontend con autenticación, caché, cliente HTTP y utilidades de middleware construida con RxJS y patrones modernos.
Latest version: 0.0.3

# @mp-front/common

Una librería integral de TypeScript para aplicaciones frontend con autenticación, caché, cliente HTTP y utilidades de middleware construida con RxJS y patrones modernos.

## Comenzando

### Instalación

```bash
npm install @mp-front/common
```

### Dependencias Peer

```bash
npm install ioredis@5.3.2 next-auth@4.24.13 node-jose@2.2.0 rxjs@7.8.1
```

### Inicio Rápido

```typescript
import { HttpClient } from '@mp-front/common/http'
import { Logger } from '@mp-front/common/helpers'

const client = new HttpClient()
const logger = new Logger()

client.get('/api/users').subscribe(users => {
  logger.logInfo('Usuarios cargados', JSON.stringify(users))
})
```

## Arquitectura

La librería sigue una arquitectura modular que permite tree-shaking e importaciones selectivas:

```
@mp-front/common/
├── auth              # Servicios de autenticación
├── engine            # Integración con Azure Entra ID
├── http              # Cliente HTTP con RxJS
├── cache             # Caché de sesión y terminal
├── cache-providers   # Implementación de caché Redis
├── helpers           # Utilidades (Logger, Encoder, Encrypter)
├── errors            # Sistema de manejo de errores
├── rxjs              # Utilidades y manejadores RxJS
├── middleware        # Middleware de API para Next.js
└── adapters          # Adaptadores NextAuth
```

## Referencia de API

### Autenticación

#### AuthorizationService

Servicio para autenticación de tokens del backend.

```typescript
import { AuthorizationService } from '@mp-front/common/auth'

const authService = new AuthorizationService()

authService.get().subscribe({
  next: token => console.log('Token:', token),
  error: error => console.error('Error de auth:', error)
})
```

**Variables de Entorno:**
```env
API_AUTH_BACK_URL=https://api.example.com/auth
API_AUTH_BACK_USERNAME_AUTH=username
API_AUTH_BACK_PASSWORD_AUTH=password
ID_FRONT=frontend-app-id
```

### Motor Azure

#### AuthEngine

Integración con Azure Entra ID para verificación de roles y grupos.

```typescript
import { AuthEngine } from '@mp-front/common/engine'

const authEngine = new AuthEngine()

// Verificar roles de usuario
authEngine.getRoles('user@example.com', 'app-object-id').subscribe({
  next: ({ roles }) => console.log('Roles del usuario:', roles)
})

// Verificar rol específico
authEngine.inRole('user@example.com', 'role-id', 'app-object-id').subscribe({
  next: hasRole => console.log('Tiene rol:', hasRole)
})

// Verificar membresía de grupo
authEngine.inGroup('user@example.com', 'group-id').subscribe({
  next: inGroup => console.log('En grupo:', inGroup)
})
```

**Variables de Entorno:**
```env
AZURE_AD_GRAPH_GET_APP_ROLES=https://graph.microsoft.com/v1.0/applications/{id-obj}/appRoles
AZURE_AD_GRAPH_GET_USER_BY_EMAIL=https://graph.microsoft.com/v1.0/users/{user-mail}
AZURE_AD_GRAPH_GROUPS=https://graph.microsoft.com/v1.0/users/idUser/memberOf
```

### Cliente HTTP

#### HttpClient

Cliente HTTP basado en RxJS con estados de carga automáticos y manejo de errores.

```typescript
import { HttpClient } from '@mp-front/common/http'

const client = new HttpClient()

// Petición GET
client.get<User[]>('/api/users', {
  params: { page: 1, limit: 10 },
  headers: { 'Authorization': 'Bearer token' }
}).subscribe(users => {
  console.log('Usuarios:', users)
})

// Petición POST
client.post<User>('/api/users', {
  name: 'Juan Pérez',
  email: 'juan@example.com'
}).subscribe(newUser => {
  console.log('Creado:', newUser)
})

// Deshabilitar carga global
client.setIsLoadingEnabled(false)
```

**Métodos:**
- `get<T>(url, options?)` - Petición GET
- `post<T>(url, body, options?)` - Petición POST  
- `put<T>(url, body, options?)` - Petición PUT
- `patch<T>(url, body, options?)` - Petición PATCH
- `delete<T>(url, options?)` - Petición DELETE

### Gestión de Caché

#### SessionCache

Gestión de sesiones de usuario con Redis.

```typescript
import { SessionCache } from '@mp-front/common/cache'

const sessionCache = new SessionCache('user-id-123')

// Obtener sesión básica
const session = await sessionCache.getBasicSession()
console.log('Usuario:', session.user)

// Obtener sesión completa con datos de tienda
const fullSession = await sessionCache.getUserAndShoppingStore()
```

#### TerminalCache

Gestión de datos de terminal.

```typescript
import { TerminalCache } from '@mp-front/common/cache'

const terminalCache = new TerminalCache()

// Establecer datos de terminal
await terminalCache.set('SELLER001', {
  terminalId: 'TERM123',
  location: 'Tienda A',
  status: 'active'
})

// Obtener datos de terminal
const terminal = await terminalCache.get('SELLER001')

// Eliminar datos de terminal
await terminalCache.delete('SELLER001')
```

### Proveedores de Caché

#### RedisCache

Proveedor genérico de caché Redis con soporte de encriptación.

```typescript
import { RedisCache } from '@mp-front/common/cache-providers'

interface UserData {
  name: string
  email: string
}

const redis = new RedisCache<UserData>()

// Establecer datos con encriptación
await redis.set({
  prefix: 'user:123',
  idData: 'profile',
  body: { name: 'Juan', email: 'juan@example.com' },
  expire: 3600,
  encrypted: true
})

// Obtener datos
const { data, sha } = await redis.get<UserData>('user:123', 'profile')

// Eliminar datos
await redis.delete('user:123', 'profile')

// Operaciones simples
const value = await redis.simpleGet('key')
await redis.simpleHSet('hash-key', 'field', 'value')
const allFields = await redis.simpleHGetAll('hash-key')
```

**Variables de Entorno:**
```env
REDIS_URL=redis://localhost:6379
TIMEOUT_SESSION_MINUTES=60
SECRET_SIGNATURE=tu-clave-secreta-min-32-chars
```

### Utilidades

#### Logger

Logging estructurado con niveles y colores.

```typescript
import { Logger } from '@mp-front/common/helpers'

const logger = new Logger()

logger.logError('Error crítico', JSON.stringify(error))
logger.logWarn('Uso de API deprecada')
logger.logInfo('Usuario autenticado exitosamente')
logger.logDebug('Datos de petición:', JSON.stringify(data))
```

**Niveles de Log:** `error`, `warn`, `info`, `http`, `verbose`, `debug`, `silly`

**Variables de Entorno:**
```env
NEXT_PUBLIC_APP_LOGS_NAME=MiApp
NEXT_PUBLIC_LOGS_LEVEL=debug
NEXT_PUBLIC_SILENT_LOGS=false
```

#### Encoder

Codificación/decodificación Base64 para datos de API.

```typescript
import { Encoder } from '@mp-front/common/helpers'

const encoder = new Encoder()

// Codificar datos
const encoded = encoder.encode({ name: 'Juan' }, 'request-id-123')
// Resultado: { info: 'base64...', requestID: 'request-id-123' }

// Decodificar datos
const decoded = encoder.decode(encoded)
// Resultado: { name: 'Juan' }
```

#### Encrypter

Encriptación/desencriptación JWE con node-jose.

```typescript
import { Encrypter } from '@mp-front/common/helpers'

const encrypter = new Encrypter()

// Encriptar datos sensibles
const encrypted = await encrypter.encrypt({
  password: 'secret123',
  apiKey: 'key-xyz'
})

// Desencriptar datos
const decrypted = await encrypter.decrypt(encrypted)

// Verificar si los datos están encriptados
const isEncrypted = await encrypter.isEncrypted(someString)

// Generar hash SHA256
const sha = encrypter.generateSHA({ userId: '123' })
```

### Manejo de Errores

#### ErrorHandler

Gestión global de errores con patrón Singleton.

```typescript
import { ErrorHandler } from '@mp-front/common/errors'

// Suscribirse a errores globales
ErrorHandler.getInstance().getSubject().subscribe(error => {
  console.error('Error global:', error)
  showNotification({
    type: 'error',
    message: error.message,
    code: error.code
  })
})
```

#### RuntimeError

Clase de error personalizada con seguimiento de peticiones.

```typescript
import { RuntimeError, RuntimeErrorCode } from '@mp-front/common/errors'

// Lanzar error personalizado
throw new RuntimeError(RuntimeErrorCode.VALIDATION_ERROR, 'request-id-123')

// En bloques catch
try {
  // código
} catch (error) {
  throw new RuntimeError('CUSTOM_ERROR', requestId)
}
```

### Utilidades RxJS

#### LoadingHandler

Gestión global del estado de carga.

```typescript
import { LoadingHandler } from '@mp-front/common/rxjs'

// Suscribirse al estado de carga
LoadingHandler.getInstance().getSubject().subscribe(isLoading => {
  if (isLoading) {
    showSpinner()
  } else {
    hideSpinner()
  }
})

// Controlar carga manualmente
LoadingHandler.getInstance().setSubject(true)  // Mostrar carga
LoadingHandler.getInstance().setSubject(false) // Ocultar carga
```

**Integración con React:**
```typescript
import { useEffect, useState } from 'react'
import { LoadingHandler } from '@mp-front/common/rxjs'

function App() {
  const [isLoading, setIsLoading] = useState(false)

  useEffect(() => {
    const subscription = LoadingHandler.getInstance()
      .getSubject()
      .subscribe(setIsLoading)

    return () => subscription.unsubscribe()
  }, [])

  return (
    <>
      {isLoading && <Spinner />}
      {/* resto de la app */}
    </>
  )
}
```

### Middleware

#### ApiMiddleware

Middleware de API para Next.js con codificación/decodificación automática.

```typescript
import { ApiMiddleware } from '@mp-front/common/middleware'
import { of } from 'rxjs'

interface CreateUserRequest {
  name: string
  email: string
}

interface CreateUserResponse {
  id: string
  name: string
  email: string
}

class UserApiMiddleware extends ApiMiddleware {
  createUser = this.get<CreateUserRequest, CreateUserResponse>(
    (params, { requestID, headers }) => {
      // Validación
      if (!params.name || !params.email) {
        throw new RuntimeError('VALIDATION_ERROR', requestID)
      }

      // Obtener sesión
      const session = this.getSession()

      // Lógica de negocio
      const newUser = {
        id: generateId(),
        name: params.name,
        email: params.email
      }

      return of(newUser)
    }
  )
}

export const POST = new UserApiMiddleware().createUser
```

**Manejador de Subida de Archivos:**
```typescript
export const POST = middleware.getFormData<ResponseType>(
  (params, files) => {
    // Procesar archivos
    return of({
      uploadedFiles: files.map(f => ({
        name: f.name,
        size: f.size
      }))
    })
  }
)
```

### Adaptadores

#### IORedisAdapter

Adaptador NextAuth para Redis.

```typescript
import { IORedisAdapter } from '@mp-front/common/adapters'

export const authOptions = {
  adapter: IORedisAdapter({
    expire: 3600 // segundos
  }),
  // ... otras opciones de NextAuth
}
```

## Configuración

### Variables de Entorno

Crea un archivo `.env.local` con las siguientes variables:

```env
# Logging
NEXT_PUBLIC_APP_LOGS_NAME=MiApp
NEXT_PUBLIC_LOGS_LEVEL=debug
NEXT_PUBLIC_SILENT_LOGS=false

# Backend de Autenticación
API_AUTH_BACK_URL=https://api.example.com/auth
API_AUTH_BACK_USERNAME_AUTH=username
API_AUTH_BACK_PASSWORD_AUTH=password
ID_FRONT=frontend-app-id

# Azure Entra ID
AZURE_AD_GRAPH_GET_APP_ROLES=https://graph.microsoft.com/v1.0/applications/{id-obj}/appRoles
AZURE_AD_GRAPH_GET_USER_BY_EMAIL=https://graph.microsoft.com/v1.0/users/{user-mail}
AZURE_AD_GRAPH_GROUPS=https://graph.microsoft.com/v1.0/users/idUser/memberOf

# Redis
REDIS_URL=redis://localhost:6379
TIMEOUT_SESSION_MINUTES=60
PREFIX_LOGIN=app

# Encriptación
SECRET_SIGNATURE=tu-clave-secreta-min-32-chars
```

## Ejemplos

### Sistema de Autenticación Completo

```typescript
import { AuthorizationService } from '@mp-front/common/auth'
import { HttpClient } from '@mp-front/common/http'
import { SessionCache } from '@mp-front/common/cache'
import { firstValueFrom } from 'rxjs'

class AuthSystem {
  private authService = new AuthorizationService()
  private httpClient = new HttpClient()
  private sessionCache: SessionCache

  async login(userId: string) {
    // Obtener token
    const token = await firstValueFrom(this.authService.get())

    // Configurar sesión
    this.sessionCache = new SessionCache(userId)
    const session = await this.sessionCache.getBasicSession()

    // Hacer petición autenticada
    this.httpClient.get('/api/protected', {
      headers: { Authorization: `Bearer ${token}` }
    }).subscribe(data => {
      console.log('Datos protegidos:', data)
    })
  }
}
```

### Sistema de Caché Multi-nivel

```typescript
import { RedisCache } from '@mp-front/common/cache-providers'

class CacheSystem<T> {
  private redis = new RedisCache<T>()
  private memoryCache = new Map<string, T>()

  async get(key: string): Promise<T | null> {
    // Nivel 1: Memoria
    if (this.memoryCache.has(key)) {
      return this.memoryCache.get(key)!
    }

    // Nivel 2: Redis
    try {
      const { data } = await this.redis.get<T>(key, 'data')
      this.memoryCache.set(key, data)
      return data
    } catch {
      return null
    }
  }

  async set(key: string, value: T, ttl: number = 3600) {
    this.memoryCache.set(key, value)
    
    await this.redis.set({
      prefix: key,
      idData: 'data',
      body: value,
      expire: ttl,
      encrypted: true
    })
  }
}
```

### Sistema Global de Errores

```typescript
import { ErrorHandler, RuntimeError } from '@mp-front/common/errors'
import { LoadingHandler } from '@mp-front/common/rxjs'

class GlobalErrorSystem {
  constructor() {
    this.setupErrorHandling()
  }

  private setupErrorHandling() {
    ErrorHandler.getInstance().getSubject().subscribe(error => {
      // Ocultar carga
      LoadingHandler.getInstance().setSubject(false)

      // Registrar error
      console.error('Error:', error)

      // Mostrar notificación
      this.showErrorNotification(error)
    })
  }

  private showErrorNotification(error: RuntimeError) {
    // La implementación depende de tu sistema de notificaciones
  }
}
```

## Mejores Prácticas

### Gestión de Suscripciones RxJS

```typescript
import { Component, OnDestroy } from '@angular/core'
import { Subject, takeUntil } from 'rxjs'

class MyComponent implements OnDestroy {
  private destroy$ = new Subject<void>()

  ngOnInit() {
    this.httpClient.get('/api/data')
      .pipe(takeUntil(this.destroy$))
      .subscribe(data => {
        // Procesar datos
      })
  }

  ngOnDestroy() {
    this.destroy$.next()
    this.destroy$.complete()
  }
}
```

### Seguridad de Tipos

```typescript
// Definir interfaces
interface User {
  id: string
  name: string
  email: string
}

// Usar tipos genéricos
const redis = new RedisCache<User>()
const client = new HttpClient()

client.get<User>('/api/user/1').subscribe(user => {
  // TypeScript sabe que user es de tipo User
  console.log(user.name)
})
```

### Manejo de Errores

```typescript
import { catchError, of } from 'rxjs'

client.get<Data>('/api/data')
  .pipe(
    catchError(error => {
      console.error('Error:', error)
      return of(null) // Valor por defecto
    })
  )
  .subscribe(data => {
    if (data) {
      // Procesar datos
    }
  })
```

## Solución de Problemas

### Problemas de Conexión Redis

```typescript
import { RedisCache } from '@mp-front/common/cache-providers'

const redis = new RedisCache()

redis.statusHost()
  .then(() => console.log('Redis conectado'))
  .catch(error => console.error('Error de conexión:', error))
```

### Los Logs No Aparecen

Verifica estas variables de entorno:
1. `NEXT_PUBLIC_SILENT_LOGS=false`
2. `NEXT_PUBLIC_LOGS_LEVEL` está configurado correctamente
3. El nivel de log es apropiado para tus mensajes

### Fallas de Encriptación

Verifica:
1. `SECRET_SIGNATURE` está definido en `.env`
2. La clave tiene al menos 32 caracteres
3. Se usa la misma clave para operaciones de encriptar/desencriptar

## Guía de Migración

### De v0.0.1 a v0.0.2

- Actualiza las rutas de importación para usar importaciones modulares
- Reemplaza métodos deprecados con la nueva API
- Actualiza nombres de variables de entorno

## Contribuir

1. Haz fork del repositorio
2. Crea una rama de feature
3. Realiza tus cambios
4. Agrega pruebas
5. Envía un pull request

## Licencia

Privado - Solo uso interno