Skip to content

Commit 67aa631

Browse files
authored
Merge pull request #53 from iJosueeh/dev
Dev
2 parents 2ae1625 + 55d86ad commit 67aa631

15 files changed

Lines changed: 212 additions & 161 deletions

File tree

IDEA.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
**Project Name:** MediTrack
2+
**Description:** A specialized Inventory Management System designed for Health Clinics (Postas de Salud). It ensures the constant availability of critical medical supplies, optimizes batch traceability, and facilitates decision-making through a high-fidelity visual interface.
3+
4+
**Tech Stack Context:**
5+
- Core & UI: Java 21 (LTS) with JavaFX 21.
6+
- UI Framework: AtlantaFX (Primer Theme) for a modern, Fluent/Minimalist design, utilizing Ikonli for iconography.
7+
- Database: SQL Server running in a Docker Container.
8+
- Build & Testing: Maven, JUnit 5 for logic/integration testing, and GitHub Actions for CI.
9+
10+
**Core Domain Rules & Constraints:**
11+
- Inventory Algorithm: All dispensing logic MUST strictly implement the FEFO (First Expire, First Out) algorithm to ensure efficient batch usage.
12+
- Data Integrity: Inventory movements (Inputs, Outputs, Attentions) must be handled via strict ACID transactions.
13+
- Traceability: The system must enforce sequential, descriptive identifiers per venue (e.g., CAT-001, PAC-001-000001).
14+
- Security: Access must be strictly controlled via Role-Based Access Control (RBAC) with dynamic management of venue managers.
15+
- Integration: Patient clinical records and attention history must be deeply integrated into the dispensing flow, not treated as an isolated module.

README.md

Lines changed: 37 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,64 @@
11
<h1 align="center">🏥 MediTrack</h1>
22
<p align="center">
3-
<strong>Sistema Especializado de Gestión de Inventario para Postas de Salud</strong>
3+
<em>Sistema de Gestión de Inventario para Postas de Salud</em>
44
</p>
55

66
<p align="center">
7-
<img src="https://img.shields.io/badge/Java-21-orange?style=for-the-badge&logo=openjdk" alt="Java">
8-
<img src="https://img.shields.io/badge/JavaFX-21-blue?style=for-the-badge" alt="JavaFX">
9-
<img src="https://img.shields.io/badge/Maven-Build-green?style=for-the-badge&logo=apache-maven" alt="Maven">
10-
<img src="https://img.shields.io/badge/CI%2FCD-GitHub%20Actions-brightgreen?style=for-the-badge&logo=github-actions" alt="CI/CD">
7+
<img src="https://img.shields.io/badge/Java-21-orange?style=flat-square&logo=openjdk" alt="Java">
8+
<img src="https://img.shields.io/badge/JavaFX-21-blue?style=flat-square" alt="JavaFX">
9+
<img src="https://img.shields.io/badge/AtlantaFX-Primer-6C63FF?style=flat-square" alt="AtlantaFX">
10+
<img src="https://img.shields.io/badge/SQL%20Server-CC2927?style=flat-square&logo=microsoft-sql-server&logoColor=white" alt="SQL Server">
11+
<img src="https://img.shields.io/badge/Maven-3.9-C71A36?style=flat-square&logo=apache-maven&logoColor=white" alt="Maven">
1112
</p>
1213

1314
---
1415

15-
### 📝 Descripción
16-
**MediTrack** es una solución robusta diseñada específicamente para la administración eficiente de recursos médicos en **Postas de Salud**. Su objetivo es garantizar la disponibilidad constante de suministros críticos, optimizar la trazabilidad de lotes y facilitar la toma de decisiones mediante una interfaz intuitiva de alta fidelidad visual.
16+
## 📝 Descripción
1717

