Compare commits

...

5 Commits

Author SHA1 Message Date
Marco Lanzara
d8f22f81da 🚀 Release v1.0.52
- Tipo: patch
- Database backup: database-backups/vigilanzaturni_v1.0.52_20251024_145327.sql.gz
- Data: 2025-10-24 14:53:47
2025-10-24 14:53:47 +00:00
marco370
b1ba5b91c0 Improve system description and database schema details for security shift management
Update replit.md to include a more detailed overview of the VigilanzaTurni system and refine the database schema description.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: e0b5b11c-5b75-4389-8ea9-5f3cd9332f88
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/e0b5b11c-5b75-4389-8ea9-5f3cd9332f88/HO4k2VG
2025-10-24 14:27:28 +00:00
marco370
5830d08c55 Improve user interface for managing guard schedules and shifts
Add new UI components and update existing ones to enhance the user experience for scheduling and managing guard shifts.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: e0b5b11c-5b75-4389-8ea9-5f3cd9332f88
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/e0b5b11c-5b75-4389-8ea9-5f3cd9332f88/HO4k2VG
2025-10-24 14:26:28 +00:00
marco370
468d6477eb Adjust time display to show correct local times for users
Update time formatting logic to consistently display scheduled times in the 'Europe/Rome' timezone, resolving inconsistencies caused by UTC storage.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: e0b5b11c-5b75-4389-8ea9-5f3cd9332f88
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/e0b5b11c-5b75-4389-8ea9-5f3cd9332f88/HO4k2VG
2025-10-24 14:22:57 +00:00
marco370
74bd542309 Fix incorrect shift assignments due to timezone conversion errors
Address timezone discrepancies by implementing a function to calculate the correct offset for Europe/Rome, ensuring accurate conversion of shift start and end times from local time to UTC for assignment processing.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: e0b5b11c-5b75-4389-8ea9-5f3cd9332f88
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/e0b5b11c-5b75-4389-8ea9-5f3cd9332f88/7VmWFMu
2025-10-24 13:50:00 +00:00
8 changed files with 147 additions and 126 deletions

View File

@ -85,14 +85,14 @@ interface GeneralPlanningResponse {
}
// Helper per formattare orario in formato italiano 24h (HH:MM)
// IMPORTANTE: usa timeZone UTC per evitare shift di +2 ore
// IMPORTANTE: Gli orari nel DB sono UTC, visualizzali in timezone Europe/Rome
const formatTime = (dateString: string) => {
const date = new Date(dateString);
return date.toLocaleTimeString("it-IT", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "UTC" // Evita conversione timezone locale (+2h in Italia)
timeZone: "Europe/Rome" // Converti da UTC a Italy time
});
};

View File

