Tarea entregada - Primer Commit

This commit is contained in:
RodoIsAlnum
2025-10-24 22:40:20 -06:00
commit 7a763ed972
6 changed files with 1053 additions and 0 deletions

269
CitationsView.qml Normal file
View File

@@ -0,0 +1,269 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls.Material 2.15
Item {
RowLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 20
// Lista de citas
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
border.color: Material.color(Material.Grey)
border.width: 1
radius: 5
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
Label {
text: "Lista de Citas"
font.pixelSize: 18
font.bold: true
}
ListView {
id: citationsListView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 5
model: window.citationsData
delegate: Rectangle {
width: citationsListView.width
height: 110
color: mouseArea.containsMouse ? Material.color(Material.Grey, Material.Shade100) : "white"
border.color: Material.color(Material.Grey, Material.Shade300)
radius: 5
RowLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
ColumnLayout {
Layout.fillWidth: true
spacing: 5
Label {
text: "Cita #" + modelData.cita_id
font.pixelSize: 16
font.bold: true
}
Label {
text: "Detalles: " + (modelData.detalles || "Sin detalles")
font.pixelSize: 13
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
Label {
text: "Médico ID: " + modelData.medico_id
font.pixelSize: 12
color: Material.color(Material.Blue)
}
Label {
text: "Paciente ID: " + modelData.paciente_id
font.pixelSize: 12
color: Material.color(Material.Teal)
}
}
Button {
text: "✏️"
flat: true
onClicked: {
citationForm.editMode = true
citationForm.currentId = modelData.cita_id
citationForm.detallesField.text = modelData.detalles
citationForm.medicoIdField.text = modelData.medico_id.toString()
citationForm.pacienteIdField.text = modelData.paciente_id.toString()
}
}
Button {
text: "🗑️"
flat: true
Material.foreground: Material.Red
onClicked: {
deleteDialog.citationId = modelData.cita_id
deleteDialog.open()
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
}
}
ScrollBar.vertical: ScrollBar {}
}
}
}
// Formulario de citas
Rectangle {
id: citationForm
Layout.preferredWidth: 350
Layout.fillHeight: true
border.color: Material.color(Material.Grey)
border.width: 1
radius: 5
property bool editMode: false
property int currentId: -1
property alias detallesField: detallesInput
property alias medicoIdField: medicoIdInput
property alias pacienteIdField: pacienteIdInput
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
Label {
text: citationForm.editMode ? "Editar Cita" : "Nueva Cita"
font.pixelSize: 18
font.bold: true
}
Label {
text: "Seleccionar Médico:"
font.pixelSize: 14
}
ComboBox {
id: medicoCombo
Layout.fillWidth: true
model: window.medicsData
textRole: "nombre"
displayText: currentIndex >= 0 ? window.medicsData[currentIndex].nombre + " " + window.medicsData[currentIndex].apellido : "Seleccione médico"
Material.accent: Material.Teal
}
Label {
text: "Seleccionar Paciente:"
font.pixelSize: 14
}
ComboBox {
id: pacienteCombo
Layout.fillWidth: true
model: window.patientsData
textRole: "nombre"
displayText: currentIndex >= 0 ? window.patientsData[currentIndex].nombre + " " + window.patientsData[currentIndex].apellido : "Seleccione paciente"
Material.accent: Material.Teal
}
TextField {
id: detallesInput
Layout.fillWidth: true
placeholderText: "Detalles de la cita"
Material.accent: Material.Teal
}
// Campos ocultos para modo edición
TextField {
id: medicoIdInput
visible: false
}
TextField {
id: pacienteIdInput
visible: false
}
RowLayout {
Layout.fillWidth: true
spacing: 10
Button {
Layout.fillWidth: true
text: citationForm.editMode ? "Actualizar" : "Agregar"
Material.background: Material.Teal
Material.foreground: "white"
onClicked: {
var medicoId = citationForm.editMode ?
parseInt(medicoIdInput.text) :
(medicoCombo.currentIndex >= 0 ? window.medicsData[medicoCombo.currentIndex].medico_id : -1)
var pacienteId = citationForm.editMode ?
parseInt(pacienteIdInput.text) :
(pacienteCombo.currentIndex >= 0 ? window.patientsData[pacienteCombo.currentIndex].paciente_id : -1)
if (detallesInput.text && medicoId > 0 && pacienteId > 0) {
if (citationForm.editMode) {
apiManager.updateCitation(
citationForm.currentId,
detallesInput.text,
medicoId,
pacienteId
)
} else {
apiManager.addCitation(
detallesInput.text,
medicoId,
pacienteId
)
}
detallesInput.text = ""
medicoCombo.currentIndex = -1
pacienteCombo.currentIndex = -1
citationForm.editMode = false
}
}
}
Button {
text: "Cancelar"
visible: citationForm.editMode
onClicked: {
detallesInput.text = ""
medicoCombo.currentIndex = -1
pacienteCombo.currentIndex = -1
citationForm.editMode = false
}
}
}
Item {
Layout.fillHeight: true
}
}
}
}
// Dialog de confirmación de eliminación
Dialog {
id: deleteDialog
anchors.centerIn: parent
title: "Confirmar eliminación"
modal: true
standardButtons: Dialog.Yes | Dialog.No
property int citationId: -1
Label {
text: "¿Está seguro que desea eliminar esta cita?"
}
onAccepted: {
apiManager.deleteCitation(deleteDialog.citationId)
}
}
}

