npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cbm-common/branch-card

v0.0.1

Published

Componente Angular standalone para mostrar información detallada de sucursales empresariales. Incluye visualización de datos, estados operativos, navegación entre sucursales y integración completa con el sistema de autenticación CBM.

Readme

Branch Card

Componente Angular standalone para mostrar información detallada de sucursales empresariales. Incluye visualización de datos, estados operativos, navegación entre sucursales y integración completa con el sistema de autenticación CBM.

📦 Instalación

npm install @cbm-common/branch-card

⚙️ Configuración

Importación del Componente

El componente es standalone, por lo que se puede importar directamente:

import { CbmBranchCardComponent } from '@cbm-common/branch-card';

@Component({
  selector: 'app-branch-selector',
  standalone: true,
  imports: [CbmBranchCardComponent],
  template: `
    <cbm-branch-card
      [route-change]="['/dashboard']"
      [change-branch]="true"
    />
  `
})
export class BranchSelectorComponent {}

🎯 Propiedades de Entrada (Inputs)

route-change: string | string[] (Requerido)

Ruta de navegación cuando se cambia de sucursal. Puede ser una ruta simple o un array de rutas.

// Ruta simple
[route-change]="'/dashboard'"

// Array de rutas con parámetros
[route-change]="['/branch', branchId, 'dashboard']"

// Ruta relativa
[route-change]="'./products'"

change-branch: boolean (Opcional, default: true)

Controla si se muestra el botón para cambiar de sucursal.

// Mostrar botón de cambio
[change-branch]="true"

// Ocultar botón de cambio
[change-branch]="false"

🏗️ Arquitectura del Componente

Patrón de Diseño

El componente sigue el patrón Presentation Component integrado con Authentication Service:

CbmBranchCardComponent
├── Sistema de autenticación CBM
├── Gestión de estado con Signals
├── Navegación programática
├── Shadow DOM para encapsulación
├── Tailwind CSS para estilos
└── Iconos SVG embebidos

Estado y Señales

export class CbmBranchCardComponent {
  // Señales para estado reactivo
  expaded = signal<boolean>(false);

  // Datos computados
  routeChange = computed(() => this['route-change']());
  changeBranch = computed(() => this['change-branch']());

  // Datos de autenticación
  companyBranch = this.authService.authData.companyBranchData;
}

Integración con Auth Service

constructor(
  private readonly authService: CbmAuthService,
  private readonly router: Router,
  private readonly route: ActivatedRoute
) {
  // Configuración automática del token
  authService.authToken = localStorage.getItem('tokenGlobal')!;
}

🎨 Características Visuales

Diseño Responsive

  • Mobile-first: Optimizado para dispositivos móviles
  • Layout flexible: Columna en móvil, fila en desktop
  • Transiciones suaves: Animaciones CSS para expansión/colapso

Tema y Estilos

  • Tailwind CSS: Framework de utilidades completo
  • Shadow DOM: Encapsulación total de estilos
  • Paleta CBM: Colores consistentes (info, success, danger, secondary)
  • Iconos SVG: Material Design Icons embebidos

Estados Visuales

/* Estados de sucursal */
.activo: bg-light-success text-success
.inactivo: bg-light-danger text-danger

/* Ambiente */
.produccion: bg-light-secondary text-secondary
.pruebas: bg-light-secondary text-secondary

🚀 Ejemplos de Uso

Ejemplo Básico

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [CbmBranchCardComponent],
  template: `
    <div class="dashboard-container">
      <header class="mb-6">
        <h1 class="text-2xl font-bold mb-4">Panel de Control</h1>

        <cbm-branch-card
          [route-change]="['/branch-selector']"
          [change-branch]="true"
        />
      </header>

      <main class="dashboard-content">
        <!-- Contenido del dashboard -->
      </main>
    </div>
  `
})
export class DashboardComponent {}

Ejemplo con Navegación Dinámica

@Component({
  selector: 'app-branch-manager',
  standalone: true,
  imports: [CbmBranchCardComponent],
  template: `
    <div class="branch-manager">
      <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
        <div class="branch-info">
          <h2 class="text-xl font-semibold mb-4">Sucursal Actual</h2>

          <cbm-branch-card
            [route-change]="getBranchRoute()"
            [change-branch]="canChangeBranch()"
          />
        </div>

        <div class="branch-actions">
          <h2 class="text-xl font-semibold mb-4">Acciones</h2>
          <!-- Botones de acción -->
        </div>
      </div>
    </div>
  `,
  styles: [`
    .branch-manager {
      @apply p-6 bg-gray-50 min-h-screen;
    }

    .branch-info {
      @apply bg-white rounded-lg shadow-sm p-6;
    }

    .branch-actions {
      @apply bg-white rounded-lg shadow-sm p-6;
    }
  `]
})
export class BranchManagerComponent {
  currentBranchId = 'branch-123';

