Historial de canvis
Tot el que hem afegit a GESEM Planner
v29 · actual
v29 Actual
07 maig 2026
Parser iCal pro: RRULE + TZID + cache persistent · Sincronitzacions que mai més es perden
🔁 Events recurrents (RRULE + EXDATE)
- El parser ara expandeix events recurrents segons l'estàndard RFC 5545. Si un formador té "Reunió cada dilluns" al seu calendari, l'app ara bloqueja tots els dilluns (abans només el primer).
- Suport per a:
FREQ=DAILY/WEEKLY/MONTHLY/YEARLY,INTERVAL,COUNT,UNTIL,BYDAY=MO,WE,FR,BYMONTHDAY=15 - EXDATE respectat: si un usuari diu "tots els dilluns excepte el 18 de maig", l'app exclou correctament aquesta data
- Salvaguardes: horitzó de 18 mesos vista, màxim 400 ocurrències per event (evita bombes computacionals)
- Cobert amb 7 tests unitaris automàtics
🌍 Timezone correcte (UTC → Madrid)
- Events amb sufix
Z(UTC) ara es converteixen correctament a wall-clock de Europe/Madrid abans d'assignar la data - Cas concret cobert: un event Google a les 23:30Z de l'11 de maig es comptabilitza al dia 12 a Madrid (DST +2h), no al dia 11
- Usa
Intl.DateTimeFormatsense dependències externes
💾 Cache de calendari persistent · 3 capes de defensa
- Client (localStorage): la pàgina restaura les dades de sincronització abans del primer render, evitant el flicker de "pendent de sincronitzar"
- Servidor (SQLite): el cache d'iCal sobreviu reinicis del servei (
systemctl restart, deploy, crash) - Stale-while-error: si una sincronització falla per xarxa, l'app retorna dades antigues amb flag
staleen lloc de mostrar res - Els cards de formador ara mostren "fa N min" indicant quan es va sincronitzar per última vegada
🐛 Fix events all-day TRANSPARENT no detectats
- Causa: Google Calendar marca per defecte tots els events all-day com a
TRANSP:TRANSPARENT. El parser els ignorava per ser "no-blocking" segons la spec iCal - Conseqüència: un formador que posava "Vacances" o "Formació externa" en un dia sencer NO sortia com ocupat. Resultat: conflictes invisibles al planificar.
- Fix: all-day events compten com ocupats independentment del flag TRANSP (mantenint el comportament estricte només per events amb hora, on TRANSP:TRANSPARENT és un cas legítim)
- Impacte real: el calendari del Ferran Garola passava de detectar 2 events a detectar tots 5 ✓
v28 Minor
07 maig 2026
Edició completa d'agents · Preview pre-reserva · Auto-refresc /formadors · UX +
👤 Edició completa d'agents comercials
- Doble clic a qualsevol chip d'agent obre un modal d'edició
- Permet canviar nom + color i pujar foto pròpia (avatar circular 256×256, alta resolució retina)
- Tota la rodona de l'avatar és clicable per pujar foto · overlay fosc amb càmera al passar el ratolí
- En renomenar un agent, el sistema migra automàticament totes les reserves associades (propagació al servidor + memòria local)
- Endpoint nou
PUT /api/agents/:nomque detecta col·lisions i rebutja noms duplicats - Botó d'eliminar integrat al modal amb avís si hi ha reserves afectades
📧 Preview email abans de crear la reserva
- "Reservar + email (Comercial)" ara obre primer un preview editable del correu
- 4 accions disponibles: Cancel·lar · Copiar · Crear reserva sense enviar · Crear reserva i enviar
- Si l'usuari cancel·la, la reserva NO es crea (abans es creava sempre)
- L'editor permet retocar destinatari, assumpte i cos abans de l'enviament SMTP final
🎨 Reorganització del card de proposta
- Email formador i WhatsApp ara queden a l'esquerra del card (acció ràpida)
- Eliminat el botó "Reservar" simple (era confús amb "Reservar + email")
- L'únic botó d'acció ara és "Reservar + email (Comercial)", alineat a la dreta
🔄 Auto-refresc de calendaris a /formadors
- Cada vegada que entres a la pàgina de formadors, es força un re-fetch de tots els iCal connectats
- Toast resum quan acaba la sincronització (a part del cron diari de les 03:00)
- Garanteix que la pàgina sempre mostra dades fresques sense haver de clicar el botó de refresc manual
📐 Panell "Nova petició" més ample
- Amplada màxima del panell esquerre 420px → 540px (+28%)
- Més espai per als formularis sense haver de scrollejar lateralment ni truncar text llarg
📷 Imatges en alta resolució
- Bug: les fotos es desaven a 42×42 (formadors) i 84×84 (agents) — quedaven pixelades en pantalles retina
- Fix: processador d'imatge unificat
processAvatarImage():- Output 256×256 (suficient per a retina fins 128px de visualització)
- Cover crop centrat (manté aspect ratio · no deforma)
- Downsample en 2 passos amb smoothing high-quality
- JPEG q=0.92 si la font és JPG (3-5× més petit) · PNG si cal transparència
- Límit de mida pujat de 2MB → 20MB (body parser servidor: 32MB per cobrir el padding base64)
🐛 Fix botó "Restaurar esborrany"
- Causa: el banner tenia un
onclick="..."function"..."amb cometes dobles dins de cometes dobles, que trencaven el parser HTML - Fix: extret el codi a funcions netes
restorePeticioDraftWithToast()idiscardPeticioDraftWithToast()sense embolics de cometes - Ara el botó "Restaurar" recupera correctament tot el draft (camps, dies bloquejats, dates excloses, agent, formador preferit)
v27 Minor
07 maig 2026
IA real a /canvis · Suggeridor de formador · Google Calendar 2-way OAuth
🧠 IA real a la pàgina /canvis
- Substituïda la lògica heurística per crides reals al model Llama 3.3 70B via Groq (
suggestChangesalib/ai.js) - L'IA rep tot el context: reserva, motiu del canvi, dates ja ocupades per altres reserves GESEM, festius, formadors alternatius amb les seves especialitats i ratings
- Genera 3 propostes amb score, raonament detallat i color codificat (best / opt2 / opt3)
- Si l'IA falla per qualsevol motiu, fallback automàtic a la lògica heurística antiga (sense interrupció per l'usuari)
- Endpoint nou:
POST /api/ai/suggest-changes
🤖 Suggeridor automàtic del millor formador
- Botó nou "Suggerir IA" al costat del selector de formador a la pàgina
/peticio - L'IA analitza el client + curs + especialitat i puntua tots els formadors segons:
- Continuïtat amb el client (històric de cursos previs) → +25 punts
- Match d'especialitat exacta → +20 punts
- Disponibilitat declarada → +15/+5/-10
- Rating ≥4.5 → +10
- Marge econòmic òptim → +10/+5/-5
- Retorna el TOP 3 amb raonament curt + marge calculat per a cadascun
- Endpoint nou:
POST /api/ai/suggest-formador
🔗 Google Calendar 2-way sync (OAuth 2.0)
- Cada formador pot ara connectar el seu Google Calendar directament des del seu perfil amb un sol clic
- Quan es confirma una reserva, el sistema crea automàticament tots els events al calendari del formador (un per sessió, amb torn correcte i descripció completa)
- Els events porten extendedProperties per poder-los identificar i esborrar després si cal
- Refresh tokens persistits a SQLite: la connexió no caduca encara que l'access token sí
- Es manté la lectura iCal antiga com a fallback: ningú perd funcionalitat
- Endpoints nous:
/api/google/status,/auth/start,/callback,/connections,/disconnect,/sync-reserva - Mòdul nou:
lib/google.js· Configuració Cloud Console: cal afegirGOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRETiGOOGLE_REDIRECT_URIal.envdel servidor
v26 Minor
07 maig 2026
Cron de calendaris · Avatars d'agents · Fix manteniment
Cron diari de calendaris
- Cron al servidor 🌙 que cada nit a les 03:00 refetcha tots els iCal dels formadors i actualitza la cache (
refreshAllCalendarsCacheaserver.js) - Refresc on-demand al carregar Petició: en obrir
/peticio, es dispara un refresc en segon pla de tots els calendaris perquè la proposta es generi amb dades fresques - Endpoint manual
POST /api/admin/refresh-calendarsper disparar el refresc des de fora (per scripts, monitoring...)
Avatars dels agents comercials
- Si un agent té el mateix nom que un formador amb foto, ara el botó d'agent mostra la foto del formador en lloc de les inicials. Útil per a Jordi Llopart i altres que apareguin a tots dos llocs.
Fix mode manteniment
- Cause: el navegador cachejava la pàgina
/peticio, així que en activar el manteniment seguia mostrant l'app antiga. - Fix: totes les rutes HTML serveixen ara amb
Cache-Control: no-store, no-cache+ headersPragmaiExpires. La pàgina de manteniment també. - Resultat: al activar/desactivar el toggle a
/admin, la transició és instantània a la pestanya del navegador (només cal recarregar).
v25 Minor
06 maig 2026
Bulk delete · Mode manteniment · Email redissenyat
Selecció múltiple a Reserves
- Checkboxes a la taula de reserves cancel·lades / arxivades (VF)
- Capçalera amb "Seleccionar totes" i barra superior amb el comptador
- Botó 🗑 Eliminar seleccionades amb confirmació prèvia
- Eliminació en bloc — molt més ràpid per netejar històric
Mode manteniment
- Pàgina
/adminnova amb panell de configuració d'administrador (no apareix a la sidebar normal) - Toggle de manteniment: quan està actiu, tots els usuaris veuen
/maintenanceamb un missatge personalitzable /admini/api/admin/*queden sempre accessibles per poder desactivar el mode des de dins- API REST per llegir/escriure l'estat (
GET/POST /api/admin/maintenance) - L'estat es persisteix a SQLite (mateix store que la resta)
- Pàgina de manteniment amb auto-refresc cada 60s i logo SVG inline
- Resum de l'estat de SMTP i IA a
/admin
Email del formador redissenyat
- Logo SVG inline (no més caixa verda buida): el logo de stacked cards ara es renderitza correctament a Gmail, Outlook i la majoria de clients
- Bloc CALENDARI duplicat eliminat: la funció
stripCalendarBlock()detecta i treu el calendari del cos d'usuari per no aparèixer dues vegades (text + taula) - 3 cards de resum amb estadístiques (Sessions / Hores totals / Torn) en colors distintius (verd / blau / lila)
- Taula de sessions amb files alternades (zebra striping) per millor lectura
- Estructura semàntica amb
<table role="presentation">per màxima compatibilitat amb clients d'email antics - Preheader (text breu que es veu a la safata d'entrada al costat del subject)
v24 Major
06 maig 2026
Email del formador amb Acceptar/Declinar + auto-add al calendari (.ics)
Feature gran (#4)
- Botó "✨ Enviar amb confirmació" al modal d'email del formador. Envia un email HTML amb:
- Disseny polit (logo GESEM, taula de sessions amb dies de la setmana)
- Botó verd ✓ Acceptar les dates
- Botó vermell ✕ Declinar / proposar canvis
- Fitxer
.icsadjunt amb totes les sessions (auto-add al calendari amb un sol clic)
- Tokens HMAC signats (
lib/tokens.js): cada link de l'email és únic i caduca a 60 dies. Impossible que un atacant endevini reserves alienes. - Pàgina pública de resposta a
/r/:tokenamb 3 vistes:- View · mostra detalls + botons Accept/Decline si encara no ha respost
- Accept · "Gràcies!" + descarregar .ics
- Decline · form per al motiu
- Auto-actualització de la reserva: si formador accepta, l'estat passa a
confirmada. Si declina, queda registrat amb el motiu. - Notificació automàtica al comercial: quan formador accepta o declina, el comercial rep email amb la novetat.
- Generador
.ics(lib/ics.js): RFC 5545, suporta múltiples sessions com a VEVENTs separats.
UX millorada
- Sidebar plegable redissenyada: amaga TOT el text correctament, mostra tooltips a la dreta en hover (descobertes de la funcionalitat amb només icones), animació d'aparició suau, traducció automàtica de les tooltips quan canvies idioma.
Backend nou
POST /api/email/send-formador-confirm· envia HTML + .ics + genera tokenGET /r/:token· pàgina de resposta del formadorPOST /r/:token/decline· processa declinació amb motiuGET /r/:token/ics· descarrega el calendari un cop acceptat- Variables noves al
.env:TOKEN_SECRET(signatura HMAC),BASE_URL(URL pública del servidor)
⚠️ Limitació actual
- L'URL del servidor és
http://192.168.3.208:3001(LAN GESEM). Si un formador clica el botó des de fora de l'oficina, no podrà accedir-hi. Cal exposar via Cloudflare Tunnel, reverse proxy públic, o VPN. De moment funciona només dins de la xarxa GESEM.
v23 Minor
06 maig 2026
Wave 2 · Sidebar plegable, refresh calendaris, sol·licitar URL, save draft formador
Noves funcionalitats
- Sidebar plegable ◀ · Botó a l'appbar per col·lapsar la sidebar a només icones (60px) — més espai per a la pàgina. Estat persistent a
localStorage. - Refresc automàtic de calendaris 🔄 · En crear una nova reserva, el calendari del formador assignat s'invalida i es re-fetcha del seu iCal (en segon pla). La resta de formadors també s'actualitzen amb prioritat menor.
- Botó "✉️ Demanar URL calendari" al modal de formador · envia un email amb instruccions detallades (Google Calendar, Outlook, iCloud) perquè el formador comparteixi la seva URL iCal.
- Save draft del formulari formador · si omples mig formulari nou i tanques per accident, l'app et proposa restaurar-lo en obrir-lo de nou. Inclou foto, especialitats i tots els camps. Caduca als 7 dies.
Pendent (Wave 3)
- Vista calendari mensual del formador a la pàgina Petició (#3)
- Botons accept/decline a l'email del formador + auto-add al seu calendari (#4)
v22 Minor
06 maig 2026
Bugfixes + neteja UX (wave 1 de 13)
Bugs arreglats
- "undefined dies ocupats" a les targetes de formador:
calData[id]tenia dues representacions inconsistents. Unificat a{slots, fullDayDates}. - Disponibilitat irreal: ara, si el formador té calendari sincronitzat, el % es calcula de veritat (dies laborables ocupats vs lliures als propers 60 dies). Si no, fallback al càlcul antic.
- Email al formador incloïa dates no disponibles: ara filtra les ocupades del cos principal i les afegeix com a "ALERTA · caldrà alternatives".
- Email "De" incorrecte: 2 modals tenien
gestio@gesem.eshardcoded. Ara mostrencomunicacions@gesem.cat(coincideix amb el From SMTP real).
UX polish
- Eliminada la pàgina Dashboard:
/redirigeix ara a/peticio, i/dashboardredirigeix a/peticioper compatibilitat. - Tret botó "Copiar telèfon" de les targetes de formador (només queden Email i WhatsApp directe).
- Eliminar formador: nou botó al modal d'edició (només visible quan s'edita un existent), amb confirmació mostrant si té reserves actives.
- Eliminar reserva permanentment: nou botó 🗑 a la taula de Gestió per a reserves cancel·lades o arxivades (VF).
Pendent (Wave 2-3)
- Sidebar plegable / desplegable
- Botó "Sol·licitar URL calendari" al formador
- Save draft del formulari de formador
- Refresc automàtic dels calendaris al crear petició
- Vista calendari mensual del formador a Petició
- Botons accept/decline a l'email del formador + auto-add al seu calendari
v21 Major
06 maig 2026
IA real al parser d'emails (Groq + Llama 3.3)
IA real (no més heurístiques)
- Mòdul
lib/ai.js🤖 que crida l'API de Groq (compatible OpenAI) amb Llama 3.3 70B - Substitueix les regex i keyword matching anteriors a la pàgina Entrades → Email comercial
- Precisió esperada: ~95% en emails complexos (vs ~30% amb heurística)
- Detecta dates relatives ("passada la Setmana Santa"), sinònims, modalitats híbrides, agents implícits
- Retorna "notes" amb què és ambigu o què falta a l'email — visible com a banner groc al resultat
- Fallback automàtic a heurística si l'API falla (sense crash)
Endpoints nous
GET /api/ai/status— comprova si l'IA està configurada (sense exposar la key)POST /api/ai/test— verifica connexió a Groq sense gastar quota d'inferènciaPOST /api/ai/parse-email— analitza un email i retorna els camps estructurats + temps + model utilitzat
UX
- Header del resultat indica model usat i temps de resposta (~500-1000ms via Groq)
- Banner groc "💡 Notes IA" quan l'IA detecta ambigüitats
- Confiança real (0-100) calculada per l'IA segons claredat del email
Cost
- 0 € · Groq free tier inclou 14.400 req/dia (= 600/hora)
- Velocitat: ~500 tokens/s (10× més ràpid que altres APIs)
- Privadesa: emails es processen al cloud de Groq
v20 Major
06 maig 2026
Enviament real d'emails (SMTP) + deploy a producció
Sistema d'emails real
- Mòdul SMTP 📤 amb
nodemailer: substitueix el "copiar al portapapers" per enviament directe - Configuració via
.env(mai al codi committat) carregada per systemd ambEnvironmentFile - Connexió segura SSL al port 465 a
mail.gesem.cat - 3 endpoints nous:
GET /api/email/status— comprova si està configurat (sense exposar la contrasenya)POST /api/email/test— verifica la connexió SMTP sense enviarPOST /api/email/send— envia un email amb {to, subject, text/html, replyTo, cc, bcc}
- Botó 📤 Enviar afegit als 3 modals d'email (client, formador, pendents per agent) + funció
sendEmailViaSMTP()al frontend - Estat visual durant l'enviament: el botó passa a "Enviant..." i la toast mostra "✓ Email enviat a [destinatari]"
Deploy a producció
- App en producció a
http://192.168.3.208:3001(servidor Ubuntu intern de la xarxa GESEM) - Servei systemd amb auto-restart i hardening (ProtectSystem, NoNewPrivileges, ReadWritePaths)
- Backup diari programat amb cron a les 03:00
- Scripts de deploy reutilitzables:
deploy/setup-server.sh,deploy/gesem-planner.service - Workflow d'actualització: build ZIP local → SCP → unzip al servidor → restart
- Autenticació via SSH keys (Ed25519) — sense contrasenyes per a futures actualitzacions
Seguretat
.enval servidor amb permisos600(només l'usuari del procés).gitignoreexclou.envi.env.localper defecte- L'endpoint
/api/email/statusmai retorna la contrasenya en text pla (la mostra com***)
v19 Minor
04 maig 2026
Sistema d'idiomes (CA / ES) bilingüe
Internacionalització (i18n)
- Sistema d'i18n complet 🌐 amb suport per a català i castellà (
public/js/i18n.js, ~26 KB) - 180+ traduccions que cobreixen sidebar, appbar, formularis, filtres, tabs, KPIs, toasts i confirm dialogs
- Detecció automàtica de l'idioma del navegador a la primera visita
- Persistència a
localStorage— la preferència segueix entre sessions - Sense recarregar: el canvi d'idioma s'aplica instantàniament a tota la pàgina (event
langchange)
Selector d'idioma millorat
- Popover al peu de la sidebar amb les 2 opcions visibles (🇨🇦 Català · 🇪🇸 Español)
- L'idioma actiu es destaca amb fons emerald i check verd ✓
- Animació suau d'aparició (slide + fade)
- Es tanca al clicar fora o seleccionar opció
Cobertura per pàgina
- Petició: 44 atributs (formulari sencer · sections · placeholders · botons distribució)
- Canvis: 30 atributs (4 tipus de canvi · opcions · tabs)
- Entrades: 25 atributs (3 tabs · botons IA · arxiu)
- Formadors: 22 atributs (cerca · filtres · panells laterals)
- Gestió: 21 atributs (chips d'estat · ordenacions)
- Dashboard: 20 atributs (KPIs · panel titles · salutació)
- Toasts a app.js: 12 missatges dinàmics traduïts amb fallback segur
v18 Major
04 maig 2026
Dashboard, navegació i pàgina 404
Noves pàgines
- Dashboard 📊 com a pàgina d'inici (
/dashboard): KPIs (reserves actives, pendents, facturat mes, hores, formadors), donut de distribució per estat, properes 7 sessions, top formadors per hores, top clients per facturat €, alertes automàtiques - Pàgina 404 personalitzada amb logo i enllaços a totes les seccions (substitueix
Cannot GET)
Millores de navegació
/ara redirigeix a /dashboard (era/peticio)- Logo de la sidebar és clicable → torna al dashboard
- Mode fosc i Changelog al peu de la sidebar (sota usuari)
- Appbar simplificat: botó únic ⌨️ que obre les dreceres de teclat
- Headers
Cache-Control: no-storeal 404 (impedeixen cachejar pàgines inexistents)
Fixes
- Estructura HTML del peu de la sidebar: eliminats
<span>i</div>orfes que trencaven el layout
v17 Minor
04 maig 2026
Polish UX i optimitzacions
Noves funcionalitats
- Mode fosc 🌙 amb toggle persistent i detecció automàtica del SO
- Pàgina 404 personalitzada amb enllaços ràpids a totes les seccions
- Pàgina
/changelog(aquesta) amb l'historial complet de versions - Save draft automàtic del formulari Petició a
localStorage(banner per restaurar) - Estats buits informatius a Reserves, Canvis i Arxiu (amb CTA)
- Diàleg de confirmació polit per accions destructives (cancel·lar reserva, eliminar...)
Millores
- Endpoint
/api/bootstrap: 4 fetches → 1 sol (latència reduïda) - Menú d'opcions a l'app bar (substitueix el toggle de tema de la sidebar)
v16 Minor
04 maig 2026
Performance i mobile
Noves funcionalitats
- Mobile responsive: sidebar com a drawer lliscant amb backdrop
- Botó hamburger a l'app bar (només mòbil) per obrir/tancar la sidebar
- Keyboard shortcuts estil Linear:
Ctrl+Kpalette,G+P/R/C/F/Enavegació,Nnova petició,?ajuda - Compressió gzip al servidor:
app.jsde 144 KB → 37 KB a la xarxa
Neteja
- Eliminats
index.html(209 KB),index.html.bakilogos.html - Alliberats 432 KB del repositori
v15 Patch
04 maig 2026
Identitat de marca
Disseny
- Logo nou: stacked cards en degradat verd (representa reserves apilades)
- Favicon SVG escalable a totes les pàgines
apple-touch-iconitheme-colorper a iOS i Chrome mòbil
v14 Major
04 maig 2026
Pàgines separades amb URLs reals
Arquitectura
- SPA monolítica → 5 pàgines independents (
/peticio,/gestio,/canvis,/formadors,/entrades) - CSS extret a
public/css/styles.css - JS extret a
public/js/app.jsamb routing automàtic - Botó "Enrere" del navegador funciona, URLs es poden compartir/bookmark
- Rutes Express netes, redirecció
/→/peticio
v13 Major
04 maig 2026
Redisseny estil Notion / Stripe
Disseny
- Sidebar lateral amb icones (substitueix top nav)
- Tipografia Inter (Google Fonts)
- Paleta off-white càlid + emerald primary
- Cards amb shadow subtil + hover elevat
- Botons, badges, pills i inputs refinats
- Backdrop blur als modals
- Scrollbars estilats fins
v12 Major
04 maig 2026
Migració a SQLite + sistema de backups
Backend
- SQLite (
better-sqlite3) en mode WAL substitueix els fitxers JSON - Atomicitat: impossible corrompre dades per escriptures concurrents
- Script
scripts/migrate.jsper migrar JSON → DB
Backups
- Script
scripts/backup.jsamb online backup (segur amb el servidor actiu) - Tasca programada Windows diària a les 03:00
- Retenció: últims 14 backups (esborrat automàtic)
- S'executa també amb bateria, es desperta si l'ordinador estava apagat
v11 Patch
abans de l'auditoria
Versió original (punt de partida)
Capacitats existents
- 5 pàgines: Petició, Gestió, Canvis, Formadors, Entrades
- Integració iCal real per saber disponibilitat de formadors
- Scoring sofisticat (especialitat, càrrega, disponibilitat, marge)
- Càlcul automàtic de dates (festius, exclusions, distribució)
- Plantilles d'email per a clients i formadors (copia al portapapers)
- Persistència en fitxers JSON