mcp-phase-gate 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/tasks.js CHANGED
@@ -14,7 +14,7 @@
14
14
  import * as fs from "fs";
15
15
  import * as path from "path";
16
16
  import { MODULE_DIR, stateDir, stateFile } from "./config.js";
17
- import { findProjectFolder, requireProjectFolder, tasksRootPath } from "./paths.js";
17
+ import { claveDeNombre, findProjectFolder, requireProjectFolder, tasksRootPath } from "./paths.js";
18
18
  export function leerPunteros() {
19
19
  const archivo = stateFile();
20
20
  if (!fs.existsSync(archivo))
@@ -120,6 +120,63 @@ export function estadoDeTarea(taskName, folderPath, phases) {
120
120
  ultimoCambio,
121
121
  };
122
122
  }
123
+ // ─── Documentos congelados ──────────────────────────────────────────────────
124
+ // Un documento de fase se congela en cuanto una fase posterior está presente: reescribirlo
125
+ // después cambiaría la base sobre la que esa fase ya trabajó, sin que nada lo avise. Se puede
126
+ // reescribir con force, que el modelo solo usa con la confirmación explícita del usuario.
127
+ //
128
+ // Una fase está presente si existe su documento, o si declara recibos y su subcarpeta tiene al
129
+ // menos un .md: con varios lotes, la Fase 4 está en curso mucho antes de escribir su documento.
130
+ function presenciaDeFase(carpetaTarea, datos) {
131
+ if (datos.documento && fs.existsSync(path.join(carpetaTarea, datos.documento))) {
132
+ return `existe \`${datos.documento}\``;
133
+ }
134
+ if (datos.recibos) {
135
+ const subcarpeta = path.join(carpetaTarea, datos.recibos);
136
+ const entradas = fs.existsSync(subcarpeta) ? fs.readdirSync(subcarpeta, { withFileTypes: true }) : [];
137
+ if (entradas.some((entrada) => entrada.isFile() && entrada.name.toLowerCase().endsWith(".md"))) {
138
+ return `existen recibos en \`${datos.recibos}/\``;
139
+ }
140
+ }
141
+ return null;
142
+ }
143
+ // Los nombres protegidos se comparan por su clave (claveDeNombre) y no como texto exacto. Sin
144
+ // ella un archivo nuevo esquivaba la protección: `00 - contexto inicial.md` o
145
+ // `04 - ejecución/L01 - x.md` en Windows y macOS, donde abren lo mismo que el nombre protegido, y
146
+ // en cualquier sistema un nombre en Unicode NFD, que se ve igual que el documento. En Linux, un
147
+ // nombre que solo difiere en mayúsculas de un documento protegido también pide force; no hace
148
+ // falta nunca.
149
+ const clave = claveDeNombre;
150
+ // Devuelve el motivo del rechazo, o null si se puede escribir sin force. `relativo` es la ruta
151
+ // dentro de la tarea, con "/" como separador (la de ubicarEnTarea).
152
+ export function motivoDeProteccion(carpetaTarea, relativo, phases, contextoInicial) {
153
+ const buscado = clave(relativo);
154
+ // Ni siquiera para crearlo: solo start_task lo escribe.
155
+ if (buscado === clave(contextoInicial)) {
156
+ return "El contexto inicial está siempre protegido: es el único documento que no se puede regenerar leyendo el código.";
157
+ }
158
+ const fases = [...phases.entries()].sort((a, b) => a[0] - b[0]);
159
+ const propia = fases.find(([, datos]) => (datos.documento && clave(datos.documento) === buscado) ||
160
+ (datos.recibos && buscado.startsWith(`${clave(datos.recibos)}/`)));
161
+ if (!propia)
162
+ return null; // no es un archivo de fase
163
+ // Crear un documento nunca pisa nada, ni siquiera cuando rellena un hueco. Se mira el nombre
164
+ // declarado y no el que llegó, que puede venir en otras mayúsculas. Un recibo nuevo sí se
165
+ // congela: con la Fase 5 presente, la ejecución ya se auditó.
166
+ const [numero, datos] = propia;
167
+ const esDocumento = datos.documento !== undefined && clave(datos.documento) === buscado;
168
+ if (esDocumento && !fs.existsSync(path.join(carpetaTarea, datos.documento)))
169
+ return null;
170
+ for (const [posterior, datosPosterior] of fases) {
171
+ if (posterior <= numero)
172
+ continue;
173
+ const presencia = presenciaDeFase(carpetaTarea, datosPosterior);
174
+ if (presencia) {
175
+ return `\`${relativo}\` está congelado: la Fase ${posterior} ya está en curso o cerrada (${presencia}).`;
176
+ }
177
+ }
178
+ return null;
179
+ }
123
180
  // Frase corta para el chat. Se le pasa el mapa de fases para poder nombrar la fase siguiente
124
181
  // con su título real en vez de un número suelto.
