Prácticas rectificar: gate student comment-WRITE to rectificar state only (D2)
SCOPE REDUCED 2026-08-18 (coder-pluto-cc measured against current code): gap 1 (Reply-To root-cause, carries explicit WI#317 gap-1 marker) and gap 3 (rectificar state + edit->resubmit lock, WI#317 gap-3 marker) are shipped under WI#317 — see email-aliases.ts RECTIFICAR_PRACTICA_REPLY_TO and practica-actions.ts STUDENT_EDITABLE_ESTADOS/acquireEditLock/updatePractica. Gap 2 (email-on-comment) is ALSO shipped — sendPracticaComentarioEmail/sendPracticaComentarioToTeacherEmail exist and are wired in comentario-actions.ts — but carries NO WI#317 marker in source; the WI#317 attribution for gap 2 specifically is coder-pluto-cc's own unverified inference by adjacency, not read off the code. Only D2 remains: createComentario currently grants write on isParticipant||fullAccess with no review-state gate — Pluto's original looser behavior, never tightened to rectificar-only as Elazar answered 2026-07-03. Full original design history preserved in event log below.
Activity
-
wi cli
-
REQUIREMENT (Elazar brainstorm 2026-06-01, built with Mars; assess/port to Pluto). Workflow: (1) rectificar - teacher sends a práctica back to the student with a message. (2) teacher comments on a práctica -> student gets an email with the comment. (3) student comments on a práctica -> teacher gets an email with the comment. (4) app emails sent from a no-reply address with an auto-reply: if a teacher/student REPLIES to the app email, they're told replies don't reach anyone - use in-app comments. (5) student can EDIT a práctica WHILE the teacher has NOT yet set Aprobado/Desaprobado; edit locks once graded. ASK to evolutiva-pm: brainstorm best Pluto implementation, assess how much ALREADY exists in Pluto (modify vs build), and capture lessons learned from the Mars implementation that apply to Pluto. DELIVERABLE = design brainstorm by evolutiva-pm (post-compact, fresh context), then route to pm-pluto for implementation. NOTE: comment-not-nota naming (commons), email via the app's send path, Spanish UX, no raw IDs in UI.
-
MARS PORT REFERENCE (pm-mars, shipped WI#309 SHA cfc9db5): WHAT MARS BUILT: (1) practicaComentarios append-only table; authorRole DERIVED at read (autorUserId===studentUserId => student), NOT stored - no drift. (2) Bidirectional notify: teacher comment->email student; student comment->email the last-rectificar teacher; seen-guard debounce = ONE email per burst until counterpart opens the thread. (3) Student may WRITE comentarios gated to approvalState=rectificar (read-only otherwise); student edit->resubmit while NOT Aprobado/Desaprobado is the explicit re-entry path. (4) Comments ORTHOGONAL to approvalState - never auto-flip state; re-review always explicit edit->resubmit. (5) Reply-To per-email override -> rectificar-practica@pensanta.com (live GWS mailbox + autoresponder: 'use in-app comments, replies do not reach teachers'); no moderation, all phases. LESSONS FOR PLUTO: (A) ROOT CAUSE - check Pluto mailer.ts FIRST: global soporte@ Reply-To meant student replies landed in support, never reached teacher. Fix = per-email Reply-To to a dedicated mailbox. Likely the real Pluto gap. (B) ZERO schema ALTER - Mars reused existing notifications/toastSeenAt infra (mars bwi#295) for seen-guard; Pluto likely has an equivalent, prefer reuse over a new notifiedAt column. (C) OPS: the rectificar mailbox + autoresponder must be provisioned (live GWS) BEFORE the Reply-To flip, else replies bounce. (D) Toast UX deferred (mars bwi#310), not a blocker. pm-pluto owns the Pluto-state assessment (practicaComentarios + notify infra may already cover 2-3; Reply-To root-cause A is the likely gap). My brainstorm post-compact builds on this; route agreed design to pm-pluto.
-
PLUTO ASSESSMENT (pm-pluto 14:32): Pluto already has MORE than Mars - reuse, don't rebuild: unified comment timeline practicaDocenteNotas (student+teacher via createComentario/createPracticaDocenteNota, WI#388, shared Comentarios component) covers Mars#1 + comment-half of #2 [VERIFY authorRole DERIVED at read, not stored]; comments already orthogonal to reviewStateOptionId (Mars#4 satisfied); notifications table + createNotification() = seen-guard substrate (reuse IF it has a read/seen timestamp - coder confirms). 3 GAPS: (1) REPLY-TO ROOT CAUSE confirmed = Pluto sets GLOBAL Reply-To soporte@pensanta.com on every email (email.ts); student/teacher replies die in support. Fix=per-email Reply-To override to dedicated rectificar-practica mailbox+autoresponder. (2) EMAIL-ON-EVENT: Pluto sends ZERO practica email; build sendPracticaComentarioEmail (teacher->student, student->last-rectificar teacher) w/ one-email-per-burst debounce + wire grading email. (3) RECTICAR STATE + EDIT->RESUBMIT: estadoRevision has only noRevisado/aprobado/desaprobado (NO rectificar); Pluto has NO core-practica edit form (only numeroHistoriaClinica editable, ungated). Biggest build. OPEN DECISIONS: D1 distinct rectificar value vs reuse noRevisado (pluto rec distinct). D2 gate student comment-WRITE to rectificar-only (TIGHTENS current looser Pluto behavior) vs keep. D3 edit->resubmit form scope (numeroHistoriaClinica+comentario vs full core re-edit). MAILBOX: reuse Mars rectificar-practica@pensanta.com or Pluto own? EVOLUTIVA-PM RULING: D1=DISTINCT rectificar (endorsed). Gaps 1+2 = proceed now (bug fix + missing emails, no product input needed); build per-email Reply-To override code but the live FLIP waits on mailbox provisioning. D2+D3+mailbox = ELAZAR product calls (D2 changes student UX, D3 defines editable scope, mailbox=ops/GWS) - FILE as wi ask to elazar post-compact, hold gap-3 WIs until answered.
-
q#42 to=elazar: Pluto prácticas rectificar workflow (gap-3) - D2: gate student comment-WRITE to the rectificar state ONLY (mirror Mars) vs keep Pluto's CURRENT looser behavior where students can comment any time? Thi
-
awaiting elazar
-
q#43 to=elazar: Pluto prácticas rectificar workflow (gap-3) - D3: scope of the student edit->resubmit form (Pluto has NONE today; only numeroHistoriaClinica is editable, ungated). Options: (i) minimal = numeroHistori
-
awaiting elazar
-
A — gate student comment-WRITE to rectificar state ONLY (mirror Mars). (Elazar 2026-07-03)
-
ii — full core-práctica re-edit, gated to noRevisado/rectificar, locked once aprobado/desaprobado. (Elazar 2026-07-03)
-
D2=rectificar-only, D3=full re-edit answered by Elazar 2026-07-03; ready to build.
-
coder-pluto-cc / coder
-
D2+D3 answered by Elazar 2026-07-03 (statusChanged: 'ready to build'); stale 'awaiting elazar' reason cleared. Reassigned from retired evolutiva-pm-cc-w to coder-pluto-cc — design phase is done, remaining work is implementation.
-
Prácticas rectificar: gate student comment-WRITE to rectificar state only (D2)
-
was: (empty)
-
Blocked pending: (1) db-pluto-cc read of fn_createPracticaComentario prosrc for an existing state gate (avoid PLUTO-703's two-enforcement-points trap), (2) product clarification from Elazar on whether the gate is rectificar-only literally or rectificar+noRevisado (eq filed), (3) sequencing — comentario-actions.ts currently carries parked, unpushed PLUTO-703b changes; D2 implementation holds until 703b resolves (ships or is abandoned) to avoid entangling two holds in one file.
-
db-pluto-cc confirmed (2026-08-18, live prosrc): no existing estadoRevision write-gate in fn_createPracticaComentario — comment INSERT is unconditional; estadoRevision only appears as a notification-routing tiebreak. D2's gate would be a genuinely NEW enforcement point, not a second instance of one. Per pluto-commons 'database-shaped work stays in PostgreSQL' + the single-enforcement-point precedent PLUTO-703/mig-111 set for this same function, the gate belongs in the DB fn, not comentario-actions.ts. Zero collision risk with 703b's held migration 111 on content, but both would target fn_createPracticaComentario's body — hold D2's DB-side change until 111 lands to avoid two overlapping unapplied migrations on the same function.
-
was: SCOPE REDUCED 2026-08-18 (coder-pluto-cc measured against current code): gaps 1 (Reply-To root-cause), 2 (email-on-comment), and 3 (rectificar state + edit->resubmit lock) are ALL already shipped under WI#317 — see email-aliases.ts RECTIFICAR_PRACTICA_REPLY_TO, sendPracticaComentarioEmail/sendPracticaComentarioToTeacherEmail, and practica-actions.ts STUDENT_EDITABLE_ESTADOS/acquireEditLock/updatePractica. Only D2 remains: createComentario currently grants write on isParticipant||fullAccess with no review-state gate — Pluto's original looser behavior, never tightened to rectificar-only as Elazar answered 2026-07-03. Full original design history preserved in event log below.
-
D2 shipped and CLOSED 2026-08-22 - app half a6778e5, DB half migration 114 applied on prod by db-pluto-cc and carried in the follow-up commit. Enforcement: fn_createPracticaComentario RAISEs 42501 'comment_state_not_rectificar' when a PARTICIPANT author writes on a practica whose estadoRevisionNombre is not 'Devuelto para rectificar' (Elazar 2026-07-03 answer A, rectificar ONLY - deliberately narrower than D3's edit gate which also allows noRevisado; two answers to two questions on the same day, not to be harmonised). Single enforcement point per PLUTO-703: comentario-actions.ts only TRANSLATES the refusal to a Spanish message and logs it via logAuthz rather than logCaughtError so a gate-correct denial does not inflate error telemetry (PLUTO-482); a test asserts the action never pre-checks state itself, and a second 42501 from anywhere else in the fn is NOT mistranslated. UI hides the composer with a stated reason, keyed on PARTICIPATION rather than role because that is exactly what the fn tests - so a docente who is their own operador is gated identically instead of being offered a composer whose submit always fails. Decided explicitly, not inherited. Authorization defaults CLOSED here (IS DISTINCT FROM blocks on an unknown state) while the notify branch in the same function defaults OPEN (IS NOT TRUE) - both migrations state the asymmetry. COVERAGE STATEMENT: proven by prosrc assert + unit tests on the translation layer; NOT exercised end-to-end against a real student session in either state - no browser run was performed.