Mike (@mikerb95)CodeByMike

Los cuatro procesos de negocio del sistema en notación BPMN: cobro de campo, acceso al portal de clientes, respuesta de seguridad y ciclo de monitoreo. A diferencia de los diagramas de secuencia, aquí lo que se lee es quién hace cada cosa ydónde se decide el camino — por eso cada participante tiene su carril y toda bifurcación pasa por una compuerta.

El SVG se genera en el servidor desde un modelo tipado (src/data/bpmn.ts); las posiciones, el ruteo de las flechas y el corte de las etiquetas los calcula src/lib/bpmn-layout.ts, con tests que verifican que ninguna flecha atraviese una figura ajena y que ningún nodo quede encimado.

Para el documento de arquitectura —que es vertical— existe la misma página transpuesta y sobre papel: cada participante en una columna, el proceso bajando, una hoja A4 por proceso.

Notación

  • Evento de iniciodispara el proceso
  • Evento intermedioalgo ocurre a mitad del flujo
  • Inicio temporizadolo dispara el reloj, no una persona
  • Temporizador de bordecorta la tarea si se pasa del plazo
  • Evento de finel proceso termina bien
  • Fin por errortermina por una condición de fallo
  • Tarea de usuariola ejecuta una persona
  • Tarea de serviciola ejecuta el sistema
  • Tarea de envíoemite un mensaje hacia afuera
  • Tarea de scriptlógica automática interna
  • Compuerta exclusivaun solo camino de salida

Los 5 tipos de compuerta

Las cinco se dibujan con el mismo rombo: lo único que cambia es el marcador de dentro. Ese detalle no es decorativo — decide si el proceso sigue por un camino o por varios, y si al juntarse espera a los demás o no. De las cinco, estos diagramas solo necesitan la exclusiva; las otras cuatro van aquí con el ejemplo de dónde encajarían en este sistema, para que se entienda la diferencia.

  • Compuerta ExclusivaXORen uso

    Se marca con una X.

    Al dividir
    Toma UN solo camino: el primero cuya condición se cumple. Las ramas son mutuamente excluyentes.
    Al juntar
    Deja pasar cada camino que llega, sin esperar a los demás.

    Es la única que usan estos cuatro diagramas: “¿Link vigente?”, “¿Credenciales válidas?”, “¿IP bloqueada?”.

  • Compuerta Basada en eventos

    Se marca con un pentágono dentro de un doble círculo.

    Al dividir
    También toma un solo camino, pero no lo decide una condición que el proceso evalúa: lo decide cuál de los eventos que espera ocurre primero. Es una carrera.
    Al juntar
    No se usa para juntar caminos; su sentido es abrir la espera.

    Encajaría en el cobro de campo: tras enviar el link, el proceso espera a que llegue el webhook de la pasarela o a que venza la vigencia, lo que pase antes. Hoy ese vencimiento se modela como una condición evaluada al abrir el link, que es como está implementado de verdad.

  • Compuerta ParalelaAND

    Se marca con un signo +.

    Al dividir
    Activa TODOS los caminos a la vez, sin evaluar ninguna condición.
    Al juntar
    Espera a que lleguen todos los caminos antes de continuar. Si uno no llega, el proceso se queda ahí.

    Sería lo correcto para el chequeo de monitores si el sondeo y la verificación del certificado corrieran a la vez; hoy van en secuencia dentro de la misma tarea.

  • Compuerta InclusivaOR

    Se marca con un círculo.

    Al dividir
    Activa todos los caminos cuya condición se cumple: uno, varios o todos. No son excluyentes entre sí.
    Al juntar
    Espera solo a los caminos que llegaron a activarse, no a todos los posibles.

    Describiría la notificación de un incidente si hubiera que avisar por push, por correo y en el panel según lo grave que sea: varios canales a la vez, no uno solo.

  • Compuerta Compleja

    Se marca con un asterisco.

    Al dividir
    Para condiciones que no caben en las anteriores; su comportamiento se explica con una expresión escrita al lado.
    Al juntar
    Sincroniza según esa misma expresión, por ejemplo “sigue cuando hayan llegado 3 de los 5 caminos”.

    Es la menos usada de las cinco, y con razón: si hay que leer un párrafo para saber qué hace, el dibujo dejó de comunicar por sí mismo.

Cobro de campo con link de pago

Del acuerdo verbal frente al cliente al pago conciliado en la base. El proceso vive sobre la máquina de estados idempotente de pagos: ningún reintento, reenvío ni webhook repetido puede cobrar dos veces.

src/pages/cobrar.astro · src/pages/c/[code].astro · src/lib/payments.ts · src/pages/api/payments/webhook.ts

