Marca de implante: proposal and approval path - client answered 'todos los docentes' but no suggestion surface exists
CLIENT ANSWERED consulta 4, "Quien aprueba una marca nueva": "Todos los docentes." That is a ROLE, not a person, which makes it implementable rather than an operational agreement someone has to remember. Terra already has a docente role and already gates surfaces by it, so this rides existing machinery. WHAT THE ANSWER DOES NOT PROVIDE, and the gap is the whole feature: there is no suggestion path today. The catalog was seeded from the client's Drive material, and an alumno who needs a marca that is not in the 19 has nowhere to put it - so "todos los docentes aprueban" answers the second half of a workflow whose first half does not exist. What is needed is a way to propose a marca, a queue a docente can see, and an approve/reject that writes to the catalog. DO NOT INVENT A NEW MECHANISM FOR THIS. terra already has the shape twice over: solicitudes de correccion (shared correctionRequests shell plus terra-owned detail, four states pending/approved/denied/stale) and supportTickets ridden as changeRequest/clientQuestion with ownerAppKey='terra'. Read pm/06-lessons-applied.md and pm/01-product.md before designing, and reuse whichever fits rather than adding a third request surface with its own states. TWO DECISIONS THAT ARE NOT OURS. Whether ANY docente approving is enough or it needs a specific one - "todos" reads as any-of, but that means the first docente to look decides for the catedra, and he may not have meant that. And whether a proposed marca is usable on the practica that proposed it while pending, or blocks the save - blocking is safer for the catalog and worse for the alumno standing in a surgery. THE CATALOG IS A SHARED TABLE. lookupOptions carries an ownerAppKey column rather than string prefixes, so a write path here touches co-tenant state and belongs in db-terra-ca's lane as a migration, not in a Node action. See the standing DB write contracts. Not started. Filed so the answer does not decay into a sentence in a document nobody re-reads.
Activity
-
BOTH DECISIONS THE ROW LEFT OPEN ARE NOW MADE, because leaving them open was the actual blocker and they are mine to make. Elazar delegated design decisions to this lane; he does not arbitrate them. DECISION ONE - ANY DOCENTE MAY APPROVE, AND THE APPROVER IS RECORDED. The client said "todos los docentes", which reads as any-of, and the row correctly flagged that any-of means the first docente to look decides for the catedra. I am accepting that, with attribution as the mitigation rather than a quorum: the request carries who approved it, so a wrong approval is traceable and arguable instead of anonymous. The cost of a wrong approval here is a brand row somebody later edits - the catalogue is additive and reversible, and no clinical decision hangs on which brand string exists. A quorum mechanism would be machinery earned by a consequence this feature does not have. DECISION TWO - A PENDING MARCA DOES NOT BLOCK THE SAVE, and this is the one where the safe-looking option is the wrong one. Blocking protects the catalogue and fails the person: an alumno standing in a surgery with an implant whose brand is not in the 19 cannot wait for a docente to log in. The practica saves carrying the proposed brand as TEXT attached to the pending request, never as a catalogue entry, and resolves to the real catalogue row if and when it is approved. The catalogue never holds an unapproved value, which is what blocking was actually protecting, and the alumno is never stopped. WHAT I HAVE NOT DECIDED AND WILL NOT UNTIL IT IS READ: which existing request surface this rides. The row is explicit that terra already has the shape twice - correctionRequests (shared shell, terra-owned detail, pending/approved/denied/stale) and supportTickets ridden as changeRequest/clientQuestion with ownerAppKey='terra' - and that adding a third would be the failure. Assigned to coder-terra-ca as an INVESTIGATION with a verdict and named files, not a design: which one fits, what each already does that this needs, what it would have to grow. They are told to answer "neither" plainly if that is true, because a forced fit arriving under the name of an existing surface is worse than a stated mismatch. NO CODE AND NO SCHEMA AUTHORISED YET. lookupOptions carries ownerAppKey, so any write path is a migration in db-terra-ca's lane. Nothing is drafted.
-
VERDICT IN, AND IT CAME WITH AN OWNERSHIP CONTRADICTION THAT MATTERS MORE THAN THE VERDICT. coder-terra-ca read both existing request surfaces and answered with named files rather than a preference. RIDE supportTickets UNDER changeRequest. NOT correctionRequests. The product boundary is explicit rather than inferred: pm/01-product.md assigns change requests and client Q&A to shared supportTickets with categoryValue changeRequest/clientQuestion, staff and client queues, lifecycle and threaded discussion, and says in terms never to merge them with solicitudes de correccion. pm/03-data-model.md repeats the distinction as typed practica-FK target versus plain prose. WHY correctionRequests CANNOT TAKE IT, from the migration rather than the doc. terra_2_antecedentes_correction_details.sql defines terraCorrectionRequestDetails as a detail on the shared correctionRequests shell with a NOT NULL FK practicaId to terraPracticas, a partial unique enforcing one open request per practica, and a trigger requiring shell and status alignment. Its states are pending, approved, denied, stale. That is a narrow correction lifecycle anchored to an EXISTING practica. A marca proposal has no practica to correct - it proposes a catalogue value - so riding it would mean adding generic request body, assignment and threaded discussion to a surface the product explicitly separated, and the hard NOT NULL FK would have to be defeated or faked. WHAT supportTickets ALREADY SUPPLIES: subject/body, createdBy, assignedTo, resolvedBy/resolvedAt, a sibling supportTicketMessages for conversation, a category/status matrix, and staff/client scoped queue resolvers already read by src/lib/support-queue.ts. Request text, lifecycle, discussion and an attributable resolver - which is exactly what my "any docente approves, and the approver is recorded" decision needs, so attribution is not something this feature has to build. THE FIT LIMIT, STATED PLAINLY BY THEM RATHER THAN GLOSSED: terra has NO generic changeRequest or clientQuestion writer or reader anywhere in current source. The only live writer is src/lib/access-request-actions.ts, pinned to accesoCuenta, and the live snapshot therefore proves only the accesoCuenta state vocabulary (open/inProgress/waitingUser/resolved/closed) - it is NOT evidence that a changeRequest-specific matrix exists. The category is product-designated, not implemented. That is a bounded extension of the chosen surface, and it is a real cost that has to be paid rather than a formality. NOW THE PART THAT BLOCKS EVERYTHING: WE DO NOT KNOW WHOSE TABLE IT IS. db/schema-live.md:623 says "supportTickets is shared, venus-owned, and terra has ZERO rows in it (measured 2026-08-22)". src/app/sin-acceso/page.tsx:24 attributes the staff queue to enamel_003, an ENAMEL migration. Both cannot be the last word. I verified both citations myself rather than relaying the report. A GO request routed to the wrong owner produces an approval that binds nothing, which is the worst of the three possible outcomes because it looks like the right one. Asked db-enamel-cc to settle it, explicitly without requesting a GO in the same message. NOTHING DRAFTED. No code, no schema, no migration. The surface is chosen; the owner is not established; the write path stays unwritten until it is.
-
OWNERSHIP SETTLED AND TERRA'S RECORD WAS WRONG, NOT STALE. db-enamel-cc checked enamel_003 - their own migration - and the live table, and answered: supportTickets is ENAMEL-owned, not venus's. schema-live.md:623 said "venus-owned" and there was no handover for that line to be mis-recording. It was never true. THE EVIDENCE IS THE MIGRATION HEADER, QUOTED: "ONE supportTickets TABLE FOR ALL APPS, not one per app... a ticket is a ticket: the same human files one from terra today and enamel next term." appKey-partitioned cross-tenant infrastructure, built that way on day one, same category as users/userGroups/groups, inside enamel's gate. The header goes further and names venus's per-app mars/pluto/venus tables as the pattern this table exists specifically NOT to repeat - so the doc did not merely get the owner wrong, it assigned the table to the lane whose approach it was written against. WHERE THE ERROR CAME FROM, AND IT IS A RULE RATHER THAN A TYPO. Two lines below the wrong heading, the same census correctly records createVenusSupportTicket and listVenusSupportTickets as venus-named functions over the table, and correctly says they are not a terra surface. A FUNCTION'S NAME PREFIX RECORDS WHO WROTE THAT FUNCTION, NOT WHO OWNS THE TABLE IT READS. The census was right about every object it listed and wrong only in the conclusion it drew from them, which is why nothing about it looked suspect for nine days. CORRECTED IN db/schema-live.md, heading and a note carrying the quote, the reason it was wrong, and the instruction that any GO touching supportTickets goes to db-enamel-cc. I fixed the doc rather than only recording the correction here, because the doc is what the next reader greps and this row is not. ROUTE IS NOW ESTABLISHED for TERRA-141's eventual write path: db-enamel-cc, not venus. Still nothing drafted - the surface is chosen, the owner is now known, and no GO has been requested yet because there is nothing concrete enough to grant against.
-
LA INVESTIGACION DE SUPERFICIE ESTA CERRADA Y EL VEREDICTO ES supportTickets COMO changeRequest, con ownerAppKey terra. correctionRequests queda descartada, y las razones son estructurales, no de estilo - por eso se sostienen. POR QUE NO correctionRequests, verificado en terra_2_antecedentes_correction_details.sql lineas 116-134 y no tomado del reporte: terraCorrectionRequestDetails tiene practicaId uuid NOT NULL REFERENCES terraPracticas, y ux_terraCorrectionRequestDetails_oneOpenPerPractica es un indice unico parcial sobre practicaId WHERE statusValue='pending' AND deletedAt IS NULL. Una propuesta de marca no tiene practica que ofrecer a ese FK, y el indice la chocaria contra si misma la segunda vez que un mismo alumno propusiera dos marcas. Ademas hay un trigger de bloqueo de estado entre shell y detalle, y ninguna superficie de mensajes. Adaptarla obligaria a caracterizar una propuesta de catalogo como una correccion de practica - que es falso - o a inventar un mecanismo generico de request/detail, que es exactamente la TERCERA FORMA que este row existe para evitar. POR QUE SI supportTickets: el producto ya lo dicto. pm/01-product.md:17 pone changeRequest y clientQuestion sobre supportTickets con ownerAppKey terra, y la linea 21 dice explicitamente que son DISTINTAS de las solicitudes de correccion; pm/03-data-model.md:44 lo repite. La tabla ya aporta solicitante atribuible, asignado/resolutor y ciclo de vida, mas supportTicketMessages hilado y resolvers de cola staff/cliente. src/lib/support-queue.ts ya es el lector terra del resolver de staff, asi que la mitad de lectura no se inventa. LO QUE HAY QUE HACER CRECER, nombrado y no especulativo: un escritor de changeRequest autorizado por rol - hoy el unico escritor terra sobre supportTickets es la llamada requestTerraAccess en src/lib/access-request-actions.ts:203-246 - su matriz de estado/categoria permitida, y una operacion de aprobacion atribuible. UNA CORRECCION AL VEREDICTO, MEDIDA, Y CAMBIA DE QUE LANE SALE LA MIGRACION. El reporte decia que la aprobacion "le pide a db-enamel-ca que agregue el valor aprobado a lookupOptions". Medido contra la base viva: lookupOptions tiene 95 filas con ownerAppKey terra y 55 con el campo vacio, y la categoria terraMarcaImplante es integramente terra - 19 filas, otra en sortOrder 19, ultima y activa. Una marca aprobada es una escritura sobre EL CATALOGO PROPIO DE TERRA, no sobre el de enamel. La migracion es de db-terra-ca y el GO de db-enamel-cc no esta en el camino critico como lo estuvo en terra_52. LO QUE ESO NO CAMBIA: terra hoy no tiene NINGUN camino de escritura hacia lookupOptions. El grep sobre src/ devuelve solo lecturas, y los dos aciertos son un comentario y un test. Es una superficie de escritura nueva, no una existente que se extiende. ESTADO: sin codigo y sin esquema autorizados. Las dos decisiones de producto ya estaban tomadas el 2026-08-31 21:31 - cualquier docente aprueba con el aprobador registrado, y una marca pendiente NO bloquea el guardado. Lo que faltaba era esta eleccion de superficie, y ya esta hecha. El siguiente paso es un borrador de migracion en el lane de db-terra-ca, que no esta pedido todavia.
-
ESTE ROW NO ESTA BLOQUEADO POR ARQUITECTURA. ESTA CONSTRUYENDO UN CIRCUITO DE APROBACION PARA UN CAMPO QUE NINGUN FORMULARIO PIDE HOY. Medido contra eprpywqwzsvrjeaforei, no inferido. LA MEDICION. terraModuloCampos tiene seis filas. El campo "Marca de implante" del modulo Implantologia, id 3a48da07, esta scopeado a CERO cirugias en terraCirugiaCampos. O sea que la marca de implante NO APARECE en el formulario de ninguna practica. Consecuencia directa y tambien contada: terraPracticasCampos tiene 3 filas vivas en total y NINGUNA es de terraMarcaImplante. Ninguna practica registro jamas una marca. Y SIN EMBARGO TODO ALREDEDOR PARECE CONSTRUIDO. lookupOptions tiene 19 marcas vivas y ordenadas - Straumann, Neodent, Nobel Biocare, Zimmer Biomet, Dentsply Sirona, Osstem, MIS, Alpha-Bio, BTI, Megagen, las cuatro nacionales, FIA, IMPLANTSIM, INSPIRAL, NUVO - y "Otra" en sortOrder 19. informes/page.tsx renderiza un filtro por marca (:182) y un Panel de desglose por marca (:264). Vocabulario sembrado mas camino de lectura renderizado se lee como una funcionalidad terminada, y no lo es: falta la unica fila que la haria aparecer en un formulario. POR QUE ESO DECIDE EL ROW. La pregunta que este row intenta contestar es como un docente PROPONE una marca que falta. Pero hoy nadie puede elegir ninguna marca, ni las 19 que estan, asi que nadie puede encontrarse con que falte una. El circuito de propuesta no tiene disparador. Construirlo ahora seria resolver el segundo problema sin haber resuelto el primero, y quedaria una cola de propuestas alimentada por un formulario que no pregunta. ADEMAS "OTRA" NO CAPTURA NADA, y esto importa para el diseno que venga: terraPracticasCampos tiene value text NOT NULL y NINGUNA columna de texto libre. Si manana se scopea el campo y alguien elige "Otra", se guarda el literal "otra" y el nombre real de la marca NO SE GUARDA EN NINGUN LADO. O sea que "Otra" no es una valvula de escape que registre el faltante para despues cosecharlo - es una perdida silenciosa de dato. Cualquier diseno de propuesta tiene que decidir esto explicitamente, porque hoy la opcion existe y parece una via de escape sin serlo. LO QUE ACEPTO DEL ANALISIS DE coder-terra-ca, verificado por mi antes de aceptarlo: correctionRequests NO SIRVE, y por una razon estructural que lei en terra_2_antecedentes_correction_details.sql:116-134 - practicaId es uuid NOT NULL con FK a terraPracticas, y hay un unique parcial de una sola pendiente por practica. Una propuesta de catalogo es de alcance catedra y puede existir sin ninguna practica. No entra en esa forma ni forzandola. Correcto y bien fundado. LO QUE NO ACEPTO ES QUE LA DECISION SEA DE ARQUITECTURA. La eleccion entre supportTickets local y la plataforma central de soporte es real y esta genuinamente diferida en TERRA-36, pero es la pregunta EQUIVOCADA para desbloquear esto. La pregunta previa es de la catedra y es de producto: se pide la marca de implante, y en que cirugias. Si la respuesta es que no se pide, este row se cancela junto con el filtro de informes que promete un desglose siempre vacio. Si se pide, recien ahi el circuito de propuesta tiene un disparador y la eleccion de mecanismo se vuelve pertinente. STATUS: pasa de inProgress a blocked. inProgress describia trabajo que no existe - coder-terra-ca reporto sin que se lo pidieran que no hay implementacion, ni esquema, ni superficie escrita, solo la investigacion. Un row en inProgress sin nada en movimiento es lo mismo que un blocker sin condicion escrita: se lee como que alguien lo esta empujando. NO LE ASIGNO MECANISMO A coder-terra-ca y su negativa a elegir uno sin autoridad fue correcta. Tampoco es de Elazar arbitrar la arquitectura - eso es mio por directiva del 2026-08-29. Lo que si es de la catedra es si el dato se pide. Esa mitad va a eq. HALLAZGO SEPARADO QUE SALE DE LA MISMA CONSULTA Y NO ES DE ESTE ROW: marcaImplante no es el unico campo sin scope. De los seis campos configurados, TRES estan scopeados a cero cirugias - Marca de implante en Implantologia, y Tipo de barrera mas Tipo de injerto en Reconstruccion del reborde alveolar. Ese modulo tiene uno solo de sus tres campos alcanzable. Va a row propio.
-
Bloqueado por una pregunta PREVIA a la arquitectura, y medido: el campo Marca de implante esta scopeado a CERO cirugias en terraCirugiaCampos, asi que ningun formulario lo pide y terraPracticasCampos no tiene ni una fila de terraMarcaImplante. No hay disparador para una propuesta: nadie puede encontrarse con que falte una marca porque nadie puede elegir ninguna. Las 19 marcas sembradas y el filtro mas el desglose de informes/page.tsx hacen que se lea como construido. La pregunta que desbloquea es de la catedra, no de arquitectura: se pide la marca de implante, y en que cirugias. Si no se pide, este row se cancela junto con el desglose que promete estar siempre vacio. La eleccion entre supportTickets local y soporte central (TERRA-36) es real pero PREMATURA. Nota de diseno para cuando se desbloquee: Otra existe en sortOrder 19 y NO captura texto libre - terraPracticasCampos.value es text NOT NULL sin columna de texto libre, asi que elegir Otra pierde el nombre real de la marca en silencio.
-
Condicion de desbloqueo AHORA FILADA Y OBSERVABLE, que es lo que le faltaba: eq 71, topic terra-campos-que-no-se-preguntan, ref_bwi 176. Pregunta a la catedra si los tres campos sin scope se preguntan o no, y en que cirugias. Este row depende de la mitad de marca de implante. Si la respuesta es que NO se pregunta, este row se cancela y con el se saca el desglose por marca de informes/page.tsx. Si la respuesta es que SI, recien ahi el circuito de propuesta tiene disparador y la eleccion de mecanismo - supportTickets local contra soporte central, TERRA-36 - se vuelve pertinente y es mia para decidir, no de Elazar. Un blocker sin pregunta filada es UNSTATED y no expira; este ya no lo es.
-
BLOQUEO EXPIRADO, Y NO POR EL PASO DEL TIEMPO: LA RAZON DEL BLOQUEO ESTABA DADA VUELTA DESDE QUE SE ESCRIBIO. Lo levanto a backlog y dejo escrito el motivo, porque un blocked que ya no lo es no lo revisa nadie - el estado mismo dice que no hay nada que mirar. QUE DECIA EL blockedReason, textual: "el campo Marca de implante esta scopeado a CERO cirugias en terraCirugiaCampos, asi que ningun formulario lo pide". LAS DOS MITADES DE ESE "ASI QUE" SON FALSAS, y la regla que las invierte estaba escrita en dos lugares antes de que yo escribiera esa frase. Alcance cero significa ABIERTO POR DEFECTO: un campo sin ninguna fila en terraCirugiaCampos aplica a TODA cirugia de su modulo. Lo dice campo-scope-core.ts:15-22 y lo implementa el resolvedor en :62-70, que deja pasar todo campo que ninguna fila menciona. Marca de implante no es el campo que no se pide en ningun formulario: es uno de los tres que se piden en TODOS los de su modulo, las diez cirugias activas de Implantologia. Y NO ES UN DESCUIDO DE SCOPE QUE HAYA QUE COMPLETAR: campo-scope-core.ts:20-22 dice que venus lleva marcaPicker en las DIEZ cirugias del modulo, o sea que su alcance cero es deliberado y verificado, y es el caso vivo que el archivo usa para explicar la regla. Ese archivo llama a marcaImplante "the live case" de un campo correctamente sin scope. CONSECUENCIA SOBRE EL ARGUMENTO CENTRAL DEL BLOQUEO. Decia que no hay disparador para una propuesta porque "nadie puede encontrarse con que falte una marca porque nadie puede elegir ninguna". Eso cae: el picker se renderiza hoy, en toda cirugia de Implantologia, con las 19 marcas vivas. Un docente SI puede encontrarse con que falte la suya. LA OTRA MITAD ES CIERTA PERO NO SOSTIENE LA MISMA CONCLUSION, y las separo a proposito porque actualizar el numero sin revisar la conclusion deja un reclamo viejo con cara de verificado. Es cierto que terraPracticasCampos no tiene ni una fila de terraMarcaImplante. Pero el motivo medido no es que la pregunta no se haga: hay 6 practicas vivas y UNA SOLA tiene filas de campos - tres valores, y es de modulo 2. No hay marcas cargadas porque casi no hay practicas cargadas. "Nunca se disparo todavia" y "no puede dispararse" son estados distintos y el blockedReason afirmaba el segundo. TAMBIEN QUEDA CONTESTADA LA PREGUNTA QUE EL ROW MANDABA A LA CATEDRA. Decia que lo que desbloquea es "se pide la marca de implante, y en que cirugias". Esta contestado y esta en la fuente, no en una eq pendiente: en las diez cirugias activas del modulo de Implantologia, por la transcripcion de venus que cita campo-scope-core.ts:20-22. Y con eso cae tambien la salida alternativa que el row se dejaba - "si no se pide, este row se cancela": si se pide. LO QUE QUEDA VIVO Y SIGUE SIN DECIDIR, o sea el row reducido a su tamano real: no hay superficie de propuesta ni camino de aprobacion para una marca nueva. La respuesta del cliente a la consulta 4 ya existe - aprueban "todos los docentes" - asi que lo que falta es construirlo, no preguntarlo. Sigue en pie tambien la nota de diseno del blockedReason viejo, que es correcta y la conservo: "Otra" existe en sortOrder 19 y NO captura texto libre, porque terraPracticasCampos.value es text NOT NULL sin columna de texto libre, asi que elegir "Otra" pierde el nombre real de la marca en silencio. Eso es un defecto INDEPENDIENTE de la superficie de propuesta y hoy es alcanzable, porque el picker se renderiza. SIGUE PREMATURA la eleccion entre supportTickets local y soporte central (TERRA-36), como decia el blockedReason. Eso no cambia. PROCEDENCIA: lo levanto de una pasada de audit-terra-ca contra el tablero abierto (2026-09-01), que marco este row como bloqueo expirado. Verifique la lectura yo antes de moverlo - lei el blockedReason y las lineas citadas de campo-scope-core.ts. Es la MISMA premisa invertida que ya habia corregido en TERRA-176 a las 03:44 de hoy, en otro row, sin darme cuenta de que tenia un hermano. La correccion no se propago sola, que es el motivo por el que esta pasada valia la pena.
-
cleared: Bloqueado por una pregunta PREVIA a la arquitectura, y medido: el campo Marca de implante esta scopeado a CERO cirugias en terraCirugiaCampos, asi que ningun formulario lo pide y terraPracticasCampos no tiene ni una fila de terraMarcaImplante. No hay disparador para una propuesta: nadie puede encontrarse con que falte una marca porque nadie puede elegir ninguna. Las 19 marcas sembradas y el filtro mas el desglose de informes/page.tsx hacen que se lea como construido. La pregunta que desbloquea es de la catedra, no de arquitectura: se pide la marca de implante, y en que cirugias. Si no se pide, este row se cancela junto con el desglose que promete estar siempre vacio. La eleccion entre supportTickets local y soporte central (TERRA-36) es real pero PREMATURA. Nota de diseno para cuando se desbloquee: Otra existe en sortOrder 19 y NO captura texto libre - terraPracticasCampos.value es text NOT NULL sin columna de texto libre, asi que elegir Otra pierde el nombre real de la marca en silencio.
-
SALE DE ACA EL DEFECTO DE 'Otra' Y AHORA ES TERRA-196. Estaba escrito como nota de diseno adentro del blockedReason de esta fila, o sea colgado del circuito de propuesta y aprobacion, que no esta construido, no tiene esquema autorizado y depende de un GO de db-enamel-cc. El defecto no depende de nada de eso: 'otra' es opcion viva sortOrder 19, terraPracticasCampos tiene siete columnas y ninguna de texto libre, y ninguna linea de src/ trata 'otra' distinto de las otras 18 - grepeado hoy. O sea que elegirla guarda el literal y pierde el nombre real sin error. Re-medi las tres cosas contra la base viva antes de mover la nota, no la copie. Esta fila queda con lo suyo: la superficie de propuesta sobre supportTickets, ya elegida, con el owner ya establecido en enamel. Lo que se va es lo que podia cerrarse antes que ella.
-
ESTA FILA SE VOLVIO MAS IMPORTANTE DE LO QUE PARECIA, y no por algo nuevo sino porque TERRA-196 colapso adentro. 2026-09-01 08:30. TERRA-196 se habia abierto como una perdida de datos silenciosa: elegir "Otra" como marca de implante guarda el codigo 'otra' y el nombre real de la marca no queda en ningun lado. Es cierto MECANICAMENTE y es FALSO como defecto, porque la cabecera de db/migrations/applied/terra_33_provisional_campo_vocab_seed.sql trae un ruling de Elazar del 2026-08-22 que lo pide exactamente asi: rige la regla de NO TEXTO LIBRE, y la fila de escape 'otra' existe POR esa regla. Textual: "una marca que no esta en la lista se registra como otra, no se tipea; Puia agrega la marca a la semilla al confirmar, en vez de que el campo acepte texto libre". O SEA QUE EL DISEÑO DELEGA LA RECUPERACION DEL DATO EN ESTA FILA. La fila de escape no pierde el nombre: lo posterga hasta que la catedra agregue la marca al vocabulario. Esta fila registra que esa superficie de sugerencia NO EXISTE. Mientras no exista, "Otra" no es un escape, es un callejon - el mecanismo que iba a recuperar el dato nunca se construyo, y el ruling que autoriza la perdida momentanea supone un camino que no esta. NUMEROS MEDIDOS HOY, con el predicado nombrado porque me equivoque una vez con esto en el mismo tick: el campo marcaImplante se pide en las DIEZ cirugias ACTIVAS del modulo 1 Implantologia (16 existen en total, ninguna borrada), y se pide en todas porque el campo tiene alcance CERO, que la regla lee como abierto por defecto - es uno de los tres campos de alcance cero de TERRA-176. En toda la base hay CERO filas con value='otra', vivas o borradas. Nadie perdio todavia el nombre de ninguna marca. POR QUE ESO NO BAJA LA PRIORIDAD SINO QUE ABARATA EL ARREGLO: cero filas significa que no hay nada que migrar ni que reconstruir. El dia que haya una sola, el nombre de esa marca ya no esta en ningun lado y ninguna superficie de sugerencia construida despues lo recupera. La ventana barata es ahora. LO QUE SIGUE SIN DECIDIRSE Y ES DE ESTA FILA: el cliente contesto "todos los docentes" sobre quien puede proponer, pero eso no dice quien APRUEBA, ni si la propuesta entra al vocabulario como provisoria o confirmada, ni que ve el usuario que eligio "Otra" mientras la propuesta esta pendiente. No lo invento: es lo que hay que preguntar.
-
CORRECCION DE ENCUADRE, y sale de una contradiccion que yo mismo levante hace cinco minutos y que resulto no serlo. 2026-09-01 08:38. LO QUE YO PLANTEE: terraMarcaImplante tiene 19 filas hoy y terra_33 sembro 15. Cuatro marcas entraron DESPUES. Esta fila afirma que no existe ninguna superficie para proponer o agregar marcas. Las dos cosas no pueden ser ciertas a la vez - salvo que hayan entrado por otro lado. ENTRARON POR OTRO LADO, Y ESE ES EL DATO. Medido por db02-terra-cc contra la base y contra el repo: las 14 filas posteriores a la siembra en las cuatro categorias entraron por MIGRACION A MANO, no por ninguna superficie. Las cuatro marcas son fia, implantsim, inspiral y nuvo, con createdAt 2026-08-22 13:36:01, y vienen de terra_35_marca_implante_espe_brands.sql (TERRA-94, db-terra-cc). Las otras diez - 4 de tipoInjerto y 6 de zonaDadora, createdAt 14:48:45 - vienen de terra_38 con su reparacion terra_39 (TERRA-96), un import cruzado desde otro repo. VERIFIQUE EL ARTEFACTO YO, no lo tome de palabra: terra_35 lineas 58-61 insertan literalmente las cuatro filas con sortOrder 15 a 18, y su cabecera en la linea 15 declara "THESE FOUR ROWS ARE CLIENT-STATED, NOT LITERATURE-DERIVED", distinguiendolas de las 15 sembradas de la literatura. La linea 6 confirma ademas que 'otra' quedo en sortOrder 19, que es el valor que se midio hoy en la base. ASI QUE NO HAY CONTRADICCION CON NADA: ni con TERRA-192, que dice que la base no tiene marcador de provisorio - estas filas no lo tienen tampoco; ni con esta fila, que dice que no hay superficie de app - no pasaron por una. PERO ESTA FILA CAMBIA DE FORMA. Estaba escrita como "falta construir una superficie". Lo que ahora esta medido es que YA EXISTE UN CAMINO Y SE USO DOS VECES: cuando la catedra manda una lista, alguien escribe una migracion a mano. O sea que la pregunta deja de ser "como hacemos para que las marcas entren" - entran - y pasa a ser si ese camino es el que se quiere. LO QUE EL CAMINO ACTUAL NO RESUELVE, y es lo que sigue justificando la fila: una migracion a mano la escribe un agente de base cuando alguien le pasa una lista. No hay forma de que un docente PROPONGA una marca, ni de que el usuario que eligio "Otra" deje registrado cual era la marca que no estaba. La marca entra en la proxima migracion si alguien se entera; si nadie se entera, no entra nunca, y el nombre no quedo escrito en ningun lado - hoy hay CERO filas con value='otra', asi que todavia no se perdio ninguno. Y HAY UN COSTO MEDIDO DE HACERLO ASI: terra_39 existe unicamente para reparar el scope drift que dejaron terra_35 y terra_38 - members viejos apuntando a values retirados. O sea que la ultima vez que se agrego vocabulario a mano hizo falta una tercera migracion para arreglar lo que rompieron las dos primeras. Eso no es un argumento contra la migracion a mano, es el precio que hay que poner sobre la mesa cuando se decida.
-
ABSORBE TERRA-196, QUE CIERRO HOY 2026-09-01 12:10 UTC. No es un cierre administrativo: esa fila trae la razon por la que ESTA importa, y sin ella TERRA-141 se lee como una mejora deseable en vez de como el unico camino de salida de un callejon que ya existe. LO QUE APORTA Y NO ESTABA ESCRITO ACA: el catalogo de marcas de implante lleva una fila de escape 'otra', y esa fila NO es un descuido ni un fallback improvisado - es un ruling explicito de Elazar del 2026-08-22, escrito textual en la cabecera de terra_33_provisional_campo_vocab_seed.sql: la regla de no-texto-libre sigue en pie, por eso terraMarcaImplante lleva una fila de escape 'otra' en vez de un fallback de texto libre; una marca que no esta en la lista se registra como otra, NO SE TIPEA; la catedra agrega la marca a la semilla al confirmar. O SEA QUE EL RULING SUPONE ESTA FILA. El escape 'otra' solo es aceptable si existe el camino para que la catedra agregue la marca. TERRA-141 registra que ese camino NO EXISTE: el cliente contesto "todos los docentes" y no hay ninguna superficie de sugerencia construida. Elegir "Otra" hoy no pierde el nombre por descuido - lo pierde porque el mecanismo que iba a recuperarlo nunca se construyo. Esa es la frase que justifica la prioridad de esta fila y viene entera de TERRA-196. LO QUE SE GUARDA ES EL CODIGO, NO LA ETIQUETA, verificado en dos lados y no tomado de un reporte: la semilla inserta ('terraMarcaImplante','otra','Otra',15), y practica-actions.ts inserta value.trim() crudo por unnest sin traducir a etiqueta. En la base queda 'otra'. DOS OPCIONES QUEDAN DESCARTADAS DE ANTEMANO, para que nadie las vuelva a proponer: sacar "Otra" contradice el ruling - el escape existe justamente para que un valor fuera de lista no quede en blanco, indistinguible de "olvidado", el mismo argumento que la fila 'noAplica' de terraZonaDadora. Y capturar texto libre lo contradice de frente. Lo unico compatible es construir el camino de agregado, que es esta fila. MEDIDO HOY, 2026-09-01 12:10 UTC, reverificado por mi contra la base viva y no arrastrado de la fila que cierro: CERO filas con value 'otra' en toda la base, sobre TRES filas totales en terraPracticasCampos. Nadie perdio todavia el nombre de ninguna marca. Eso no borra el problema - lo hace BARATO: no hay nada que migrar, cualquier cambio se hace sobre poblacion vacia. Y el cero viene con su denominador porque tres es un denominador chico y sin el se leeria como disciplina en vez de como tabla joven. TAMBIEN REVERIFICADO HOY: modulo 1, Implantologia, tiene 16 cirugias en total y 10 activas. Ese es el conteo que fijo terra_38 y NO envejecio. Y EL ERROR DE MEDICION QUE SE COMETIO AL LLEGAR A EL QUEDA ESCRITO PORQUE ES REUTILIZABLE: filtrar el modulo por nombre con un patron ilike '%mplanto%' matchea DOS modulos - Implantologia y Protesis implanto asistida - y suma los dos en un total que se presenta como si fuera uno. Da 36 y 23 en vez de 16 y 10. AGRUPAR POR IDENTIFICADOR DE MODULO, NUNCA FILTRAR POR NOMBRE. Es la misma regla de definir la poblacion por propiedad y no por un patron de texto, y aca costo una correccion publica de un numero que ya estaba bien.
-
PROPUESTA DE DISEÑO RECIBIDA DE coder-terra-ca Y RESUELTO EL UNICO PUNTO QUE DEJO SIN MEDIR, 2026-09-01 12:20 UTC. Le pedi diseño en prosa, sin codigo y sin push, y eso entrego. (1) DONDE VIVE: superficie separada de la practica, montada sobre el flujo existente de supportTickets como changeRequest, NO un campo al lado de "Otra". La razon es la correcta y coincide con el ruling: un campo al lado de "Otra" convierte el dato clinico cerrado en texto libre, que es exactamente lo prohibido. Y reutiliza una cola ya prevista en vez de inventar un tercer mecanismo. (2) QUIEN SUGIERE: requireAppUserAction(["docente"]) contra las membresias vivas de vUserGroups. Descarta ROSTER_ROLES con argumento, no por gusto: ROSTER_ROLES incluye jtp, bedel y admin, y el cliente dijo especificamente todos los docentes. Nombrar el gate real en vez de describirlo es lo que pedi. (3) QUIEN CONFIRMA: SIN RESOLVER, Y ESTA BIEN QUE ASI QUEDE. No existe hoy ningun gate que signifique "la catedra". ADMIN_ROLES es admin y bedel; ROSTER_ROLES admite jtp, bedel y admin; ninguno de los dos prueba que un docente cualquiera pueda confirmar. El carril se NEGO a proponer el paso de aprobacion hasta que se nombre el conjunto de roles, y no rellenó el hueco con el gate mas parecido. Esa negativa es correcta: elegir ADMIN_ROLES porque es el que suena a autoridad seria inventar una politica de la catedra dentro de un commit. QUEDA COMO DECISION ABIERTA, y es de Elazar o del cliente, no de este carril. (4) DONDE ATERRIZA: una aprobacion NO la escribe una accion de Node. Es una migracion de db-terra con GO que inserta codigo, etiqueta y orden terra-owned en lookupOptions y, si la categoria esta en scope include, su miembro en vocabularyScopeMembers. Correcto, y ademas declara el limite en vez de dibujarlo como propio. Y ACA ESTA LO QUE MIDO YO Y QUE CIERRA SU UNICO UNKNOWN, PORQUE CORRE EN LA MISMA DIRECCION QUE VENIMOS VIENDO TODO EL DIA. El carril escribio, con razon de ser cauto: "para terraMarcaImplante, terra_35 dice explicitamente que no toco scopes ni members porque esa categoria no tiene scope; por tanto no puedo afirmar honestamente que hoy sean dos escrituras". Se abstuvo de afirmar y pidio que db-terra lo re-mida en el GO. LA ABSTENCION ERA LA CONDUCTA CORRECTA Y LA RESPUESTA ES QUE SI SON DOS. MEDIDO CONTRA LA BASE VIVA HOY 12:20 UTC: terraMarcaImplante SI TIENE SCOPE - appKey terra, mode include - y tiene 19 miembros en vocabularyScopeMembers. Todos creados el 2026-08-22, entre las 09:18 y las 15:18. O SEA QUE terra_35 DECIA LA VERDAD CUANDO SE ESCRIBIO Y DEJO DE DECIRLA EL MISMO DIA. Los scopes se sembraron mas tarde, el 22 de agosto. Un carril que hoy lee esa cabecera y le cree - que es lo que un lector diligente hace - concluye que la categoria no tiene scope y escribe UNA sola cosa donde hacen falta DOS: la marca aparece en lookupOptions y NO aparece en el scope de terra, con lo cual el docente no la ve. La aprobacion se veria hecha y el efecto seria nulo. ES LA MISMA CLASE QUE VENIMOS MIDIENDO TODO EL DIA, ahora dentro de un archivo de migracion aplicado: una afirmacion en presente, verdadera al escribirse, sin fecha, que nadie vuelve a medir. La diferencia es que esta habria producido una aprobacion silenciosamente inefectiva. ENTONCES QUEDA FIJADO PARA EL GO, y no hay que re-derivarlo: agregar una marca son DOS escrituras, lookupOptions mas vocabularyScopeMembers, y el que la aplique debe re-medir el scope contra la base ANTES de aplicar, no leer la cabecera de terra_35. LO QUE SIGUE BLOQUEANDO ESTA FILA: la decision de quien confirma, del punto (3). Sin eso no hay que implementar el paso de aprobacion. El punto (1) y el (2) si estan resueltos.
-
Decision abierta y no de este carril: no existe ningun gate que signifique 'la catedra' - ADMIN_ROLES es admin+bedel, ROSTER_ROLES admite jtp/bedel/admin, y ninguno prueba quien puede CONFIRMAR una marca sugerida. Puntos 1 (superficie: supportTickets changeRequest) y 2 (sugiere: requireAppUserAction docente) resueltos; el paso de aprobacion no se implementa hasta que se nombre el conjunto de roles
-
CERRADA LA PREGUNTA DE LAS CUATRO MARCAS, y la respuesta es NO: el camino de aprobacion NUNCA se uso. Estaba escrita explicitamente como pregunta y no como conclusion - "si esas cuatro son confirmaciones de la catedra, entonces el camino que TERRA-141 dice que no existe SI se uso alguna vez por fuera de la app, y no hay forma de saberlo desde la base". Habia forma. Es esta. EL HECHO, medido contra lookupOptions con los timestamps completos y no solo el conteo: las 19 filas de terraMarcaImplante con ownerAppKey terra se reparten en DOS instantes y nada mas. QUINCE llevan createdAt 2026-08-22 09:18:58.62108 - al microsegundo, las quince identicas, o sea UNA transaccion. CUATRO llevan 2026-08-22 13:36:01.638913, tambien identicas entre si: fia, implantsim, inspiral y nuvo. POR QUE ESO YA DESCARTA LA HIPOTESIS ANTES DE MIRAR EL REPO: cuatro confirmaciones de la catedra llegando por un camino de aprobacion serian cuatro decisiones separadas, dispersas en el tiempo. Cuatro filas con el MISMO microsegundo son una sola escritura. La forma de la evidencia contesta la pregunta sin necesidad de saber quien escribio. Y EL ESCRITOR TAMBIEN ESTA IDENTIFICADO, para no dejarlo en inferencia: db/migrations/applied/terra_35_marca_implante_espe_brands.sql inserta las cuatro por nombre. Es una migracion, aplicada, con las marcas escritas en el archivo. EL DETALLE QUE CONFIRMA QUE FUE LA MISMA TRANSACCION Y NO DOS COSAS QUE COINCIDEN: la fila 'otra' tiene createdAt 09:18:58.62108 - o sea que venia de la siembra - pero updatedAt 2026-08-22 13:36:01.638913, EXACTAMENTE el createdAt de las cuatro nuevas. terra_35 la reubico al final del orden al meter las marcas nuevas. Un updatedAt que coincide al microsegundo con el createdAt de otras filas es la firma de una sola transaccion, y es lo que descarta que las cuatro hayan llegado por un camino distinto que casualmente corrio ese dia. QUE LE HACE ESTO A CADA FILA: A TERRA-192: cierra el unico punto que quedaba abierto como pregunta. El crecimiento post-siembra de terraMarcaImplante NO es evidencia de un camino de confirmacion no documentado; es una migracion. Las otras dos categorias que crecieron - terraTipoInjerto de 5 a 9 y terraZonaDadora de 9 a 15 - no las medi con este metodo y NO extiendo la conclusion a ellas: lo digo en vez de dejar que se lea como que las tres quedaron explicadas. A TERRA-141: REFUERZA su premisa en vez de contradecirla, que era el riesgo. La fila afirma que el camino para que la catedra agregue una marca NUNCA SE CONSTRUYO. Si las cuatro hubieran sido confirmaciones, la premisa era falsa y la fila habria que reescribirla entera. Son una migracion escrita a mano, o sea que la unica forma en que una marca entro al catalogo despues de la siembra es que alguien escribiera SQL. Eso es exactamente lo que la fila pide reemplazar. Y AGREGA UN DATO QUE LA eq 83 NO TENIA: agregar una marca hoy no es solo "no hay superficie", es que la operacion REAL que se hizo la ultima vez fue una migracion aplicada por un carril de base. Sea cual sea la respuesta sobre quien aprueba, el paso final aterriza en una migracion - que es lo que el diseño ya proponia - y ahora eso tiene un precedente medido en vez de ser una suposicion de arquitectura. RECORDATORIO QUE NO CAMBIA: agregar una marca son DOS escrituras, lookupOptions mas vocabularyScopeMembers, porque terraMarcaImplante SI tiene scope en modo include con 19 miembros. La cabecera de terra_35 dice lo contrario y era cierta cuando se escribio.
-
coder-terra-cc
-
coder02-terra-ca
-
coder02-terra-cc
-
DECIDED - DEFAULT SET, CLIENT MAY STILL OVERRIDE. Marker: T141-dec-mtwz04. Decided by the terra PM lane 2026-09-11 under Elazar's 09:27 ART standing delegation. Not Elazar's wording. STATE OF THE CLIENT ANSWER, checked not assumed: consulta 4 of /data-venus/evolutiva/terra/pm/docs/client-communications/2026-08-29-terra-consultas-para-puia.md asks "Quien aprueba una marca nueva?" and offers "un nombre o un cargo. Puede ser vos, o todos los docentes." THE "Tu respuesta:" FIELD IS EMPTY - as are all six in that document. The client has not answered, and the row has been waiting on a reply that never came. THE APPROVER SET IS CATALOG_ROLES (src/lib/route-access.ts:66 - client, docente, jtp, bedel, admin), i.e. the set that already administers the catalogue on /cirugias. WHY THIS IS SAFE TO DECIDE WITHOUT HIM: both options he offered himself are INSIDE that set. "Puede ser vos" is the client role; "o todos los docentes" is the docente role. Whichever of the two he eventually picks, CATALOG_ROLES already admits it, so this default cannot contradict his answer - it can only be narrower afterwards, and narrowing is a one-line change to a constant. Inventing a NEW role set for approvals would have been the unsafe move, and is what kept this row blocked. WHAT THIS UNBLOCKS: the approval step can now be built. Points 1 and 2 of the row were already resolved; the only missing input was a named role set, and it is named. WHAT IT DOES NOT DECIDE: whether a rejected suggestion is visible to the alumno who made it, and whether an approved marca is retroactively linked to the practica that suggested it. Those are implementation questions for whoever builds it, not policy - decide them in the build and write them on the row.
-
policy decided T141-dec-mtwz04: approver set is CATALOG_ROLES; approval step is buildable
-
CORRECTION TO T141-dec-mtwz04, SAME AUTHORITY, NARROWER RESULT. Marker: T141-dec-mtwz07. This supersedes the approver set in the previous event; everything else in it stands. WHAT I GOT WRONG: I wrote "the client has not answered, the Tu respuesta field is empty". That is true of the markdown copy at /data-venus/evolutiva/terra/pm/docs/client-communications/2026-08-29-terra-consultas-para-puia.md, and it is NOT the state of the question. This row's own title and description record that the client ANSWERED consulta 4 with "Todos los docentes" - the answer came back through another channel and never landed in that file. I read one copy of a document and treated its blank field as the fact. WHAT THAT CHANGES: CATALOG_ROLES (client, docente, jtp, bedel, admin) is WIDER than what the client said. Adopting it would have widened a gate past an explicit client ruling, on the strength of my own misreading. The whole justification for the previous set was "both options he offered are inside it, so it cannot contradict him" - that argument only holds while the answer is unknown, and it is not unknown. THE APPROVER SET IS THE docente ROLE. That is the client's literal answer, it is a role rather than a person so it is implementable, and terra already gates surfaces on it. Point 2 of this row already resolved the SUGGESTION side as requireAppUserAction docente, so the two halves now use the same role and the workflow is internally consistent. WHO IS NOT IN IT, stated so nobody re-widens it by accident: jtp, bedel, admin and client do not approve a marca THROUGH THE APP. admin and bedel keep the path they already use and have always used - a migration written by the DB lane, which is how all 19 current marcas and the four ESPE additions actually got there (measured on this row 2026-09-01). Puia is covered: the catedra head holds docente AND client on one account. STILL NOT DECIDED, and still not mine - the two the row correctly flags: whether ANY docente approving is enough or it needs a specific one, and whether a pending marca is usable on the practica that proposed it or blocks the save. Neither blocks building the surface; both must be answered before the approve action goes live, and "todos" reading as any-of is an assumption to confirm with Puia, not a conclusion.
-
coder-terra-cc
-
Two product rulings owed by Elazar: (a) is any single docente's approval enough for a new marca; (b) is a pending marca usable on the practica that proposed it, or does it block the save? The build reuses the correctionRequests rail; the catalog write is a db-terra lane migration. Triage 2026-10-04 coder-terra-cc.
-
RULED 2026-10-06 (blackboard bb-pmmaster-evolutiva-cc-muw8o371-0prqak, 4/4). Separated from the practica-authorship cluster on all three peers' objection to my framing, and they were right. WHAT THIS ROW ACTUALLY IS. I had called a marca suggestion 'just another claim awaiting evaluation', which made it look derivative of the authorship ruling. It is not. Its real content: may one actor unilaterally extend a SHARED CONTROLLED VOCABULARY that every future practica then references? That is CATALOGUE GOVERNANCE, and it needs the same answer whichever way the authorship question goes, because the catalogue is shared either way. The claim is cheap; the CATALOGUE ENTRY is the expensive part - it becomes a selectable clinical vocabulary item that other records aggregate on, and an author's claim cannot approve it. RULING (a) - APPROVAL. Build the suggestion surface that does not exist. Any docente may approve a new marca; that is literally what the client answered ('todos los docentes') and nobody built it. One docente's approval is enough. I am NOT requiring a quorum: the client stated the population, not a threshold, and inventing a threshold they did not ask for would block the surface on a mechanic nobody has specified. STILL TO SPECIFY, and db-terra-ca is right that it is not free: membership and abstention mechanics - who counts as a docente for this purpose at approval time, resolved from the live role table and not from a snapshot. Decide that in the build, do not escalate it. RULING (b) - IS A PENDING MARCA SELECTABLE. YES, selectable - but stored as the PENDING ID, never as free text. This is where I change my own position: I initially ruled pending NOT selectable, to keep an unvalidated value out of a shared vocabulary. coder-pluto-cc's version is better and I am taking it. Not-selectable forces the author to either pick a wrong marca or drop true information - the same false-precision failure I refused on VENUS-11 hours earlier, and I was about to recommit it here. Selectable-but-pending keeps the record true AND keeps the catalogue clean, because a pending id can be merged into an approved marca or rejected later with every referencing record following the merge. Free text is the thing that cannot be reconciled, and that is what the rule must forbid. So: a pending marca renders with an explicit provisional state in the UI (the 'lista provisoria' shape already used for campo provenance), reports can distinguish approved from pending, and nothing aggregates a pending value as if it were approved. Lookup-backed per the fleet rule: stable value code, no native enum, no CHECK constraint on the label set. Tier: Class-S for the surface, Class-A for the lookup-state DDL if it adds a state column - schema diff to audit either way.
-
Ruled 2026-10-06 by pmmaster-evolutiva-cc on a 4/4 blackboard - did not need Elazar. Any docente may approve; pending marcas ARE selectable but stored as the pending id, never free text. See the decision event; I reversed my own initial not-selectable position.