commit b7c4e75ec037e6bf49c5fdb355c66e57980c19fa Author: admin Date: Tue Aug 4 16:58:48 2026 +0200 commit inicial diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..235b300 Binary files /dev/null and b/.DS_Store differ diff --git a/database/contactos.db b/database/contactos.db new file mode 100755 index 0000000..29f8e46 Binary files /dev/null and b/database/contactos.db differ diff --git a/info.txt b/info.txt new file mode 100755 index 0000000..3889ddf --- /dev/null +++ b/info.txt @@ -0,0 +1,19 @@ +contactos_app/ +├── main.py +├── database/ +│ └── contactos.db +├── styles/ +│ ├── light.qss +│ └── dark.qss +├── icons/ +│ └── app.ico +└── requirements.txt + + +🏁 Empaquetado listo +Windows +pyinstaller --onefile --windowed main.py + +Linux (AppImage) +pyinstaller --onefile main.py +linuxdeploy --appdir AppDir --output appimage \ No newline at end of file diff --git a/main.py b/main.py new file mode 100755 index 0000000..6a84506 --- /dev/null +++ b/main.py @@ -0,0 +1,136 @@ +import sys +import os +from PySide6.QtWidgets import ( + QApplication, QWidget, QVBoxLayout, QLineEdit, + QTableView, QHBoxLayout, QPushButton, QLabel, QHeaderView +) +from PySide6.QtCore import Qt +from PySide6.QtSql import QSqlDatabase, QSqlTableModel +from PySide6.QtCore import QSortFilterProxyModel + +class ContactosApp(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("Contactos") + self.setMinimumSize(600, 400) + # self.setWindowIcon(os.path.join("icons", "app.ico")) + + # Layout principal + main_layout = QVBoxLayout() + self.setLayout(main_layout) + + # Header con título y switch claro/oscuro + header_layout = QHBoxLayout() + title = QLabel("📇 Contactos") + title.setStyleSheet("font-weight: bold; font-size: 16px;") + header_layout.addWidget(title) + + self.theme_btn = QPushButton("🌗 Modo oscuro") + self.theme_btn.setCheckable(True) + self.theme_btn.clicked.connect(self.toggle_theme) + header_layout.addStretch() + header_layout.addWidget(self.theme_btn) + main_layout.addLayout(header_layout) + + # Buscador + self.search_input = QLineEdit() + self.search_input.setPlaceholderText("🔍 Buscar por nombre…") + self.search_input.textChanged.connect(self.buscar) + main_layout.addWidget(self.search_input) + + # Tabla de contactos + self.table = QTableView() + main_layout.addWidget(self.table) + + # Conexión a DB SQLite + db_path = os.path.join("database", "contactos.db") + self.db = QSqlDatabase.addDatabase("QSQLITE") + self.db.setDatabaseName(db_path) + if not self.db.open(): + raise Exception("No se pudo abrir la base de datos") + + # Modelo y proxy para búsqueda + self.model = QSqlTableModel(db=self.db) + self.model.setTable("contactos") + self.model.select() + + # Encabezados más amigables + self.model.setHeaderData(1, Qt.Horizontal, "Nombre") + self.model.setHeaderData(2, Qt.Horizontal, "Teléfono") + self.model.setHeaderData(3, Qt.Horizontal, "Tel. corto") + self.model.setHeaderData(4, Qt.Horizontal, "Email") + + self.proxy = QSortFilterProxyModel() + self.proxy.setSourceModel(self.model) + self.proxy.setFilterKeyColumn(1) # Solo nombre + self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive) + + self.table.setModel(self.proxy) + self.table.hideColumn(0) + self.table.setEditTriggers(QTableView.NoEditTriggers) + self.table.setSelectionBehavior(QTableView.SelectRows) + self.table.setSelectionMode(QTableView.SingleSelection) + + self.table.setAlternatingRowColors(True) + + header = self.table.horizontalHeader() + header.setSectionResizeMode(QHeaderView.Interactive) + header.setSectionResizeMode(1, QHeaderView.Stretch) # Nombre + header.setSectionResizeMode(4, QHeaderView.Stretch) # Email + + # self.table.resizeColumnsToContents() + # self.table.setColumnWidth(1, 200) # Nombre + # self.table.setColumnWidth(2, 120) # Teléfono + # self.table.setColumnWidth(3, 80) # Tel. corto + # self.table.setColumnWidth(4, 250) # Email + + self.table.keyPressEvent = lambda event: ( + self.copiar_fila() + if event.matches(QKeySequence.Copy) + else QTableView.keyPressEvent(self.table, event) + ) + + # Aplicar tema claro por defecto + self.aplicar_tema("light") + + def buscar(self, texto): + # self.proxy.setFilterFixedString(texto) #coincidencias exactas + self.proxy.setFilterRegularExpression(texto) #buscar tipo %like% + + def toggle_theme(self): + if self.theme_btn.isChecked(): + self.aplicar_tema("dark") + self.theme_btn.setText("🌞 Modo claro") + else: + self.aplicar_tema("light") + self.theme_btn.setText("🌗 Modo oscuro") + + def aplicar_tema(self, modo): + qss_path = os.path.join("styles", f"{modo}.qss") + if os.path.exists(qss_path): + with open(qss_path, "r") as f: + self.setStyleSheet(f.read()) + + def copiar_fila(self): + selection = self.table.selectionModel().selectedRows() + if not selection: + return + + row = selection[0].row() + columnas = self.proxy.columnCount() + + datos = [] + for col in range(columnas): + if self.table.isColumnHidden(col): + continue + index = self.proxy.index(row, col) + datos.append(str(self.proxy.data(index))) + + texto = " | ".join(datos) + QApplication.clipboard().setText(texto) + +if __name__ == "__main__": + app = QApplication(sys.argv) + ventana = ContactosApp() + ventana.show() + sys.exit(app.exec()) diff --git a/requirements.txt b/requirements.txt new file mode 100755 index 0000000..fa8fe29 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +PySide6>=6.6 \ No newline at end of file diff --git a/styles/dark.qss b/styles/dark.qss new file mode 100755 index 0000000..b08b5f1 --- /dev/null +++ b/styles/dark.qss @@ -0,0 +1,29 @@ +QWidget { + background-color: #121212; + color: #e0e0e0; + font-size: 13px; +} + +QLineEdit { + background-color: #1e1e1e; + border: 1px solid #333; + padding: 6px; + border-radius: 6px; +} + +QTableView { + background-color: #1e1e1e; + gridline-color: #333; + selection-background-color: #2d89ef; +} + +QHeaderView::section { + background-color: #2b2b2b; + padding: 6px; + border: none; + font-weight: bold; +} + +QPushButton { + padding: 4px 8px; +} diff --git a/styles/light.qss b/styles/light.qss new file mode 100755 index 0000000..837e6de --- /dev/null +++ b/styles/light.qss @@ -0,0 +1,29 @@ +QWidget { + background-color: #f0f0f0; + color: #1e1e1e; + font-size: 13px; +} + +QLineEdit { + background-color: #ffffff; + border: 1px solid #ccc; + padding: 6px; + border-radius: 6px; +} + +QTableView { + background-color: #ffffff; + gridline-color: #ddd; + selection-background-color: #2d89ef; +} + +QHeaderView::section { + background-color: #e0e0e0; + padding: 6px; + border: none; + font-weight: bold; +} + +QPushButton { + padding: 4px 8px; +}