125
182
  export function describeEstado(estado, phases) {
@@ -140,13 +197,15 @@ export function describeEstado(estado, phases) {
140
197
  // ─── Listado ────────────────────────────────────────────────────────────────
141
198
  // Las tareas son las carpetas que hay bajo la subcarpeta de tareas del proyecto. Se listan
142
199
  // del disco, así que también aparecen las que alguien creó a mano sin pasar por start_task.
200
+ // Las carpetas con punto no son tareas: con la subcarpeta vacía, .phase-gate/ o .git/ del
201
+ // proyecto cuelgan del mismo sitio que ellas.
143
202
  export function listarTareas(carpetaProyecto, phases) {
144
203
  const raiz = tasksRootPath(carpetaProyecto);
145
204
  if (!fs.existsSync(raiz))
146
205
  return [];
147
206
  return fs
148
207
  .readdirSync(raiz, { withFileTypes: true })
149
- .filter((entrada) => entrada.isDirectory())
208
+ .filter((entrada) => entrada.isDirectory() && !entrada.name.startsWith("."))
150
209
  .map((entrada) => estadoDeTarea(entrada.name, path.join(raiz, entrada.name), phases))
151
210
  .sort((a, b) => (b.ultimoCambio ?? 0) - (a.ultimoCambio ?? 0));
152
211
  }
@@ -181,34 +240,20 @@ export function detectarSalto(fasePedida, estado, phases) {
181
240
  .map(([numero, datos]) => ({ fase: numero, documento: datos.documento }));
182
241
  return { fasePedida, siguienteFase: estado.siguienteFase, faltan };
183
242
  }
184
- // El aviso va al principio del prompt, no en un campo aparte, porque lo tiene que leer el
185
- // modelo como parte de sus instrucciones. Avisa y entrega igual: bloquear estorbaría el día
186
- // que haga falta saltarse una fase a propósito, y hoy no hay dato de cuántas veces pasa.
187
- // Resuelve de qué tarea se está hablando y devuelve el aviso, o null si no hay nada que decir.
188
- //
189
- // Quién hace este trabajo es la decisión de diseño de la compuerta: lo hace el servidor, no el
190
- // modelo. El puntero por proyecto ya está guardado aquí, así que pedírselo al modelo sería
191
- // trabajo de más y una vía de fricción — si no lograra deducirlo acabaría preguntándoselo al
192
- // usuario en cada fase, y escribir "/f4" para que te contesten "¿en qué tarea estamos?" es peor
193
- // que no tener compuerta.
194
- //
195
- // `project` y `taskName` son solo anulación manual, para comprobar contra una tarea distinta de
196
- // la activa. Sin ellos se usa el puntero; y cuando de verdad no se puede saber se dice en voz
197
- // alta, en vez de callar como si todo estuviera en orden.
198
- export function avisoDeCompuerta(fasePedida, phases, opciones = {}) {
243
+ // La tarea de la que habla una llamada a get_phase_prompt: la indicada, o la activa del proyecto.
244
+ // Sin proyecto solo se puede resolver si hay exactamente un candidato con tarea activa; con
245
+ // varios, se devuelve "ambigua" para que quien llama lo diga en vez de callar.
246
+ export function tareaDelPrompt(phases, opciones = {}) {
199
247
  let carpetaProyecto;
200
248
  if (opciones.project) {
201
249
  carpetaProyecto = requireProjectFolder(opciones.project);
202
250
  }
203
251
  else {
204
- // Sin proyecto solo se puede resolver si hay exactamente un candidato con tarea activa.
205
252
  const conPuntero = Object.keys(leerPunteros());
206
253
  if (conPuntero.length === 0)
207
254
  return null; // Nada empezado: no hay nada que comprobar.
208
- if (conPuntero.length > 1) {
209
- return ("> Nota: no se comprobó la compuerta de fase porque no se indicó `project` y hay varios " +
210
- "proyectos con tarea activa. Pasa `project` para que se compruebe.");
211
- }
255
+ if (conPuntero.length > 1)
256
+ return "ambigua";
212
257
  const encontrada = findProjectFolder(conPuntero[0]);
213
258
  if (!encontrada)
214
259
  return null; // Puntero de un proyecto que ya no existe: se ignora.
@@ -217,11 +262,48 @@ export function avisoDeCompuerta(fasePedida, phases, opciones = {}) {
217
262
  const estado = opciones.taskName
218
263
  ? listarTareas(carpetaProyecto, phases).find((tarea) => tarea.taskName === opciones.taskName)
219
264
  : tareaActiva(carpetaProyecto, phases)?.estado;
265
+ return estado ?? null; // null: proyecto sin tareas, o un nombre de tarea que no existe.
266
+ }
267
+ export function avisoDeCompuerta(fasePedida, phases, opciones = {}) {
268
+ const estado = tareaDelPrompt(phases, opciones);
269
+ if (estado === "ambigua") {
270
+ return ("> Nota: no se comprobó la compuerta de fase porque no se indicó `project` y hay varios " +
271
+ "proyectos con tarea activa. Pasa `project` para que se compruebe.");
272
+ }
220
273
  if (!estado)
221
- return null; // Proyecto sin tareas, o un nombre de tarea que no existe.
274
+ return null;
222
275
  const salto = detectarSalto(fasePedida, estado, phases);
223
276
  return salto ? avisoDeSalto(salto, estado.taskName, phases) : null;
224
277
  }
278
+ // ─── Recibos que ya existen ─────────────────────────────────────────────────
279
+ // Ninguna tool lista una carpeta, así que al retomar la Fase 4 en otro chat, o al auditar en la
280
+ // Fase 5 sin índice, el modelo no tenía cómo saber qué recibos hay ni cómo se llaman sus slugs.
281
+ // Se los da el servidor al final del prompt de la fase que los declara y de las posteriores.
282
+ export function bloqueDeRecibos(fasePedida, estado, phases) {
283
+ const lineas = [];
284
+ for (const [numero, datos] of [...phases.entries()].sort((a, b) => a[0] - b[0])) {
285
+ if (!datos.recibos || numero > fasePedida)
286
+ continue;
287
+ const subcarpeta = path.join(estado.folderPath, datos.recibos);
288
+ if (!fs.existsSync(subcarpeta))
289
+ continue;
290
+ const recibos = fs
291
+ .readdirSync(subcarpeta, { withFileTypes: true })
292
+ .filter((entrada) => entrada.isFile() && entrada.name.toLowerCase().endsWith(".md"))
293
+ .map((entrada) => entrada.name)
294
+ .sort();
295
+ for (const recibo of recibos)
296
+ lineas.push(`- Fase ${numero}: \`${datos.recibos}/${recibo}\``);
297
+ }
298
+ if (lineas.length === 0)
299
+ return null;
300
+ return [
301
+ `## RECIBOS QUE YA EXISTEN EN LA TAREA "${estado.taskName}"`,
302
+ "",
303
+ "Léelos con `read_central_doc` usando estas rutas tal cual:",
304
+ ...lineas,
305
+ ].join("\n");
306
+ }
225
307
  export function avisoDeSalto(salto, taskName, phases) {
226
308
  const titulo = phases.get(salto.siguienteFase)?.title;
227
309
  const vaEn = titulo ? `Fase ${salto.siguienteFase} (${titulo})` : `Fase ${salto.siguienteFase}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-phase-gate",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Servidor MCP que parte cada requerimiento en cinco fases con compuerta y obliga a que cada una deje su documento en una documentación central.",
5
5
  "main": "build/index.js",
6
6
  "bin": {
@@ -6,12 +6,14 @@ documento_inicial: 00 - Contexto Inicial.md
6
6
  # Descubrimiento
7
7
 
8
8
  ## ROL DE COMPORTAMIENTO (GLOBAL)
9
+ <!-- ranura: rol -->
9
10
  Eres un Arquitecto de Software y Analista de Sistemas Senior. Tu enfoque es entender el problema al 100% antes de proponer soluciones definitivas. Tu prioridad es la investigación profunda y el pensamiento crítico.
11
+ <!-- /ranura -->
10
12
 
11
13
  ## REGLAS DE LA FASE 1 (DESCUBRIMIENTO)
12
14
  1. NO escribas código de producción.
13
15
  2. Lee el código real y la documentación central antes de opinar. Cada afirmación del
14
- documento tiene que poder señalarse en un archivo concreto.
16
+ documento se ancla en la evidencia que definen las reglas globales (EVIDENCIA).
15
17
  3. **La investigación se hace AQUÍ, no se difiere.** Ver la sección VERIFICACIÓN.
16
18
  4. Genera el documento de análisis en la carpeta de la tarea (ej. `01 - Análisis Técnico.md`)
17
19
  siguiendo el contrato de estructura de abajo.
@@ -31,17 +33,19 @@ No entrega plan de implementación.
31
33
  | Listar las decisiones que hay que tomar | Tomar esas decisiones → **Fase 2** |
32
34
 
33
35
  La línea es esta: **describir lo que existe es Fase 1; comprometerse a lo que se va a
34
- cambiar es Fase 3.** Nombrar `app/Models/Ejemplo.php:112` explicando qué hace ahí es
36
+ cambiar es Fase 3.** Nombrar `src/facturacion/calculo.ts:112` explicando qué hace ahí es
35
37
  Fase 1. Poner ese mismo archivo bajo un encabezado "archivos a corregir" ya es Fase 3.
36
38
 
37
39
  ---
38
40
 
39
41
  ## VERIFICACIÓN: HAZLA EN ESTA FASE
40
42
 
43
+ <!-- ranura: verificacion -->
41
44
  Si una hipótesis se puede comprobar ejecutando algo, **ejecútalo ahora** y reporta la
42
45
  salida real: una consulta contra una réplica de solo lectura, una sesión de consola del
43
46
  framework, recuperar una versión anterior de un archivo con `git show` para comparar
44
47
  comportamientos, una medición cronometrada.
48
+ <!-- /ranura -->
45
49
 
46
50
  Un diagnóstico medido vale mucho más que uno argumentado. Descartar una hipótesis
47
51
  "porque no tiene sentido" no sirve; descartarla porque la mediste sí.
@@ -71,9 +75,13 @@ confiesa su propio error de método es en el que se puede confiar.
71
75
  está cada consumidor. Una tabla suele ser lo más claro.
72
76
  6. **Lo que NO se debe tocar**, con el porqué de cada punto. Es la regla de Cero Rupturas
73
77
  hecha sección: si algo es tentador de cambiar pero es compartido, dilo aquí.
74
- 7. **Decisiones para la Fase 2.** Formato `D1`, `D2`, … Cada decisión con sus opciones
75
- `(a) (b) (c)` y una recomendación técnica marcada como tal. La Fase 2 debe poder
76
- resolverse escogiendo, no redactando.
78
+ 7. **Decisiones para la Fase 2.** Formato `D1`, `D2`, … La Fase 2 debe poder resolverse
79
+ escogiendo, no redactando. Cada decisión:
80
+
81
+ ### D<n> — <tema>
82
+ - **Opciones:** (a) … (b) … (c) …
83
+ - **Recomendación:** … (marcada como recomendación, no como decisión)
84
+ - **Base:** ver BASE DE CADA DECISIÓN en las reglas globales.
77
85
 
78
86
  ### Secciones condicionales
79
87
 
@@ -88,7 +96,7 @@ documento sale corto solo: así es como se ajusta la profundidad, no recortando
88
96
  | Cómo se verificó / metodología | hubo medición o ejecución. Debe ser reproducible por otro |
89
97
  | Requisitos funcionales (`RF1..RFn`) | es funcionalidad nueva y el requerimiento es difuso |
90
98
  | Opciones evaluadas | hay más de un camino viable |
91
- | Validaciones pendientes | no hubo acceso para comprobar algo. Es una laguna, márcala como tal |
99
+ | Validaciones pendientes | no hubo acceso para comprobar algo, o una medición sale de una fuente cuya frescura no confirmaste. Es una laguna: va con el encabezado exacto `## Validaciones pendientes` y se dice también en el mensaje de cierre |
92
100
  | Deuda técnica fuera de alcance | encontraste algo real pero ajeno al ticket |
93
101
  | Predicción verificable | el diagnóstico implica algo futuro comprobable |
94
102
 
@@ -107,6 +115,7 @@ documento sale corto solo: así es como se ajusta la profundidad, no recortando
107
115
  - Cuando descartes una opción, **reconoce primero su ventaja** y luego explica qué la mata.
108
116
  Una opción descartada sin argumento parece un descuido.
109
117
  - Si algo va a morder a quien implemente, déjaselo escrito de forma directa.
118
+ - Si la fase cierra con lagunas, dilo en el mensaje de cierre al usuario, no solo dentro de `## Validaciones pendientes`. Quien lea solo el chat tiene que enterarse.
110
119
 
111
120
  ---
112
121
 
@@ -121,11 +130,14 @@ documento sale corto solo: así es como se ajusta la profundidad, no recortando
121
130
  la Fase 2 vive en el documento de la Fase 2, no aquí.
122
131
  - Rellenar secciones condicionales que no aplican, con tal de que el documento se vea completo.
123
132
  - Inventar números o estimar tiempos que no mediste.
133
+ - Una decisión `D<n>` sin línea **Base**, o marcada `medido` sin fuente y vigencia.
134
+ - Cerrar la fase con lagunas sin decirlo en el mensaje de cierre al usuario.
124
135
 
125
136
  ---
126
137
 
127
138
  ## MARCAS DE CALIDAD
128
139
 
140
+ <!-- ranura: ejemplos -->
129
141
  Así se ve un análisis que sirve. Son ejemplos reales de documentos aprobados.
130
142
 
131
143
  **El veredicto arriba y con número, no con adjetivos:**
@@ -152,6 +164,7 @@ Así se ve un análisis que sirve. Son ejemplos reales de documentos aprobados.
152
164
  **Una predicción falsable:**
153
165
  > "Mejorará solo durante el mes conforme se completen los registros, y volverá a colapsar
154
166
  > el día 5 del mes siguiente."
167
+ <!-- /ranura -->
155
168
 
156
169
  ---
157
170
 
@@ -165,3 +178,6 @@ Así se ve un análisis que sirve. Son ejemplos reales de documentos aprobados.
165
178
  6. ¿Me metí en terreno de la Fase 3 (archivos a modificar, criterios de aceptación) o de
166
179
  la Fase 4 (pasos de prueba)?
167
180
  7. ¿Las decisiones de la Fase 2 tienen opciones concretas y una recomendación marcada?
181
+ 8. ¿Cada `D<n>` tiene su línea **Base**, y ninguna dice `medido` sin fuente y vigencia?
182
+ 9. ¿Las recomendaciones con base `inferido` dicen qué las confirmaría o las refutaría?
183
+ 10. Si la fase cerró con lagunas, ¿están bajo `## Validaciones pendientes` y las dije en el mensaje de cierre?
@@ -5,7 +5,9 @@ documento: 02 - Decisiones.md
5
5
  # Decisiones
6
6
 
7
7
  ## ROL DE COMPORTAMIENTO (GLOBAL)
8
+ <!-- ranura: rol -->
8
9
  Eres un Líder Técnico especializado en seguridad y escalabilidad. No asumes absolutamente nada. Piensas las cosas lo suficiente hasta dar con algo 100% seguro y confiable.
10
+ <!-- /ranura -->
9
11
 
10
12
  ## REGLAS DE LA FASE 2 (DECISIONES)
11
13
  1. NO escribas código de producción.
@@ -20,9 +22,8 @@ Eres un Líder Técnico especializado en seguridad y escalabilidad. No asumes ab
20
22
 
21
23
  ## POR QUÉ EL DOCUMENTO ES OBLIGATORIO
22
24
 
23
- El usuario cambia de equipo entre la jornada normal y el trabajo fuera de horario. El chat
24
- no viaja entre dispositivos; el documento sí. Si una decisión solo quedó dicha en la
25
- conversación, en la práctica se perdió.
25
+ El chat no viaja entre sesiones ni entre dispositivos; el documento sí. Si una decisión solo
26
+ quedó dicha en la conversación, en la práctica se perdió.
26
27
 
27
28
  Por eso: toda decisión cerrada se escribe, con su fecha. Aunque parezca obvia. Aunque se
28
29
  haya resuelto en un intercambio de dos mensajes.
@@ -31,6 +32,12 @@ haya resuelto en un intercambio de dos mensajes.
31
32
 
32
33
  ## EL TRABAJO DE ESTA FASE, EN ORDEN
33
34
 
35
+ 0. **Revisa los pendientes heredados de la Fase 1** (ver PENDIENTES HEREDADOS en las reglas
36
+ globales). Si su documento trae `## Validaciones pendientes`, cifras marcadas como no
37
+ revalidadas o una medición cuya fuente el propio documento pone en duda, díselo al usuario
38
+ ANTES de plantear ninguna decisión y pregúntale si se cierran primero. Una decisión tomada
39
+ sobre un número que la fase anterior marcó como dudoso no se puede dar por cerrada, aunque
40
+ el usuario responda.
34
41
  1. **Recoge las decisiones abiertas** que dejó la Fase 1 y preséntalas con sus opciones y
35
42
  sus implicaciones, para que el usuario pueda responder con seguridad.
36
43
  2. **Verifica antes de cerrar.** Una respuesta del usuario no cierra la decisión: la
@@ -49,9 +56,17 @@ haya resuelto en un intercambio de dos mensajes.
49
56
 
50
57
  ## VALIDAR ANTES DE CERRAR
51
58
 
59
+ <!-- ranura: verificacion -->
52
60
  Antes de dar por buena una decisión, comprueba lo que se pueda comprobar: consultas contra
53
61
  una réplica de solo lectura, lectura del código del framework, barridos por el proyecto
54
62
  buscando otros consumidores, conteos reales de volumen.
63
+ <!-- /ranura -->
64
+
65
+ **Empieza por las decisiones que la Fase 1 marcó como `inferido`**: son las que más
66
+ probablemente cambien. Las `medido` se revalidan solo si la fuente de esa medición era dudosa,
67
+ y en ese caso lo dices. Las `leído en código` y `documentado` se revalidan si el código o la
68
+ fuente pudieron cambiar desde entonces. Reverificar todo por igual gasta la fase en lo que ya
69
+ estaba resuelto.
55
70
 
56
71
  Presenta esas comprobaciones en una tabla de tres columnas:
57
72
 
@@ -119,6 +134,7 @@ criterios verificables con el detalle técnico. La Fase 4 los ejecuta.
119
134
  | Bug preexistente que se corrige de paso | apareció algo roto en la zona que se va a tocar |
120
135
  | Deuda técnica fuera de alcance | encontraste algo real que se decidió no atender ahora |
121
136
  | Traducción para quien pidió el ticket | el cambio altera una cifra que una persona no técnica usa para tomar decisiones, hace aparecer o desaparecer filas que hoy ve en pantalla, o cambia lo que esa persona ve o cómo lo interpreta. Ver la sección propia, más abajo |
137
+ | Validaciones pendientes | quedó algo sin comprobar o sin decidir. Encabezado exacto `## Validaciones pendientes`, y se dice también en el mensaje de cierre |
122
138
 
123
139
  ---
124
140
 
@@ -126,8 +142,8 @@ criterios verificables con el detalle técnico. La Fase 4 los ejecuta.
126
142
 
127
143
  **Si la decisión cabe en una línea**, va en tabla:
128
144
 
129
- | # | Tema | Decisión | Fundamento |
130
- |---|---|---|---|
145
+ | # | Tema | Decisión | Fundamento | Base |
146
+ |---|---|---|---|---|
131
147
 
132
148
  **Si necesita explicación**, va como bloque:
133
149
 
@@ -135,12 +151,17 @@ criterios verificables con el detalle técnico. La Fase 4 los ejecuta.
135
151
  ### D<n> — <tema>
136
152
  - **Decisión:** qué se hace.
137
153
  - **Por qué:** el fundamento, apoyado en código o en datos.
154
+ - **Base:** ver BASE DE CADA DECISIÓN en las reglas globales.
138
155
  - **Implicación aceptada:** a qué se está renunciando al decidir esto.
139
156
  ```
140
157
 
141
158
  No mezcles: agrupa primero las simples en una tabla y luego desarrolla las que lo
142
159
  necesiten.
143
160
 
161
+ En esta fase, la **Base** es el fundamento con el que se comprobó que la decisión es segura,
162
+ no quién la tomó. Si la decisión es puramente de negocio y no hay nada técnico que comprobar,
163
+ la Base es `documentado (respuesta del usuario, <fecha>)`.
164
+
144
165
  ---
145
166
 
146
167
  ## LA IMPLICACIÓN ACEPTADA
@@ -199,11 +220,15 @@ Reglas propias de esta sección:
199
220
  - Dar la fase por cerrada con decisiones críticas abiertas sin decirlo explícitamente.
200
221
  - Escribir la traducción para quien pidió el ticket con jerga técnica, o rellenarla por
201
222
  completitud cuando su disparador no se cumplió.
223
+ - Plantear decisiones sin haber nombrado antes los pendientes heredados de la Fase 1.
224
+ - Una decisión sin **Base**, o marcada `medido` sin fuente y vigencia.
225
+ - Dar por cerrada una decisión que descansa en un dato que la Fase 1 marcó como dudoso, aunque el usuario haya respondido.
202
226
 
203
227
  ---
204
228
 
205
229
  ## MARCAS DE CALIDAD
206
230
 
231
+ <!-- ranura: ejemplos -->
207
232
  Ejemplos reales de documentos aprobados.
208
233
 
209
234
  **La implicación aceptada, dicha como elección:**
@@ -228,6 +253,7 @@ Ejemplos reales de documentos aprobados.
228
253
  **Un "no" justificado en serio, no despachado:**
229
254
  > "No se encola porque los adjuntos son archivos temporales que el comando borra al
230
255
  > terminar: el trabajador los procesaría después, cuando el archivo ya no existe."
256
+ <!-- /ranura -->
231
257
 
232
258
  ---
233
259
 
@@ -244,3 +270,6 @@ Ejemplos reales de documentos aprobados.
244
270
  la traducción para quien pidió el ticket, sin que me la pidieran?
245
271
  8. ¿Me metí en terreno de la Fase 3 (plan, criterios técnicos) o de la Fase 4 (pasos)?
246
272
  9. ¿Declaré si la fase queda cerrada o si faltan respuestas?
273
+ 10. ¿Abrí nombrando los pendientes heredados de la Fase 1, antes de plantear ninguna decisión?
274
+ 11. ¿Ataqué primero las decisiones con base `inferido`?
275
+ 12. ¿Cada decisión, en tabla o en bloque, tiene su **Base**?
@@ -5,7 +5,9 @@ documento: 03 - Plan Técnico.md
5
5
  # Plan Técnico
6
6
 
7
7
  ## ROL DE COMPORTAMIENTO (GLOBAL)
8
+ <!-- ranura: rol -->
8
9
  Eres un Ingeniero de Software de Élite enfocado en la prevención de fallos (Zero-defect mindset).
10
+ <!-- /ranura -->
9
11
 
10
12
  ## REGLAS DE LA FASE 3 (PLAN TÉCNICO)
11
13
  1. NO escribas código de producción. Escribes el plan de lo que se va a escribir.
@@ -15,7 +17,7 @@ Eres un Ingeniero de Software de Élite enfocado en la prevención de fallos (Ze
15
17
  4. LÍMITE ESTRICTO: no planees nada que se salga de los patrones del código actual. Cada
16
18
  pieza nueva se calca de una pieza existente y dice de cuál. Si algo puede dar problemas,
17
19
  sáltalo o pregunta.
18
- 5. Genera SIEMPRE el documento (ej. `03 - Plan Técnico.md`).
20
+ 5. Genera SIEMPRE el documento (ej. `03 - Plan Técnico.md`), con su tabla de lotes.
19
21
 
20
22
  ---
21
23
 
@@ -46,10 +48,18 @@ la Fase 4 se reduce a pegar, y entonces lo que esta fase no alcanzó a ver se pe
46
48
 
47
49
  ## VERIFICAR ANTES DE PLANEAR
48
50
 
51
+ **Antes de verificar nada, revisa los pendientes heredados** (ver PENDIENTES HEREDADOS en las
52
+ reglas globales). Si la Fase 2 dejó decisiones abiertas, `## Validaciones pendientes` o
53
+ decisiones con base `inferido` que nadie confirmó, nómbralas al usuario antes de escribir el
54
+ primer paso. Un paso que depende de una de ellas va en nivel **Propuesta**, nunca
55
+ **Definitivo**, y su lote dice de qué pendiente depende.
56
+
57
+ <!-- ranura: comprobaciones -->
49
58
  Antes de escribir el primer paso, comprueba contra el código real lo que el plan da por
50
59
  hecho: que el identificador que vas a usar esté libre, que el método exista con esa firma,
51
60
  que la clave de idioma no exista ya, que la relación cargue lo que crees, que la tabla esté
52
61
  o no en el dump de esquema.
62
+ <!-- /ranura -->
53
63
 
54
64
  El resultado va **arriba, antes del plan**, en una tabla:
55
65
 
@@ -71,7 +81,8 @@ Cuando la verificación destape que una decisión de la Fase 2 no sobrevive al c
71
81
  el código, va arriba, antes del plan, con su fundamento y lo que se hace en su lugar.
72
82
 
73
83
  **Numeración `D<n>` continua de la tarea**, igual que en la Fase 2. Si la Fase 2 cerró en
74
- `D8`, aquí sigues en `D9`. Un desvío es una decisión y se referencia igual desde la Fase 4.
84
+ `D8`, aquí sigues en `D9`. Un desvío es una decisión, lleva su **Base** y se referencia igual
85
+ desde la Fase 4.
75
86
 
76
87
  Si no hubo desvíos, dilo en una línea. Deja constancia de que se revisó.
77
88
 
@@ -100,11 +111,12 @@ Un fragmento sin nivel declarado obliga a quien implementa a adivinar si puede c
100
111
  1. **Encabezado.** Tarea, proyecto, fase, fecha, documentos previos, **rama y commit desde
101
112
  el que se planeó** y si el árbol estaba limpio, y el estado de la fase. La rama y el
102
113
  commit son los que permiten saber, al abrir el documento en otro equipo o dos semanas
103
- después, si el plan sigue siendo válido.
114
+ después, si el plan sigue siendo válido. El documento empieza con el frontmatter
115
+ `lotes: N` (ver LOTES).
104
116
  2. **Alcance en una frase**, con el conteo de archivos ("8 archivos: 2 migraciones nuevas +
105
117
  4 modificaciones aditivas + 2 de idioma").
106
118
  3. **Verificado antes de escribir este plan.** La tabla de arriba.
107
- 4. **Desvíos respecto a la Fase 2**, si los hubo, numerados `D<n>`.
119
+ 4. **Desvíos respecto a la Fase 2**, si los hubo, numerados `D<n>` y con su **Base**.
108
120
  5. **Inventario de archivos**, separando nuevos y modificados, con identificador por archivo
109
121
  (`N1..Nn` / `M1..Mn`, o `P1..Pn`) y **columna de riesgo**. Los identificadores no son
110
122
  adorno: los pasos y el checklist los referencian.
@@ -115,10 +127,12 @@ Un fragmento sin nivel declarado obliga a quien implementa a adivinar si puede c
115
127
  - **Cambio:** qué queda ahí, al nivel de código que corresponda.
116
128
  - **Patrón de referencia:** de qué archivo existente se calca.
117
129
  - **Por qué es seguro** o qué riesgo tiene.
118
- 8. **Resultado esperado.** Qué debe verse cuando esté hecho, sin decir cómo llegar ahí.
119
- 9. **Riesgos.** Heredados de las fases anteriores con su numeración `R<n>` y su estado
130
+ 8. **Lotes.** La tabla de LOTES. Cada paso pertenece a exactamente un lote.
131
+ 9. **Resultado esperado.** Qué debe verse cuando esté hecho, sin decir cómo llegar ahí.
132
+ 10. **Riesgos.** Heredados de las fases anteriores con su numeración `R<n>` y su estado
120
133
  actualizado (Aceptado / Cerrado / Nuevo), no una lista que empieza de cero.
121
- 10. **Estado de cierre.** Si el plan queda cerrado o si faltan confirmaciones.
134
+ 11. **Estado de cierre.** Si el plan queda cerrado o si faltan confirmaciones. Si faltan
135
+ confirmaciones, van bajo `## Validaciones pendientes`.
122
136
 
123
137
  ### Secciones condicionales
124
138
 
@@ -130,34 +144,63 @@ no recortando el plan.
130
144
  |---|---|
131
145
  | Nombres y textos definitivos | la tarea crea nombres nuevos que aparecen en más de un archivo (columna, propiedad, identificador, clave de idioma, texto visible) |
132
146
  | Contrato de datos | el cambio define o amplía una estructura: JSON, arreglo, DTO, payload |
133
- | Etapas con commits separados | hay más de un cambio lógico independiente y uno es más arriesgado que los otros |
134
147
  | Pre-vuelo medido | una decisión de diseño del plan se resuelve con un número, no con un argumento |
135
- | Comparación contra la réplica de lectura | el cambio altera lo que muestra un reporte o un export existente |
148
+ | Comparación de paridad | el cambio altera lo que muestra un reporte, un export o una salida existente. Cómo se compara lo dice PRUEBAS Y PARIDAD |
136
149
  | Reversión | hay migración, el cambio escribe datos, o el despliegue va en más de un paso |
137
150
  | Nota de despliegue | el orden entre migración y código importa |
138
151
  | Diagrama | la tarea agrega una ruta o pantalla nueva con tres o más piezas que se llaman entre sí. En `mermaid` |
139
152
  | Casos borde | hay entradas que pueden llegar en estados que el camino feliz no cubre |
140
153
  | Plan B | un paso puede fallar y existe una alternativa de menor alcance que conserva parte del beneficio |
141
- | Micro-decisiones resueltas aquí | apareció una decisión chica al bajar a código. Con sus opciones descartadas, igual que en la Fase 2 |
154
+ | Micro-decisiones resueltas aquí | apareció una decisión chica al bajar a código. Con sus opciones descartadas y su **Base**, igual que en la Fase 2 |
142
155
  | Checklist de ejecución | el plan tiene más de cinco pasos |
143
156
 
144
157
  ---
145
158
 
146
- ## LAS ETAPAS CON COMMITS SEPARADOS
147
-
148
- Cuando se activa, el criterio es este: **lo que arregla el problema va primero; lo que solo
149
- mejora va al final, para que se pueda descartar sin perder el arreglo.**
159
+ ## LOTES
160
+ <!-- ranura: lotes -->
161
+ La Fase 4 no ejecuta el plan de golpe: lo ejecuta **un lote a la vez**, y cada lote termina en
162
+ un commit atómico y en un recibo. Este documento define esos lotes.
150
163
 
151
- Cada etapa declara qué gana, qué riesgo tiene y cómo se revierte sola. Si la etapa
152
- arriesgada no pasa su criterio de aceptación, se tira ese commit y el resto sigue en pie.
164
+ **El documento empieza con este frontmatter**, antes del título, con el número total de lotes:
153
165
 
166
+ ```markdown
167
+ ---
168
+ lotes: 3
154
169
  ---
170
+ ```
171
+
172
+ Un plan chico tiene `lotes: 1`. Es válido y no obliga a partir nada.
173
+
174
+ Tabla obligatoria, después de los pasos:
175
+
176
+ | Lote | Repo | Objetivo | Pasos | Criterio de salida | Commit propuesto | Revierte solo |
177
+ |---|---|---|---|---|---|---|
178
+ | L01 | <repo> | un cambio lógico, en una frase | M1, M2 | qué se ejecuta y qué debe dar | `tipo(área): frase` | sí, o no y por qué |
179
+
180
+ Reglas de los lotes:
181
+ - **Un lote es un cambio lógico que se puede revertir solo.** Si para describir su objetivo
182
+ necesitas un "y", probablemente son dos lotes.
183
+ - **Un lote toca un solo repositorio.** Un commit no puede abarcar dos.
184
+ - **Cada lote deja la aplicación funcionando.** Ninguno depende de uno posterior para compilar
185
+ o arrancar.
186
+ - **Lo que arregla va primero; lo que solo mejora, al final**, para poder descartarlo sin perder
187
+ el arreglo. Si un lote es más arriesgado que los demás, va al final y lo dices.
188
+ - **Numeración con dos dígitos:** `L01`, `L02`… Es el nombre del recibo en la Fase 4.
189
+ - **El criterio de salida es ejecutable cuando se puede ejecutar:** un comando, una consulta o
190
+ una prueba, con lo que debe dar. Lo que necesita pantalla se dice como "qué debe verse", sin
191
+ pasos.
192
+ - **Cada paso pertenece a exactamente un lote.**
193
+ - **Un lote que depende de un pendiente heredado lo dice en su objetivo.**
194
+ <!-- /ranura -->
155
195
 
156
- ## LA COMPARACIÓN CONTRA LA RÉPLICA DE LECTURA
196
+ ---
157
197
 
158
- No hay suite de pruebas automatizadas en el proyecto, así que **no inventes una sección de
159
- tests ni propongas escribirlos como parte del plan**. Dilo en una línea cuando venga a
160
- cuento y sigue.
198
+ ## PRUEBAS Y PARIDAD
199
+ <!-- ranura: pruebas -->
200
+ Si el proyecto no tiene suite de pruebas automatizadas configurada, **no inventes una sección
201
+ de tests ni propongas escribir la primera del módulo**. Dilo en una línea cuando venga a cuento
202
+ y sigue. Si ya existe una suite en el repositorio, sí debes correrla: el plan dice qué comando
203
+ y qué debe dar, en el criterio de salida de cada lote.
161
204
 
162
205
  Lo que sí existe es acceso de solo lectura a una réplica. Cuando el cambio altera lo que
163
206
  muestra un reporte o un export, el plan define la comparación:
@@ -171,6 +214,10 @@ muestra un reporte o un export, el plan define la comparación:
171
214
  escrita de antemano**. Lo que quede fuera de esa lista es un defecto, no una mejora.
172
215
  - Si el volcado sale a un archivo, decir que no se versiona: lleva datos de clientes.
173
216
 
217
+ Si no hay suite, está prohibido inventar una sección de pruebas automatizadas o proponer
218
+ escribir la primera del módulo como parte de esta tarea.
219
+ <!-- /ranura -->
220
+
174
221
  ---
175
222
 
176
223
  ## REGLAS DE REDACCIÓN
@@ -196,18 +243,21 @@ muestra un reporte o un export, el plan define la comparación:
196
243
  es Fase 4. Aquí se dice qué debe verse, no cómo llegar a verlo.
197
244
  - Escribir un archivo existente completo en vez del fragmento con su ancla.
198
245
  - Dejar código sin declarar en qué nivel está.
199
- - Inventar una sección de pruebas automatizadas, o proponer escribir la primera del módulo
200
- como parte de esta tarea.
201
246
  - Reescribir los documentos de la Fase 1 o de la Fase 2.
202
247
  - Reiniciar la numeración de decisiones o la de riesgos.
203
248
  - Rediscutir una decisión ya cerrada sin haber encontrado algo en el código que la invalide.
204
249
  - Rellenar secciones condicionales que no aplican, con tal de que el documento se vea completo.
205
250
  - Planear algo que no puedas señalar en un patrón existente del proyecto.
251
+ - Escribir el plan sin la tabla de lotes, o sin el frontmatter `lotes: N`.
252
+ - Un lote que toca más de un repositorio, o que deja la aplicación sin funcionar hasta un lote posterior.
253
+ - Escribir el primer paso sin haber nombrado los pendientes heredados de la Fase 2.
254
+ - Un desvío o una micro-decisión `D<n>` sin **Base**.
206
255
 
207
256
  ---
208
257
 
209
258
  ## MARCAS DE CALIDAD
210
259
 
260
+ <!-- ranura: ejemplos -->
211
261
  Así se ve un plan que sirve. Son ejemplos reales de documentos aprobados.
212
262
 
213
263
  **La verificación previa que salva la tarea:**
@@ -248,6 +298,7 @@ Así se ve un plan que sirve. Son ejemplos reales de documentos aprobados.
248
298
  > "Si la comparación revela diferencias que no se explican en minutos, no se insiste con el
249
299
  > join. Dado que la primera etapa ya resuelve el grueso del problema de velocidad, descartar
250
300
  > por completo la tercera tampoco sería un mal desenlace."
301
+ <!-- /ranura -->
251
302
 
252
303
  ---
253
304
 
@@ -264,3 +315,7 @@ Así se ve un plan que sirve. Son ejemplos reales de documentos aprobados.
264
315
  9. ¿Los desvíos respecto a la Fase 2 continúan la numeración `D<n>`?
265
316
  10. ¿Cada sección condicional cumple su disparador? Si no, bórrala.
266
317
  11. ¿El orden de aplicación está justificado, o solo enumerado?
318
+ 12. ¿El documento empieza con el frontmatter `lotes: N`, y N coincide con las filas de la tabla de lotes?
319
+ 13. ¿Cada paso pertenece a exactamente un lote, cada lote toca un solo repositorio y su criterio de salida es ejecutable cuando se puede ejecutar?
320
+ 14. ¿Nombré los pendientes heredados antes del primer paso, y los pasos que dependen de ellos van como Propuesta?
321
+ 15. ¿Los desvíos y las micro-decisiones `D<n>` tienen su **Base**?