231
MedicsView.qml Normal file
View File

@@ -0,0 +1,231 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls.Material 2.15
Item {
RowLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 20
// Lista de médicos
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
border.color: Material.color(Material.Grey)
border.width: 1
radius: 5
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
Label {
text: "Lista de Médicos"
font.pixelSize: 18
font.bold: true
}
ListView {
id: medicsListView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 5
model: window.medicsData
delegate: Rectangle {
width: medicsListView.width
height: 90
color: mouseArea.containsMouse ? Material.color(Material.Grey, Material.Shade100) : "white"
border.color: Material.color(Material.Grey, Material.Shade300)
radius: 5
RowLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
ColumnLayout {
Layout.fillWidth: true
spacing: 5
Label {
text: modelData.nombre + " " + modelData.apellido
font.pixelSize: 16
font.bold: true
}
Label {
text: "Especialidad: " + (modelData.especialidad || "N/A")
font.pixelSize: 14
color: Material.color(Material.Teal)
}
Label {
text: "ID: " + modelData.medico_id
font.pixelSize: 12
color: Material.color(Material.Grey)
}
}
Button {
text: "✏️"
flat: true
onClicked: {
medicForm.editMode = true
medicForm.currentId = modelData.medico_id
medicForm.nombreField.text = modelData.nombre
medicForm.apellidoField.text = modelData.apellido
medicForm.especialidadField.text = modelData.especialidad
}
}
Button {
text: "🗑️"
flat: true
Material.foreground: Material.Red
onClicked: {
deleteDialog.medicId = modelData.medico_id
deleteDialog.medicName = modelData.nombre + " " + modelData.apellido
deleteDialog.open()
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
}
}
ScrollBar.vertical: ScrollBar {}
}
}
}
// Formulario de médicos
Rectangle {
id: medicForm
Layout.preferredWidth: 350
Layout.fillHeight: true
border.color: Material.color(Material.Grey)
border.width: 1
radius: 5
property bool editMode: false
property int currentId: -1
property alias nombreField: nombreInput
property alias apellidoField: apellidoInput
property alias especialidadField: especialidadInput
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
Label {
text: medicForm.editMode ? "Editar Médico" : "Nuevo Médico"
font.pixelSize: 18
font.bold: true
}
TextField {
id: nombreInput
Layout.fillWidth: true
placeholderText: "Nombre"
Material.accent: Material.Teal
}
TextField {
id: apellidoInput
Layout.fillWidth: true
placeholderText: "Apellido"
Material.accent: Material.Teal
}
TextField {
id: especialidadInput
Layout.fillWidth: true
placeholderText: "Especialidad"
Material.accent: Material.Teal
}
RowLayout {
Layout.fillWidth: true
spacing: 10
Button {
Layout.fillWidth: true
text: medicForm.editMode ? "Actualizar" : "Agregar"
Material.background: Material.Teal
Material.foreground: "white"
onClicked: {
if (nombreInput.text && apellidoInput.text && especialidadInput.text) {
if (medicForm.editMode) {
apiManager.updateMedic(
medicForm.currentId,
nombreInput.text,
apellidoInput.text,
especialidadInput.text
)
} else {
apiManager.addMedic(
nombreInput.text,
apellidoInput.text,
especialidadInput.text
)
}
nombreInput.text = ""
apellidoInput.text = ""
especialidadInput.text = ""
medicForm.editMode = false
}
}
}
Button {
text: "Cancelar"
visible: medicForm.editMode
onClicked: {
nombreInput.text = ""
apellidoInput.text = ""
especialidadInput.text = ""
medicForm.editMode = false
}
}
}
Item {
Layout.fillHeight: true
}
}
}
}
// Dialog de confirmación de eliminación
Dialog {
id: deleteDialog
anchors.centerIn: parent
title: "Confirmar eliminación"
modal: true
standardButtons: Dialog.Yes | Dialog.No
property int medicId: -1
property string medicName: ""
Label {
text: "¿Está seguro que desea eliminar al médico " + deleteDialog.medicName + "?"
}
onAccepted: {
apiManager.deleteMedic(deleteDialog.medicId)
}
}
}

