Mike (@mikerb95) CodeByMike

Formato extendido (precondiciones, flujo principal, flujos alternos, excepciones y postcondiciones) para los 10 casos de uso más críticos del sistema: los que involucran dinero, seguridad o estado que debe ser consistente ante fallos.

Caso de uso CU-04 Iniciar sesión como administrador Administrador (Mike)
Descripción El administrador se autentica con GitHub y accede al panel si su login está en la allowlist.
Precondición El administrador tiene una cuenta de GitHub válida. Su login está registrado en la allowlist de src/lib/auth.ts.
Secuencia normal
Paso Acción
1 El administrador visita /admin sin sesión activa.
2 El middleware detecta ausencia de sesión y redirige a /api/auth/signin.
3 El administrador autoriza la app OAuth de GitHub.
4 Auth.js valida el login contra la allowlist.
5 Se emite un JWT de sesión y se registra el dispositivo en admin_sessions.
6 El administrador es redirigido al panel /admin.
Flujos alternos
Login no autorizado
  1. 1. GitHub autentica correctamente pero el login no está en la allowlist.
  2. 2. Auth.js rechaza la sesión y muestra error de acceso denegado.
Postcondición El administrador tiene una sesión JWT activa y un registro en admin_sessions con IP y user-agent.
Excepciones
Paso Acción
1 GitHub OAuth no disponible: el login falla con mensaje de error genérico, sin exponer detalles internos.
Caso de uso CU-09 Recibir alerta de monitor caído Cron externo (cron-job.org / Vercel Cron)
Descripción El cron externo dispara el chequeo, detecta una caída, abre un incidente y notifica por push.
Precondición Existe un monitor activo y no pausado en la tabla monitors. El cron externo tiene configurado el CRON_SECRET válido.
Secuencia normal
Paso Acción
1 El cron externo llama a /api/cron/uptime-check con el secreto.
2 El sistema itera los monitores activos y hace la petición HTTP configurada (método, texto esperado, umbral de latencia).
3 La respuesta falla (status inesperado, timeout o texto ausente).
4 Se inserta un monitor_check con ok=false.
5 Si es el primer fallo consecutivo, se abre un monitor_incidents con startedAt.
6 Se actualiza monitors.lastStatus a "down" y se dispara una notificación push (ntfy).
Flujos alternos
Recuperación
  1. 1. Un chequeo posterior tiene éxito.
  2. 2. Se cierra el incidente abierto con resolvedAt y durationSec.
  3. 3. Se notifica la recuperación.
Degradación por latencia
  1. 1. La respuesta es exitosa pero supera latencyThresholdMs.
  2. 2. lastStatus pasa a "degraded" sin abrir incidente.
Postcondición El estado materializado del monitor refleja el último chequeo; el historial permite reconstruir el SLO.
Excepciones
Paso Acción
1 El endpoint del monitor no responde en absoluto (timeout de red): se registra como fallo con error de timeout.
Caso de uso CU-12 Procesar un pago con idempotencia Pasarela de pagos (Wompi)
Descripción La pasarela envía un webhook de pago; el sistema aplica el evento respetando idempotencia y orden.
Precondición Existe un payment en estado created o pending con un idempotencyKey único.
Secuencia normal
Paso Acción
1 La pasarela (Wompi) envía un webhook con el resultado de la transacción.
2 El sistema busca el payment por reference/gatewayTxId.
3 Se registra el evento crudo en payment_events (incluyendo si es duplicado o fuera de orden).
4 Si el evento es válido y en orden, se aplica la transición de estado (created→pending→approved/declined).
5 Se responde 200 a la pasarela para confirmar recepción.
Flujos alternos
Evento duplicado
  1. 1. El gatewayTxId ya fue procesado.
  2. 2. Se marca duplicate=true en payment_events.
  3. 3. No se modifica el estado del payment.
Evento fuera de orden
  1. 1. Llega un evento "pending" después de uno "approved".
  2. 2. Se marca outOfOrder=true.
  3. 3. El estado terminal previo se conserva (nunca retrocede).
Postcondición El estado del payment refleja fielmente la transacción real, con bitácora completa auditable para sustentación.
Excepciones
Paso Acción
1 El monto del evento no coincide con el del payment: se marca amountMismatch=true y se genera una alerta; el evento nunca se aplica.
Caso de uso CU-14 Bloquear una IP maliciosa Administrador (Mike)
Descripción El sensor clasifica un request hostil; el administrador (o el auto-block) añade la IP a la blocklist con TTL.
Precondición El sensor de seguridad (sensor.ts) está observando requests entrantes.
Secuencia normal
Paso Acción
1 Llega un request al middleware.
2 observeRequest clasifica el request contra las firmas conocidas (classify.ts).
3 Se detecta una firma de severidad alta/crítica (p. ej. intento de path traversal).
4 Se registra un security_events con category, severity y ruleId.
5 El cron de auto-block evalúa la reincidencia de esa IP y decide bloquearla con TTL escalonado (1h → 24h → 7d).
6 La IP queda en blocked_ips con expiresAt obligatorio.
Flujos alternos
Bloqueo manual
  1. 1. El administrador revisa un evento en el panel y decide bloquear la IP manualmente.
  2. 2. Se inserta en blocked_ips con source=manual.
