@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 embebidosEstado 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ónConstrucció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 productionCompilación de Estilos
# Compilar Tailwind CSS
npm run tailwind
# Modo watch para desarrollo
npm run tailwind --watchPruebas
# 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
- Fork el repositorio
- Crea una rama para tu feature (
git checkout -b feature/nueva-funcionalidad) - Commit tus cambios (
git commit -am 'Agrega nueva funcionalidad') - Push a la rama (
git push origin feature/nueva-funcionalidad) - 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:
- 📧 Email: [email protected]
- 💬 Slack: #desarrollo-cbm
- 📋 Issues: GitHub Issues
🔄 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.