218
PatientsView.qml Normal file
View File

@@ -0,0 +1,218 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls.Material 2.15
Item {
RowLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 20
// Lista de pacientes
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
border.color: Material.color(Material.Grey)
border.width: 1
radius: 5
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
Label {
text: "Lista de Pacientes"
font.pixelSize: 18
font.bold: true
}
ListView {
id: patientsListView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 5
model: window.patientsData
delegate: Rectangle {
width: patientsListView.width
height: 80
color: mouseArea.containsMouse ? Material.color(Material.Grey, Material.Shade100) : "white"
border.color: Material.color(Material.Grey, Material.Shade300)
radius: 5
RowLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
ColumnLayout {
Layout.fillWidth: true
spacing: 5
Label {
text: modelData.nombre + " " + modelData.apellido
font.pixelSize: 16
font.bold: true
}
Label {
text: "ID: " + modelData.paciente_id
font.pixelSize: 12
color: Material.color(Material.Grey)
}
Label {
text: "Registro: " + (modelData.registro || "N/A")
font.pixelSize: 12
color: Material.color(Material.Grey)
}
}
Button {
text: "✏️"
flat: true
onClicked: {
patientForm.editMode = true
patientForm.currentId = modelData.paciente_id
patientForm.nombreField.text = modelData.nombre
patientForm.apellidoField.text = modelData.apellido
}
}
Button {
text: "🗑️"
flat: true
Material.foreground: Material.Red
onClicked: {
deleteDialog.patientId = modelData.paciente_id
deleteDialog.patientName = modelData.nombre + " " + modelData.apellido
deleteDialog.open()
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
}
}
ScrollBar.vertical: ScrollBar {}
}
}
}
// Formulario de pacientes
Rectangle {
id: patientForm
Layout.preferredWidth: 350
Layout.fillHeight: true
border.color: Material.color(Material.Grey)
border.width: 1
radius: 5
property bool editMode: false
property int currentId: -1
property alias nombreField: nombreInput
property alias apellidoField: apellidoInput
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
Label {
text: patientForm.editMode ? "Editar Paciente" : "Nuevo Paciente"
font.pixelSize: 18
font.bold: true
}
TextField {
id: nombreInput
Layout.fillWidth: true
placeholderText: "Nombre"
Material.accent: Material.Teal
}
TextField {
id: apellidoInput
Layout.fillWidth: true
placeholderText: "Apellido"
Material.accent: Material.Teal
}
RowLayout {
Layout.fillWidth: true
spacing: 10
Button {
Layout.fillWidth: true
text: patientForm.editMode ? "Actualizar" : "Agregar"
Material.background: Material.Teal
Material.foreground: "white"
onClicked: {
if (nombreInput.text && apellidoInput.text) {
if (patientForm.editMode) {
apiManager.updatePatient(
patientForm.currentId,
nombreInput.text,
apellidoInput.text
)
} else {
apiManager.addPatient(
nombreInput.text,
apellidoInput.text
)
}
nombreInput.text = ""
apellidoInput.text = ""
patientForm.editMode = false
}
}
}
Button {
text: "Cancelar"
visible: patientForm.editMode
onClicked: {
nombreInput.text = ""
apellidoInput.text = ""
patientForm.editMode = false
}
}
}
Item {
Layout.fillHeight: true
}
}
}
}
// Dialog de confirmación de eliminación
Dialog {
id: deleteDialog
anchors.centerIn: parent
title: "Confirmar eliminación"
modal: true
standardButtons: Dialog.Yes | Dialog.No
property int patientId: -1
property string patientName: ""
Label {
text: "¿Está seguro que desea eliminar al paciente " + deleteDialog.patientName + "?"
}
onAccepted: {
apiManager.deletePatient(deleteDialog.patientId)
}
}
}