Postcondición Requests posteriores de esa IP reciben 403 seco hasta que expire el bloqueo.
Excepciones
Paso Acción
1 La lectura de blocklist falla (timeout de DB): el middleware falla abierto y deja pasar el request (nunca bloquea por error interno).
Caso de uso CU-18 Consultar documentación del proyecto Administrador (Mike)
Descripción El administrador navega /docs para revisar requerimientos, casos de uso, diagramas y el kanban del propio portfolio.
Precondición El administrador tiene sesión activa en /admin.
Secuencia normal
Paso Acción
1 El administrador hace clic en "Documentación" en la sidebar.
2 Se muestra el hub /docs con visión general, alcance y mapa de subpáginas.
3 El administrador navega a una subpágina (RF, RNF, CU, diagramas o kanban) usando DocsNav.
4 La página renderiza el contenido desde src/data/documentacion.ts o src/data/iteraciones-portfolio.ts.
Flujos alternos
Consulta de diagrama
  1. 1. El administrador entra a una página de diagrama (secuencia/componentes/clases).
  2. 2. El navegador renderiza el diagrama Mermaid desde el texto embebido en la página.
Postcondición El administrador cuenta con la documentación de ingeniería completa del proyecto sin salir del panel.
Caso de uso CU-06 Registrar y dar seguimiento a un proyecto Administrador (Mike)
Descripción El administrador crea un proyecto, registra interacciones de seguimiento y documenta decisiones de arquitectura.
Precondición El administrador tiene sesión activa. Opcionalmente existe un cliente en la tabla clients al que asociar el proyecto.
Secuencia normal
Paso Acción
1 El administrador crea el proyecto desde /admin/projects con POST /api/admin/projects (requiere slug y title), quedando en estado "activo" y no visible al público.
2 Registra una interacción de seguimiento (llamada, reunión, tarea) con POST /api/admin/interactions, insertando en la tabla interactions con tipo, título, cuerpo y próxima acción.
3 Marca la interacción como resuelta con PUT /api/admin/interactions (done, doneAt).
4 Documenta una decisión de arquitectura con POST /api/admin/projects/[id]/adrs, insertando en project_adrs (contexto, decisión, justificación, estado).
5 Opcionalmente marca el ADR como isPublic para exponerlo en la vitrina pública del proyecto.
Flujos alternos
Actualizar estado del proyecto
  1. 1. El administrador cambia el estado con PUT /api/admin/projects/[id] a pausado, completado o archivado.
Editar o borrar un ADR
  1. 1. El administrador corrige o elimina una decisión previa vía PUT/DELETE sobre project_adrs.
Postcondición El proyecto queda con un historial trazable de interacciones y decisiones arquitectónicas en interactions y project_adrs.
Excepciones
Paso Acción
1 El POST de creación llega sin slug o title: la API responde 400 sin tocar la base de datos.
Caso de uso CU-08 Registrar costos y calcular P&L Administrador (Mike)
Descripción El administrador registra el costo de un servicio, quién lo paga y cuánto se factura al cliente.
Precondición El proyecto existe. ENCRYPTION_KEY está configurada si el costo incluye credenciales cifradas. Existen tasas de cambio en app_settings para costos que no están en USD.
Secuencia normal
Paso Acción
1 El administrador registra un servicio o costo (ciclo de facturación, moneda, quién paga y a quién se factura) insertando en project_services.
2 Registra el ingreso cobrado o pendiente en la tabla finances.
3 La vista del proyecto invoca projectPnL() (src/lib/pnl.ts) con los servicios, las finanzas y las tasas de cambio.
4 projectPnL calcula el costo mensual equivalente en USD por servicio y lo proyecta desde la fecha de inicio del proyecto.
5 Se obtiene el margen estimado restando el costo acumulado a los ingresos cobrados.
6 El panel muestra ingresos, costo mensual/anual, costo acumulado y margen, coloreado según sea positivo o negativo.
Flujos alternos
Editar o eliminar un servicio
  1. 1. El administrador ajusta o borra un costo; el P&L se recalcula en el siguiente render, sin job asíncrono.
Costo sin tasa de cambio
  1. 1. Un costo en moneda sin tasa configurada se excluye del total y se muestra como advertencia con link a /admin/settings.
