diff --git a/app/models/ues.py b/app/models/ues.py
index 5c458620b32e7e27d7545b2c5a68641719c32da8..5ff4258b140c19dd15ae2dab1a64783be7828a3a 100644
--- a/app/models/ues.py
+++ b/app/models/ues.py
@@ -184,11 +184,8 @@ class UniteEns(models.ScoDocModel):
         """
         return 1 if self.semestre_idx is None else (self.semestre_idx - 1) // 2 + 1
 
-    def is_locked(self):
-        """True if UE should not be modified
-        (contains modules used in a locked formsemestre)
-        """
-        # XXX todo : à ré-écrire avec SQLAlchemy
+    def is_locked(self) -> tuple[bool, str]:
+        """True if UE should not be modified"""
         from app.scodoc import sco_edit_ue
 
         return sco_edit_ue.ue_is_locked(self.id)
diff --git a/app/scodoc/sco_edit_apc.py b/app/scodoc/sco_edit_apc.py
index 805fa1fb52302400846671131a8cb2bcf0a0edba..8943b2342171829f38c6cdabb0352d46f2f76e0c 100644
--- a/app/scodoc/sco_edit_apc.py
+++ b/app/scodoc/sco_edit_apc.py
@@ -215,9 +215,11 @@ def html_ue_infos(ue):
         and ue.modules.count() == 0
         and ue.matieres.count() == 0
     )
+    titre = f"UE {ue.acronyme} {ue.titre or ''}"
     return render_template(
         "pn/ue_infos.j2",
-        titre=f"UE {ue.acronyme} {ue.titre or ''}",
+        title=titre,  # titre html de la page
+        titre=titre,  # dans la page
         ue=ue,
         formsemestres=formsemestres,
         nb_etuds_valid_ue=nb_etuds_valid_ue,
diff --git a/app/scodoc/sco_edit_ue.py b/app/scodoc/sco_edit_ue.py
index 40989539e2d2a1297565b029386f398ebd2da32a..5dc218e9cce42d7c762fc1b92c4ae2e22a63f91d 100644
--- a/app/scodoc/sco_edit_ue.py
+++ b/app/scodoc/sco_edit_ue.py
@@ -44,6 +44,8 @@ from app.models import (
     FormSemestreUEComputationExpr,
     FormSemestreUECoef,
     Matiere,
+    Module,
+    ModuleImpl,
     UniteEns,
 )
 from app.models import ApcValidationRCUE, ScolarFormSemestreValidation, ScolarEvent
@@ -59,7 +61,6 @@ from app.scodoc.sco_exceptions import (
     ScoNonEmptyFormationObject,
 )
 
-from app.scodoc import html_sco_header
 from app.scodoc import codes_cursus
 from app.scodoc import sco_edit_apc
 from app.scodoc import sco_edit_matiere
@@ -1066,10 +1067,12 @@ du programme" (menu "Semestre") si vous avez un semestre en cours);
     warn, _ = sco_formsemestre_validation.check_formation_ues(formation)
     H.append(warn)
 
+    titre = f"Programme {formation.acronyme} v{formation.version}"
     return render_template(
         "sco_page_dept.j2",
         content="".join(H),
-        page_title=f"Formation {formation.acronyme} v{formation.version}",
+        title=titre,
+        page_title=titre,
         cssstyles=["libjs/jQuery-tagEditor/jquery.tag-editor.css", "css/ue_table.css"],
         javascripts=[
             "libjs/jinplace-1.2.1.min.js",
@@ -1200,7 +1203,8 @@ def _ue_table_ues(
                             }">transformer en UE ordinaire</a>&nbsp;"""
                     )
                 H.append("</span>")
-        ue_editable = editable and not ue_is_locked(ue["ue_id"])
+        ue_locked, ue_locked_reason = ue_is_locked(ue["ue_id"])
+        ue_editable = editable and not ue_locked
         if ue_editable:
             H.append(
                 f"""<a class="stdlink" href="{
@@ -1208,7 +1212,9 @@ def _ue_table_ues(
                     }">modifier</a>"""
             )
         else:
-            H.append('<span class="locked">[verrouillé]</span>')
+            H.append(
+                f'<span class="locked fontred">[verrouillée: {ue_locked_reason}]</span>'
+            )
         H.append(
             _ue_table_matieres(
                 parcours,
@@ -1500,8 +1506,10 @@ def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False):
     # check
     ue_id = args["ue_id"]
     ue = ue_list({"ue_id": ue_id})[0]
-    if (not bypass_lock) and ue_is_locked(ue["ue_id"]):
-        raise ScoLockedFormError()
+    if not bypass_lock:
+        ue_locked, ue_locked_reason = ue_is_locked(ue["ue_id"])
+        if ue_locked:
+            raise ScoLockedFormError(msg=f"UE verrouillée: {ue_locked_reason}")
     # check: acronyme unique dans cette formation
     if "acronyme" in args:
         new_acro = args["acronyme"]
@@ -1525,20 +1533,38 @@ def do_ue_edit(args, bypass_lock=False, dont_invalidate_cache=False):
         formation.invalidate_module_coefs()
 
 
-def ue_is_locked(ue_id):
-    """True if UE should not be modified
-    (contains modules used in a locked formsemestre)
+def ue_is_locked(ue_id: int) -> tuple[bool, str]:
+    """True if UE should not be modified:
+    utilisée dans un formsemestre verrouillé ou validations de jury de cette UE.
+    Renvoie aussi une explication.
     """
-    r = ndb.SimpleDictFetch(
-        """SELECT ue.id
-        FROM notes_ue ue, notes_modules mod, notes_formsemestre sem, notes_moduleimpl mi
-        WHERE ue.id = mod.ue_id
-        AND mi.module_id = mod.id AND mi.formsemestre_id = sem.id
-        AND ue.id = %(ue_id)s AND sem.etat = false
-        """,
-        {"ue_id": ue_id},
-    )
-    return len(r) > 0
+    # before 9.7.23: contains modules used in a locked formsemestre
+    # starting from 9.7.23: + existence de validations de jury de cette UE
+    ue = UniteEns.query.get(ue_id)
+    if not ue:
+        return True, "inexistante"
+    if ue.formation.is_apc():
+        # en APC, interdit toute modification d'UE si utilisée dans un semestre verrouillé
+        if False in [formsemestre.etat for formsemestre in ue.formation.formsemestres]:
+            return True, "utilisée dans un semestre verrouillé"
+    else:
+        # en classique: interdit si contient des modules utilisés dans des semestres verrouillés
+        # en effet, dans certaines (très anciennes) formations, une UE peut avoir des modules de
+        # différents semestre
+        if (
+            Module.query.filter(Module.ue_id == ue_id)
+            .join(Module.modimpls)
+            .join(ModuleImpl.formsemestre)
+            .filter_by(etat=False)
+            .count()
+        ):
+            return True, "avec modules utilisés dans des semestres verrouillés"
+
+    nb_validations = ScolarFormSemestreValidation.query.filter_by(ue_id=ue_id).count()
+    if nb_validations > 0:
+        return True, f"avec {nb_validations} validations de jury"
+
+    return False, ""
 
 
 UE_PALETTE = [
diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index a54cb42c286cdd17cee294a3c4eaded2f21994e5..06234400ef37d7f9373fd4965d54089d4061b09a 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -1185,6 +1185,9 @@ span.trombi_box a img {
 }
 
 /* markup non semantique pour les cas simples */
+.smallnote {
+  font-size: 80%;
+}
 
 .fontred {
   color: red;
diff --git a/app/templates/pn/form_ues.j2 b/app/templates/pn/form_ues.j2
index 823579004212e9419d5a1259120b7e5a05f7c7c6..57ac315d8b2d9912b37b8186f7b48f2f441ebe5a 100644
--- a/app/templates/pn/form_ues.j2
+++ b/app/templates/pn/form_ues.j2
@@ -81,10 +81,13 @@
                             {% endfor %}
                 </div>
                 {% endif %}
-                {% if editable and not ue.is_locked() %}
-                <a class="stdlink" href="{{ url_for('notes.ue_edit',
+                {% set ue_is_locked = ue.is_locked() %}
+                {% if editable and not ue_is_locked[0] %}
+                    <a class="stdlink" href="{{ url_for('notes.ue_edit',
                         scodoc_dept=g.scodoc_dept, ue_id=ue.id)
                         }}">modifier</a>
+                {% else %}
+                    <span class ="fontred smallnote">{{ue_is_locked[1]}}</span>
                 {% endif %}
 
                 {% if ue.type != codes_cursus.UE_SPORT %}
@@ -98,7 +101,7 @@
                             pas de niveau de compétence associé !
                         </span>
                     {% endif %}
-                    {% if editable and not ue.is_locked() %}
+                    {% if editable and not ue_is_locked[0] %}
                         <a class="stdlink" href="{{
                             url_for('notes.parcour_formation', scodoc_dept=g.scodoc_dept,
                                 formation_id=formation.id )
diff --git a/app/templates/pn/ue_infos.j2 b/app/templates/pn/ue_infos.j2
index 3b08044e0ca825f879488f2e50cce05cf5d278b0..ea779459f7d4bcc00eb03c3779470d616c14d531 100644
--- a/app/templates/pn/ue_infos.j2
+++ b/app/templates/pn/ue_infos.j2
@@ -58,8 +58,8 @@
     </ul>
 
     <ul>
-        {% if ue.is_locked %}
-        <li><em>non modifiable car utilisée dans des semestres verrouillés</em></li>
+        {% if ue.is_locked()[0] %}
+        <li><em class="fontred">non modifiable : {{ue.is_locked()[1]}}</em></li>
         {% else %}
         <li><a href="{{url_for('notes.ue_edit', scodoc_dept=g.scodoc_dept, ue_id=ue.id)}}">modifier cette UE</a>
         </li>