  getBranchRoute(): string[] {
    return ['/branches', this.currentBranchId, 'edit'];
  }

  canChangeBranch(): boolean {
    // Lógica para determinar si puede cambiar
    return this.hasPermission('branch.change');
  }

  private hasPermission(permission: string): boolean {
    // Implementar lógica de permisos
    return true;
  }
}

Ejemplo con Múltiples Sucursales

@Component({
  selector: 'app-branch-list',
  standalone: true,
  imports: [CbmBranchCardComponent, NgFor],
  template: `
    <div class="branch-list">
      <h1 class="text-3xl font-bold mb-6">Sucursales Disponibles</h1>

      <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        <div *ngFor="let branch of availableBranches; trackBy: trackByBranchId"
             class="branch-card-container">

          <cbm-branch-card
            [route-change]="['/branches', branch.id, 'select']"
            [change-branch]="branch.enabled"
          />
        </div>
      </div>

      @if (availableBranches.length === 0) {
        <div class="no-branches text-center py-12">
          <p class="text-gray-500 text-lg">No hay sucursales disponibles</p>
        </div>
      }
    </div>
  `,
  styles: [`
    .branch-list {
      @apply p-6 max-w-7xl mx-auto;
    }

    .branch-card-container {
      @apply transform transition-transform hover:scale-105;
    }

    .no-branches {
      @apply bg-white rounded-lg shadow-sm;
    }
  `]
})
export class BranchListComponent {
  availableBranches: Branch[] = [
    { id: '1', name: 'Sucursal Centro', enabled: true },
    { id: '2', name: 'Sucursal Norte', enabled: false },
    { id: '3', name: 'Sucursal Sur', enabled: true }
  ];

  trackByBranchId(index: number, branch: Branch): string {
    return branch.id;
  }
}

interface Branch {
  id: string;
  name: string;
  enabled: boolean;
}

Ejemplo con Gestión de Estado Global

// Servicio para gestión de sucursales
@Injectable({ providedIn: 'root' })
export class BranchStateService {
  private currentBranchSubject = new BehaviorSubject<BranchData | null>(null);
  currentBranch$ = this.currentBranchSubject.asObservable();

  setCurrentBranch(branch: BranchData) {
    this.currentBranchSubject.next(branch);
    localStorage.setItem('currentBranch', JSON.stringify(branch));
  }

  getCurrentBranch(): BranchData | null {
    const stored = localStorage.getItem('currentBranch');
    return stored ? JSON.parse(stored) : null;
  }
}

// Componente que usa el servicio
@Component({
  selector: 'app-branch-aware-dashboard',
  standalone: true,
  imports: [CbmBranchCardComponent, AsyncPipe],
  template: `
    <div class="dashboard">
      <header class="mb-6">
        <cbm-branch-card
          [route-change]="['/branch-selector']"
          [change-branch]="true"
        />
      </header>

      @if (currentBranch$ | async; as currentBranch) {
        <div class="branch-context">
          <h2>Trabajando en: {{ currentBranch.trade_name }}</h2>
          <p>Ambiente: {{ currentBranch.environment === 2 ? 'Producción' : 'Pruebas' }}</p>
        </div>
      }

      <main class="dashboard-content">
        <!-- Contenido específico de la sucursal -->
      </main>
    </div>
  `
})
export class BranchAwareDashboardComponent {
  currentBranch$ = this.branchState.currentBranch$;

  constructor(private branchState: BranchStateService) {}
}

Ejemplo con Tema Personalizado

@Component({
  selector: 'app-custom-branch-card',
  standalone: true,
  imports: [CbmBranchCardComponent],
  template: `
    <div class="custom-theme">
      <cbm-branch-card
        [route-change]="['/custom-route']"
        [change-branch]="true"
      />
    </div>
  `,
  styles: [`
    .custom-theme {
      /* Variables CSS personalizadas */
      --cbm-primary: #ff6b35;
      --cbm-secondary: #f7931e;
      --cbm-success: #28a745;
      --cbm-danger: #dc3545;
      --cbm-info: #17a2b8;
    }

    .custom-theme ::ng-deep .branch-card {
      border-radius: 12px;
      box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
    }

    .custom-theme ::ng-deep .branch-logo {
      border: 3px solid var(--cbm-primary);
    }
  `]
})
export class CustomBranchCardComponent {}

⚠️ Manejo de Errores y Estados

Estados de Autenticación

