commit inicial

This commit is contained in:
2026-08-04 16:58:48 +02:00
commit b7c4e75ec0
7 changed files with 214 additions and 0 deletions
Vendored
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Executable
+19
View File
@@ -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
Executable
+136
View File
@@ -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())
+1
View File
@@ -0,0 +1 @@
PySide6>=6.6
+29
View File
@@ -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;
}
+29
View File
@@ -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;
}