5
README.md Normal file
View File

@@ -0,0 +1,5 @@
## API CONSUMER PC2
El consumidor de la API del caso práctico 2.
Está hecho con Python y Qt

142
main.qml Normal file
View File

@@ -0,0 +1,142 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls.Material 2.15
ApplicationWindow {
id: window
visible: true
width: 1200
height: 800
title: "Sistema de Gestión Médica"
Material.theme: Material.Light
Material.accent: Material.Teal
Material.primary: Material.Blue
property var patientsData: []
property var medicsData: []
property var citationsData: []
// Conexiones con el backend
Connections {
target: apiManager
function onDataLoaded(dataType, jsonData) {
var data = JSON.parse(jsonData)
if (dataType === "patients") {
patientsData = data
} else if (dataType === "medics") {
medicsData = data
} else if (dataType === "citations") {
citationsData = data
}
}
function onOperationComplete(operation, success, message) {
messageDialog.title = success ? "Éxito" : "Error"
messageDialog.text = message
messageDialog.open()
}
}
Component.onCompleted: {
apiManager.loadData("patients")
apiManager.loadData("medics")
apiManager.loadData("citations")
}
// Dialog para mensajes
Dialog {
id: messageDialog
anchors.centerIn: parent
modal: true
standardButtons: Dialog.Ok
property alias text: messageLabel.text
Label {
id: messageLabel
}
}
// Header
header: ToolBar {
Material.background: Material.Blue
RowLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
Label {
text: "🏥 Sistema de Gestión Médica"
font.pixelSize: 20
font.bold: true
color: "white"
Layout.fillWidth: true
}
Button {
text: "🔄 Actualizar"
flat: true
Material.foreground: "white"
onClicked: {
apiManager.loadData("patients")
apiManager.loadData("medics")
apiManager.loadData("citations")
}
}
}
}
// Main content
TabBar {
id: tabBar
width: parent.width
TabButton {
text: "👥 Pacientes"
width: implicitWidth
}
TabButton {
text: "👨‍⚕️ Médicos"
width: implicitWidth
}
TabButton {
text: "📅 Citas"
width: implicitWidth
}
}
StackLayout {
width: parent.width
anchors.top: tabBar.bottom
anchors.bottom: parent.bottom
currentIndex: tabBar.currentIndex
// TAB 1: Pacientes
Item {
PatientsView {
anchors.fill: parent
}
}
// TAB 2: Médicos
Item {
MedicsView {
anchors.fill: parent
}
}
// TAB 3: Citas
Item {
CitationsView {
anchors.fill: parent
}
}
}
}

188
main_ui.py Normal file
View File