// Verificación de autenticación
ngOnInit() {
  if (!this.authService.authToken) {
    console.error('No se encontró token de autenticación');
    this.router.navigate(['/login']);
    return;
  }

  if (!this.companyBranch) {
    console.warn('No se encontraron datos de sucursal');
    // Mostrar mensaje de error o estado de carga
  }
}

Manejo de Navegación

changeRedirect(): void {
  try {
    const route = Array.isArray(this.routeChange())
      ? this.routeChange() as string[]
      : [this.routeChange() as string];

    this.router.navigate(route, {
      relativeTo: this.route,
      queryParams: { returnUrl: this.router.url }
    });
  } catch (error) {
    console.error('Error al navegar:', error);
    // Mostrar notificación de error
  }
}

Estados de Carga

// Template con manejo de estados
@if (companyBranch; as branch) {
  <!-- Mostrar datos de sucursal -->
} @else if (loading) {
  <div class="loading-state">
    <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-info"></div>
    <span>Cargando información de sucursal...</span>
  </div>
} @else {
  <div class="error-state">
    <p>No se pudo cargar la información de la sucursal</p>
    <button (click)="retryLoad()">Reintentar</button>
  </div>
}

🔧 Configuración Avanzada

Personalización de Iconos

// Extensión del componente con iconos personalizados
@Component({
  selector: 'cbm-custom-branch-card',
  template: `
    <ng-container *ngTemplateOutlet="originalTemplate" />

    <ng-template #customEmailIcon>
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-5 h-5">
        <!-- Icono personalizado -->
      </svg>
    </ng-template>
  `
})
export class CustomBranchCardComponent extends CbmBranchCardComponent {
  // Sobrescribir templates de iconos
  protected customEmailIcon = this.customEmailIcon;
}

Configuración de Animaciones

// Animaciones personalizadas para expansión
@Component({
  styles: [`
    .branch-details {
      transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    }

    .branch-details.expanded {
      max-height: 500px;
      opacity: 1;
    }

    .branch-details.collapsed {
      max-height: 0;
      opacity: 0;
      overflow: hidden;
    }
  `]
})
export class AnimatedBranchCardComponent extends CbmBranchCardComponent {
  override toggleExpanded() {
    this.expaded.update(expanded => !expanded);
    // Animación personalizada adicional
  }
}

Integración con Servicios Externos

// Servicio para datos adicionales de sucursal
@Injectable({ providedIn: 'root' })
export class BranchDataService {
  constructor(private http: HttpClient) {}

  getBranchMetrics(branchId: string): Observable<BranchMetrics> {
    return this.http.get<BranchMetrics>(`/api/branches/${branchId}/metrics`);
  }

  updateBranchSettings(branchId: string, settings: BranchSettings): Observable<void> {
    return this.http.put<void>(`/api/branches/${branchId}/settings`, settings);
  }
}

// Uso en componente
export class EnhancedBranchCardComponent extends CbmBranchCardComponent {
  branchMetrics$ = this.branchData.getBranchMetrics(this.companyBranch._id);

  updateSettings() {
    const settings: BranchSettings = {
      notifications: true,
      autoBackup: false
    };

    this.branchData.updateBranchSettings(this.companyBranch._id, settings)
      .subscribe(() => {
        console.log('Configuración actualizada');
      });
  }
}

📋 Dependencias

Peer Dependencies (Requeridas)

{
  "@angular/common": ">=20.1.5",
  "@angular/core": ">=20.1.5",
  "@angular/router": ">=20.1.5",
  "@cbm-common/auth-service": "0.0.x"
}

Dependencias Internas

{
  "tslib": "^2.3.0"
}

Dependencias de Desarrollo

{
  "tailwindcss": "^4.1.11",
  "postcss": "^8.5.6",
  "@tailwindcss/postcss": "^4.1.11"
}

🛠️ Desarrollo

Estructura del Proyecto

branch-card/
├── src/
│   ├── lib/
│   │   ├── branch-card.component.ts       # Componente principal
│   │   ├── branch-card.component.html     # Template del componente
│   │   ├── branch-card.component.css      # Estilos Tailwind
│   │   ├── styles.css                     # CSS compilado
│   │   └── branch-card.component.spec.ts  # Pruebas unitarias
│   └── public-api.ts                      # API pública
├── ng-package.json                        # Configuración empaquetado
├── package.json                           # Dependencias
└── README.md                              # Esta documentación

Construcción

# Construir la librería
ng build branch-card

# Construir en modo watch
ng build branch-card --watch

# Construir para producción
ng build branch-card --configuration production

Compilación de Estilos

# Compilar Tailwind CSS
npm run tailwind