ClienteOperador en campoBackendPasarela (Wompi)nonoEl cliente aceptapagarConfigura monto,descripción yvigenciavigencia 72 h por defectoCrea el pagoidempotente y sucódigo cortoEnvía el link porWhatsAppAbre /c/[code] yrevisa el monto¿Link vigente?Vencido o ya pagadoPaga en la pasarelaProcesa latransacciónWebhook recibido¿Firma y montocorrectos?Descartado conalerta pushAplica la transiciónal pagoPago conciliado

La compuerta de firma y monto es el corazón del control: si la pasarela reporta un monto distinto al del pago, el evento se registra como evidencia con la alerta correspondiente y el estado NO se transiciona.

Tiempos del proceso(5)— cada valor, con la constante que lo fija
ConceptoValorDónde está fijado
Vigencia del link de cobroSe elige al crear el cobro; 72 h es el valor por defecto porque cubre un fin de semana entero sin dejar el link vivo indefinidamente.24 h · 72 h · 7 días · sin vencimientoEXPIRY_OPTIONS / DEFAULT_EXPIRY — src/lib/cobros.ts
Momento en que se evalúa el vencimientoNo hay proceso en espera: nada corre mientras el cliente no abre el link, así que el vencimiento se comprueba en ese instante contra expiresAt.al abrir el link, no por temporizadorisExpired() — src/lib/cobros.ts
Límite de peticiones al link públicoEl código corto es adivinable por fuerza bruta; el límite la vuelve inviable sin estorbar a un cliente real.30 por minuto y por IPenforceLimit("cobro-link") — src/middleware.ts
Presupuesto de la consulta al limitador durablePasado ese plazo se deja pasar el request (fail-open): el limitador no puede volverse la causa de la caída.150 mstimeoutMs — src/lib/security/ratelimit-durable.ts
Reintentos al aplicar el evento de la pasarelaConcurrencia optimista: si dos webhooks del mismo pago compiten, el que pierde reintenta con la versión nueva en vez de pisar el estado.hasta 5MAX_RETRIES — src/lib/payments.ts

Invitación y acceso al portal de clientes

Alta de un usuario de cliente y su primer inicio de sesión. Autenticación propia (scrypt + cookie de sesión), completamente separada de la del administrador.

src/lib/portal/invitations.ts · src/lib/portal/login.ts · src/lib/portal/session.ts

AdministradorPortal (backend)Usuario del clientenonomensaje únicoSe acuerda daraccesoInvita el correo conun rol¿Correo libre paraeste cliente?Rechazo: pertenece aotro clienteEmite token con TTL yanula los anterioresEnvía el correo deinvitaciónDefine su contraseñaInvitación vencidaDeriva scrypt yconsume el tokenInicia sesión¿Credencialesválidas?sesión 30 díasCrea la sesión y sucookie propia15 min tras 10 intentosCuenta el intento ybloquea al llegar altopeAcceso solo a losdatos de su cliente72 hVence el token

El correo es único global: si ya pertenece a otro cliente, la invitación se rechaza en vez de reasignarlo. Reasignar sería una fuga de datos entre clientes servida en bandeja.

Tiempos del proceso(6)— cada valor, con la constante que lo fija
ConceptoValorDónde está fijado
Vigencia de la invitaciónDa margen a un cliente que abre el correo el lunes después de recibirlo el viernes.72 hINVITE_TTL_MS — src/lib/portal/invitations.ts
Vigencia del enlace de restablecimientoMucho más corto que la invitación a propósito: en un restablecimiento el buzón ya es un vector activo, así que la ventana de abuso se recorta.30 minRESET_TTL_MS — src/lib/portal/invitations.ts
Bloqueo por intentos fallidosFrena la fuerza bruta sin dejar que un tercero deje fuera a un cliente legítimo de forma indefinida.15 min tras 10 intentosLOCK_MS / MAX_ATTEMPTS — src/lib/portal/login.ts
Duración de la sesión del portalCada visita empuja el vencimiento; un cliente que entra una vez al mes no tiene que volver a autenticarse.30 días, renovablesSESSION_TTL_MS — src/lib/portal/session.ts
Refresco del registro de actividad de la sesiónSin este freno, cada request escribiría en la tabla de sesiones solo para actualizar la marca de "visto por última vez".como mucho cada 5 minWRITE_THROTTLE_MS — src/lib/portal/session.ts
Límite de intentos de autenticaciónEs una segunda barrera por IP, independiente del bloqueo por cuenta: sin ella, atacar 500 cuentas distintas saldría gratis.10 por minuto y por IPenforceLimit("portal-auth") — src/middleware.ts

Detección y respuesta de seguridad (micro-SIEM)

Recorrido de un request por el middleware: clasificación, blocklist, honeypot y rate limiting durable, con el registro del evento y la alerta al operador.

src/middleware.ts · src/lib/security/{sensor,classify,blocklist,ratelimit-durable,events}.ts