@@ -0,0 +1,188 @@
import sys
import requests
import json
import datetime
import time
from PySide6.QtCore import QObject, Slot, Signal, Property, QTimer
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtWidgets import QApplication
from PySide6.QtQuickControls2 import QQuickStyle
# API Configuration
URI = 'http://localhost:5244/api/'
CITATION = URI + 'Cita'
PATIENTS = URI + 'Paciente'
MEDICS = URI + 'Medico'
HEADERS = {"content-type": "application/json"}
class ApiManager(QObject):
"""Backend manager for API calls"""
dataLoaded = Signal(str, str) # Signal(dataType, jsonData)
operationComplete = Signal(str, bool, str) # Signal(operation, success, message)
def __init__(self):
super().__init__()
# GET Methods
@Slot(str)
def loadData(self, dataType):
"""Load data from API (patients, medics, or citations)"""
try:
url_map = {
"patients": PATIENTS,
"medics": MEDICS,
"citations": CITATION
}
response = requests.get(url_map[dataType], headers=HEADERS)
data = response.json()
self.dataLoaded.emit(dataType, json.dumps(data))
except Exception as e:
self.operationComplete.emit(f"load_{dataType}", False, str(e))
# PATIENTS
@Slot(str, str)
def addPatient(self, nombre, apellido):
try:
datat = {
"nombre": nombre,
"apellido": apellido,
"registro": datetime.date.fromtimestamp(time.time()).isoformat()
}
response = requests.post(PATIENTS, json=datat, headers=HEADERS)
self.operationComplete.emit("add_patient", True, "Paciente agregado exitosamente")
self.loadData("patients")
except Exception as e:
self.operationComplete.emit("add_patient", False, str(e))
@Slot(int, str, str)
def updatePatient(self, pat_id, nombre, apellido):
try:
datat = {
"paciente_id": pat_id,
"nombre": nombre,
"apellido": apellido,
"registro": datetime.date.fromtimestamp(time.time()).isoformat()
}
response = requests.put(f"{PATIENTS}/{pat_id}", json=datat, headers=HEADERS)
self.operationComplete.emit("update_patient", True, "Paciente actualizado exitosamente")
self.loadData("patients")
except Exception as e:
self.operationComplete.emit("update_patient", False, str(e))
@Slot(int)
def deletePatient(self, pat_id):
try:
response = requests.delete(f"{PATIENTS}/{pat_id}", headers=HEADERS)
self.operationComplete.emit("delete_patient", True, "Paciente eliminado exitosamente")
self.loadData("patients")
except Exception as e:
self.operationComplete.emit("delete_patient", False, str(e))
# MEDICS
@Slot(str, str, str)
def addMedic(self, nombre, apellido, especialidad):
try:
datat = {
"nombre": nombre,
"apellido": apellido,
"especialidad": especialidad,
"registro": datetime.date.fromtimestamp(time.time()).isoformat()
}
response = requests.post(MEDICS, json=datat, headers=HEADERS)
self.operationComplete.emit("add_medic", True, "Médico agregado exitosamente")
self.loadData("medics")
except Exception as e:
self.operationComplete.emit("add_medic", False, str(e))
@Slot(int, str, str, str)
def updateMedic(self, med_id, nombre, apellido, especialidad):
try:
datat = {
"medico_id": med_id,
"nombre": nombre,
"apellido": apellido,
"especialidad": especialidad,
"registro": datetime.date.fromtimestamp(time.time()).isoformat()
}
response = requests.put(f"{MEDICS}/{med_id}", json=datat, headers=HEADERS)
self.operationComplete.emit("update_medic", True, "Médico actualizado exitosamente")
self.loadData("medics")
except Exception as e:
self.operationComplete.emit("update_medic", False, str(e))
@Slot(int)
def deleteMedic(self, med_id):
try:
response = requests.delete(f"{MEDICS}/{med_id}", headers=HEADERS)
self.operationComplete.emit("delete_medic", True, "Médico eliminado exitosamente")
self.loadData("medics")
except Exception as e:
self.operationComplete.emit("delete_medic", False, str(e))
# CITATIONS
@Slot(str, int, int)
def addCitation(self, detalles, med_id, pat_id):
try:
datat = {
"detalles": detalles,
"medico_id": med_id,
"paciente_id": pat_id
}
response = requests.post(CITATION, json=datat, headers=HEADERS)
self.operationComplete.emit("add_citation", True, "Cita agregada exitosamente")
self.loadData("citations")
except Exception as e:
self.operationComplete.emit("add_citation", False, str(e))
@Slot(int, str, int, int)
def updateCitation(self, cit_id, detalles, med_id, pat_id):
try:
datat = {
"cita_id": cit_id,
"detalles": detalles,
"medico_id": med_id,
"paciente_id": pat_id
}
response = requests.put(f"{CITATION}/{cit_id}", json=datat, headers=HEADERS)
self.operationComplete.emit("update_citation", True, "Cita actualizada exitosamente")
self.loadData("citations")
except Exception as e:
self.operationComplete.emit("update_citation", False, str(e))
@Slot(int)
def deleteCitation(self, cit_id):
try:
response = requests.delete(f"{CITATION}/{cit_id}", headers=HEADERS)
self.operationComplete.emit("delete_citation", True, "Cita eliminada exitosamente")
self.loadData("citations")
except Exception as e:
self.operationComplete.emit("delete_citation", False, str(e))
def main():
app = QApplication(sys.argv)
QQuickStyle.setStyle("Material")
# Create API manager
api_manager = ApiManager()
# Create QML engine
engine = QQmlApplicationEngine()
# Expose API manager to QML
engine.rootContext().setContextProperty("apiManager", api_manager)
# Load QML file
qml_file = "main.qml"
engine.load(qml_file)
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec())
if __name__ == "__main__":
main()