18-
---
19-
20-
### ✨ Características Destacadas
21-
22-
| Característica | Detalle |
23-
| :--- | :--- |
24-
| **📦 Catálogo de Productos** | Registro completo de medicamentos con códigos DIGEMID, categorías y unidades de medida. |
25-
| **🧪 Dispensación Inteligente** | Algoritmo **FEFO** (First Expire, First Out) automático para asegurar el uso eficiente de lotes. |
26-
| **💊 Transacciones ACID** | Motor de movimientos de inventario (Entradas/Salidas/Atenciones) con integridad total de datos. |
27-
| **👥 Gestión de Pacientes** | Registro clínico y consulta de historial de atenciones integrado en el flujo de dispensación. |
28-
| **🆔 IDs Secuenciales** | Sistema de identificadores legibles y descriptivos por sede (`CAT-001`, `PAC-001-000001`). |
29-
| **🛡️ Seguridad & Usuarios** | Control de acceso basado en roles (RBAC) con identificación dinámica de jefaturas. |
30-
| **📊 Dashboard Dinámico** | Métricas en tiempo real de salud de inventario, stock crítico y actividad diaria. |
31-
| **🏢 Gestión de Sedes** | Control centralizado de múltiples postas médicas y sus responsables. |
32-
| **🖥️ Interfaz Moderna** | Experiencia de usuario optimizada con *AtlantaFX* (Primer Theme) y soporte completo para **UTF-8**. |
18+
MediTrack administra recursos médicos en postas de salud. Garantiza disponibilidad de suministros, trazabilidad de lotes y toma de decisiones mediante una interfaz de escritorio moderna.
3319

34-
---
35-
36-
### 🛠️ Stack Tecnológico
20+
## 🛠️ Stack
3721

38-
- **Core:** Java 21 (LTS)
39-
- **UI:** JavaFX 21 + **AtlantaFX** (Fluent/Minimalist Design)
40-
- **Iconos:** Ikonli (FontAwesome 5 Packs)
41-
- **Database:** SQL Server (Docker Container)
42-
- **Testing:** JUnit 5 (Pruebas de Integración y Lógica)
43-
- **Build & CI:** Maven + GitHub Actions
22+
-**Core:** Java 21 (Eclipse Adoptium)
23+
- 🎨 **UI:** JavaFX 21 + AtlantaFX (Primer Theme)
24+
- 🎯 **Iconos:** Ikonli (FontAwesome 5)
25+
- 🗄️ **Base de datos:** SQL Server (Docker)
26+
- 🔨 **Build:** Maven + GitHub Actions
4427

45-
---
28+
## 🚀 Inicio Rápido
4629

