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.ubicacion,
this.imagenUrl, this.imagenUrl,
required this.fechaRegistro, required this.fechaRegistro,
String? distribucionId,
}); });
factory Componente.fromMap(Map<String, dynamic> map) { 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:flutter/material.dart';
import 'package:provider/provider.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_flow_chart/flutter_flow_chart.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
import 'package:nethive_neo/theme/theme.dart'; import 'package:nethive_neo/theme/theme.dart';
import 'package:nethive_neo/providers/nethive/componentes_provider.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/vista_topologia_por_negocio_model.dart';
import 'package:nethive_neo/models/nethive/componente_model.dart'; import 'package:nethive_neo/models/nethive/vista_conexiones_por_cables_model.dart';
import 'package:nethive_neo/models/nethive/conexion_componente_model.dart';
class TopologiaPage extends StatefulWidget { class TopologiaPage extends StatefulWidget {
const TopologiaPage({Key? key}) : super(key: key); const TopologiaPage({Key? key}) : super(key: key);
@@ -306,8 +304,7 @@ class _TopologiaPageState extends State<TopologiaPage>
]; ];
// MDF -> IDF2 (Fibra) // MDF -> IDF2 (Fibra)
mdfElement.next = [ mdfElement.next!.add(
...mdfElement.next ?? [],
ConnectionParams( ConnectionParams(
destElementId: idf2Element.id, destElementId: idf2Element.id,
arrowParams: ArrowParams( arrowParams: ArrowParams(
@@ -315,7 +312,7 @@ class _TopologiaPageState extends State<TopologiaPage>
thickness: 4, thickness: 4,
), ),
), ),
]; );
// IDF1 -> Switch A1 (UTP) // IDF1 -> Switch A1 (UTP)
idf1Element.next = [ idf1Element.next = [
@@ -329,8 +326,7 @@ class _TopologiaPageState extends State<TopologiaPage>
]; ];
// IDF1 -> Switch A2 (UTP) // IDF1 -> Switch A2 (UTP)
idf1Element.next = [ idf1Element.next!.add(
...idf1Element.next ?? [],
ConnectionParams( ConnectionParams(
destElementId: switch2Element.id, destElementId: switch2Element.id,
arrowParams: ArrowParams( arrowParams: ArrowParams(
@@ -338,7 +334,7 @@ class _TopologiaPageState extends State<TopologiaPage>
thickness: 3, thickness: 3,
), ),
), ),
]; );
// IDF2 -> Switch B1 (UTP) // IDF2 -> Switch B1 (UTP)
idf2Element.next = [ idf2Element.next = [
@@ -352,8 +348,7 @@ class _TopologiaPageState extends State<TopologiaPage>
]; ];
// IDF2 -> Switch B2 (UTP - Desconectado) // IDF2 -> Switch B2 (UTP - Desconectado)
idf2Element.next = [ idf2Element.next!.add(
...idf2Element.next ?? [],
ConnectionParams( ConnectionParams(
destElementId: switch4Element.id, destElementId: switch4Element.id,
arrowParams: ArrowParams( arrowParams: ArrowParams(
@@ -361,11 +356,10 @@ class _TopologiaPageState extends State<TopologiaPage>
thickness: 2, thickness: 2,
), ),
), ),
]; );
// MDF -> Servidor (Dedicado) // MDF -> Servidor (Dedicado)
mdfElement.next = [ mdfElement.next!.add(
...mdfElement.next ?? [],
ConnectionParams( ConnectionParams(
destElementId: serverElement.id, destElementId: serverElement.id,
arrowParams: ArrowParams( arrowParams: ArrowParams(
@@ -373,7 +367,7 @@ class _TopologiaPageState extends State<TopologiaPage>
thickness: 5, thickness: 5,
), ),
), ),
]; );
} }
@override @override
@@ -934,6 +928,8 @@ class _TopologiaPageState extends State<TopologiaPage>
return Icons.network_check; return Icons.network_check;
case 'Server': case 'Server':
return Icons.dns; return Icons.dns;
case 'Router':
return Icons.router;
default: default:
return Icons.device_unknown; return Icons.device_unknown;
} }
@@ -949,6 +945,8 @@ class _TopologiaPageState extends State<TopologiaPage>
return const Color(0xFF9C27B0); return const Color(0xFF9C27B0);
case 'Server': case 'Server':
return const Color(0xFFE91E63); return const Color(0xFFE91E63);
case 'Router':
return const Color(0xFFFF5722);
default: default:
return Colors.grey; return Colors.grey;
} }
@@ -988,8 +986,15 @@ class _TopologiaPageState extends State<TopologiaPage>
return; 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 // Construir la topología con datos reales del negocio seleccionado
await _buildRealNetworkTopology(); await _buildRealNetworkTopologyOptimized();
} catch (e) { } catch (e) {
print('Error al cargar datos de topología: ${e.toString()}'); print('Error al cargar datos de topología: ${e.toString()}');
_showErrorDialog('Error al cargar la 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(); dashboard.removeAllElements();
final componentesProvider = final componentesProvider =
Provider.of<ComponentesProvider>(context, listen: false); Provider.of<ComponentesProvider>(context, listen: false);
// Obtener componentes agrupados por tipo // Usar los datos optimizados
final mdfComponents = componentesProvider.getComponentesPorTipo('mdf'); final mdfComponents = componentesProvider.getComponentesMDFOptimizados();
final idfComponents = componentesProvider.getComponentesPorTipo('idf'); final idfComponents = componentesProvider.getComponentesIDFOptimizados();
final switchesAcceso = componentesProvider final switchesAcceso = componentesProvider
.getComponentesPorTipo('switch') .getComponentesPorTipoOptimizado('switch')
.where((s) => !mdfComponents.contains(s) && !idfComponents.contains(s)) .where((s) => !s.esMDF && !s.esIDF)
.toList(); .toList();
final routers = componentesProvider.getComponentesPorTipo('router'); final routers =
final servidores = componentesProvider.getComponentesPorTipo('servidor'); componentesProvider.getComponentesPorTipoOptimizado('router');
final servidores =
componentesProvider.getComponentesPorTipoOptimizado('servidor');
print('Componentes encontrados:'); print('Componentes optimizados encontrados:');
print('- MDF: ${mdfComponents.length}'); print('- MDF: ${mdfComponents.length}');
print('- IDF: ${idfComponents.length}'); print('- IDF: ${idfComponents.length}');
print('- Switches de acceso: ${switchesAcceso.length}'); print('- Switches de acceso: ${switchesAcceso.length}');
@@ -1030,63 +1037,63 @@ class _TopologiaPageState extends State<TopologiaPage>
Map<String, FlowElement> elementosMap = {}; Map<String, FlowElement> elementosMap = {};
// Crear elementos MDF // Crear elementos MDF usando datos optimizados
if (mdfComponents.isNotEmpty) { if (mdfComponents.isNotEmpty) {
final mdfElement = _createMDFElement( final mdfElement = _createMDFElementOptimized(
mdfComponents, Offset(currentX + espacioX * 2, currentY)); mdfComponents, Offset(currentX + espacioX * 2, currentY));
dashboard.addElement(mdfElement); dashboard.addElement(mdfElement);
elementosMap[mdfComponents.first.id] = mdfElement; elementosMap[mdfComponents.first.componenteId] = mdfElement;
currentY += espacioY; currentY += espacioY;
} }
// Crear elementos IDF // Crear elementos IDF usando datos optimizados
double idfX = currentX; double idfX = currentX;
for (var idfComp in idfComponents) { for (var idfComp in idfComponents) {
final idfElement = final idfElement = _createIDFElementOptimized(
_createIDFElement(idfComp, Offset(idfX, currentY + espacioY)); idfComp, Offset(idfX, currentY + espacioY));
dashboard.addElement(idfElement); dashboard.addElement(idfElement);
elementosMap[idfComp.id] = idfElement; elementosMap[idfComp.componenteId] = idfElement;
idfX += espacioX; idfX += espacioX;
} }
// Crear switches de acceso // Crear switches de acceso usando datos optimizados
double switchX = currentX; double switchX = currentX;
currentY += espacioY * 2; currentY += espacioY * 2;
for (var switchComp in switchesAcceso) { for (var switchComp in switchesAcceso) {
final switchElement = final switchElement =
_createSwitchElement(switchComp, Offset(switchX, currentY)); _createSwitchElementOptimized(switchComp, Offset(switchX, currentY));
dashboard.addElement(switchElement); dashboard.addElement(switchElement);
elementosMap[switchComp.id] = switchElement; elementosMap[switchComp.componenteId] = switchElement;
switchX += espacioX * 0.8; switchX += espacioX * 0.8;
} }
// Crear servidores // Crear servidores usando datos optimizados
if (servidores.isNotEmpty) { if (servidores.isNotEmpty) {
currentY += espacioY; currentY += espacioY;
double serverX = currentX + espacioX; double serverX = currentX + espacioX;
for (var servidor in servidores) { for (var servidor in servidores) {
final serverElement = final serverElement =
_createServerElement(servidor, Offset(serverX, currentY)); _createServerElementOptimized(servidor, Offset(serverX, currentY));
dashboard.addElement(serverElement); dashboard.addElement(serverElement);
elementosMap[servidor.id] = serverElement; elementosMap[servidor.componenteId] = serverElement;
serverX += espacioX * 0.8; serverX += espacioX * 0.8;
} }
} }
// Crear routers/firewalls // Crear routers/firewalls usando datos optimizados
if (routers.isNotEmpty) { if (routers.isNotEmpty) {
double routerX = currentX; double routerX = currentX;
for (var router in routers) { for (var router in routers) {
final routerElement = _createRouterElement( final routerElement = _createRouterElementOptimized(
router, Offset(routerX, currentY - espacioY * 3)); router, Offset(routerX, currentY - espacioY * 3));
dashboard.addElement(routerElement); dashboard.addElement(routerElement);
elementosMap[router.id] = routerElement; elementosMap[router.componenteId] = routerElement;
routerX += espacioX; routerX += espacioX;
} }
} }
// Crear conexiones basadas en la base de datos // Crear conexiones basadas en la vista optimizada de cables
await _createRealConnections(elementosMap, componentesProvider); await _createOptimizedConnections(elementosMap, componentesProvider);
// Si no hay elementos reales, mostrar mensaje // Si no hay elementos reales, mostrar mensaje
if (elementosMap.isEmpty) { if (elementosMap.isEmpty) {
@@ -1096,18 +1103,14 @@ class _TopologiaPageState extends State<TopologiaPage>
setState(() {}); setState(() {});
} }
FlowElement _createMDFElement( FlowElement _createMDFElementOptimized(
List<Componente> mdfComponents, Offset position) { List<VistaTopologiaPorNegocio> mdfComponents, Offset position) {
final mainComponent = mdfComponents.first; final mainComponent = mdfComponents.first;
final componentesProvider =
Provider.of<ComponentesProvider>(context, listen: false);
final categoria =
componentesProvider.getCategoriaById(mainComponent.categoriaId);
return FlowElement( return FlowElement(
position: position, position: position,
size: const Size(180, 140), size: const Size(180, 140),
text: 'MDF\n${mainComponent.nombre}', text: 'MDF\n${mainComponent.componenteNombre}',
textColor: Colors.white, textColor: Colors.white,
textSize: 14, textSize: 14,
textIsBold: true, textIsBold: true,
@@ -1118,13 +1121,14 @@ class _TopologiaPageState extends State<TopologiaPage>
elevation: 8, elevation: 8,
data: { data: {
'type': 'MDF', 'type': 'MDF',
'componenteId': mainComponent.id, 'componenteId': mainComponent.componenteId,
'name': mainComponent.nombre, 'name': mainComponent.componenteNombre,
'status': mainComponent.activo ? 'active' : 'disconnected', 'status': mainComponent.activo ? 'active' : 'disconnected',
'description': mainComponent.descripcion ?? 'Main Distribution Frame', 'description': mainComponent.descripcion ?? 'Main Distribution Frame',
'ubicacion': mainComponent.ubicacion ?? 'Sin ubicación', 'ubicacion': mainComponent.ubicacion ?? 'Sin ubicación',
'categoria': categoria?.nombre ?? 'Sin categoría', 'categoria': mainComponent.categoriaComponente,
'componentes': mdfComponents.length, 'componentes': mdfComponents.length,
'distribucion': mainComponent.distribucionNombre ?? 'MDF Principal',
}, },
handlers: [ handlers: [
Handler.bottomCenter, Handler.bottomCenter,
@@ -1134,16 +1138,12 @@ class _TopologiaPageState extends State<TopologiaPage>
); );
} }
FlowElement _createIDFElement(Componente idfComponent, Offset position) { FlowElement _createIDFElementOptimized(
final componentesProvider = VistaTopologiaPorNegocio idfComponent, Offset position) {
Provider.of<ComponentesProvider>(context, listen: false);
final categoria =
componentesProvider.getCategoriaById(idfComponent.categoriaId);
return FlowElement( return FlowElement(
position: position, position: position,
size: const Size(160, 120), size: const Size(160, 120),
text: 'IDF\n${idfComponent.nombre}', text: 'IDF\n${idfComponent.componenteNombre}',
textColor: Colors.white, textColor: Colors.white,
textSize: 12, textSize: 12,
textIsBold: true, textIsBold: true,
@@ -1158,16 +1158,17 @@ class _TopologiaPageState extends State<TopologiaPage>
elevation: 6, elevation: 6,
data: { data: {
'type': 'IDF', 'type': 'IDF',
'componenteId': idfComponent.id, 'componenteId': idfComponent.componenteId,
'name': idfComponent.nombre, 'name': idfComponent.componenteNombre,
'status': idfComponent.activo 'status': idfComponent.activo
? (idfComponent.enUso ? 'active' : 'warning') ? (idfComponent.enUso ? 'active' : 'warning')
: 'disconnected', : 'disconnected',
'description': 'description':
idfComponent.descripcion ?? 'Intermediate Distribution Frame', idfComponent.descripcion ?? 'Intermediate Distribution Frame',
'ubicacion': idfComponent.ubicacion ?? 'Sin ubicación', 'ubicacion': idfComponent.ubicacion ?? 'Sin ubicación',
'categoria': categoria?.nombre ?? 'Sin categoría', 'categoria': idfComponent.categoriaComponente,
'enUso': idfComponent.enUso, 'enUso': idfComponent.enUso,
'distribucion': idfComponent.distribucionNombre ?? 'IDF',
}, },
handlers: [ handlers: [
Handler.topCenter, Handler.topCenter,
@@ -1178,17 +1179,12 @@ class _TopologiaPageState extends State<TopologiaPage>
); );
} }
FlowElement _createSwitchElement( FlowElement _createSwitchElementOptimized(
Componente switchComponent, Offset position) { VistaTopologiaPorNegocio switchComponent, Offset position) {
final componentesProvider =
Provider.of<ComponentesProvider>(context, listen: false);
final categoria =
componentesProvider.getCategoriaById(switchComponent.categoriaId);
return FlowElement( return FlowElement(
position: position, position: position,
size: const Size(140, 100), size: const Size(140, 100),
text: 'Switch\n${switchComponent.nombre}', text: 'Switch\n${switchComponent.componenteNombre}',
textColor: Colors.white, textColor: Colors.white,
textSize: 10, textSize: 10,
textIsBold: true, textIsBold: true,
@@ -1203,12 +1199,13 @@ class _TopologiaPageState extends State<TopologiaPage>
elevation: switchComponent.activo ? 4 : 2, elevation: switchComponent.activo ? 4 : 2,
data: { data: {
'type': 'AccessSwitch', 'type': 'AccessSwitch',
'componenteId': switchComponent.id, 'componenteId': switchComponent.componenteId,
'name': switchComponent.nombre, 'name': switchComponent.componenteNombre,
'status': switchComponent.activo ? 'active' : 'disconnected', 'status': switchComponent.activo ? 'active' : 'disconnected',
'description': switchComponent.descripcion ?? 'Switch de Acceso', 'description': switchComponent.descripcion ?? 'Switch de Acceso',
'ubicacion': switchComponent.ubicacion ?? 'Sin ubicación', 'ubicacion': switchComponent.ubicacion ?? 'Sin ubicación',
'categoria': categoria?.nombre ?? 'Sin categoría', 'categoria': switchComponent.categoriaComponente,
'enUso': switchComponent.enUso,
}, },
handlers: [ handlers: [
Handler.topCenter, Handler.topCenter,
@@ -1216,239 +1213,158 @@ class _TopologiaPageState extends State<TopologiaPage>
); );
} }
FlowElement _createServerElement( FlowElement _createServerElementOptimized(
Componente serverComponent, Offset position) { VistaTopologiaPorNegocio serverComponent, Offset position) {
final componentesProvider =
Provider.of<ComponentesProvider>(context, listen: false);
final categoria =
componentesProvider.getCategoriaById(serverComponent.categoriaId);
return FlowElement( return FlowElement(
position: position, position: position,
size: const Size(160, 100), size: const Size(150, 100),
text: 'Servidor\n${serverComponent.nombre}', text: 'Servidor\n${serverComponent.componenteNombre}',
textColor: Colors.white, textColor: Colors.white,
textSize: 12, textSize: 11,
textIsBold: true, textIsBold: true,
kind: ElementKind.rectangle, kind: ElementKind.rectangle,
backgroundColor: const Color(0xFFE91E63), backgroundColor: serverComponent.activo
borderColor: const Color(0xFFC2185B), ? const Color(0xFFE91E63)
: const Color(0xFF757575),
borderColor: serverComponent.activo
? const Color(0xFFC2185B)
: const Color(0xFF424242),
borderThickness: 3, borderThickness: 3,
elevation: 6, elevation: serverComponent.activo ? 6 : 2,
data: { data: {
'type': 'Server', 'type': 'Server',
'componenteId': serverComponent.id, 'componenteId': serverComponent.componenteId,
'name': serverComponent.nombre, 'name': serverComponent.componenteNombre,
'status': serverComponent.activo ? 'active' : 'disconnected', 'status': serverComponent.activo ? 'active' : 'disconnected',
'description': serverComponent.descripcion ?? 'Servidor', 'description': serverComponent.descripcion ?? 'Servidor de red',
'ubicacion': serverComponent.ubicacion ?? 'Sin ubicación', 'ubicacion': serverComponent.ubicacion ?? 'Sin ubicación',
'categoria': categoria?.nombre ?? 'Sin categoría', 'categoria': serverComponent.categoriaComponente,
'enUso': serverComponent.enUso,
}, },
handlers: [ handlers: [
Handler.topCenter, 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.leftCenter,
Handler.rightCenter, 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 { ComponentesProvider componentesProvider) async {
// Obtener conexiones reales de la base de datos try {
final conexiones = componentesProvider.conexiones; 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 elementoOrigen = elementosMap[conexionCable.origenId];
final origenElement = elementosMap[conexion.componenteOrigenId]; final elementoDestino = elementosMap[conexionCable.destinoId];
final destinoElement = elementosMap[conexion.componenteDestinoId];
if (origenElement != null && destinoElement != null) { if (elementoOrigen != null && elementoDestino != null) {
// Determinar el color y grosor de la conexión basado en los tipos de componentes // Usar la información real del cable para determinar color y grosor
final colorConexion = final colorConexion = _getColorFromCableData(conexionCable);
_getConnectionColor(conexion, componentesProvider); final grosorConexion = _getThicknessFromCableData(conexionCable);
final grosorConexion =
_getConnectionThickness(conexion, componentesProvider);
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 // Crear la conexión en el FlowChart
origenElement.next = [ elementoOrigen.next ??= [];
...origenElement.next ?? [],
elementoOrigen.next!.add(
ConnectionParams( ConnectionParams(
destElementId: destinoElement.id, destElementId: elementoDestino.id,
arrowParams: ArrowParams( arrowParams: ArrowParams(
color: colorConexion, color: colorConexion,
thickness: grosorConexion, thickness: grosorConexion,
), ),
), ),
]; );
} } else {
}
// 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);
}
}
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
}
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;
}
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( print(
'MDF: ${mdfElements.length}, IDF: ${idfElements.length}, Switches: ${switchElements.length}'); 'Elementos no encontrados para conexión: ${conexionCable.origenId} -> ${conexionCable.destinoId}');
// 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 setState(() {});
for (int i = 0; i < idfElements.length && i < switchElements.length; i++) { } catch (e) {
final idf = idfElements[i]; print('Error en _createOptimizedConnections: ${e.toString()}');
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,
),
),
];
}
} }
} }
void _showNoComponentsMessage() { Color _getColorFromCableData(VistaConexionesPorCables conexionCable) {
// Usar el método del modelo para obtener el color
final colorHex = conexionCable.getColorForVisualization();
return _hexToColor(colorHex);
}
double _getThicknessFromCableData(VistaConexionesPorCables conexionCable) {
// Usar el método del modelo para obtener el grosor
return conexionCable.getThicknessForVisualization();
}
Color _hexToColor(String hex) {
hex = hex.replaceAll('#', '');
return Color(int.parse('FF$hex', radix: 16));
}
void _showNoBusinessSelectedDialog() {
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( 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( content: const Text(
'No se encontraron componentes de red para este negocio.\n\n' 'Para visualizar la topología de red, primero debe seleccionar un negocio desde la página de empresas.',
'Para ver una topología completa, agregue componentes en el módulo de Inventario con ubicaciones como "MDF", "IDF", etc.',
), ),
actions: [ actions: [
TextButton( TextButton(
@@ -1458,44 +1374,28 @@ class _TopologiaPageState extends State<TopologiaPage>
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
// Ir al módulo de inventario (esto depende de tu estructura de navegación) // Navegar a la página de empresas
// context.go('/infrastructure/inventory'); // router.pushNamed('/infrastructure/empresas');
}, },
child: const Text('Ir a Inventario'), child: const Text('Ir a Empresas'),
), ),
], ],
), ),
); );
} }
void _showNoBusinessSelectedDialog() { void _showErrorDialog(String mensaje) {
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Negocio no seleccionado'), title: const Row(
content: const Text( children: [
'No se ha seleccionado ningún negocio. Por favor, regrese a la página de negocios y seleccione "Acceder a Infraestructura" en la tabla.', Icon(Icons.error, color: Colors.red),
), SizedBox(width: 8),
actions: [ Text('Error'),
TextButton(
onPressed: () {
Navigator.of(context).pop();
// Regresar a la página de empresas/negocios
context.go('/empresa-negocios');
},
child: const Text('Ir a Negocios'),
),
], ],
), ),
); content: Text(mensaje),
}
void _showErrorDialog(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Error'),
content: Text(message),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), 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:flutter/material.dart';
import 'package:nethive_neo/helpers/globals.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/providers/nethive/componentes_provider.dart';
import 'package:nethive_neo/theme/theme.dart'; import 'package:nethive_neo/theme/theme.dart';
import 'package:nethive_neo/models/nethive/componente_model.dart'; import 'package:nethive_neo/models/nethive/componente_model.dart';
import 'package:nethive_neo/models/nethive/categoria_componente_model.dart';
class EditComponenteDialog extends StatefulWidget { class EditComponenteDialog extends StatefulWidget {
final ComponentesProvider provider; final ComponentesProvider provider;

File diff suppressed because it is too large Load Diff