@ -159,21 +159,31 @@ export default function MyShiftsFixed() {
</div>
) : (
dayShifts.map((shift) => {
// Parsing sicuro orari
// Parsing sicuro orari (DB in UTC → visualizza in Europe/Rome)
let startTime = "N/A";
let endTime = "N/A";
if (shift.plannedStartTime) {
const parsedStart = parseISO(shift.plannedStartTime);
const parsedStart = new Date(shift.plannedStartTime);
if (isValid(parsedStart)) {
startTime = format(parsedStart, "HH:mm");
startTime = parsedStart.toLocaleTimeString("it-IT", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "Europe/Rome"
});
}
}
if (shift.plannedEndTime) {
const parsedEnd = parseISO(shift.plannedEndTime);
const parsedEnd = new Date(shift.plannedEndTime);
if (isValid(parsedEnd)) {
endTime = format(parsedEnd, "HH:mm");
endTime = parsedEnd.toLocaleTimeString("it-IT", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "Europe/Rome"
});
}
}

View File

@ -201,21 +201,31 @@ export default function SitePlanningView() {
</div>
) : (
dayGuards.map((guard, index) => {
// Parsing sicuro orari
// Parsing sicuro orari (DB in UTC → visualizza in Europe/Rome)
let startTime = "N/A";
let endTime = "N/A";
if (guard.plannedStartTime) {
const parsedStart = parseISO(guard.plannedStartTime);
const parsedStart = new Date(guard.plannedStartTime);
if (isValid(parsedStart)) {
startTime = format(parsedStart, "HH:mm");
startTime = parsedStart.toLocaleTimeString("it-IT", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "Europe/Rome"
});
}
}
if (guard.plannedEndTime) {
const parsedEnd = parseISO(guard.plannedEndTime);
const parsedEnd = new Date(guard.plannedEndTime);
if (isValid(parsedEnd)) {
endTime = format(parsedEnd, "HH:mm");
endTime = parsedEnd.toLocaleTimeString("it-IT", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "Europe/Rome"
});
}
}

118
replit.md
View File

@ -1,7 +1,7 @@
# VigilanzaTurni - Sistema Gestione Turni per Istituti di Vigilanza
## Overview
VigilanzaTurni is a professional 24/7 shift management system for security companies, designed to streamline operations and enhance efficiency. It supports multi-role authentication (Admin, Coordinator, Guard, Client) and multi-location operations, managing over 250 security personnel across different branches (Roccapiemonte, Milano, Roma). Key capabilities include comprehensive guard and site management, 24/7 shift planning, a live operational dashboard with KPIs, reporting for worked hours, and a notification system.
VigilanzaTurni is a professional 24/7 shift management system for security companies, designed to streamline operations and enhance efficiency. It supports multi-role authentication (Admin, Coordinator, Guard, Client) and multi-location operations, managing over 250 security personnel across different branches. Key capabilities include comprehensive guard and site management, 24/7 shift planning, a live operational dashboard with KPIs, reporting for worked hours, and a notification system. The project aims to provide a robust, scalable solution for security companies, improving operational control and resource allocation.
## User Preferences
- Interfaccia in italiano
@ -29,20 +29,21 @@ VigilanzaTurni is a professional 24/7 shift management system for security compa
- **Componenti**: Shadcn UI with an operational design.
### Database Schema
The database includes tables for `users`, `guards`, `certifications`, `sites`, `shifts`, `shift_assignments`, `notifications`, `customers`, `service_types`, and various tables for advanced scheduling and constraints (`guard_constraints`, `site_preferences`, `contract_parameters`, `training_courses`, `holidays`, `holiday_assignments`, `absences`, `absence_affected_shifts`). Service types include specialized parameters like `fixedPostHours`, `patrolPassages`, `inspectionFrequency`, and `responseTimeMinutes`. Sites support multi-location (`location` field), contract management (`contractReference`, `contractStartDate`, `contractEndDate`), and service type association (`serviceTypeId` FK to `service_types.id`).
The database supports managing users, guards, certifications, sites, shifts, shift assignments, notifications, customers, and service types. It also includes tables for advanced scheduling constraints such as guard constraints, site preferences, contract parameters, training courses, holidays, and absences. Service types include specialized parameters like `fixedPostHours`, `patrolPassages`, `inspectionFrequency`, and `responseTimeMinutes`.
### Core Features
- **Multi-Sede Operational Planning**: Location-first approach for shift planning, filtering sites, guards, and vehicles by selected branch.
- **Service Type Classification**: Service types are classified as "fisso" (fixed posts) or "mobile" (patrols, inspections) to route sites to appropriate planning modules (Planning Fissi, Planning Mobile).
- **Planning Fissi**: Weekly planning grid showing all sites with active contracts, allowing direct shift creation for multiple days with guard availability checks.
- **Planning Mobile**: Dedicated guard-centric interface for mobile services, displaying guard availability and hours for mobile-classified sites. Includes interactive Leaflet map showing sites with GPS coordinates and automatic re-centering based on selected location.
- **Customer Management**: Full CRUD operations for customers with comprehensive details.
- **Customer-Centric Reports**: New reports aggregating data by customer, replacing site-based billing, with specific counters for fixed posts (hours), patrols (passages), inspections, and interventions. CSV export is supported.
- **Multi-Sede Operational Planning**: Location-first approach for shift planning, filtering resources by selected branch.
- **Service Type Classification**: Classifies services as "fisso" (fixed posts) or "mobile" (patrols, inspections) to route sites to appropriate planning modules.
- **Planning Fissi**: Weekly planning grid for fixed posts, enabling shift creation with guard availability checks.
- **Planning Mobile**: Guard-centric interface for mobile services, displaying guard availability and hours, with an interactive Leaflet map showing sites.
- **Customer Management**: Full CRUD operations for customer details and customer-centric reporting with CSV export.
- **Dashboard Operativa**: Live KPIs and real-time shift status.
- **Gestione Guardie**: Complete profiles with skill matrix, certification management, and unique badge numbers.
- **Gestione Siti/Commesse**: Sites are associated with service types from the `service_types` table via `serviceTypeId` (FK). Service types are managed in the "Tipologie Servizi" page and include specialized parameters. Sites include service schedule, contract management, location assignment, and customer assignment (`customerId` FK to `customers.id`).
- **Gestione Guardie**: Complete profiles with skill matrix, certification management, and badge numbers.
- **Gestione Siti/Commesse**: Sites are associated with service types, including schedule, contract management, and location assignment. Automatic geocoding is supported.
- **Pianificazione Turni**: 24/7 calendar, manual guard assignment, basic constraints, and shift statuses.
- **Advanced Planning**: Management of guard constraints, site preferences, contract parameters, training courses, holidays, and absences.
- **Advanced Planning**: Management of guard constraints, site preferences, contract parameters, training courses, holidays, and absences. Includes patrol route persistence and exclusivity constraints between fixed and mobile shifts.
- **Guard Planning Views**: Dedicated views for guards to see their fixed post shifts and mobile patrol routes.
- **Site Planning View**: Coordinators can view all guards assigned to a specific site over a week.
### User Roles
- **Admin**: Full access.
@ -51,96 +52,7 @@ The database includes tables for `users`, `guards`, `certifications`, `sites`, `
- **Client**: View assigned sites, service reporting, KPIs.
### Critical Date/Timezone Handling
To prevent timezone-related bugs, especially when assigning shifts, dates should always be constructed from components (`new Date(year, month-1, day)`) and never parsed from ISO strings directly using `parseISO()` or `new Date(string ISO)`. Date validation should use regex instead of `parseISO()`.
## Recent Changes (October 2025)
### Automatic Geocoding Integration (October 23, 2025)
- **Issue**: Users had to manually find and enter GPS coordinates for sites, which was time-consuming and error-prone
- **Solution**:
- **Backend (`server/routes.ts`)**:
- Created POST `/api/geocode` endpoint integrating Nominatim API (OpenStreetMap)
- Implemented in-memory rate limiter enforcing 1 request/second to comply with Nominatim usage policy
- Added compliant User-Agent header: "VigilanzaTurni/1.0 (Security Shift Management System; contact: support@vigilanzaturni.it)"
- Returns latitude, longitude, displayName, and full address object
- **Frontend (`client/src/pages/sites.tsx`)**:
- Added "📍 Trova Coordinate" button in both create and edit site dialogs
- Button auto-populates latitude/longitude fields from address
- Disabled state when address missing or geocoding in progress
- Toast notifications for success (with found address) and error handling
- Dedicated section with bg-muted/50 highlighting for GPS coordinates
- **Impact**: Users can now automatically geocode site addresses with a single click, ensuring accurate GPS positioning for Planning Mobile map without manual coordinate lookup
### Planning Mobile - Leaflet Map Integration (October 23, 2025)
- **Issue**: Planning Mobile page had errors in backend endpoints and lacked interactive map functionality
- **Solution**:
- **Backend Fixes**:
- Removed non-existent fields (`city` from sites, `isActive` from guards) from queries
- Changed `innerJoin` to `leftJoin` for service types to handle sites without serviceTypeId
- Fixed `orderBy` syntax (single field instead of multiple)
- Added `hasDriverLicense` filter for guards (mobile services require driving)
- **Map Integration**:
- Implemented Leaflet + react-leaflet with OpenStreetMap tiles (100% free, no API key)
- Interactive markers for sites with GPS coordinates and classification="mobile"
- Popup details showing site name, address, and service type
- Automatic re-centering via `key={selectedLocation}` forcing MapContainer remount
- Graceful fallback for sites without coordinates
- **Impact**: Planning Mobile now fully functional with interactive map for patrol/inspection route planning
### Planning Mobile - Interactive Features (October 23, 2025)
- **Issue**: Planning Mobile needed interactive map controls and patrol route sequencing workflow
- **Critical Bug Fix - Geocoding**:
- **Problem**: Toast showed "Indirizzo: undefined" when geocoding sites
- **Root Cause**: `apiRequest()` returns Response object, not parsed JSON
- **Fix**: Added `const result = await response.json()` after apiRequest in both handleGeocode() and handleGeocodeEdit()
- **Impact**: Geocoding now correctly displays full address from Nominatim (e.g., "Via Tiburtina, Roma, Lazio, Italia")
- **New Features**:
- **Zoom-to-Site**:
- MapController component with useMap() hook for programmatic map control
- Navigation button on each site card triggers flyTo() animation (zoom level 16)
- Toast feedback: "Mappa centrata - Visualizzazione di [nome sito]"
- **Guard Assignment**:
- "Assegna Guardia" button on site cards
- Validates guard selection from dropdown
- Toast feedback: "[Sito] assegnato a [Nome Cognome Guardia]"
- **Patrol Route Sequencing**:
- Click markers on map to build patrol route sequence
- Visual feedback: Green "Tappa N" badges on markers and site cards
- Dedicated UI section showing route in construction with numbered sequence
- Remove/clear/save route controls
- Auto-reset route when changing guard or location
- **Note**: Backend persistence (save to database) not yet implemented - marked as TODO for future development
- **Impact**: Operators can now interact with the map, assign guards to sites, and visually plan patrol routes by clicking sites in sequence
### Sites Form Fix - ServiceTypeId Integration (October 2025)
- **Issue**: Sites form used hardcoded `shiftType` enum values instead of dynamic service types from the database
- **Solution**:
- Changed Sites form to use `serviceTypeId` (FK to `service_types.id`) instead of deprecated `shiftType` field
- Added dynamic service type dropdown loading from `/api/service-types` endpoint
- Updated both create and edit forms to properly handle service type selection
- Card display now shows service type label from database instead of hardcoded labels
- **Impact**: Sites now correctly reference service types configured in "Tipologie Servizi" page, ensuring consistency across the system
### Advanced Planning System Complete (October 23, 2025)
- **Implementation**: Full planning system with guard views, exclusivity constraints, and database persistence
- **Features**:
- **Patrol Route Database Persistence**:
- Backend endpoints: GET/POST/PUT/DELETE `/api/patrol-routes`
- Database schema: `patrol_routes` table with `patrol_route_stops` for sequence
- Planning Mobile loads existing routes when guard selected, saves to DB
- Green markers on map for sites in current patrol route
- **Exclusivity Constraint (fisso/mobile)**:
- Validation in 3 backend endpoints: POST `/api/patrol-routes`, POST `/api/shift-assignments`, POST `/api/shifts/:shiftId/assignments`
- Guards cannot be assigned to both fixed posts and mobile patrols on same date
- Clear error messages when constraint violated
- **Guard Planning Views**:
- `/my-shifts-fixed`: Guards view their fixed post shifts with orari, dotazioni (armato, automezzo), location, sito
- `/my-shifts-mobile`: Guards view patrol routes with sequenced site list, addresses, vehicle assignment
- Backend endpoints: GET `/api/my-shifts/fixed`, GET `/api/my-shifts/mobile` with date range filters
- **Site Planning View**:
- `/site-planning-view`: Coordinators view all guards assigned to a site across a week
- Shows guard name, badge, orari, dotazioni for each assignment
- Backend endpoint: GET `/api/site-planning/:siteId` with date range filters
- **Impact**: Complete end-to-end planning system supporting both coordinator and guard roles with database-backed route planning and operational equipment tracking
The system handles timezone conversions for shift times, converting Italy local time from the frontend to UTC for database storage, and back to Italy local time for display, accounting for DST.
## External Dependencies
- **Replit Auth**: For OpenID Connect (OIDC) based authentication.
@ -151,5 +63,5 @@ To prevent timezone-related bugs, especially when assigning shifts, dates should
- **TanStack Query**: For data fetching and state management.
- **Wouter**: For client-side routing.
- **date-fns**: For date manipulation and formatting.
- **Leaflet**: Interactive map library with react-leaflet bindings and OpenStreetMap tiles (free).
- **Nominatim**: OpenStreetMap geocoding API for automatic address-to-coordinates conversion (free, rate limited to 1 req/sec).
- **Leaflet**: Interactive map library with react-leaflet bindings and OpenStreetMap tiles.
- **Nominatim**: OpenStreetMap geocoding API for automatic address-to-coordinates conversion.

View File

@ -10,6 +10,65 @@ import { differenceInDays, differenceInHours, differenceInMinutes, startOfWeek,
import { z } from "zod";
import { fromZodError } from "zod-validation-error";
/**
* Calcola l'offset di Europe/Rome rispetto a UTC per una specifica data/ora.
* Gestisce correttamente orari serali e transizioni DST.
* Italy: UTC+1 (ora solare inverno) / UTC+2 (ora legale estate)
* @param year, month (0-11), day, hour, minute
* @returns offset in ore (1 o 2)
*/
function getItalyTimezoneOffsetHours(year: number, month: number, day: number, hour: number, minute: number = 0): number {
// Crea timestamp UTC esatto per l'input Italy time
// Useremo formatToParts per ottenere tutti i componenti date/time
const utcTimestamp = Date.UTC(year, month, day, hour, minute, 0);
const utcDate = new Date(utcTimestamp);
// Ottieni tutti i componenti in Europe/Rome timezone
const formatter = new Intl.DateTimeFormat('en-US', {
timeZone: 'Europe/Rome',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false
});
const parts = formatter.formatToParts(utcDate);
const italyYear = parseInt(parts.find(p => p.type === 'year')?.value || '0');
const italyMonth = parseInt(parts.find(p => p.type === 'month')?.value || '0') - 1;
const italyDay = parseInt(parts.find(p => p.type === 'day')?.value || '0');
let italyHour = parseInt(parts.find(p => p.type === 'hour')?.value || '0');
const italyMinute = parseInt(parts.find(p => p.type === 'minute')?.value || '0');
// formatToParts restituisce hour=24 per mezzanotte (00:00 del giorno successivo)
// Il giorno è già stato incrementato automaticamente da formatToParts
// Normalizziamo solo l'ora: 24:00 → 00:00
const normalizedHour = italyHour === 24 ? 0 : italyHour;
// Crea timestamp Italy come se fosse UTC (per calcolare differenza)
const italyAsUtcTimestamp = Date.UTC(italyYear, italyMonth, italyDay, normalizedHour, italyMinute, 0);
// Calcola differenza in millisecondi e converti in ore
const offsetMs = italyAsUtcTimestamp - utcTimestamp;
const offsetHours = Math.round(offsetMs / (1000 * 60 * 60));
console.log("🕐 Offset calculation:", {
input: `${year}-${String(month+1).padStart(2,'0')}-${String(day).padStart(2,'0')} ${String(hour).padStart(2,'0')}:${String(minute).padStart(2,'0')}`,
utcTimestamp: utcDate.toISOString(),
italyComponents: { year: italyYear, month: italyMonth+1, day: italyDay, hour: italyHour, minute: italyMinute },
offsetHours
});
// Italy è sempre UTC+1 o UTC+2
if (offsetHours !== 1 && offsetHours !== 2) {
console.error("⚠️ Unexpected offset:", offsetHours, "- defaulting to UTC+1");
return 1;
}
return offsetHours;
}
// Determina quale sistema auth usare basandosi sull'ambiente
const USE_LOCAL_AUTH = process.env.DOMAIN === "vt.alfacom.it" || !process.env.REPLIT_DOMAINS;
@ -1283,6 +1342,15 @@ export async function registerRoutes(app: Express): Promise<Server> {
try {
const { siteId, date, guardId, startTime, durationHours, consecutiveDays = 1, vehicleId, force = false } = req.body;
// DEBUG: Log per capire il problema timezone
console.log("🔍 DEBUG assign-guard - Input ricevuto:", {
date,
startTime,
durationHours,
serverTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
serverOffset: new Date().getTimezoneOffset()
});
if (!siteId || !date || !guardId || !startTime || !durationHours) {
return res.status(400).json({
message: "Missing required fields: siteId, date, guardId, startTime, durationHours"
@ -1312,6 +1380,16 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
const [hours, minutes] = startTime.split(":").map(Number);
// CRITICAL: Gli orari dal frontend sono in fuso orario Europe/Rome (UTC+1 o UTC+2)
// Calcola offset corretto per convertire a UTC
const italyOffsetHours = getItalyTimezoneOffsetHours(year, month - 1, day, hours, minutes);
console.log("🕐 DEBUG Timezone setup:", {
inputDate: date,
inputTime: `${String(hours).padStart(2,'0')}:${String(minutes).padStart(2,'0')}`,
italyOffsetHours,
isDST: italyOffsetHours === 2
});
// Atomic transaction: create assignments for all consecutive days
const result = await db.transaction(async (tx) => {
const createdAssignments = [];
@ -1342,9 +1420,20 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
}
// Calculate planned start and end times in LOCAL timezone
const plannedStart = new Date(actualYear, actualMonth, actualDay, hours, minutes, 0, 0);
const plannedEnd = new Date(actualYear, actualMonth, actualDay, hours + durationHours, minutes, 0, 0);
// Calculate planned start and end times converting from Europe/Rome to UTC
// IMPORTANTE: l'utente inserisce orari in fuso orario Italia (Europe/Rome)
// Il server è in UTC, quindi dobbiamo convertire Italy time → UTC
// Formula: UTC time = Italy time - offset
// Esempio: 09:00 Italy (UTC+2) = 07:00 UTC
const plannedStart = new Date(Date.UTC(actualYear, actualMonth, actualDay, hours - italyOffsetHours, minutes, 0, 0));
const plannedEnd = new Date(Date.UTC(actualYear, actualMonth, actualDay, hours + durationHours - italyOffsetHours, minutes, 0, 0));
console.log("🕐 DEBUG Timestamp conversion:", {
inputTime: `${String(hours).padStart(2,'0')}:${String(minutes).padStart(2,'0')}`,
italyOffset: `UTC+${italyOffsetHours}`,
plannedStartUTC: plannedStart.toISOString(),
plannedEndUTC: plannedEnd.toISOString()
});
// Find or create shift for this site/date (full day boundaries in LOCAL timezone)
const dayStart = new Date(actualYear, actualMonth, actualDay, 0, 0, 0, 0);

View File

@ -1,7 +1,13 @@
{
"version": "1.0.51",
"lastUpdate": "2025-10-24T13:29:30.617Z",
"version": "1.0.52",
"lastUpdate": "2025-10-24T14:53:47.910Z",
"changelog": [
{
"version": "1.0.52",
"date": "2025-10-24",
"type": "patch",
"description": "Deployment automatico v1.0.52"
},
{
"version": "1.0.51",
"date": "2025-10-24",
@ -295,12 +301,6 @@
"date": "2025-10-17",
"type": "patch",
"description": "Deployment automatico v1.0.3"
},
{
"version": "1.0.2",
"date": "2025-10-17",
"type": "patch",
"description": "Deployment automatico v1.0.2"
}
]
}