47-
### 🚀 Guía de Inicio Rápido
48-
49-
#### Requisitos
50-
- [JDK 21+](https://adoptium.net/)
51-
- [Maven 3.8+](https://maven.apache.org/download.cgi)
52-
- [Docker & Docker Compose](https://www.docker.com/)
53-
54-
#### Configuración de la Base de Datos (Docker)
55-
Este proyecto utiliza SQL Server en un contenedor Docker para facilitar el desarrollo.
30+
**Requisitos:** JDK 21+, Maven 3.8+, Docker
5631

5732
```bash
58-
# 1. Crear archivo .env a partir del template (ya realizado por el asistente)
59-
# cp .env.template .env
60-
61-
# 2. Iniciar el contenedor de la base de datos
33+
# 1. Levantar SQL Server
6234
docker-compose up -d
63-
```
64-
*Nota: El primer inicio ejecuta automáticamente `meditrack_init.sql` que crea las tablas, agrega columnas faltantes (si actualiza desde una versión anterior) y carga los datos iniciales. Espere unos 45 segundos antes de ejecutar la aplicación.*
65-
66-
*Para actualizar una base de datos existente, simplemente reinicie el contenedor con `docker-compose down && docker-compose up -d` - el script detectará y agregará automáticamente las columnas/tablas faltantes.*
6735

68-
#### Instalación y Ejecución
69-
```bash
70-
# 1. Compilar el proyecto
36+
# 2. Compilar y ejecutar
7137
./mvnw clean compile
72-
73-
# 2. Ejecutar la aplicación
7438
./mvnw javafx:run
7539
```
7640

77-
#### Ejecución de Pruebas
78-
```bash
79-
./mvnw test
80-
```
41+
> 💡 En Windows usar `mvnw.cmd`. El primer inicio tarda ~45s en inicializar la BD.
8142
82-
---
43+
## 📂 Estructura
8344

84-
### 📂 Documentación Técnica
85-
Para una inmersión profunda en el desarrollo, consulta nuestras guías:
45+
```
46+
src/main/java/.../meditrackapp/
47+
├── 📦 application/ # Casos de uso
48+
├── ⚙️ core/ # Config y utilidades
49+
├── 🧩 domain/ # Entidades y lógica de negocio
50+
├── 📋 features/ # Módulos (auth, inventory, patients, users...)
51+
├── 🔌 infrastructure/ # Persistencia JDBC
52+
└── 🧩 shared/ # Sidebar, navbar, componentes comunes
53+
```
54+
55+
## 📖 Documentación
8656

87-
* 📘 [**Arquitectura**](./docs/ARCHITECTURE.md): Estructura de paquetes, estrategia de IDs y transaccionalidad.
88-
* 📋 [**Requerimientos Funcionales**](./docs/FUNCTIONAL_REQUIREMENTS.md): Estado actual de implementación de los módulos.
89-
* 🎨 [**Guía de Diseño**](./docs/UI_GUIDE.md): Paleta de colores y componentes UI.
90-
* 🧪 [**Testing y CI**](./docs/TESTING_CI.md): Estrategia de pruebas y automatización.
57+
- 📘 [Arquitectura](./docs/ARCHITECTURE.md)
58+
- 📋 [Requerimientos Funcionales](./docs/FUNCTIONAL_REQUIREMENTS.md)
59+
- 🎨 [Guía de Diseño UI](./docs/UI_GUIDE.md)
60+
- 🧪 [Testing y CI](./docs/TESTING_CI.md)
9161

9262
---
93-
<p align="center">
94-
Diseñado con 💙 para la mejora de la Salud Pública | 2026
95-
</p>
63+
64+
<p align="center"><sub>© 2026 MediTrack — Diseñado con 💙 para la Salud Pública</sub></p>

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ services:
1212
- DB_NAME=${DB_NAME}
1313
- DB_USER=${DB_USER}
1414
- DB_PASSWORD=${DB_PASSWORD}
15+
- TZ=America/Lima
1516
command: >
1617
/bin/bash -c "
1718
/opt/mssql/bin/sqlservr &
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.utp.meditrackapp.core.util;
2+
3+
import java.time.LocalDateTime;
4+
import java.time.ZoneId;
5+
6+
public final class DateTimeProvider {
7+
8+
private static final ZoneId LIMA_ZONE = ZoneId.of("America/Lima");
9+
10+
private DateTimeProvider() {}
11+
12+
public static LocalDateTime now() {
13+
return LocalDateTime.now(LIMA_ZONE);
14+
}
15+
}

src/main/java/com/utp/meditrackapp/domain/entities/Sede.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.utp.meditrackapp.domain.entities;
22

3+
import com.utp.meditrackapp.core.util.DateTimeProvider;
4+
35
import java.time.LocalDateTime;
46

57
/**
@@ -65,7 +67,7 @@ public void toggleEstado() {
6567
public void bloquear(String motivo) {
6668
this.isBloqueada = 1;
6769
this.motivoBloqueo = motivo;
68-
this.fechaBloqueo = LocalDateTime.now();
70+
this.fechaBloqueo = DateTimeProvider.now();
6971
}
7072

7173
/**

src/main/java/com/utp/meditrackapp/features/attentions/ui/AtencionController.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,11 @@ protected void onBuscarAtenciones() {
292292
showAlert(Alert.AlertType.INFORMATION, "Sin resultados", "No se encontraron pacientes con: " + query);
293293
return;
294294
}
295+
if (pacientes.size() >= 5) {
296+
showAlert(Alert.AlertType.INFORMATION, "Búsqueda limitada",
297+
"Se mostraron las primeras 5 coincidencias para \"" + query + "\".\n"
298+
+ "Sea más específico (use DNI o nombre completo) para refinar.");
299+
}
295300
if (pacientes.size() == 1) {
296301
cargarAtencionesPaciente(pacientes.get(0));
297302
} else {
@@ -312,8 +317,11 @@ private Paciente mostrarSelectorPaciente(List<Paciente> pacientes) {
312317
.collect(Collectors.toList());
313318
ChoiceDialog<String> dialog = new ChoiceDialog<>(opciones.get(0), opciones);
314319
dialog.setTitle("Seleccionar Paciente");
315-
dialog.setHeaderText("Se encontraron múltiples pacientes:");
316-
dialog.setContentText("Seleccione:");
320+
String hint = pacientes.size() >= 5
321+
? "Se encontraron múltiples pacientes (máx. 5 coincidencias mostradas)."
322+
: "Se encontraron " + pacientes.size() + " pacientes:";
323+
dialog.setHeaderText(hint);
324+
dialog.setContentText("Seleccione uno:");
317325
dialog.initModality(javafx.stage.Modality.APPLICATION_MODAL);
318326
dialog.initOwner(rootPane.getScene().getWindow());
319327
Optional<String> result = dialog.showAndWait();
@@ -507,6 +515,11 @@ protected void onBuscarPacienteDisp() {
507515
showAlert(Alert.AlertType.INFORMATION, "Sin resultados", "No se encontraron pacientes con: " + query);
508516
return;
509517
}
518+
if (pacientes.size() >= 5) {
519+
showAlert(Alert.AlertType.INFORMATION, "Búsqueda limitada",
520+
"Se mostraron las primeras 5 coincidencias para \"" + query + "\".\n"
521+
+ "Sea más específico (use DNI o nombre completo) para refinar.");
522+
}
510523
if (pacientes.size() == 1) {
511524
selectPacienteDisp(pacientes.get(0));
512525
} else {

src/main/java/com/utp/meditrackapp/features/users/ui/UsuarioController.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,8 @@ private void setupActionsColumn() {
443443
btnDelete.setMinWidth(26);
444444
btnDelete.setMaxWidth(26);
445445
btnDelete.setOnAction(e -> handleDeleteUser(getTableView().getItems().get(getIndex())));
446+
447+
box.getStyleClass().add("actions-cell");
446448
}
447449

448450
@Override

src/main/java/com/utp/meditrackapp/infrastructure/persistence/jdbc/JdbcAtencionRepository.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.utp.meditrackapp.core.config.DatabaseConfig;
44
import com.utp.meditrackapp.core.models.enums.EntidadPrefix;
5+
import com.utp.meditrackapp.core.util.DateTimeProvider;
56
import com.utp.meditrackapp.core.util.IdGenerator;
67
import com.utp.meditrackapp.core.validation.SedeAccessValidator;
78
import com.utp.meditrackapp.domain.entities.Atencion;
@@ -31,14 +32,15 @@ public Atencion save(Connection conn, Atencion atencion) {
3132
atencion.setId(IdGenerator.generateSedeDependentId(conn, "atenciones", EntidadPrefix.ATENCION, atencion.getSedeId(), 6));
3233
}
3334

34-
String sql = "INSERT INTO atenciones (id, sede_id, paciente_id, usuario_id, numero_receta, medico, fecha_atencion) VALUES (?, ?, ?, ?, ?, ?, GETDATE())";
35+
String sql = "INSERT INTO atenciones (id, sede_id, paciente_id, usuario_id, numero_receta, medico, fecha_atencion) VALUES (?, ?, ?, ?, ?, ?, ?)";
3536
try (PreparedStatement ps = conn.prepareStatement(sql)) {
3637
ps.setString(1, atencion.getId());
3738
ps.setString(2, atencion.getSedeId());
3839
ps.setString(3, atencion.getPacienteId());
3940
ps.setString(4, atencion.getUsuarioId());
4041
ps.setString(5, atencion.getNumeroReceta());
4142
ps.setString(6, atencion.getMedico());
43+
ps.setTimestamp(7, Timestamp.valueOf(DateTimeProvider.now()));
4244
ps.executeUpdate();
4345
}
4446
} catch (SQLException e) {

src/main/java/com/utp/meditrackapp/infrastructure/persistence/jdbc/JdbcDashboardRepository.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.utp.meditrackapp.infrastructure.persistence.jdbc;
22

33
import com.utp.meditrackapp.core.config.DatabaseConfig;
4+
import com.utp.meditrackapp.core.util.DateTimeProvider;
45
import com.utp.meditrackapp.core.validation.SedeAccessValidator;
56
import com.utp.meditrackapp.domain.ports.out.DashboardRepository;
67

@@ -56,7 +57,7 @@ public int getLotesPorVencerCount(int dias) {
5657
String sedeId = SedeAccessValidator.getSedeParaConsulta();
5758

5859
StringBuilder sql = new StringBuilder(
59-
"SELECT COUNT(*) as total FROM lotes WHERE fecha_vencimiento <= DATEADD(day, ?, GETDATE()) AND fecha_vencimiento >= GETDATE()"
60+
"SELECT COUNT(*) as total FROM lotes WHERE fecha_vencimiento <= DATEADD(day, ?, ?) AND fecha_vencimiento >= ?"
6061
);
6162

6263
if (sedeId != null) {
@@ -65,8 +66,11 @@ public int getLotesPorVencerCount(int dias) {
6566

6667
try (Connection conn = dbConfig.getConnection();
6768
PreparedStatement ps = conn.prepareStatement(sql.toString())) {
69+
java.sql.Timestamp now = Timestamp.valueOf(DateTimeProvider.now());
6870
int i = 1;
6971
ps.setInt(i++, dias);
72+
ps.setTimestamp(i++, now);
73+
ps.setTimestamp(i++, now);
7074
if (sedeId != null) {
7175
ps.setString(i++, sedeId);
7276
}
@@ -140,7 +144,7 @@ public int getVolumenMovimientos(int dias) {
140144
String sedeId = SedeAccessValidator.getSedeParaConsulta();
141145

142146
StringBuilder sql = new StringBuilder(
143-
"SELECT COUNT(*) as total FROM movimientos WHERE fecha_registro >= DATEADD(day, -?, GETDATE())"
147+
"SELECT COUNT(*) as total FROM movimientos WHERE fecha_registro >= DATEADD(day, -?, ?)"
144148
);
145149

146150
if (sedeId != null) {
@@ -151,6 +155,7 @@ public int getVolumenMovimientos(int dias) {
151155
PreparedStatement ps = conn.prepareStatement(sql.toString())) {
152156
int i = 1;
153157
ps.setInt(i++, dias);
158+
ps.setTimestamp(i++, Timestamp.valueOf(DateTimeProvider.now()));
154159
if (sedeId != null) {
155160
ps.setString(i++, sedeId);
156161
}
@@ -173,7 +178,7 @@ public List<Map<String, Object>> getTendenciaInventario(int meses) {
173178
" SUM(CASE WHEN m.tipo_id = 'MOV-T-01' THEN m.cantidad ELSE 0 END) - " +
174179
" SUM(CASE WHEN m.tipo_id = 'MOV-T-02' THEN m.cantidad ELSE 0 END) as total " +
175180
"FROM movimientos m " +
176-
"WHERE m.fecha_registro >= DATEADD(month, -?, GETDATE()) "
181+
"WHERE m.fecha_registro >= DATEADD(month, -?, ?) "
177182
);
178183

179184
if (sedeId != null) {
@@ -186,6 +191,7 @@ public List<Map<String, Object>> getTendenciaInventario(int meses) {
186191
PreparedStatement ps = conn.prepareStatement(sql.toString())) {
187192
int i = 1;
188193
ps.setInt(i++, meses);
194+
ps.setTimestamp(i++, Timestamp.valueOf(DateTimeProvider.now()));
189195
if (sedeId != null) {
190196
ps.setString(i++, sedeId);
191197
}

src/main/java/com/utp/meditrackapp/infrastructure/persistence/jdbc/JdbcMovimientoRepository.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.utp.meditrackapp.core.config.DatabaseConfig;
44
import com.utp.meditrackapp.core.models.enums.EntidadPrefix;
5+
import com.utp.meditrackapp.core.util.DateTimeProvider;
56
import com.utp.meditrackapp.core.util.IdGenerator;
67
import com.utp.meditrackapp.core.validation.SedeAccessValidator;
78
import com.utp.meditrackapp.domain.entities.Movimiento;
@@ -31,7 +32,7 @@ public Movimiento save(Connection conn, Movimiento movimiento) {
3132
}
3233

3334
String sql = "INSERT INTO movimientos (id, tipo_id, motivo_id, sede_id, usuario_id, lote_id, cantidad, observacion, fecha_registro) " +
34-
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, GETDATE())";
35+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
3536

3637
try (PreparedStatement ps = conn.prepareStatement(sql)) {
3738
ps.setString(1, movimiento.getId());
@@ -42,6 +43,7 @@ public Movimiento save(Connection conn, Movimiento movimiento) {
4243
ps.setString(6, movimiento.getLoteId());
4344
ps.setInt(7, movimiento.getCantidad());
4445
ps.setString(8, movimiento.getObservacion());
46+
ps.setTimestamp(9, Timestamp.valueOf(DateTimeProvider.now()));
4547
ps.executeUpdate();
4648
}
4749
} catch (SQLException e) {

0 commit comments

Comments
 (0)