# Modo watch para desarrollo
npm run tailwind --watch

Pruebas

# Ejecutar pruebas unitarias
ng test branch-card

# Ejecutar pruebas con coverage
ng test branch-card --code-coverage

# Pruebas end-to-end
ng e2e branch-card

🎯 Mejores Prácticas

1. Gestión de Estado

// Usar servicio centralizado para estado de sucursales
@Injectable({ providedIn: 'root' })
export class BranchManagementService {
  private branchesSubject = new BehaviorSubject<BranchData[]>([]);
  branches$ = this.branchesSubject.asObservable();

  loadBranches() {
    // Cargar sucursales desde API
    this.http.get<BranchData[]>('/api/branches')
      .subscribe(branches => {
        this.branchesSubject.next(branches);
      });
  }

  switchBranch(branchId: string) {
    // Lógica para cambiar de sucursal
    this.authService.switchBranch(branchId);
    this.loadBranches();
  }
}

2. Optimización de Rendimiento

// Lazy loading de datos adicionales
export class OptimizedBranchCardComponent extends CbmBranchCardComponent {
  private metricsSubject = new BehaviorSubject<BranchMetrics | null>(null);
  metrics$ = this.metricsSubject.asObservable();

  ngOnInit() {
    super.ngOnInit();

    // Cargar métricas solo cuando se expande
    this.expaded.subscribe(expanded => {
      if (expanded && !this.metricsSubject.value) {
        this.loadMetrics();
      }
    });
  }

  private loadMetrics() {
    this.branchService.getBranchMetrics(this.companyBranch._id)
      .subscribe(metrics => {
        this.metricsSubject.next(metrics);
      });
  }
}

3. Accesibilidad

// Mejoras de accesibilidad
@Component({
  template: `
    <section
      role="region"
      [attr.aria-labelledby]="'branch-' + companyBranch._id"
      [attr.aria-expanded]="expaded()"
    >
      <h2 [id]="'branch-' + companyBranch._id" class="sr-only">
        Información de sucursal {{ companyBranch.trade_name }}
      </h2>

      <button
        [attr.aria-label]="expaded() ? 'Contraer detalles' : 'Expandir detalles'"
        (click)="expaded.set(!expaded())"
      >
        <!-- Contenido del botón -->
      </button>
    </section>
  `
})
export class AccessibleBranchCardComponent extends CbmBranchCardComponent {
  // Implementar navegación por teclado
  @HostListener('keydown', ['$event'])
  onKeyDown(event: KeyboardEvent) {
    if (event.key === 'Enter' || event.key === ' ') {
      this.expaded.set(!this.expaded());
      event.preventDefault();
    }
  }
}

4. Manejo de Errores Robusto

// Sistema de manejo de errores
export class ErrorHandlingBranchCardComponent extends CbmBranchCardComponent {
  private errorSubject = new BehaviorSubject<string | null>(null);
  error$ = this.errorSubject.asObservable();

  override ngOnInit() {
    try {
      super.ngOnInit();
    } catch (error) {
      this.handleError('Error al inicializar componente', error);
    }
  }

  private handleError(message: string, error: any) {
    console.error(message, error);
    this.errorSubject.next(message);
    // Enviar a servicio de logging
    this.loggingService.logError(error);
  }

  retryLoad() {
    this.errorSubject.next(null);
    this.ngOnInit();
  }
}

🤝 Contribución

  1. Fork el repositorio
  2. Crea una rama para tu feature (git checkout -b feature/nueva-funcionalidad)
  3. Commit tus cambios (git commit -am 'Agrega nueva funcionalidad')
  4. Push a la rama (git push origin feature/nueva-funcionalidad)
  5. Abre un PullRequest

📄 Licencia

Este proyecto está bajo la Licencia MIT - ver el archivo LICENSE para más detalles.

📞 Soporte

Para soporte técnico o reportes de bugs, contacta al equipo de desarrollo de CBM:

🔄 Changelog

v0.0.1

  • ✅ Componente standalone para visualización de sucursales
  • ✅ Integración completa con CBM Auth Service
  • ✅ Diseño responsive con Tailwind CSS
  • ✅ Shadow DOM para encapsulación de estilos
  • ✅ Sistema de expansión/colapso de información
  • ✅ Navegación programática entre sucursales
  • ✅ Estados visuales (activo/inactivo, producción/pruebas)
  • ✅ Iconos SVG embebidos (Material Design)
  • ✅ Documentación completa en español

Nota: Este componente está optimizado para sistemas empresariales multi-sucursal. La integración con el servicio de autenticación permite una gestión centralizada de sucursales y estados operativos.