datos invocados correctamente, falta que se hagan visibles

This commit is contained in:
Abraham
2025-07-28 23:14:59 -07:00
parent 476714a307
commit 730754930d
6 changed files with 1242 additions and 1079 deletions

View File

@@ -23,6 +23,7 @@ class Componente {
this.ubicacion,
this.imagenUrl,
required this.fechaRegistro,
String? distribucionId,
});
factory Componente.fromMap(Map<String, dynamic> map) {

View File

@@ -0,0 +1,141 @@
import 'dart:convert';
class VistaConexionesPorCables {
final String conexionId;
final String? descripcion;
final bool activo;
final String origenId;
final String componenteOrigen;
final String destinoId;
final String componenteDestino;
final String? cableId;
final String? cableUsado;
final String? tipoCable;
final String? color;
final double? tamano;
final String? tipoConector;
VistaConexionesPorCables({
required this.conexionId,
this.descripcion,
required this.activo,
required this.origenId,
required this.componenteOrigen,
required this.destinoId,
required this.componenteDestino,
this.cableId,
this.cableUsado,
this.tipoCable,
this.color,
this.tamano,
this.tipoConector,
});
factory VistaConexionesPorCables.fromMap(Map<String, dynamic> map) {
return VistaConexionesPorCables(
conexionId: map['conexion_id']?.toString() ?? '',
descripcion: map['descripcion']?.toString(),
activo: map['activo'] == true,
origenId: map['origen_id']?.toString() ?? '',
componenteOrigen: map['componente_origen']?.toString() ?? '',
destinoId: map['destino_id']?.toString() ?? '',
componenteDestino: map['componente_destino']?.toString() ?? '',
cableId: map['cable_id']?.toString(),
cableUsado: map['cable_usado']?.toString(),
tipoCable: map['tipo_cable']?.toString(),
color: map['color']?.toString(),
tamano: map['tamaño']?.toDouble(),
tipoConector: map['tipo_conector']?.toString(),
);
}
Map<String, dynamic> toMap() {
return {
'conexion_id': conexionId,
'descripcion': descripcion,
'activo': activo,
'origen_id': origenId,
'componente_origen': componenteOrigen,
'destino_id': destinoId,
'componente_destino': componenteDestino,
'cable_id': cableId,
'cable_usado': cableUsado,
'tipo_cable': tipoCable,
'color': color,
'tamaño': tamano,
'tipo_conector': tipoConector,
};
}
factory VistaConexionesPorCables.fromJson(String source) =>
VistaConexionesPorCables.fromMap(json.decode(source));
String toJson() => json.encode(toMap());
// Método para obtener el color del cable para visualización
String getColorForVisualization() {
if (color == null || color!.isEmpty) {
// Color por defecto basado en tipo de cable
switch (tipoCable?.toLowerCase()) {
case 'fibra':
case 'fibra optica':
return '#00BCD4'; // Cyan
case 'utp':
case 'cat6':
case 'cat5e':
return '#FFEB3B'; // Yellow
case 'coaxial':
return '#FF9800'; // Orange
default:
return '#2196F3'; // Blue por defecto
}
}
// Convertir nombre de color a código hex
switch (color!.toLowerCase()) {
case 'azul':
case 'blue':
return '#2196F3';
case 'rojo':
case 'red':
return '#F44336';
case 'verde':
case 'green':
return '#4CAF50';
case 'amarillo':
case 'yellow':
return '#FFEB3B';
case 'naranja':
case 'orange':
return '#FF9800';
case 'morado':
case 'purple':
return '#9C27B0';
case 'cyan':
return '#00BCD4';
case 'gris':
case 'gray':
return '#757575';
default:
return '#2196F3';
}
}
// Método para determinar el grosor de línea basado en el tipo de cable
double getThicknessForVisualization() {
switch (tipoCable?.toLowerCase()) {
case 'fibra':
case 'fibra optica':
return 5.0; // Más grueso para backbone
case 'utp':
case 'cat6':
return 4.0;
case 'cat5e':
return 3.0;
case 'coaxial':
return 3.5;
default:
return 3.0;
}
}
}

View File

@@ -0,0 +1,183 @@
import 'dart:convert';
class VistaTopologiaPorNegocio {
final String negocioId;
final String nombreNegocio;
final String? distribucionId;
final String? tipoDistribucion;
final String? distribucionNombre;
final String componenteId;
final String componenteNombre;
final String? descripcion;
final String categoriaComponente;
final bool enUso;
final bool activo;
final String? ubicacion;
final String? imagenUrl;
final DateTime fechaRegistro;
VistaTopologiaPorNegocio({
required this.negocioId,
required this.nombreNegocio,
this.distribucionId,
this.tipoDistribucion,
this.distribucionNombre,
required this.componenteId,
required this.componenteNombre,
this.descripcion,
required this.categoriaComponente,
required this.enUso,
required this.activo,
this.ubicacion,
this.imagenUrl,
required this.fechaRegistro,
});
factory VistaTopologiaPorNegocio.fromMap(Map<String, dynamic> map) {
return VistaTopologiaPorNegocio(
negocioId: map['negocio_id']?.toString() ?? '',
nombreNegocio: map['nombre_negocio']?.toString() ?? '',
distribucionId: map['distribucion_id']?.toString(),
tipoDistribucion: map['tipo_distribucion']?.toString(),
distribucionNombre: map['distribucion_nombre']?.toString(),
componenteId: map['componente_id']?.toString() ?? '',
componenteNombre: map['componente_nombre']?.toString() ?? '',
descripcion: map['descripcion']?.toString(),
categoriaComponente: map['categoria_componente']?.toString() ?? '',
enUso: map['en_uso'] == true,
activo: map['activo'] == true,
ubicacion: map['ubicacion']?.toString(),
imagenUrl: map['imagen_url']?.toString(),
fechaRegistro:
DateTime.tryParse(map['fecha_registro']?.toString() ?? '') ??
DateTime.now(),
);
}
Map<String, dynamic> toMap() {
return {
'negocio_id': negocioId,
'nombre_negocio': nombreNegocio,
'distribucion_id': distribucionId,
'tipo_distribucion': tipoDistribucion,
'distribucion_nombre': distribucionNombre,
'componente_id': componenteId,
'componente_nombre': componenteNombre,
'descripcion': descripcion,
'categoria_componente': categoriaComponente,
'en_uso': enUso,
'activo': activo,
'ubicacion': ubicacion,
'imagen_url': imagenUrl,
'fecha_registro': fechaRegistro.toIso8601String(),
};
}
factory VistaTopologiaPorNegocio.fromJson(String source) =>
VistaTopologiaPorNegocio.fromMap(json.decode(source));
String toJson() => json.encode(toMap());
// Método para obtener el tipo de componente principal basado en IDs
String get tipoComponentePrincipal {
final categoria = categoriaComponente.toLowerCase();
// Clasificación basada en los nombres de categorías exactos
if (categoria == 'cable') return 'cable';
if (categoria == 'switch') return 'switch';
if (categoria == 'patch panel') return 'patch_panel';
if (categoria == 'rack') return 'rack';
if (categoria == 'ups') return 'ups';
if (categoria == 'mdf') return 'mdf';
if (categoria == 'idf') return 'idf';
// Clasificación por contenido para compatibilidad
if (categoria.contains('switch')) return 'switch';
if (categoria.contains('router') || categoria.contains('firewall'))
return 'router';
if (categoria.contains('servidor') || categoria.contains('server'))
return 'servidor';
if (categoria.contains('cable')) return 'cable';
if (categoria.contains('patch') || categoria.contains('panel'))
return 'patch_panel';
if (categoria.contains('rack')) return 'rack';
if (categoria.contains('ups')) return 'ups';
if (categoria.contains('organizador')) return 'organizador';
return 'otro';
}
// Método mejorado para determinar si es MDF
bool get esMDF {
final categoria = categoriaComponente.toLowerCase();
return categoria == 'mdf' ||
tipoDistribucion?.toUpperCase() == 'MDF' ||
ubicacion?.toLowerCase().contains('mdf') == true;
}
// Método mejorado para determinar si es IDF
bool get esIDF {
final categoria = categoriaComponente.toLowerCase();
return categoria == 'idf' ||
tipoDistribucion?.toUpperCase() == 'IDF' ||
ubicacion?.toLowerCase().contains('idf') == true;
}
// Método para obtener el nivel de prioridad del componente (para ordenamiento en topología)
int get prioridadTopologia {
if (esMDF) return 1; // Máxima prioridad para MDF
if (esIDF) return 2; // Segunda prioridad para IDF
switch (tipoComponentePrincipal) {
case 'router':
return 3;
case 'switch':
return 4;
case 'servidor':
return 5;
case 'patch_panel':
return 6;
case 'rack':
return 7;
case 'ups':
return 8;
case 'cable':
return 9;
case 'organizador':
return 10;
default:
return 11;
}
}
// Método para determinar el color del componente en el diagrama
String getColorForDiagram() {
if (esMDF) return '#2196F3'; // Azul para MDF
if (esIDF) {
return enUso
? '#4CAF50'
: '#FF9800'; // Verde si está en uso, naranja si no
}
switch (tipoComponentePrincipal) {
case 'router':
return '#FF5722'; // Naranja rojizo
case 'switch':
return '#9C27B0'; // Morado
case 'servidor':
return '#E91E63'; // Rosa
case 'patch_panel':
return '#607D8B'; // Azul gris
case 'rack':
return '#795548'; // Marrón
case 'ups':
return '#FFC107'; // Ámbar
case 'cable':
return '#4CAF50'; // Verde
case 'organizador':
return '#9E9E9E'; // Gris
default:
return activo ? '#2196F3' : '#757575';
}
}
}

View File

@@ -1,13 +1,11 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_flow_chart/flutter_flow_chart.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:nethive_neo/theme/theme.dart';
import 'package:nethive_neo/providers/nethive/componentes_provider.dart';
import 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart';
import 'package:nethive_neo/models/nethive/componente_model.dart';
import 'package:nethive_neo/models/nethive/conexion_componente_model.dart';
import 'package:nethive_neo/models/nethive/vista_topologia_por_negocio_model.dart';
import 'package:nethive_neo/models/nethive/vista_conexiones_por_cables_model.dart';
class TopologiaPage extends StatefulWidget {
const TopologiaPage({Key? key}) : super(key: key);
@@ -306,8 +304,7 @@ class _TopologiaPageState extends State<TopologiaPage>
];
// MDF -> IDF2 (Fibra)
mdfElement.next = [
...mdfElement.next ?? [],
mdfElement.next!.add(
ConnectionParams(
destElementId: idf2Element.id,
arrowParams: ArrowParams(
@@ -315,7 +312,7 @@ class _TopologiaPageState extends State<TopologiaPage>
thickness: 4,
),
),
];
);
// IDF1 -> Switch A1 (UTP)
idf1Element.next = [
@@ -329,8 +326,7 @@ class _TopologiaPageState extends State<TopologiaPage>
];
// IDF1 -> Switch A2 (UTP)
idf1Element.next = [
...idf1Element.next ?? [],
idf1Element.next!.add(
ConnectionParams(
destElementId: switch2Element.id,
arrowParams: ArrowParams(
@@ -338,7 +334,7 @@ class _TopologiaPageState extends State<TopologiaPage>
thickness: 3,
),
),
];
);
// IDF2 -> Switch B1 (UTP)
idf2Element.next = [
@@ -352,8 +348,7 @@ class _TopologiaPageState extends State<TopologiaPage>
];
// IDF2 -> Switch B2 (UTP - Desconectado)
idf2Element.next = [
...idf2Element.next ?? [],
idf2Element.next!.add(
ConnectionParams(
destElementId: switch4Element.id,
arrowParams: ArrowParams(
@@ -361,11 +356,10 @@ class _TopologiaPageState extends State<TopologiaPage>
thickness: 2,
),
),
];
);
// MDF -> Servidor (Dedicado)
mdfElement.next = [
...mdfElement.next ?? [],
mdfElement.next!.add(
ConnectionParams(
destElementId: serverElement.id,
arrowParams: ArrowParams(
@@ -373,7 +367,7 @@ class _TopologiaPageState extends State<TopologiaPage>
thickness: 5,
),
),
];
);
}
@override
@@ -934,6 +928,8 @@ class _TopologiaPageState extends State<TopologiaPage>
return Icons.network_check;
case 'Server':
return Icons.dns;
case 'Router':
return Icons.router;
default:
return Icons.device_unknown;
}
@@ -949,6 +945,8 @@ class _TopologiaPageState extends State<TopologiaPage>
return const Color(0xFF9C27B0);
case 'Server':
return const Color(0xFFE91E63);
case 'Router':
return const Color(0xFFFF5722);
default:
return Colors.grey;
}
@@ -988,8 +986,15 @@ class _TopologiaPageState extends State<TopologiaPage>
return;
}
// Cargar toda la topología del negocio seleccionado usando el método optimizado
await componentesProvider.cargarTopologiaCompletaOptimizada(
componentesProvider.negocioSeleccionadoId!);
// Mostrar estadísticas detalladas para debug
_mostrarEstadisticasComponentes();
// Construir la topología con datos reales del negocio seleccionado
await _buildRealNetworkTopology();
await _buildRealNetworkTopologyOptimized();
} catch (e) {
print('Error al cargar datos de topología: ${e.toString()}');
_showErrorDialog('Error al cargar la topología: ${e.toString()}');
@@ -1000,23 +1005,25 @@ class _TopologiaPageState extends State<TopologiaPage>
}
}
Future<void> _buildRealNetworkTopology() async {
Future<void> _buildRealNetworkTopologyOptimized() async {
dashboard.removeAllElements();
final componentesProvider =
Provider.of<ComponentesProvider>(context, listen: false);
// Obtener componentes agrupados por tipo
final mdfComponents = componentesProvider.getComponentesPorTipo('mdf');
final idfComponents = componentesProvider.getComponentesPorTipo('idf');
// Usar los datos optimizados
final mdfComponents = componentesProvider.getComponentesMDFOptimizados();
final idfComponents = componentesProvider.getComponentesIDFOptimizados();
final switchesAcceso = componentesProvider
.getComponentesPorTipo('switch')
.where((s) => !mdfComponents.contains(s) && !idfComponents.contains(s))
.getComponentesPorTipoOptimizado('switch')
.where((s) => !s.esMDF && !s.esIDF)
.toList();
final routers = componentesProvider.getComponentesPorTipo('router');
final servidores = componentesProvider.getComponentesPorTipo('servidor');
final routers =
componentesProvider.getComponentesPorTipoOptimizado('router');
final servidores =
componentesProvider.getComponentesPorTipoOptimizado('servidor');
print('Componentes encontrados:');
print('Componentes optimizados encontrados:');
print('- MDF: ${mdfComponents.length}');
print('- IDF: ${idfComponents.length}');
print('- Switches de acceso: ${switchesAcceso.length}');
@@ -1030,63 +1037,63 @@ class _TopologiaPageState extends State<TopologiaPage>
Map<String, FlowElement> elementosMap = {};
// Crear elementos MDF
// Crear elementos MDF usando datos optimizados
if (mdfComponents.isNotEmpty) {
final mdfElement = _createMDFElement(
final mdfElement = _createMDFElementOptimized(
mdfComponents, Offset(currentX + espacioX * 2, currentY));
dashboard.addElement(mdfElement);
elementosMap[mdfComponents.first.id] = mdfElement;
elementosMap[mdfComponents.first.componenteId] = mdfElement;
currentY += espacioY;
}
// Crear elementos IDF
// Crear elementos IDF usando datos optimizados
double idfX = currentX;
for (var idfComp in idfComponents) {
final idfElement =
_createIDFElement(idfComp, Offset(idfX, currentY + espacioY));
final idfElement = _createIDFElementOptimized(
idfComp, Offset(idfX, currentY + espacioY));
dashboard.addElement(idfElement);
elementosMap[idfComp.id] = idfElement;
elementosMap[idfComp.componenteId] = idfElement;
idfX += espacioX;
}
// Crear switches de acceso
// Crear switches de acceso usando datos optimizados
double switchX = currentX;
currentY += espacioY * 2;
for (var switchComp in switchesAcceso) {
final switchElement =
_createSwitchElement(switchComp, Offset(switchX, currentY));
_createSwitchElementOptimized(switchComp, Offset(switchX, currentY));
dashboard.addElement(switchElement);
elementosMap[switchComp.id] = switchElement;
elementosMap[switchComp.componenteId] = switchElement;
switchX += espacioX * 0.8;
}
// Crear servidores
// Crear servidores usando datos optimizados
if (servidores.isNotEmpty) {
currentY += espacioY;
double serverX = currentX + espacioX;
for (var servidor in servidores) {
final serverElement =
_createServerElement(servidor, Offset(serverX, currentY));
_createServerElementOptimized(servidor, Offset(serverX, currentY));
dashboard.addElement(serverElement);
elementosMap[servidor.id] = serverElement;
elementosMap[servidor.componenteId] = serverElement;
serverX += espacioX * 0.8;
}
}
// Crear routers/firewalls
// Crear routers/firewalls usando datos optimizados
if (routers.isNotEmpty) {
double routerX = currentX;
for (var router in routers) {
final routerElement = _createRouterElement(
final routerElement = _createRouterElementOptimized(
router, Offset(routerX, currentY - espacioY * 3));
dashboard.addElement(routerElement);
elementosMap[router.id] = routerElement;
elementosMap[router.componenteId] = routerElement;
routerX += espacioX;
}
}
// Crear conexiones basadas en la base de datos
await _createRealConnections(elementosMap, componentesProvider);
// Crear conexiones basadas en la vista optimizada de cables
await _createOptimizedConnections(elementosMap, componentesProvider);
// Si no hay elementos reales, mostrar mensaje
if (elementosMap.isEmpty) {
@@ -1096,18 +1103,14 @@ class _TopologiaPageState extends State<TopologiaPage>
setState(() {});
}
FlowElement _createMDFElement(
List<Componente> mdfComponents, Offset position) {
FlowElement _createMDFElementOptimized(
List<VistaTopologiaPorNegocio> mdfComponents, Offset position) {
final mainComponent = mdfComponents.first;
final componentesProvider =
Provider.of<ComponentesProvider>(context, listen: false);
final categoria =
componentesProvider.getCategoriaById(mainComponent.categoriaId);
return FlowElement(
position: position,
size: const Size(180, 140),
text: 'MDF\n${mainComponent.nombre}',
text: 'MDF\n${mainComponent.componenteNombre}',
textColor: Colors.white,
textSize: 14,
textIsBold: true,
@@ -1118,13 +1121,14 @@ class _TopologiaPageState extends State<TopologiaPage>
elevation: 8,
data: {
'type': 'MDF',
'componenteId': mainComponent.id,
'name': mainComponent.nombre,
'componenteId': mainComponent.componenteId,
'name': mainComponent.componenteNombre,
'status': mainComponent.activo ? 'active' : 'disconnected',
'description': mainComponent.descripcion ?? 'Main Distribution Frame',
'ubicacion': mainComponent.ubicacion ?? 'Sin ubicación',
'categoria': categoria?.nombre ?? 'Sin categoría',
'categoria': mainComponent.categoriaComponente,
'componentes': mdfComponents.length,
'distribucion': mainComponent.distribucionNombre ?? 'MDF Principal',
},
handlers: [
Handler.bottomCenter,
@@ -1134,16 +1138,12 @@ class _TopologiaPageState extends State<TopologiaPage>
);
}
FlowElement _createIDFElement(Componente idfComponent, Offset position) {
final componentesProvider =
Provider.of<ComponentesProvider>(context, listen: false);
final categoria =
componentesProvider.getCategoriaById(idfComponent.categoriaId);
FlowElement _createIDFElementOptimized(
VistaTopologiaPorNegocio idfComponent, Offset position) {
return FlowElement(
position: position,
size: const Size(160, 120),
text: 'IDF\n${idfComponent.nombre}',
text: 'IDF\n${idfComponent.componenteNombre}',
textColor: Colors.white,
textSize: 12,
textIsBold: true,
@@ -1158,16 +1158,17 @@ class _TopologiaPageState extends State<TopologiaPage>
elevation: 6,
data: {
'type': 'IDF',
'componenteId': idfComponent.id,
'name': idfComponent.nombre,
'componenteId': idfComponent.componenteId,
'name': idfComponent.componenteNombre,
'status': idfComponent.activo
? (idfComponent.enUso ? 'active' : 'warning')
: 'disconnected',
'description':
idfComponent.descripcion ?? 'Intermediate Distribution Frame',
'ubicacion': idfComponent.ubicacion ?? 'Sin ubicación',
'categoria': categoria?.nombre ?? 'Sin categoría',
'categoria': idfComponent.categoriaComponente,
'enUso': idfComponent.enUso,
'distribucion': idfComponent.distribucionNombre ?? 'IDF',
},
handlers: [
Handler.topCenter,
@@ -1178,17 +1179,12 @@ class _TopologiaPageState extends State<TopologiaPage>
);
}
FlowElement _createSwitchElement(
Componente switchComponent, Offset position) {
final componentesProvider =
Provider.of<ComponentesProvider>(context, listen: false);
final categoria =
componentesProvider.getCategoriaById(switchComponent.categoriaId);
FlowElement _createSwitchElementOptimized(
VistaTopologiaPorNegocio switchComponent, Offset position) {
return FlowElement(
position: position,
size: const Size(140, 100),
text: 'Switch\n${switchComponent.nombre}',
text: 'Switch\n${switchComponent.componenteNombre}',
textColor: Colors.white,
textSize: 10,
textIsBold: true,
@@ -1203,12 +1199,13 @@ class _TopologiaPageState extends State<TopologiaPage>
elevation: switchComponent.activo ? 4 : 2,
data: {
'type': 'AccessSwitch',
'componenteId': switchComponent.id,
'name': switchComponent.nombre,
'componenteId': switchComponent.componenteId,
'name': switchComponent.componenteNombre,
'status': switchComponent.activo ? 'active' : 'disconnected',
'description': switchComponent.descripcion ?? 'Switch de Acceso',
'ubicacion': switchComponent.ubicacion ?? 'Sin ubicación',
'categoria': categoria?.nombre ?? 'Sin categoría',
'categoria': switchComponent.categoriaComponente,
'enUso': switchComponent.enUso,
},
handlers: [
Handler.topCenter,
@@ -1216,239 +1213,158 @@ class _TopologiaPageState extends State<TopologiaPage>
);
}
FlowElement _createServerElement(
Componente serverComponent, Offset position) {
final componentesProvider =
Provider.of<ComponentesProvider>(context, listen: false);
final categoria =
componentesProvider.getCategoriaById(serverComponent.categoriaId);
FlowElement _createServerElementOptimized(
VistaTopologiaPorNegocio serverComponent, Offset position) {
return FlowElement(
position: position,
size: const Size(160, 100),
text: 'Servidor\n${serverComponent.nombre}',
size: const Size(150, 100),
text: 'Servidor\n${serverComponent.componenteNombre}',
textColor: Colors.white,
textSize: 12,
textSize: 11,
textIsBold: true,
kind: ElementKind.rectangle,
backgroundColor: const Color(0xFFE91E63),
borderColor: const Color(0xFFC2185B),
backgroundColor: serverComponent.activo
? const Color(0xFFE91E63)
: const Color(0xFF757575),
borderColor: serverComponent.activo
? const Color(0xFFC2185B)
: const Color(0xFF424242),
borderThickness: 3,
elevation: 6,
elevation: serverComponent.activo ? 6 : 2,
data: {
'type': 'Server',
'componenteId': serverComponent.id,
'name': serverComponent.nombre,
'componenteId': serverComponent.componenteId,
'name': serverComponent.componenteNombre,
'status': serverComponent.activo ? 'active' : 'disconnected',
'description': serverComponent.descripcion ?? 'Servidor',
'description': serverComponent.descripcion ?? 'Servidor de red',
'ubicacion': serverComponent.ubicacion ?? 'Sin ubicación',
'categoria': categoria?.nombre ?? 'Sin categoría',
'categoria': serverComponent.categoriaComponente,
'enUso': serverComponent.enUso,
},
handlers: [
Handler.topCenter,
],
);
}
FlowElement _createRouterElement(
Componente routerComponent, Offset position) {
final componentesProvider =
Provider.of<ComponentesProvider>(context, listen: false);
final categoria =
componentesProvider.getCategoriaById(routerComponent.categoriaId);
return FlowElement(
position: position,
size: const Size(160, 100),
text: 'Router\n${routerComponent.nombre}',
textColor: Colors.white,
textSize: 12,
textIsBold: true,
kind: ElementKind.rectangle,
backgroundColor: const Color(0xFFFF5722),
borderColor: const Color(0xFFD84315),
borderThickness: 3,
elevation: 6,
data: {
'type': 'Router',
'componenteId': routerComponent.id,
'name': routerComponent.nombre,
'status': routerComponent.activo ? 'active' : 'disconnected',
'description': routerComponent.descripcion ?? 'Router/Firewall',
'ubicacion': routerComponent.ubicacion ?? 'Sin ubicación',
'categoria': categoria?.nombre ?? 'Sin categoría',
},
handlers: [
Handler.bottomCenter,
Handler.topCenter,
Handler.leftCenter,
Handler.rightCenter,
],
);
}
Future<void> _createRealConnections(Map<String, FlowElement> elementosMap,
FlowElement _createRouterElementOptimized(
VistaTopologiaPorNegocio routerComponent, Offset position) {
return FlowElement(
position: position,
size: const Size(160, 100),
text: 'Router\n${routerComponent.componenteNombre}',
textColor: Colors.white,
textSize: 11,
textIsBold: true,
kind: ElementKind.rectangle,
backgroundColor: routerComponent.activo
? const Color(0xFFFF5722)
: const Color(0xFF757575),
borderColor: routerComponent.activo
? const Color(0xFFE64A19)
: const Color(0xFF424242),
borderThickness: 3,
elevation: routerComponent.activo ? 6 : 2,
data: {
'type': 'Router',
'componenteId': routerComponent.componenteId,
'name': routerComponent.componenteNombre,
'status': routerComponent.activo ? 'active' : 'disconnected',
'description': routerComponent.descripcion ?? 'Router/Firewall',
'ubicacion': routerComponent.ubicacion ?? 'Sin ubicación',
'categoria': routerComponent.categoriaComponente,
'enUso': routerComponent.enUso,
},
handlers: [
Handler.topCenter,
Handler.bottomCenter,
Handler.leftCenter,
Handler.rightCenter,
],
);
}
Future<void> _createOptimizedConnections(
Map<String, FlowElement> elementosMap,
ComponentesProvider componentesProvider) async {
// Obtener conexiones reales de la base de datos
final conexiones = componentesProvider.conexiones;
try {
print('Creando conexiones optimizadas...');
print(
'Conexiones con cables encontradas: ${componentesProvider.conexionesConCables.length}');
print('Creando ${conexiones.length} conexiones...');
for (var conexionCable in componentesProvider.conexionesConCables) {
if (!conexionCable.activo) continue;
for (var conexion in conexiones) {
final origenElement = elementosMap[conexion.componenteOrigenId];
final destinoElement = elementosMap[conexion.componenteDestinoId];
final elementoOrigen = elementosMap[conexionCable.origenId];
final elementoDestino = elementosMap[conexionCable.destinoId];
if (origenElement != null && destinoElement != null) {
// Determinar el color y grosor de la conexión basado en los tipos de componentes
final colorConexion =
_getConnectionColor(conexion, componentesProvider);
final grosorConexion =
_getConnectionThickness(conexion, componentesProvider);
if (elementoOrigen != null && elementoDestino != null) {
// Usar la información real del cable para determinar color y grosor
final colorConexion = _getColorFromCableData(conexionCable);
final grosorConexion = _getThicknessFromCableData(conexionCable);
print('Conectando: ${origenElement.text} -> ${destinoElement.text}');
print(
'Creando conexión: ${conexionCable.componenteOrigen} -> ${conexionCable.componenteDestino}');
if (conexionCable.tipoCable != null) {
print(
' Cable: ${conexionCable.tipoCable} (${conexionCable.color ?? 'sin color'})');
}
// Agregar la conexión al elemento origen
origenElement.next = [
...origenElement.next ?? [],
ConnectionParams(
destElementId: destinoElement.id,
arrowParams: ArrowParams(
color: colorConexion,
thickness: grosorConexion,
// Crear la conexión en el FlowChart
elementoOrigen.next ??= [];
elementoOrigen.next!.add(
ConnectionParams(
destElementId: elementoDestino.id,
arrowParams: ArrowParams(
color: colorConexion,
thickness: grosorConexion,
),
),
),
];
);
} else {
print(
'Elementos no encontrados para conexión: ${conexionCable.origenId} -> ${conexionCable.destinoId}');
}
}
}
// Si no hay conexiones en la BD, crear conexiones automáticas basadas en la ubicación
if (conexiones.isEmpty) {
print('No hay conexiones en BD, creando automáticas...');
_createAutomaticConnections(elementosMap, componentesProvider);
setState(() {});
} catch (e) {
print('Error en _createOptimizedConnections: ${e.toString()}');
}
}
Color _getConnectionColor(
ConexionComponente conexion, ComponentesProvider componentesProvider) {
// Obtener los componentes origen y destino
final origenComp =
componentesProvider.getComponenteById(conexion.componenteOrigenId);
final destinoComp =
componentesProvider.getComponenteById(conexion.componenteDestinoId);
if (origenComp == null || destinoComp == null) return Colors.grey;
// Determinar el tipo de conexión basado en las ubicaciones
final origenUbicacion = origenComp.ubicacion?.toUpperCase() ?? '';
final destinoUbicacion = destinoComp.ubicacion?.toUpperCase() ?? '';
// MDF a IDF = Fibra (cyan)
if (origenUbicacion.contains('MDF') && destinoUbicacion.contains('IDF')) {
return Colors.cyan;
}
// IDF a Switch = UTP (yellow)
if (origenUbicacion.contains('IDF')) {
return Colors.yellow;
}
// Servidor = Dedicado (purple)
final origenCategoria =
componentesProvider.getCategoriaById(origenComp.categoriaId);
final destinoCategoria =
componentesProvider.getCategoriaById(destinoComp.categoriaId);
if ((origenCategoria?.nombre?.toLowerCase().contains('servidor') ??
false) ||
(destinoCategoria?.nombre?.toLowerCase().contains('servidor') ??
false)) {
return Colors.purple;
}
return Colors.green; // Por defecto
Color _getColorFromCableData(VistaConexionesPorCables conexionCable) {
// Usar el método del modelo para obtener el color
final colorHex = conexionCable.getColorForVisualization();
return _hexToColor(colorHex);
}
double _getConnectionThickness(
ConexionComponente conexion, ComponentesProvider componentesProvider) {
final origenComp =
componentesProvider.getComponenteById(conexion.componenteOrigenId);
final destinoComp =
componentesProvider.getComponenteById(conexion.componenteDestinoId);
if (origenComp == null || destinoComp == null) return 2.0;
final origenUbicacion = origenComp.ubicacion?.toUpperCase() ?? '';
final destinoUbicacion = destinoComp.ubicacion?.toUpperCase() ?? '';
// Conexiones principales más gruesas
if (origenUbicacion.contains('MDF') && destinoUbicacion.contains('IDF')) {
return 4.0;
}
return conexion.activo ? 3.0 : 2.0;
double _getThicknessFromCableData(VistaConexionesPorCables conexionCable) {
// Usar el método del modelo para obtener el grosor
return conexionCable.getThicknessForVisualization();
}
void _createAutomaticConnections(Map<String, FlowElement> elementosMap,
ComponentesProvider componentesProvider) {
// Crear conexiones automáticas cuando no hay datos en la BD
final mdfElements = elementosMap.values
.where((e) => (e.data as Map)['type'] == 'MDF')
.toList();
final idfElements = elementosMap.values
.where((e) => (e.data as Map)['type'] == 'IDF')
.toList();
final switchElements = elementosMap.values
.where((e) => (e.data as Map)['type'] == 'AccessSwitch')
.toList();
print('Creando conexiones automáticas...');
print(
'MDF: ${mdfElements.length}, IDF: ${idfElements.length}, Switches: ${switchElements.length}');
// Conectar MDF a IDFs
for (var mdf in mdfElements) {
for (var idf in idfElements) {
mdf.next = [
...mdf.next ?? [],
ConnectionParams(
destElementId: idf.id,
arrowParams: ArrowParams(
color: Colors.cyan,
thickness: 4,
),
),
];
}
}
// Conectar IDFs a Switches
for (int i = 0; i < idfElements.length && i < switchElements.length; i++) {
final idf = idfElements[i];
final switchesParaEsteIdf = switchElements.skip(i * 2).take(2);
for (var switch_ in switchesParaEsteIdf) {
idf.next = [
...idf.next ?? [],
ConnectionParams(
destElementId: switch_.id,
arrowParams: ArrowParams(
color: Colors.yellow,
thickness: 3,
),
),
];
}
}
Color _hexToColor(String hex) {
hex = hex.replaceAll('#', '');
return Color(int.parse('FF$hex', radix: 16));
}
void _showNoComponentsMessage() {
void _showNoBusinessSelectedDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Sin componentes'),
title: const Row(
children: [
Icon(Icons.business, color: Colors.orange),
SizedBox(width: 8),
Text('Negocio no seleccionado'),
],
),
content: const Text(
'No se encontraron componentes de red para este negocio.\n\n'
'Para ver una topología completa, agregue componentes en el módulo de Inventario con ubicaciones como "MDF", "IDF", etc.',
'Para visualizar la topología de red, primero debe seleccionar un negocio desde la página de empresas.',
),
actions: [
TextButton(
@@ -1458,44 +1374,28 @@ class _TopologiaPageState extends State<TopologiaPage>
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
// Ir al módulo de inventario (esto depende de tu estructura de navegación)
// context.go('/infrastructure/inventory');
// Navegar a la página de empresas
// router.pushNamed('/infrastructure/empresas');
},
child: const Text('Ir a Inventario'),
child: const Text('Ir a Empresas'),
),
],
),
);
}
void _showNoBusinessSelectedDialog() {
void _showErrorDialog(String mensaje) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Negocio no seleccionado'),
content: const Text(
'No se ha seleccionado ningún negocio. Por favor, regrese a la página de negocios y seleccione "Acceder a Infraestructura" en la tabla.',
title: const Row(
children: [
Icon(Icons.error, color: Colors.red),
SizedBox(width: 8),
Text('Error'),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
// Regresar a la página de empresas/negocios
context.go('/empresa-negocios');
},
child: const Text('Ir a Negocios'),
),
],
),
);
}
void _showErrorDialog(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Error'),
content: Text(message),
content: Text(mensaje),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
@@ -1505,4 +1405,95 @@ class _TopologiaPageState extends State<TopologiaPage>
),
);
}
void _showNoComponentsMessage() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Row(
children: [
Icon(Icons.info, color: Colors.blue),
SizedBox(width: 8),
Text('Sin componentes'),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'No se encontraron componentes de infraestructura para este negocio.',
),
const SizedBox(height: 16),
const Text(
'Para visualizar la topología, necesita:',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text('• Registrar componentes (switches, routers, etc.)'),
const Text('• Configurar distribuciones (MDF/IDF)'),
const Text('• Crear conexiones entre componentes'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Entendido'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
// Navegar a la página de componentes
// router.pushNamed('/infrastructure/componentes');
},
child: const Text('Gestionar Componentes'),
),
],
),
);
}
// Método para mostrar estadísticas detalladas de componentes
void _mostrarEstadisticasComponentes() {
final provider = Provider.of<ComponentesProvider>(context, listen: false);
print('\n=== ESTADÍSTICAS DETALLADAS DE COMPONENTES ===');
print('Total de componentes cargados: ${provider.topologiaOptimizada.length}');
if (provider.topologiaOptimizada.isEmpty) {
print('❌ No hay componentes cargados');
return;
}
// Agrupar por categoría
final componentesPorCategoria = <String, List<VistaTopologiaPorNegocio>>{};
for (final componente in provider.topologiaOptimizada) {
final categoria = componente.categoriaComponente;
componentesPorCategoria.putIfAbsent(categoria, () => []).add(componente);
}
print('\n📊 Componentes por categoría:');
componentesPorCategoria.forEach((categoria, lista) {
print(' - $categoria: ${lista.length} componentes');
for (final comp in lista) {
print('${comp.componenteNombre} (${comp.tipoComponentePrincipal}) - Activo: ${comp.activo}');
}
});
// Clasificación por tipo principal
final mdfComponents = provider.getComponentesMDFOptimizados();
final idfComponents = provider.getComponentesIDFOptimizados();
final switchComponents = provider.getComponentesPorTipoOptimizado('switch');
final routerComponents = provider.getComponentesPorTipoOptimizado('router');
final servidorComponents = provider.getComponentesPorTipoOptimizado('servidor');
print('\n🔧 Clasificación por tipo:');
print(' - MDF: ${mdfComponents.length}');
print(' - IDF: ${idfComponents.length}');
print(' - Switches: ${switchComponents.length}');
print(' - Routers: ${routerComponents.length}');
print(' - Servidores: ${servidorComponents.length}');
print('\n🔗 Conexiones disponibles: ${provider.conexionesConCables.length}');
print('===============================================\n');
}
}

View File

@@ -1,10 +1,8 @@
import 'package:flutter/material.dart';
import 'package:nethive_neo/helpers/globals.dart';
import 'package:provider/provider.dart';
import 'package:nethive_neo/providers/nethive/componentes_provider.dart';
import 'package:nethive_neo/theme/theme.dart';
import 'package:nethive_neo/models/nethive/componente_model.dart';
import 'package:nethive_neo/models/nethive/categoria_componente_model.dart';
class EditComponenteDialog extends StatefulWidget {
final ComponentesProvider provider;

File diff suppressed because it is too large Load Diff