Cliente HTTPMiddlewareMicro-SIEMOperadornononoLlega un requestClasifica método,ruta y cabecerascaché 30 s¿IP bloqueada?403 seco, sin pistas¿Tocó un honeypot?1 h → 24 h → 7 díasBloquea la IP con TTLescaladoLa IP cae en lablocklistventana de 60 s¿Excede el límite?429 con Retry-AfterRegistra el eventosin bloquear larespuestaNotifica si laseveridad lo ameritaContinúa a la páginao APIRespuesta servidaRevisión en el panel

Todo el carril de seguridad es fail-open: si el clasificador, el limitador o el registro fallan, el request sigue su curso. Un sistema de defensa capaz de tumbar el sitio que protege es una superficie de ataque nueva, no una defensa.

Tiempos del proceso(5)— cada valor, con la constante que lo fija
ConceptoValorDónde está fijado
Presupuesto del middleware antes de ceder el pasoEs el único tiempo de espera del camino caliente. Agotado el plazo, el request pasa sin verificar: el coste de un falso negativo es menor que el de tumbar el sitio.150 ms para la consulta durabletimeoutMs — src/lib/security/ratelimit-durable.ts
Ventana del limitadorVentana fija por IP: 10 peticiones para autenticación del portal, 30 para el resto de autenticación y para los links de cobro, 600 como paraguas general.60 swindowMs — src/middleware.ts
Caché en memoria de la lista de bloqueoEvita ir a la base en cada request. El precio es que desbloquear a una IP tarda hasta medio minuto en surtir efecto, que es aceptable en esa dirección.30 sCACHE_TTL_MS — src/lib/security/blocklist.ts
Duración del bloqueo, escalada por reincidenciaTodo bloqueo caduca solo. Un bloqueo permanente por una regla automática convierte cualquier falso positivo en un daño indefinido.1 h → 24 h → 7 díasBLOCK_TTL_STEPS_SEC — src/lib/security/blocklist.ts
Registro del evento y alertaSe dispara y se olvida: el usuario nunca espera a que termine de escribirse la auditoría.no bloquea la respuestarecordSecurityEvent — src/lib/security/events.ts

Chequeo de monitor, incidente y recuperación

Ciclo disparado por el cron externo: sondeo de cada servicio, materialización del estado, apertura o cierre del incidente y notificación al operador solo en las transiciones.

src/pages/api/cron/uptime-check.ts · /status

Cron externoEndpoint de chequeoServicio monitoreadoRegistro (Turso)Operadornonononocada 5 minDisparo del cron¿Secreto del cronválido?401 sin ejecutarnadaSondea el servicio ymide la respuestaResponde dentro delplazoRegistra el sondeo yel estado del monitordegradado si >2 s¿Sondeo correcto?¿Ya había incidente?¿Había incidenteabierto?Abre el incidente ymarca caídaActualiza el últimoerrorCierra el incidentecon su duraciónNada que cerrarAviso de serviciocaídoAviso de recuperaciónEstado publicado enla página pública12 sSe agota el tiempo ycuenta como caída

Se notifica en la transición, nunca en cada sondeo: un monitor caído genera un aviso al caer y otro al recuperarse, no uno cada cinco minutos.

Tiempos del proceso(7)— cada valor, con la constante que lo fija
ConceptoValorDónde está fijado
Cadencia del cicloEs el único tiempo del sistema que no vive en el código: el disparo es externo, y el endpoint no impone cadencia propia. Define el peor caso de detección de una caída.cada 5 minconfigurado en cron-job.org, fuera del repositorio
Plazo del sondeo HTTPPasado el plazo se aborta la petición y el sondeo cuenta como fallo. Sin corte, un servicio que no cierra la conexión colgaría el ciclo entero.12 sREQUEST_TIMEOUT_MS — src/lib/monitors.ts
Umbral de degradadoSepara "responde pero va lento" de "responde bien". Un servicio degradado no abre incidente, pero sí se ve en la página de estado.2 s por defecto, ajustable por monitorlatencyThresholdMs — src/db/schema.ts, src/lib/monitors.ts
Plazo del apretón de manos TLSMenor que el del sondeo HTTP porque comprobar el certificado es un extra: si tarda, se omite en vez de retrasar el ciclo.8 sSSL_TIMEOUT_MS — src/lib/monitors.ts
Refresco de la fecha del certificadoAbrir un socket TLS es caro y la fecha de expiración cambia una vez cada varios meses: comprobarla cada 5 min sería puro desperdicio.como mucho cada 12 hSSL_REFRESH_MS — src/pages/api/cron/uptime-check.ts
Retención del historial de sondeosCubre de sobra la ventana de 30 días que publica la página de estado y mantiene la tabla acotada sin intervención.90 díasCHECK_RETENTION_DAYS — src/pages/api/cron/uptime-check.ts
Caducidad de sesiones de administración inactivasLa purga viaja en este mismo ciclo, en modo fail-open: si falla, no debe tumbar el chequeo de monitores.24 h sin actividadIDLE_EXPIRY_MS — src/lib/device-sessions.ts