Postcondición El P&L del proyecto refleja el nuevo costo o ingreso desde el siguiente GET del detalle, sin desfase.
Excepciones
Paso Acción
1 Falta ENCRYPTION_KEY al guardar credenciales de un servicio: la API responde 500 pidiendo configurar la clave de cifrado.
Caso de uso CU-11 Ejecutar backup manual Administrador (Mike)
Descripción El administrador dispara un backup de la base de datos hacia Blob storage desde el panel.
Precondición El administrador tiene sesión activa (o, en el modo automático, el cron externo dispone del CRON_SECRET). Vercel Blob está habilitado en el proyecto.
Secuencia normal
Paso Acción
1 El administrador dispara el backup manual desde /admin/backup.
2 runBackup() consulta en paralelo las tablas de negocio (clients, projects, messages, finances, projectServices, projectAdrs, briefings, entre otras).
3 Arma un dump JSON con metadatos de versión y fecha, más el contenido de cada tabla.
4 Sube el dump a Vercel Blob como backups/portfolio-{fecha}-{timestamp}.json con acceso privado.
5 Devuelve al panel la URL, el tamaño y el pathname del backup generado.
6 El panel lista los últimos 30 backups ordenados por fecha para verificación visual.
Flujos alternos
Backup automático por cron
  1. 1. Vercel Cron llama al mismo endpoint con el CRON_SECRET en lugar de sesión de administrador, ejecutando runBackup() sin intervención manual.
Consultar historial sin ejecutar
  1. 1. El administrador solo lista los backups existentes, sin generar uno nuevo.
Postcondición Queda un archivo JSON inmutable en Vercel Blob con una fotografía completa de las tablas de negocio en ese momento.
Excepciones
Paso Acción
1 Falla la conexión a la base de datos durante el respaldo: el endpoint responde 500 y el fallo queda registrado en logs, sin generar un blob parcial.
Caso de uso CU-13 Inyectar un fallo de chaos engineering Administrador (Mike)
Descripción El administrador activa un flag de fallo temporal en una ruta y observa cómo el monitoreo lo detecta.
Precondición El administrador tiene sesión activa. La ruta objetivo no pertenece a /admin, /api/admin ni /api/auth (protegidas contra auto-sabotaje).
Secuencia normal
Paso Acción
1 El administrador crea un flag de chaos desde /admin/lab/chaos, indicando tipo (latencia, error 500 o caída de servicio), ruta objetivo y TTL.
2 El sistema valida el tipo y la ruta, aplica topes de seguridad (latencia máxima y TTL máximo) y calcula la expiración.
3 El flag se inserta activo en la tabla chaos_flags y se invalida la caché para que aplique de inmediato.
4 En cada request, el middleware evalúa los flags activos (con caché corta) y busca una coincidencia con la ruta solicitada.
5 Si coincide, aplica el fallo simulado: introduce latencia, o responde error 500/503 con un header que identifica que es chaos.
6 El chequeo de uptime del cron detecta la caída simulada en su siguiente sondeo, igual que detectaría una caída real.
Flujos alternos
Expiración natural
  1. 1. Al vencer el TTL, el flag deja de aplicarse automáticamente, sin que el administrador tenga que desactivarlo.
Botón de pánico
  1. 1. El administrador desactiva todos los flags activos de una sola vez desde el panel.
Postcondición Las rutas coincidentes sufren el fallo simulado hasta que el flag expira o se apaga manualmente, permitiendo validar que el monitoreo lo detecta.
Excepciones
Paso Acción
1 Si la lectura de flags falla por un problema de base de datos, el middleware falla abierto: el request pasa limpio y nunca se cae el sitio real por un error del propio motor de caos.
Caso de uso CU-16 Presentar un proyecto a un cliente Cliente
Descripción El administrador controla remotamente el avance de una presentación que el cliente ve en su navegador.
Precondición Existe una presentación creada para el proyecto con al menos un slide subido.
Secuencia normal
Paso Acción
1 El administrador crea la presentación, que queda identificada con un token de acceso aleatorio y arranca en el primer slide.
2 Sube las imágenes de los slides, asociadas a la presentación en orden.
3 Comparte con el cliente el enlace de vista, que este abre en su navegador en pantalla completa.
4 La vista del cliente consulta el estado actual de la presentación cada fracción de segundo (polling corto sobre HTTP).
5 El administrador controla el avance desde otra pestaña o su celular, avanzando o retrocediendo slides.
6 Cada acción de control actualiza el slide actual persistido en base de datos.
7 En el siguiente ciclo de polling, la vista del cliente detecta el cambio y hace la transición al nuevo slide.
Flujos alternos
Agregar slides en vivo
  1. 1. El administrador sube un slide nuevo mientras la presentación está en curso; la vista del cliente lo incorpora sin recargar la página.
Navegación directa
  1. 1. El administrador salta a un slide específico desde la grilla de miniaturas, en lugar de avanzar de a uno.
Postcondición El slide actual queda sincronizado entre el control del administrador y la vista del cliente mediante estado persistido, sin necesidad de una conexión en tiempo real (WebSockets).
Excepciones
Paso Acción
1 Si la presentación consultada no existe, la vista del cliente ignora la respuesta fallida y sigue reintentando sin romper la interfaz.