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
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
| Concepto | Valor | Dó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 vencimiento | EXPIRY_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 temporizador | isExpired() — 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 IP | enforceLimit("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 ms | timeoutMs — 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 5 | MAX_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
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
| Concepto | Valor | Dó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 h | INVITE_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 min | RESET_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 intentos | LOCK_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, renovables | SESSION_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 min | WRITE_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 IP | enforceLimit("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
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
| Concepto | Valor | Dó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 durable | timeoutMs — 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 s | windowMs — 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 s | CACHE_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ías | BLOCK_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 respuesta | recordSecurityEvent — 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
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
| Concepto | Valor | Dó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 min | configurado 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 s | REQUEST_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 monitor | latencyThresholdMs — 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 s | SSL_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 h | SSL_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ías | CHECK_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 actividad | IDLE_EXPIRY_MS — src/lib/device-sessions.ts |