crearPractica refusals name a CONDITION and no EXIT - 14/29 are the duplicate DNI+fecha+sede collision (original 'records NO reason' premise FALSIFIED)
PREMISE FALSIFIED 2026-09-11 - see the verified event of that date for the full measurement; the original description below the line is retained there. The row was filed off a detail-only query. The reason string is a TOP-LEVEL appEvents.message column written by recordActionErrorToast as message: safeMessage; only {code,role,action,mechanism} goes into detail. Re-measured over the same 30d prod window: 29 action-error toast rows, ALL 29 carrying a specific populated reason. Nothing was ever dropped, so there is nothing to plumb. What IS still true: detail.code is the generic 'practica.error' on all 29, which makes the code useless for GROUPING - but the diagnosis it was meant to enable is already available from message, so carrying the code into detail is a nice-to-have and not the fix. MEASURED DISTRIBUTION (events/distinct-users): 14/6 duplicate DNI+fecha+sede collision; 6/6 future fecha; 3/1 historia-clinica format; 3/1 image upload; 2/2 fecha before 01/Ene/2025; 1/1 no pieza dentaria or region. NOT A DOUBLE-SUBMIT, measured not assumed: of the 14 collisions ZERO repeat within 10s, 4 within 2min, 6 are that user's first - users deliberately re-attempt a minute later, so NO idempotency guard and NO disabled-submit-button is warranted. THE ACTUAL SCOPE, same shape as VENUS-391: (a) the collision refusal names a CONDITION with no EXIT - it does not say whether to open the existing practica, nor how to record a genuinely distinct second procedure on the same patient/day/sede; the STRING is the fix. (b) client-side fecha validation removes the 8 date rows (6 future + 2 pre-2025) before a submit is spent. (c) optional: carry the server code into detail. INSTRUMENT CAVEAT: an earlier query of mine reported 66 duplicate rows - WRONG, an AND/OR precedence bug spanned all time and ignored the message filter. The correct 30d figure is 14. Discard 66 anywhere it is quoted.
Activity
-
PREMISE FALSIFIED, same shape as VENUS-391. Re-measured 2026-09-11, 30d prod window: 29 action-error toast rows, and ALL 29 carry a populated appEvents.message with a SPECIFIC reason. The row was filed off a detail-only query; the reason string is a TOP-LEVEL COLUMN written by recordActionErrorToast as message: safeMessage, and only {code,role,action,mechanism} goes into detail. So 'blocked submits record NO reason' is wrong - nothing was ever dropped. What IS true: detail.code is the generic practica.error on all 29, so the code is useless for grouping; but the diagnosis it was meant to enable is already available, and carrying the code into detail is a nice-to-have, not the fix. THE REAL DISTRIBUTION, 30d, events/distinct-users: 14/6 'Ya existe una practica cargada con ese DNI, fecha y sede'; 6/6 'La fecha de la practica no puede ser futura'; 3/1 'Historia clinica invalida (1-40 caracteres alfanumericos)'; 3/1 'No pudimos subir la imagen'; 2/2 'La fecha no puede ser anterior al 01/Ene/2025'; 1/1 'Selecciona al menos una pieza dentaria o una region anatomica'. Two shapes, different fixes. (a) ONE-OFF MISTAKES: future-date is exactly 1 event per user across 6 users - a slip, not a wall. (b) STUCK USERS: one user hit the historia-clinica format 3 times and one hit image-upload 3 times; those are individuals who could not get through. THE DOMINANT CLASS IS NOT A DOUBLE-SUBMIT, measured rather than assumed: of the 14 duplicate-collision events, ZERO are a repeat within 10 seconds, 4 are a repeat within 2 minutes, 6 are that user's first. So no idempotency guard or disabled-submit-button is warranted - users deliberately re-attempt a minute later. THE DEFECT IS THE VENUS-391 DEFECT AGAIN: the refusal names a CONDITION and no EXIT. 'Ya existe una practica cargada con ese DNI, fecha y sede' does not tell the operator whether to open the existing practica, or that a genuinely distinct second procedure on the same patient/day/sede needs a different discriminator. Client-side date validation would also remove the 8 date rows (future + pre-2025) before a submit is ever spent. CAVEAT ON THE INSTRUMENT: an earlier query of mine reported 66 duplicate rows - that figure is WRONG, an AND/OR precedence bug made it span all time and ignore the message filter. The correct 30d figure is 14. Discard 66 wherever it may have been quoted.
-
crearPractica refusals name a CONDITION, not an EXIT - 14/29 are the duplicate DNI+fecha+sede message (the 'no reason recorded' premise is falsified: all 29 carry appEvents.message)
-
crearPractica refusals name a CONDITION and no EXIT - 14/29 are the duplicate DNI+fecha+sede collision (original 'records NO reason' premise FALSIFIED)
-
was: MEASURED 2026-08-22 on prod appEvents, 30d window: 30 toast rows with detail.mechanism='action-error' across 12 users; 22 of them are the generic bucket (20 role=alumno_postgrado action=crearPractica, 2 role=titular). The detail object is {code,role,action,mechanism} - there is NO field carrying WHY the server action refused. So 22 blocked práctica submissions by real students in one month are unexplainable from telemetry: we know a submit failed and who, not what refused it. crearPractica returns discriminated codes (input-invalid, db-fault, already-participant, etc) and the toast layer discards them into 'practica.error'. Fix: carry the server-returned code (NOT the user-facing message, which can hold PII) into detail. Cross-check: only 4 clientWindowError + 1 clientUnhandledRejection + 1 approveAccessRequestAttachFailed exist at level error/fatal in the same 30d, so these 22 are invisible to the error rail entirely - the app looks quiet while a student is stuck. Relates VENUS-275 (advisory rows mislabeled error-shown) and VENUS-276 (unclassified pairs).
-
SHIPPED 92edf3b (venus 1.17.19), on origin/main - verified by reading origin, not from the push report. Both legs of the measured scope landed; leg (c) was NOT done and does not need to be. (a) THE COLLISION STRING. New server-only-FREE module src/lib/practica-collision-outcome.ts carries the refusal, and BOTH create sites in practica-actions.ts collapse onto it - the pre-tx dedup SELECT and the 23505 race catch - because a user who loses the race committed the same act as one refused up front, and a second copy of a rule is a rule that drifts (VENUS-378). The gate is NOT relaxed: the unique index is what stops the mutual-pick duplicate. New wording names BOTH exits, and both are measured against prod rather than assumed: creator is a participant on 3079/3080 non-deleted practicas (so open-it-from Practicas is live); 1735/3080 (56%) carry >1 participant and ~1830/4909 participations sit on a practica somebody else created (so ask-the-other-participant is not hypothetical); selfRegisteredAt is NULL on ALL 4909 participations, so nobody self-joins and the second alumnos ONLY route is being ADDED by the loader - /editar, 48h from COALESCE(editWindowOpenedAt, createdAt). The string also warns AWAY from /corregir, which can structurally NEVER touch participantes (correction-fields.ts excludes it, apply is practica-ROW-only); support twice spent an edit-window reopen on a user sitting there. It deliberately does not name the loader - that would disclose a clinical association to a user who by construction cannot see the practica. No new error code invented: the existing "duplicate" from the closed PracticaErrorCode vocabulary, already used by the edit path. (b) THE DATE LEG. date-validity gains practicaFechaRangeCode (discriminant), practica-checks maps it to two NEW guardrail codes, and practica-form now reads PRACTICA_FECHA_FLOOR instead of a second hardcoded copy. THE DateDmyField BOUNDS WERE NEVER THE GUARD AND LOOKED LIKE ONE: minIso/maxIso only tighten the YEAR input min/max, a type=number min/max is ADVISORY not enforcement, and a controlled React value handed to a server action never runs native constraint validation at all - and it is bypassed entirely for a future date INSIDE the current year, which is the dominant case. practica-checks STILL does not import log-guardrail and must not: the submit-loop emit IS the VENUS-390 friction instrument and a blur-time emit would destroy the only signal able to say whether this fix worked. (c) carrying the code into detail: NOT done, and the row itself already says why - the diagnosis it would enable is already available from appEvents.message. Dropping it is not a remainder. FALSIFIED, 7 controls, and one caught the harness: F0 control-is-live green, F1 strip the second exit 4 fail, F2 revert to pre-fix wording 5, F3 drop the s.fecha gate 1, F4 delete fecha-futura 2, F6 drift the guardrail floor literal 1. F5 reported 52/52 GREEN and was NOT accepted - re-applied the mutation and diffed to prove it HAD applied, so F5 was genuinely vacuous and the test was RETARGETED to state the observed outcome and disclaim the guard it cannot be. 55 tests pass across 4 files. MEASURED REMAINDERS, deliberately NOT in this row, so nobody re-measures them: (1) historia-clinica format, 3/29 events but only 1 user - same client-gap shape, different wiring, left out. (2) image upload 3/29, 1 user - untouched. (3) editarPractica has its OWN collision pair (practica-actions.ts ~:1043 and ~:1439, "Ya existe OTRA practica...") with the same condition-no-exit defect but a DIFFERENT correct exit (the user already owns a practica and is moving it onto a taken tuple, so ask-to-be-added is wrong advice) and it is UNMEASURED - all 29 refusals are crearPractica. Candidate row, not part of this one. (4) /editar is also capped at MAX_EDITS=3, so the named exit can be closed before 48h; not worth lengthening the string, but support should know the message is honest in the common case only - the right answer to "I followed it and Editar refused me" is an edit-window reopen. NOT CLOSING YET: /api/health still reads 1.17.18 at 16:03Z. Closes once it reads past 1.17.19.
-
FIXED + DEPLOYED. /api/health reads version 1.17.20, deploymentId dpl_5VnsccPYXL2bGw21dgCrFACLif6k at 2026-09-11T16:05:27Z - past the 1.17.19 this shipped in (92edf3b), plus 1.17.20 (459e43a, an unrelated db-lane harness push carried in the same deploy window). COVERAGE STATEMENT, because a version bump is a DEPLOY oracle and not a content one: what is verified is that a build containing 92edf3b is serving. The refusal STRING itself was not exercised in production and structurally cannot be without either an authenticated session or manufacturing a real collision on live clinical data, which is not something to do to prove a message. The string is instead pinned by practica-collision-outcome.test.ts executing THE STRING THAT SHIPS (module, not an inline literal), with a MUST-FAIL CONTROL holding the exact pre-fix wording and asserting it names NEITHER exit. The client date checks are likewise pinned by unit tests, not by a live probe. THE REAL VERIFICATION IS THE NEXT 30 DAYS AND IT IS ALREADY INSTRUMENTED: the same query that produced the 29/14/8 baseline, re-run over the next window, is the outcome measure. If the collision class stays at ~14 per 30d the string did not help and the next move is the /editar route itself, not more wording. If the 8 date rows do not go to ~0 the client checks are not reaching the submit path. practica-checks deliberately still does NOT emit logGuardrail at blur time precisely so that counter stays comparable to the VENUS-390 baseline - that split is what makes this measurable at all, and it is pinned by a mock that THROWS on import. Remainders are enumerated in the verified event of 2026-09-11 (historia-clinica 3/29, image upload 3/29, the UNMEASURED editarPractica collision pair at ~:1043/~:1439, and MAX_EDITS=3 as a second way the named exit can close early). None are in scope here and none need re-measuring to file.