Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Producto SaaS multi-empresa que detecta dónde existe descoordinación entre áreas, procesos, personas y sistemas de una organización, identifica el eslabón más débil del flujo y recomienda qué mejorar primero, con seguimiento de avance en el tiempo.README 전문 보기 ↗
Producto SaaS multi-empresa que detecta dónde existe descoordinación entre áreas, procesos, personas y sistemas de una organización, identifica el eslabón más débil del flujo y recomienda qué mejorar primero, con seguimiento de avance en el tiempo.
Nota de vigencia (2026-10-14): la narración histórica de este README describe en detalle el avance hasta el 2026-09-29 y no constituye por sí sola el estado completo actual. El historial de Git y la documentación versionada incluyen trabajo posterior, entre otros: endurecimiento de RLS, MFA y revocación de sesiones (ver
docs/engineering/LESSONS_LEARNED_SESSION_RLS_MFA.md), el piloto OTIF en producción, el flujo de declaración de Fill Rate (captura manual o pegado, cálculo server-side y aislamiento multi-tenant), y documentación operativa de pg-boss (verdocs/engineering/DEUDA_TECNICA_JOBS_PGBOSS_HUERFANOS.md). Antes de planificar trabajo nuevo, debe contrastarse este resumen con el código, los ADR y el historial vigente del repositorio.
Fase 4 del prompt maestro de Bigdevelopment — en construcción. Alcance de aquí en adelante definido por MVP-DEFINITIVO.md (v1.3): reconcilia la especificación V2 con lo ya construido, sin tocar el Incremento 1. Hoja de ruta completa aprobada por Alex — Incrementos 2 a 7, incluyendo Market Signals (aprobado el 2026-09-15). Las dos decisiones que solo Alex podía tomar (países iniciales, catálogo de KPIs) ya están confirmadas. Se completó además una revisión de viabilidad técnica en cuatro rondas — Ronda 1 (arquitectura, herramientas, seguridad, DDD + auditoría), Ronda 2 (las mismas 4 dimensiones más un quinto agente de implementación/developer, cada una con su plan de trabajo detallado por incremento), Ronda 3 (Calidad 1/QA, Calidad 2/código, Finanzas/costos + auditoría de consolidación) y Ronda 4 (cierre de brechas: con las decisiones de Alex del 2026-09-16 ya fijas, se resolvieron con diseños concretos casi todos los ítems abiertos, y se aplicó al repo CI, pre-commit hook, docs/PATRONES.md y cobertura de tests) — veredicto: viable con condiciones, ninguna cambia el alcance. El plan de tareas concreto hasta terminar el proyecto, con los planes detallados por dimensión y los hallazgos de las 4 rondas, está en PLAN-DE-TRABAJO.md (v4.0). requirements.md, ADR-0001, ADR-0002 y ADR-0003 siguen aprobados y consistentes entre sí (ver Sección 12 de requirements.md para la revisión final de viabilidad, GO para Fase 4). Scaffolding completo: proyecto Next.js + TypeScript + Tailwind + Prisma, el modelo de datos completo (prisma/schema.prisma), el motor v1 (src/engine/, funcion pura, sin Prisma ni Next — ADR-0002) con 34/34 pruebas unitarias pasando (salud, criticidad, riesgo, RF7 con desempate determinista, RF16, cobertura RF10), el middleware de aislamiento multi-tenant (src/infra/prisma/tenantClient.ts) y las políticas RLS (prisma/rls.sql).
Repositorio en GitHub: https://github.com/alexhsantamaria/ChainPulse (rama main).
Bloqueo de entorno resuelto: binaries.prisma.sh estaba bloqueado en los entornos de Claude (cloud y Mac vía sesión) por política de red (403, blocked-by-allowlist). Corriendo npm run prisma:generate desde una máquina con acceso normal a internet (PC Windows, fuera de cualquier sesión de Claude) funciona sin problema.
Bug encontrado y corregido al validar el schema por primera vez: al modelo Empresa le faltaba la relación inversa hacia Conexion (tenía usuarios, eslabones y ciclos, pero no conexiones), lo que rompía prisma generate con P1012. Corregido agregando conexiones Conexion[] a Empresa (commit 73942fc).
Con el fix aplicado, la cadena completa corre limpia en Windows: npm run prisma:generate, npm run typecheck, npm run lint, npm run test (19/19) y npm run build — todo verde.
Base de datos: Neon, ya en producción de desarrollo. Se creó el proyecto en Neon (Postgres gestionado, región São Paulo), se corrió npm run prisma:migrate contra esa base (migración inicial aplicada) y se aplicó prisma/rls.sql completo desde el SQL Editor de Neon — las 8 políticas RLS de aislamiento multi-tenant están activas y verificadas. Se creó además el rol restringido chainpulse_app (sin privilegios de dueño de tabla, por lo tanto sujeto de verdad a RLS — ver nota de seguridad abajo) para las consultas de la app en desarrollo; el rol neondb_owner queda reservado solo para migraciones futuras. Con esto se cierra el criterio de "listo" del Incremento 1 que pedía correr RLS con una base real (ADR-0002).
Autenticación (RF1/RF4): implementada y validada de punta a punta. Auth.js v5 (Credentials) con hash Argon2id, MFA (TOTP) verificable para ADMINISTRADOR, rate limiting con bloqueo temporal y sin enumeración de usuarios — según ADR-0003. email es único global (no compuesto con empresaId); el login resuelve el tenant vía la función Postgres login_lookup() (prisma/auth_functions.sql, SECURITY DEFINER, única excepción acotada al aislamiento RLS — ver addendum de ADR-0003). Probado en Windows contra Neon: npm run prisma:seed crea el usuario de prueba, el login en /login autentica y /api/auth/session devuelve el JWT con empresaId/rol correctos.
RF1 (registro de cuenta) y activación de MFA: implementados y validados de punta a punta. /registro crea la empresa y el administrador en una transacción atómica (src/infra/auth/registro.ts) e inicia sesión automáticamente con las mismas credenciales; /activar-mfa muestra el código QR (motor ya existente en src/infra/auth/mfa.ts) y exige confirmar un código antes de dejar la cuenta operativa, según manda ADR-0003 para ADMINISTRADOR. Probado en Windows: registro → sesión automática → escaneo del QR con una app real (Google Authenticator) → código aceptado → mfaHabilitado activo. Ver addendum de ADR-0003 para el detalle de cómo se resolvió crear un tenant nuevo bajo RLS (más simple de lo previsto: no hizo falta una función SECURITY DEFINER). src/middleware.ts protege /activar-mfa y /dashboard/* (todavía sin construir, pero ya queda cubierto en el matcher) redirigiendo a /login sin sesión — corre en el Edge runtime de Next.js sobre la sesión JWT, sin tocar la base de datos. Corregido un bug real detectado al probarlo en Windows: el middleware no puede importar Argon2/Prisma en el Edge runtime, así que la configuración de Auth.js se separó en src/auth.config.ts (sin el proveedor Credentials, la usa el middleware) y src/auth.ts (completo, para las rutas de API) — ver addendum de ADR-0003. Validado en Windows: sin sesión, /activar-mfa redirige a /login.
RF4 (invitar responsables): implementado y validado de punta a punta en Windows. Un administrador invita a un responsable de eslabón desde /dashboard/eslabones/[id]/invitar (link "Invitar responsable" junto a cada eslabón). La invitación no se persiste en una tabla propia: se codifica como un JWT firmado (src/infra/auth/invitacion.ts, librería jose, ya usada indirectamente por Auth.js) con el email, el eslabón y el tenant, válido 7 días — decisión tomada porque este entorno no puede correr prisma migrate dev (bloqueo de red ya documentado), así que una tabla Invitacion nueva hubiese quedado sin poder aplicarse hasta la próxima sesión en Windows; con un token stateless no hace falta migración. Quien recibe el correo (enviado con Resend, src/infra/email/resend.ts) entra a /invitacion/aceptar?token=..., elige nombre y contraseña, y ahí recién se crea el Usuario (rol: RESPONSABLE, eslabonId fijo del token) — nunca hay una fila con contraseña vacía. Trade-off aceptado y documentado: el administrador no puede ver ni revocar invitaciones ya enviadas antes de que se acepten (no hay fila que listar); se puede agregar una tabla de auditoría más adelante sin romper el mecanismo. 4 pruebas unitarias del token (firma, alteración, secreto distinto) en src/infra/auth/__tests__/invitacion.test.ts. Requiere RESEND_API_KEY en .env (ver .env.example) — sin un dominio propio verificado en Resend, el modo sandbox solo permite enviar a la dirección con la que se creó la cuenta de Resend, suficiente para probar el flujo ahora. Probado en Windows: invitación enviada desde /dashboard/eslabones/[id]/invitar → correo recibido con el eslabón/empresa correctos → cuenta de responsable creada en /invitacion/aceptar → login exitoso con esa cuenta.
Prisma sin motor Rust (Windows ARM64): Prisma no publica un motor de consultas nativo para Windows ARM64 (query_engine-windows.dll.node is not a valid Win32 application al correr npm run prisma:seed; issue de Prisma cerrado como "not planned"). Se cambió a engineType = "client" en prisma/schema.prisma (modo GA "sin motor Rust" desde Prisma 6.16) con @prisma/adapter-pg como driver adapter — ver addendum de ADR-0003. No afecta a prisma generate/prisma migrate dev (usan el schema-engine, un proceso separado que sí corre bien vía emulación); solo cambia cómo el cliente en runtime (src/infra/prisma/client.ts, prisma/seed.ts) se conecta a Postgres.
RF2/RF3 (declarar la cadena): implementados y validados de punta a punta en Windows. Un administrador logueado declara los eslabones de su cadena en /dashboard/eslabones y las conexiones (dependencias) entre ellos en /dashboard/conexiones — grafo dirigido con origenId/destinoId únicos por par (Conexion.@@unique([origenId, destinoId])). Al declarar una conexión, los 5 datos de RF3 (grado de dependencia, impacto sobre la promesa al cliente, si existe alternativa, tiempo tolerable, tiempo de recuperación) se piden agrupados en un solo formulario, pero son opcionales al guardar: una conexión sin los 5 confirmados queda marcada completa: false (src/infra/conexiones/completitud.ts, con pruebas unitarias propias) y se puede completar después desde /dashboard/conexiones/[id]/editar (PATCH /api/conexiones/[id]) — igual que exige el criterio de aceptación de RF3. Todas las rutas nuevas pasan por tenantClient() (RLS + filtro de tenant) y están protegidas por el middleware (/dashboard/:path*). Probado en Windows: 4 eslabones y 3 conexiones declaradas contra Neon, todas quedaron completa: true con los 5 datos confirmados.
RF5-RF7 (ciclos de pulso reales): implementado y validado de punta a punta en Windows. Un administrador abre un ciclo de pulso desde /dashboard/ciclos (src/infra/ciclos/abrirCiclo.ts) — solo puede haber un ciclo ABIERTO por empresa a la vez — y se notifica por correo (Resend) a los responsables elegibles: los que tienen un eslabón asignado que participa de al menos una conexión completa. Cada responsable completa el cuestionario en /dashboard/ciclos/[id]/responder (escala Likert 1-5, con "no sé"/"no aplica") para las conexiones de su eslabón — una respuesta por conexión por ciclo por responsable, según el @@unique ya existente en el schema (src/infra/ciclos/registrarRespuestas.ts). Al cerrar el ciclo (src/infra/ciclos/cerrarCiclo.ts, botón de administrador), por cada conexión completa se calcula salud/criticidad/riesgo con el motor v1 ya probado, se persiste ResultadoConexion como snapshot inmutable (nunca una referencia viva a Conexion — RNF6), se calcula el conjunto no dominado de eslabones más débiles y el índice de integración (RF16), y se persiste ResultadoCiclo. Una conexión sin ninguna respuesta válida en el ciclo no genera resultado — es ausencia de dato, no un cero — y así nace la cobertura parcial de RF10 (src/infra/ciclos/cobertura.ts). RespuestaCruda/ResultadoConexion/ResultadoCiclo no tienen empresaId propio, así que se acceden con una transacción manual + set_config, mismo patrón que aceptarInvitacion.ts. /dashboard/ciclos/[id] muestra los resultados (índice de integración, eslabón(es) más débil(es), salud/riesgo/dependencia por conexión, cobertura) — primer corte de RF9, no el panel completo. Requirió que un RESPONSABLE conozca su propio eslabonId en la sesión: se agregó a los tipos de Auth.js y a login_lookup() en Postgres (prisma/auth_functions.sql). Bug real encontrado al aplicar ese SQL en Neon: CREATE OR REPLACE FUNCTION no puede cambiar el tipo de retorno de una función existente (agregar una columna a RETURNS TABLE cuenta como cambio de tipo, error 42P13) — corregido agregando DROP FUNCTION IF EXISTS antes del CREATE (commit 236f3c8). Probado en Windows con datos reales: se abrió un ciclo (correo recibido correctamente), el responsable de "Compras" respondió el cuestionario (una sola conexión, como corresponde: solo ese eslabón tenía responsable), y al cerrar el ciclo la pantalla de resultados mostró salud 75, riesgo 42, índice de integración 90, cobertura 100% y la conexión marcada como único "eslabón más débil" — coherente con los datos declarados y la única respuesta cargada.
RF8 (recomendación priorizada): implementado y validado en Windows. Cuando se identifica el conjunto de eslabones más débiles de un ciclo (RF7), el sistema genera una recomendación con prioridad (ALTA/MEDIA/BAJA) y un texto de acción concreta para cada una (src/engine/recomendacion.ts, función pura, 7 pruebas). RF8 pide reglas según "el tipo de fricción reportada" — un campo que no existe en ningún lado del requisito (sin catálogo, sin campo en el modelo, sin criterio de aceptación) — así que, con Alex, se decidió un v1 simplificado: las reglas usan solo lo que ya se calcula por conexión (salud, grado de dependencia, si tiene alternativa declarada), sin agregar ninguna pregunta al cuestionario ni tocar el schema. La recomendación no se persiste: se calcula al leer los resultados (src/infra/ciclos/resultados.ts) a partir de los valores ya inmutables de ResultadoConexion — trade-off documentado en el propio código (si las reglas cambian más adelante, un ciclo ya cerrado mostraría el texto nuevo, aunque el puntaje numérico no cambia). Se muestra junto a cada eslabón más débil en /dashboard/ciclos/[id]. tsc/eslint/vitest limpios (41/41 tests). Probado en Windows reabriendo el ciclo ya cerrado de la prueba de RF5-RF7 (sin necesidad de abrir uno nuevo, porque no persiste nada): con salud 75, dependencia ALTA y sin alternativa declarada, mostró correctamente prioridad media y el texto sugiriendo definir un respaldo — coincide exactamente con la regla esperada.
RF9 (panel completo de resultados): implementado y validado en Windows. Faltaban dos cosas del panel completo que pide RF9: el cuarto valor por conexión (criticidad) y el histórico/tendencia entre ciclos. La criticidad tampoco se persiste como columna propia — ResultadoConexion.criticidadSnapshot ya guarda el DatosCriticidad completo (RNF6), así que se recalcula al leer con calcularCriticidad(), mismo criterio ya aceptado para la recomendación de RF8. /dashboard/ciclos/[id] ahora muestra los 4 valores (salud, criticidad, riesgo, dependencia) y, cuando hay más de un ciclo cerrado, la tendencia de salud de esa conexión ("Tendencia de salud: 60 → 68 → 75"). /dashboard/ciclos ahora muestra el índice de integración de cada ciclo cerrado y, arriba de la lista, la tendencia del índice entre ciclos cuando hay más de uno cerrado. RF10 (cobertura de respuesta explícita) ya estaba satisfecho desde RF5-RF7, sin cambios ahí. tsc/eslint/vitest limpios (41/41 tests, sin pruebas nuevas: son cambios de lectura/presentación sobre cálculo ya probado). Probado en Windows en tres ciclos sucesivos sobre la misma conexión Compras → Producción: ciclo 1 (RF5-RF7) con salud 75 → criticidad 70, riesgo 42, índice de integración 90; ciclo 2 respondido con "No sé" (sin dato válido, RF10) → no genera ResultadoConexion, índice 0; ciclo 3 con salud 25 → riesgo 62, recomendación pasa correctamente a prioridad alta (rama del motor de RF8 no probada hasta ahora, salud < 40 siempre es alta), índice 0. La tendencia de salud de la conexión mostró "75 → 25" — saltea el ciclo sin dato en vez de mostrar un 0 falso en el medio, confirmando que "ausencia de dato, no un cero" (RF10) se respeta también en la tendencia. La tendencia del índice de integración en /dashboard/ciclos mostró "90 → 0 → 0", matemáticamente correcto: con una sola conexión declarada en todo el dataset de prueba, el índice es binario (0% o 100% de la cadena en salud aceptable) — con más conexiones declaradas se vería más gradual, no es un defecto de la fórmula (ya probada con 4 tests unitarios propios). Corrección (post-análisis previo a Incremento 2): ni la tendencia de salud por conexión ni la del índice de integración chequeaban ruleVersion — encadenaban valores de cualquier ciclo cerrado sin verificar que compartieran versión del motor. requirements.md ya anticipa que las fórmulas se van a recalibrar con datos reales de los pilotos (Sección 12); sin este filtro, ese día la tendencia mostraría un "salto" que en realidad sería solo un cambio de regla. Corregido: ambas tendencias ahora se cortan en la versión vigente, sin mezclar. Sin validación visual en Windows para esto — con una sola versión existiendo hoy (engine-v1.0.0) el comportamiento observable no cambia; previene un problema futuro, no corrige uno ya visto. // limpios (41/41).
RNF1 (pruebas de integración de aislamiento multi-tenant): implementado y validado en Windows. prisma/rls.sql (RLS de Postgres) estaba escrito y aplicado a mano en Neon desde hace varias sesiones, pero nunca probado de punta a punta con una prueba automatizada — el propio archivo lo marcaba pendiente. src/infra/prisma/__tests__/aislamientoMultitenant.integration.test.ts crea dos tenants sintéticos de forma realista (empresa, administrador, 2 eslabones, 1 conexión completa, 1 ciclo cerrado con respuesta y resultados) y prueba las dos capas de RNF1 (ADR-0001, "cinturón y tirantes"): tenantClient() a nivel de aplicación, y RLS a nivel de base de datos — incluyendo el caso "el código se olvida del filtro" (consulta sin where, id explícito de otro tenant) y el caso "sin ningún tenant fijado" (debe fallar cerrado, no abierto). Cubre también las tres tablas hijas sin empresaId propio (RespuestaCruda, ResultadoConexion, ResultadoCiclo), cuyas políticas RLS por subconsulta tampoco se habían ejercitado nunca contra datos reales. Limpia sus propios tenants de prueba al final (afterAll + ON DELETE CASCADE), nunca toca las cuentas de prueba existentes. Corre aparte de npm run test (necesita DATABASE_URL real y red hacia Neon, que no existen en este entorno de Mac) — nuevo script npm run test:integration, a correr a mano en Windows, mismo criterio que prisma generate/migrate/build. Probado en Windows en dos rondas: la primera falló en beforeAll por latencia de Neon (la base "duerme" cuando está inactiva y el maxWait default de Prisma, 2 segundos, no alcanzaba para despertarla con dos conexiones simultáneas) — arreglado con más margen de tiempo y creación secuencial de los dos tenants. La segunda ronda pasó las 10 pruebas de aislamiento (las que importan: capa 1 y capa 2, incluido el caso "sin tenant fijado"), pero la limpieza (afterAll) reveló un hallazgo real, no relacionado con el aislamiento: RespuestaCruda.responsable (Usuario) no tiene onDelete: Cascade en el schema, a diferencia de sus otras dos relaciones — Postgres bloquea el borrado de un Usuario con respuestas propias (RESTRICT/NO ACTION, el default). No afecta producción hoy (no existe ninguna función de "borrar usuario" en la app), pero es una brecha real a tener en cuenta antes de construir una. Arreglado sin tocar el schema (el test borra RespuestaCruda a mano antes de borrar el tenant). Tercera ronda en Windows: 10/10 pruebas pasaron, limpieza incluida, sin errores — RNF1 queda validado de punta a punta.
RNF9 (instrumentación mínima): implementado y validado en Windows. Al revisar qué faltaba antes de empezar el Incremento 2, se detectó que RNF9 nunca se había construido a pesar de ser parte explícita del criterio de "listo" del Incremento 1 (ADR-0002, junto con RF1-RF10 y RNF1) — esta sección del README había declarado el Incremento 1 "cerrado" antes de tiempo; queda corregido acá. RNF9 pide, sin ser "un sistema de analítica de producto completo", dos señales mínimas: (a) tiempo real de completar el cuestionario, contra el objetivo de 5 minutos de RNF2, y (b) cuántas recomendaciones de RF8 se marcaron como ejecutadas. Se agregaron dos tablas nuevas, mínimas (MetricaCuestionario, RecomendacionEjecutada), sin empresaId propio — mismo patrón que RespuestaCruda/ResultadoConexion/ResultadoCiclo: set_config manual + RLS por subconsulta a CicloPulso (prisma/rls.sql actualizado con las dos políticas nuevas). La duración se mide en el cliente (ResponderCuestionarioForm.tsx guarda el instante de montaje y calcula los segundos al enviar, sin un segundo viaje al servidor solo para marcar el inicio) y se persiste junto con las respuestas si el envío es válido (registrarRespuestas.ts, campo opcional — nunca bloquea el envío del cuestionario si falta). Un administrador puede marcar una recomendación como ejecutada desde /dashboard/ciclos/[id] (botón nuevo junto a cada recomendación, idempotente vía upsert) y ve el resumen agregado en una página nueva, /dashboard/metricas (solo administrador, sin filtros ni series temporales, tal como pide el requisito). De paso, aprovechando que ésta es la primera migración de schema real de todo este segmento (el resto de RF1-RF10/RNF1 se había diseñado deliberadamente para no necesitar ninguna, por la limitación de red del Mac), se agregó onDelete: Cascade a RespuestaCruda.responsable — el gap de FK real que había encontrado RNF1 al limpiar sus tenants de prueba. tsc/eslint/vitest limpios (45/45 tests, 4 nuevos para la función pura de cálculo de métricas).
Bug real de entorno encontrado al migrar (Neon + shadow database): npx prisma migrate dev fallaba con P3014 ("permission denied to create database") porque ningún rol de Neon tiene CREATEDB — Prisma necesita una base de sombra temporal para diffear el schema y no puede crearla sola. Corregido agregando shadowDatabaseUrl al datasource de prisma/schema.prisma, apuntando a una segunda base (chainpulse_shadow) creada a mano desde la consola de Neon (eso no pasa por SQL, no necesita CREATEDB) — documentado en .env.example (SHADOW_DATABASE_URL).
Incidente real al reconstruir .env en Windows (sin relación con el código): al editar .env a mano para la migración, una edición en Notepad borró por error casi todo el archivo salvo DATABASE_URL y RATE_LIMIT_STORE_URL — se perdieron NEXTAUTH_SECRET, SEED_DATABASE_URL, RESEND_API_KEY, etc. Se reconstruyó rotando las contraseñas de neondb_owner y chainpulse_app en Neon y regenerando NEXTAUTH_SECRET; RESEND_API_KEY/RESEND_FROM_EMAIL quedaron vacíos (RF4 y el aviso de apertura de ciclo no van a poder enviar correo hasta cargarlos de nuevo desde el dashboard de Resend — no bloquea nada más, ver abrirCiclo.ts, que ya tolera fallos de envío). Detalle completo y credenciales pendientes de rotar otra vez (quedaron expuestas en el chat durante el diagnóstico) en SEGURIDAD-credenciales.md.
Probado en Windows de punta a punta con datos reales: se marcó como ejecutada la recomendación de un ciclo ya cerrado (botón "Marcar como ejecutada" → cambia a "Ejecutada el [fecha]"), /dashboard/metricas mostró correctamente "1 / 2 marcadas como ejecutadas"; luego se abrió un ciclo nuevo, se respondió el cuestionario como responsable y se cerró como administrador — /dashboard/metricas pasó a mostrar "Promedio: 0 min (8 s) sobre 2 respuestas. Dentro del objetivo de 5 minutos (RNF2): 2 / 2 (100%)" y "1 / 3 marcadas como ejecutadas" — coherente con los datos cargados. Con esto, el Incremento 1 completo (RF1-RF10, RNF1, RNF9) queda cerrado de verdad.
Recuperación de contraseña: implementado y validado de punta a punta en Windows. Faltaba pese a estar asumido por ADR-0003 junto a RF1/RF4. Desde /login ("¿Olvidaste tu contraseña?") se llega a /recuperar-contrasena, se pide el email, y si existe una cuenta con ese email se manda un correo (Resend) con un enlace de un solo uso a /recuperar-contrasena/confirmar?token=..., donde se elige una contraseña nueva. Mismo criterio que la invitación de RF4 (src/infra/auth/recuperacion.ts): token JWT firmado, sin tabla nueva — pero con una diferencia importante: el token tiene que dejar de servir después de un solo uso. Se logra sin persistir nada, con una huella (hash corto) del passwordHash vigente al emitir el token, incluida en el propio JWT; al confirmar, se recalcula esa huella contra el passwordHash actual — si no coincide (la contraseña ya cambió, con este enlace o cualquier otro medio), se trata como vencido. Vigencia de 1 hora (más corta que los 7 días de una invitación, por ser un flujo más sensible). Reutiliza login_lookup()/buscarUsuarioPorEmail() ya existente, sin SQL nuevo — no hace falta ninguna migración para esta función. Sin enumeración de cuentas (mismo criterio que el login): la respuesta de POST /api/recuperar-contrasena es idéntica exista o no el email, y aunque falle el envío del correo. Sin limitación de tasa propia todavía (documentado en el código): el peor abuso posible es mandar correos de más a una bandeja ajena, no un riesgo de cuenta — se puede sumar después sin romper el mecanismo. tsc/eslint/vitest limpios (49/49 tests, 4 nuevos que prueban las propiedades criptográficas del token —firma, huella, vencimiento— sin tocar la base; la verificación completa contra un usuario real queda para una prueba de integración futura, junto con el resto de flujos críticos de autenticación). De paso, se corrigió un test intermitente encontrado en invitacion.test.ts ("firma alterada" mutaba el último carácter de un JWT HS256, que en base64url puede tener bits de relleno sin significado real) — mismo fix aplicado al test nuevo. Probado en Windows de punta a punta: se recargó RESEND_API_KEY con una clave nueva de Resend de permiso mínimo ("Sending access", reemplazando la anterior de "Full access" que se revocó) y [email protected] (modo sandbox, sin dominio propio verificado). Desde /login se pidió la recuperación para una cuenta existente, llegó el correo real vía Resend con el enlace de un solo uso, se eligió una contraseña nueva en /recuperar-contrasena/confirmar, redirigió a /login, y el login con la contraseña nueva entró correctamente a /dashboard — el flujo completo queda validado de punta a punta.
Pruebas automatizadas ampliadas a flujos críticos de mutación: implementado y validado de punta a punta en Windows. Hasta ahora la única prueba de integración era la de RNF1 (aislamiento multi-tenant); el resto de la lógica de negocio de las mutaciones (qué reglas se cumplen, qué errores se lanzan) no tenía cobertura automatizada propia, más allá de la validación manual en Windows ya documentada en cada sección de este README. Dos archivos nuevos: src/infra/auth/__tests__/mfa.test.ts (unitario, sin base de datos — corre en npm run test, cubre generarSecretoMfa/construirOtpauthUrl/verificarCodigoMfa: código válido aceptado, código de otro secreto o vencido rechazado) y src/infra/__tests__/flujosMutacion.integration.test.ts (mismo criterio que RNF1: necesita DATABASE_URL real, corre con npm run test:integration en Windows). Este último cubre: registro de cuenta (registrarEmpresaYAdmin crea empresa+administrador atómicamente; un email duplicado lanza EmailYaRegistradoError sin dejar una empresa huérfana — la transacción realmente hace rollback completo); activación de MFA (un código TOTP válido calculado a partir del secreto persistido activa mfaHabilitado, uno inválido no); CRUD de eslabones/conexiones (una conexión sin los 5 datos de criticidad queda completa: false, completarlos la marca completa: true, declarar la misma conexión dos veces viola la restricción única P2002); y el ciclo de pulso completo abrir → responder → cerrar (RF5-RF7): no se puede abrir un segundo ciclo mientras el primero sigue ABIERTO, responder por una conexión no asignada al eslabón del responsable lanza ConexionNoAsignadaError, la métrica de duración de RNF9 se persiste junto con la respuesta, cerrar el ciclo persiste ResultadoConexion/ResultadoCiclo correctamente, y tanto cerrar un ciclo ya cerrado como responder en uno ya cerrado lanzan CicloNoAbiertoError (dos clases de error con el mismo nombre pero definidas en archivos distintos — cerrarCiclo.ts y registrarRespuestas.ts — importadas con alias en la prueba para no confundirlas). tsc/eslint/npm run test limpios en Mac (56/56 tests, sin red: los 7 nuevos son los de mfa.test.ts; los de flujosMutacion.integration.test.ts no corren ahí a propósito, mismo motivo que RNF1). Confirmado en Windows contra Neon real: npm run test:integration pasa 24/24. En el camino aparecieron y se corrigieron dos problemas propios de la prueba (no del código de producción): un falso negativo por RLS "falla cerrado" (una consulta de verificación sin siempre devolvía , aunque el rollback atómico real funcionaba bien) y contención de conexiones contra Neon al correr los dos archivos de integración en paralelo (se agregó en , más un que "despierta" la base con una consulta trivial antes del primer test del run, igual criterio que ya usaba RNF1).
Bug conocido del botón "Cerrar sesión": revisado, no se reprodujo — validado de punta a punta en Windows. Quedaba pendiente de revisar sin ningún síntoma documentado más allá de la nota en "Próximo paso" (sin ADR, TODO ni comentario en el código). Revisión del código (src/app/dashboard/CerrarSesionBoton.tsx, src/auth.ts, src/auth.config.ts, src/middleware.ts) no encontró ningún defecto evidente. Probado en Windows con la consola del navegador abierta: clic en "Cerrar sesión" redirige correctamente a /login sin ningún error en consola, y navegar manualmente a /dashboard después vuelve a redirigir a /login — la sesión se cierra de verdad, no es solo una redirección visual. Hipótesis más probable: el síntoma original se debía a un estado puntual del entorno (el servidor de desarrollo no estaba corriendo, o una sesión de navegador desactualizada) en el momento en que se detectó, no a un defecto del código. No se hizo ningún cambio de código.
Bloque A del Incremento 2 (infraestructura de jobs + rate limiting + purga): construido el 2026-09-16, con el go-ahead explícito de Alex para este bloque puntual (el resto del schema del Incremento 2 — UsuarioPlataforma, CuestionarioVersion/PreguntaVersion, motor v2, Consentimiento, HallazgoExpres — sigue sin empezar, requiere su propia confirmación). Cierra la deuda ya vencida del Incremento 1 (huellaOrigen de EvaluacionExpres nunca se purgaba) y monta la infraestructura transversal que el resto de Incremento 2 va a reutilizar (ADR-0004: cola de jobs con pg-boss v12, boss.fetch() dentro de un Route Handler corto — nunca boss.work(), porque las funciones de Vercel son de vida corta — disparado por Vercel Cron (diario, plan Hobby) más un workflow de GitHub Actions cada 15 minutos como respaldo, autenticado con CRON_SECRET vía Authorization: Bearer comparado en tiempo constante). Tabla nueva LimiteTasa (rate limiting Postgres-nativo, sin Redis, ventana fija por hora, UPSERT atómico — formaliza RF15/RF17) y src/infra/retencion.ts (purga huellaOrigen a partir de 48h, ~96h en el peor caso real con la cadencia diaria del Cron). Diseño deliberado para que ambos módulos sean seguros de importar en pruebas unitarias sin tocar Prisma: los helpers puros viven separados (src/infra/rateLimit/huella.ts) de las funciones que sí necesitan una conexión real, y estas últimas reciben el cliente de Prisma como parámetro obligatorio (nunca un valor por defecto que importe el singleton real) — evita que un test dispare por accidente la construcción de PrismaClient, que en este entorno de desarrollo falla mientras prisma generate siga bloqueado (ver más abajo). tsc/eslint/npm run test limpios en Mac (66/66 tests, 10 nuevos: 7 en huella.test.ts, 3 en retencion.test.ts). Migración escrita a mano (prisma/migrations/20260916140000_limite_tasa/, mismo criterio ya usado en todo este proyecto por el bloqueo de red). Validado de punta a punta en Windows el 2026-09-16: .env completado con HUELLA_ORIGEN_SALT/CRON_SECRET nuevos, npm install (trae pg-boss como dependencia nueva), npm run jobs:bootstrap corrido una vez sin errores (crea el schema pgboss y la cola en Neon, otorga permisos a chainpulse_app), y la validación completa en verde: prisma:generate, , , (66/66) y (, incluidas las 10 pruebas de aislamiento RNF1).
Bug real encontrado en esa validación, ajeno al Bloque A, corregido el mismo día (commit 6f5a495): la primera vez que se corrió prisma generate con red real seguido de typecheck con esta combinación exacta, salieron 9 errores de tipos en src/app/api/conexiones/route.ts, src/app/api/eslabones/route.ts, src/infra/ciclos/abrirCiclo.ts y flujosMutacion.integration.test.ts — ninguno de estos archivos es parte de Bloque A. tenantClient.ts ya documentaba la causa en su propio comentario: su extensión de Prisma está tipada con any a propósito (nunca se había podido correr prisma generate con red real antes), y el código de aplicación nunca pasaba empresaId explícito en los create() porque confiaba enteramente en que tenantClient() lo inyecta en tiempo de ejecución — funciona bien (por eso las 24/24 pruebas de integración, incluida la propia RNF1, seguían pasando), pero TypeScript en modo estricto lo marca como campo faltante. Arreglado agregando el empresaId ya disponible en cada scope, sin cambiar ningún comportamiento en runtime (tenantClient() igual lo sobrescribe con el mismo valor). De paso, un usuarios[0] con "possibly undefined" no relacionado en el mismo archivo de test, mismo patrón de non-null assertion ya usado en otras pruebas del proyecto. typecheck confirmado limpio en Windows después del fix.
Primer despliegue real en Vercel: 2026-09-16. Al revisar el despliegue para configurar CRON_SECRET, se descubrió que ChainPulse nunca había sido desplegado en Vercel — todas las validaciones "Probado en Windows" documentadas hasta ese momento corrían en local (npm run dev) contra Neon directamente, y las referencias previas a un despliegue en producción con "configuración 100% por defecto" eran una descripción aspiracional, no un hecho verificado. Con el go-ahead explícito de Alex ("Desplegar ahora"), se agregó primero el script "postinstall": "prisma generate" a package.json (commit f7d51b1), necesario para que el build de Vercel genere el cliente de Prisma; sin este paso el primer build hubiera fallado o quedado con un cliente desactualizado. Se importó el repo alexhsantamaria/ChainPulse (rama main) como proyecto nuevo en Vercel (equipo alex-2477, plan Hobby, proyecto chain-pulse), cargando las 11 variables de entorno detectadas con sus valores reales (DATABASE_URL con el connection string pooled de Neon — imprescindible para compatibilidad serverless, no el directo — más NEXTAUTH_SECRET, RESEND_API_KEY, RESEND_FROM_EMAIL, HUELLA_ORIGEN_SALT, CRON_SECRET). El primer build terminó en verde. Con el dominio asignado (chain-pulse-steel.vercel.app) ya conocido, se completó NEXTAUTH_URL=https://chain-pulse-steel.vercel.app (variable que quedó vacía en la importación inicial) y se re-desplegó para aplicar el cambio. Del lado de GitHub, se agregaron los secretos del repositorio (Settings → Secrets and variables → Actions) APP_BASE_URL=https://chain-pulse-steel.vercel.app y CRON_SECRET (mismo valor que en Vercel), necesarios para que el workflow de respaldo (.github/workflows/jobs-cron.yml) pueda llamar a la ruta de purga cada 15 minutos. Verificado en el navegador contra la URL pública de producción: / sirve el scaffold esperado y /login renderiza el formulario completo (email, contraseña, código MFA opcional) sin errores — confirma que el build, la conexión a Neon vía Prisma y NEXTAUTH_URL funcionan correctamente en producción real, no solo en local.
Resto del schema del Incremento 2 (UsuarioPlataforma, CuestionarioVersion/PreguntaVersion, motor v2, Consentimiento, HallazgoExpres): escrito y validado de punta a punta en Windows, 2026-09-16. Con el go-ahead de Alex, se agregaron al schema los 6 enums y 9 modelos que completan el diseño ya cerrado en PLAN-DE-TRABAJO.md Sección 18 (Ronda 4): UsuarioPlataforma (autenticación separada de Usuario, correo+contraseña+autenticador), CuestionarioVersion/PreguntaVersion (versionado del cuestionario de evaluación exprés v2), EvaluacionExpresV2/Respuesta (el motor v2 de 5 dimensiones), ConsentimientoExpres/ConsentimientoCuenta (append-only, reemplaza los dos booleans legacy de EvaluacionExpres, que quedan congelados sin backfill) y HallazgoExpres/HallazgoExpresTraza (patrón snapshot-JSON, separación operacional/investigación). Se exportó TENANT_SCOPED_MODELS/injectTenantFilter/uncapitalize desde tenantClient.ts y se agregó tenantTransaction.ts (patrón oficial de transacción atómica multi-modelo, Sección 18.3.B). Migración escrita a mano (prisma/migrations/20260916150000_incremento2_evaluacion_expres_v2/), mismo criterio que Bloque A por el bloqueo de red de binaries.sh en este entorno — incluye el índice único parcial de versionado, la política RLS de ConsentimientoCuenta y los REVOKE UPDATE/DELETE de append-only sobre las dos tablas de consentimiento. Validado en Windows: npm run prisma:migrate aplicó limpio ("Your database is now in sync with your schema"), y la cadena completa quedó en verde: typecheck, lint, test (66/66) y test:integration (24/24, incluidas las 10 de aislamiento RNF1) — sin ningún bug nuevo encontrado en esta validación.
Ronda 5 de revisión — auditoría del código real, 2026-09-16 (ver PLAN-DE-TRABAJO.md Secciones 19-20). A diferencia de las rondas anteriores (que revisaban planes y documentos), esta revisó el código fuente ya construido: 5 agentes en paralelo (Arquitectura, Seguridad, Modelo de datos, QA, Calidad de código) más una consolidación de Auditoría. Encontró 26 hallazgos (1 CRÍTICO ya en producción — MFA sin rate limiting propio, un TOTP incorrecto no contaba como intento fallido — más 5 ALTOS, 14 MEDIOS/BAJOS y 1 informativo). Los 26 se corrigieron en su totalidad (no solo el CRÍTICO y los ALTOS), a pedido explícito de Alex — typecheck/lint/test (87/87) en verde en este entorno; detalle hallazgo por hallazgo y corrección aplicada en PLAN-DE-TRABAJO.md Sección 20. Queda pendiente de validar por Alex con red real: npm run prisma:migrate (4 migraciones nuevas) y test:integration, mismo criterio que todas las rondas anteriores.
Seguridad de credenciales: ver SEGURIDAD-credenciales.md — checklist de contraseñas/tokens que se compartieron durante la configuración del entorno y quedan pendientes de rotar.
Corrección: el disparo inmediato de una confirmación de Cobertura ya no depende de otra confirmación para procesarse — implementado y verificado en Mac, 2026-09-29. Bug real reportado por Alex durante una prueba manual: la importación 52aed226-1b0f-4550-bd30-fdd497ba0ed3 quedó CONFIRMADA con procesadaEn null y cero observaciones, sin ningún error visible en npm run start. Diagnosticado con una consulta de solo lectura contra pgboss.job que Alex corrió en Windows: el job de esa importación (state='created') nunca fue fetcheado — en cambio, 0.6 segundos después de confirmarla, arrancó OTRO job, más viejo, de una importación distinta que llevaba 22 minutos esperando su reintento en la misma cola. Causa: el disparo inmediato de la ruta de confirmar llamaba a procesarImportacionesCsv(boss, 1), que hace boss.fetch() sobre TODA la cola compartida procesar-importacion-csv — esa consulta toma el job más viejo por created_on, sin importar qué importación disparó la llamada. Localmente, sin nada que reintente automáticamente (ver el párrafo de abajo), eso podía significar que la importación recién confirmada nunca se procesara.
Fix: nueva función procesarImportacionCsvPropia() (src/infra/kpis/cobertura/job.ts) que reclama EXCLUSIVAMENTE el job de la importación que se acaba de confirmar (UPDATE ... WHERE singleton_key = importId AND state < 'active', equivalente atómico al que hace boss.fetch() internamente — misma garantía de exclusividad: dos reclamos concurrentes sobre el mismo job nunca ganan los dos) en vez de "el más viejo de toda la cola". La ruta de confirmar ahora usa esta función en el disparo inmediato; procesarImportacionesCsv() (el fetch genérico) queda reservada para el barrido periódico del cron de respaldo, donde sí es correcto procesar lo más viejo primero sin importar de qué importación es. 4 pruebas unitarias nuevas en job.test.ts (reclamo exitoso, reclamo vacío, fallo interno propagado a boss.fail, y un job más viejo de otra importación que no afecta el reclamo propio) más una prueba de integración nueva (confirmarNoDependeDeOtraConfirmacion.integration.test.ts, Windows-only) que reproduce el escenario real contra Postgres: encola un job más viejo de otra importación ANTES de confirmar la importación de la prueba, y verifica que (a) la importación de la prueba se procesa igual y (b) el job viejo queda exactamente intacto, nunca reclamado. Verificado en Mac: tsc/eslint limpios (mismos 8 errores preexistentes del stub de Prisma, ninguno nuevo), npm run test 420/420. La prueba de integración nueva no corrió acá (necesita Neon real) — pendiente de que Alex la corra con npm run test:integration.
Cómo funciona hoy el procesamiento en segundo plano de Cobertura (worker/respaldo) — documentado a pedido de Alex, 2026-09-29. No hay ningún worker persistente en ningún entorno: ChainPulse nunca llama a boss.work(), siempre fetch+complete/fail puntual (ver el comentario de cabecera de job.ts). Hay dos disparadores, con disponibilidad muy distinta según el entorno:
procesarImportacionCsvPropia(), ver el fix de arriba): corre dentro del mismo ciclo de la petición HTTP de confirmar, en CUALQUIER entorno (Windows local o Vercel). Si tiene éxito, la importación queda procesada sin esperar nada más.GET /api/internal/jobs/run, autenticado con Authorization: Bearer $CRON_SECRET): en producción (Vercel) lo invocan el Cron nativo de Vercel (una vez al día en el plan Hobby) y, de respaldo, el workflow de GitHub Actions (.github/workflows/jobs-cron.yml, cada 15 minutos). En Windows local (npm run dev/npm run start) no existe absolutamente nada que llame a esta ruta automáticamente — ni Vercel Cron ni GitHub Actions corren contra un proceso local. Si el disparo inmediato de una confirmación falla (R2/Neon caídos en ese instante, el proceso se reinicia a mitad de camino), esa importación se queda esperando indefinidamente en Windows a menos que alguien pegue manualmente a /api/internal/jobs/run con el header correcto. Con el fix de arriba, al menos ya no depende de que se confirme OTRA importación para eventualmente destrabarse.Hallazgo adicional durante el mismo diagnóstico, todavía sin corregir, pendiente de diseño (no tocar sin decisión explícita de Alex): con supervise: false en la configuración de PgBoss (src/infra/jobs/pgBoss.ts, deliberado para un runtime serverless de vida corta) y sin ningún maintain()/expiración en todo el repo, un job que queda active (por ejemplo, el proceso se cierra a mitad de procesarUnaImportacionCsv) nunca vuelve a ser elegible para ningún fetch, en ningún entorno — confirmado con dos jobs reales en Windows atascados en active desde el 2026-09-25 y el 2026-09-28 (ver pgboss.job, singleton_key cmugznxrz00030gufcfcmtz5i y cmukrvoi800033ouf50vvpgvn). El mecanismo que pg-boss usaría si supervise estuviera activo es failJobsByTimeout (node_modules/pg-boss/dist/plans.js): state='active' AND started_on + expire_seconds < now() → boss.fail() sobre esos jobs (que pg-boss reintenta o marca failed según retry_count/retry_limit, igual que cualquier otro fallo). Replicar ese mismo criterio como un paso explícito dentro de /api/internal/jobs/run (una consulta de solo lectura para encontrar los candidatos vencidos, después boss.fail() público sobre cada uno — nunca UPDATE/DELETE crudo) es la opción más chica que no requiere activar supervise en el proceso serverless. Sigue pendiente confirmar si esas dos ImportacionCsv siguen existiendo y decidir con Alex si corresponde recuperarlas. Cierre de esta investigación (confirmado: las importaciones de origen ya no existen; jobs conservados como evidencia, sin recuperar): ver docs/engineering/DEUDA_TECNICA_JOBS_PGBOSS_HUERFANOS.md.
MVP-DEFINITIVO.md — documento único que fija el alcance del MVP: reconcilia la especificación V2 completa (Incrementos 2 a 7, incluyendo Market Signals) con el Incremento 1 ya construido, sin reabrirlo. v1.3, hoja de ruta completa aprobada por Alex el 2026-09-15.PLAN-DE-TRABAJO.md — plan de tareas concreto, ordenado por incremento, hasta terminar el proyecto (v4.0). Incluye: bloqueantes antes de tocar el schema de Prisma (Sección 4), planes de trabajo detallados por dimensión para los Incrementos 2-7 (Secciones 10-14), los hallazgos consolidados de la Ronda 3 de revisión (Sección 17), y las resoluciones concretas de la Ronda 4 de cierre de brechas (Sección 18) — de 16 hallazgos, solo la revisión legal Ley 29733 sigue pendiente de una persona externa.requirements.md — actores, glosario, requisitos funcionales y no funcionales, criterios de aceptación y alcance explícito del MVP. Aprobado, con cinco rondas de decisiones registradas: Sección 9 (modelo de puntaje y evaluación exprés), Sección 10 (niveles de visualización), Sección 11 (análisis multi-rol del estado acumulado) y Sección 12 (revisión final de viabilidad, cierre de todos los pendientes y valores numéricos de calibración inicial — GO para Fase 4).docs/ADR/0001-arquitectura-inicial.md — decisión de stack y arquitectura del MVP. Aceptado, incluidas las precisiones de la quinta ronda (ruleVersion único, gate de presentación, campos de tipo de flujo/estado).chainpulse_end_to_end.md — especificación de producto más profunda y de más largo plazo (visión, no literal): recorrido de usuario, motor de diagnóstico, contratos de datos, arquitectura propuesta. Se usa como referencia, reconciliada por ADR-0002.docs/ADR/0002-alcance-escalonado-end-to-end.md — análisis de viabilidad multi-rol (Producto, CTO, Full-Stack, UX/UI, QA, Seguridad, Negocio), reconciliación de conflictos entre /ADR-0001 y , y hoja de ruta por incrementos. Aceptado, sincronizado con RF16-RF18 y el teléfono opcional de RF13.Dos máquinas, un solo repo (GitHub como único punto de sincronización — nunca compartir carpeta entre ellas):
/Users/alx/Documents/BigDevelopment/Proyectos/ChainPulse. Se usa para escribir código, typecheck, lint y npm run test (pruebas unitarias, sin red hacia Neon). binaries.prisma.sh está bloqueado en este entorno (403), así que prisma generate/migrate no corren aquí.C:\Users\alx\Documents\BigDevelopment\Proyectos\ChainPulse. Es la única carpeta de Windows a usar (no C:\Mac\Home\Documents\BigDevelopment\Proyectos\ChainPulse, la carpeta compartida de Parallels — da problemas de permisos y oculta el .env; ver claude/lecciones-aprendidas.md para el incidente completo). Ahí vive el .env real, con las credenciales de Neon.Toda operación que necesite el .env real o una conexión real a la base de datos se corre siempre desde esa carpeta de Windows, nunca desde el Mac ni desde una sesión de Claude. Incluye, sin limitarse a: npm run prisma:generate, npm run prisma:migrate, npm run prisma:seed, cualquier script npm run seed:* (por ejemplo seed:cuestionario-v2), npm run test:integration, y npm run dev/npm run build contra Neon real.
Windows es ARM64: el Node por defecto no sirve para los binarios nativos de Prisma — hay un Node 22 x64 portátil instalado en C:\Users\alx\Tools\node22-x64, ya agregado al $PROFILE de PowerShell (cualquier ventana nueva lo activa sola, sin pasos manuales).
git clone https://github.com/alexhsantamaria/ChainPulse.git
cd ChainPulse
npm install
npm run prisma:generate
npm run typecheck # tsc --noEmit
npm run lint # eslint .
npm run test # vitest run — 49 pruebas (motor v1 + infra), sin red
npm run test:integration # vitest run --config vitest.integration.config.ts — RNF1, necesita DATABASE_URL real
npm run dev # Next.js en http://localhost:3000
prisma/schema.prisma usa una base PostgreSQL real en Neon. En desarrollo normal, DATABASE_URL (en .env, no versionado) usa el rol restringido chainpulse_app (sujeto a las políticas RLS de prisma/rls.sql, ya aplicadas en la base) y nunca hace falta tocarlo a mano. Para migraciones (prisma migrate dev/deploy), Prisma usa automáticamente directUrl (SEED_DATABASE_URL, rol neondb_owner) en vez de DATABASE_URL — no hay que cambiar ninguna variable a mano para migrar (ver el comentario de directUrl en prisma/schema.prisma; cambiar DATABASE_URL a mano fue un error real de una sesión anterior, ver claude/lecciones-aprendidas.md). npm run test:integration crea y borra sus propios dos tenants de prueba en cada corrida (nunca toca las cuentas de prueba existentes) para verificar RLS con datos reales — ver el ## Estado de RNF1 más abajo.
RF1-RF10, RNF1, RNF9 y la recuperación de contraseña están implementados y validados de punta a punta en Windows — el Incremento 1 completo (ADR-0002) queda cerrado de verdad, incluyendo el flujo que faltaba de ADR-0003. RESEND_API_KEY/RESEND_FROM_EMAIL ya se recargaron (clave nueva de Resend con permiso mínimo "Sending access"; la anterior, de "Full access", quedó revocada). El bug conocido del botón "Cerrar sesión" ya se revisó (ver sección de arriba): no se reprodujo, funciona correctamente. Las credenciales de Neon (neondb_owner, chainpulse_app) y NEXTAUTH_SECRET ya se rotaron de nuevo el 2026-09-15 y quedaron validadas de punta a punta (ver SEGURIDAD-credenciales.md). Con esto, el Incremento 1 no tiene ningún pendiente abierto. Alex subió el 2026-09-15 una nueva especificación (CHAINPULSE_ESPECIFICACION_FUNCIONAL_TECNICA_V2.md) que redefine la puerta de entrada pública (evaluación exprés, RF11-RF18, todavía no construida). La reconciliación con lo ya construido, con el alcance completo (Incrementos 2 a 7, incluyendo Market Signals) ya aprobado por Alex, queda en MVP-DEFINITIVO.md. Se corrieron además tres rondas de revisión de viabilidad técnica multi-agente — consolidadas en PLAN-DE-TRABAJO.md v3.0 — que encontraron y corrigieron un dato incorrecto (el rate limiting de la evaluación pública anónima, RF15/RF17, estaba marcado como cumplido en MVP-DEFINITIVO.md pero no está construido) y, en la Ronda 3, una inconsistencia crítica de nombres/columnas en la tabla de rate limiting entre secciones del propio plan (ya corregida). Alex ya confirmó las 3 decisiones de infraestructura de la Sección 3 (cola de jobs Postgres-nativa, Cloudflare R2, ConexionCadena). Alex confirmó el 2026-09-16 la autenticación de UsuarioPlataforma (correo+contraseña+autenticador), el presupuesto de infraestructura ($0/mes, evaluar mes a mes) y el umbral de k-anonimato (20 organizaciones) y pidió cerrar todas las brechas antes de empezar — la Ronda 4 de revisión (PLAN-DE-TRABAJO.md Sección 18) resolvió con diseños concretos casi todo lo que quedaba abierto; el único punto que sigue sin poder cerrarse sin una persona externa es la revisión legal Ley 29733. El detalle EARS del Incremento 2 (RF19-RF26: UsuarioPlataforma, versionado de cuestionario, motor v2, Consentimiento, HallazgoExpres) ya está redactado en requirements.md Sección 13. Con el go-ahead explícito de Alex del 2026-09-16 se construyó el Bloque A recomendado (cola de jobs + + purga de , ver más arriba) — Próximo paso: el resto del schema del Incremento 2 (, /, motor v2, , ) ya está escrito y validado de punta a punta en Windows (ver más arriba) — el schema completo del Incremento 2 queda cerrado sin pendientes de código. Lo que sigue es construir el flujo en sí (rutas públicas , el motor v2, las pantallas de evaluación exprés) y todavía no arrancó — requiere su propia confirmación explícita de Alex, mismo criterio de siempre. ChainPulse está desplegado y en producción real en desde el 2026-09-16 (ver hito de despliegue más arriba). La Tarea #58 (trámites Ley 29733 ante la ANPD) sigue bloqueada: Alex confirmó el 2026-09-16 que todavía no tiene a mano la razón social/RUC/domicilio legal necesarios para cerrarla. La Ronda 5 (ver más arriba) encontró 26 hallazgos sobre el código ya construido y los 26 ya se corrigieron ( Sección 20) — queda pendiente que Alex corra y contra Neon real para validar las 4 migraciones nuevas de esta ronda, mismo paso final de siempre.
tsceslintvitesttenantClient()[]fileParallelism: falsevitest.integration.config.tsbeforeAlltypechecklinttesttest:integrationrequirements.mdchainpulse_end_to_end.mddocs/ADR/0003-autenticacion.md — decisión de mecanismo de autenticación para RF1/RF4: Auth.js (NextAuth), hash Argon2id, MFA para administradores, sourced en los apuntes del módulo de Seguridad. Aceptado.SEGURIDAD-credenciales.md — checklist de credenciales expuestas durante la configuración del entorno y pendientes de rotar (no contiene secretos reales).LimiteTasahuellaOrigenUsuarioPlataformaCuestionarioVersionPreguntaVersionConsentimientoHallazgoExpressrc/app/api/public/evaluations/**https://chain-pulse-steel.vercel.appPLAN-DE-TRABAJO.mdprisma:migratetest:integration