Saltar a contenido

Diagramas del sistema

Diagramas Mermaid de los flujos principales del sistema. Renderizados nativamente por el plugin mkdocs-mermaid2-plugin.

Topología de despliegue (modo producción)

flowchart LR
    Internet((Internet))

    subgraph CF["Cloudflare Edge"]
        DNS["DNS
custodiam.es"] Tunnel["Tunnel
cloudflared"] Pages["Pages
app.custodiam.es"] BookPages["Pages
docs.custodiam.es"] end subgraph Host["PC anfitrión (modo prod)"] Compose["Docker Compose
--profile tunnel"] subgraph Stack["Stack de servicios"] API_Container["custodiam-api
:8000"] KC_Container["Keycloak 26
:8080"] DB_Container[("PostgreSQL 15")] NTFY_Container["ntfy
:80"] end end Internet -->|HTTPS| DNS DNS -->|api.custodiam.es
auth.custodiam.es
ntfy.custodiam.es| Tunnel DNS -->|app.custodiam.es| Pages DNS -->|docs.custodiam.es| BookPages Tunnel -.cloudflared connect.-> API_Container Tunnel -.cloudflared connect.-> KC_Container Tunnel -.cloudflared connect.-> NTFY_Container API_Container --> DB_Container KC_Container --> DB_Container API_Container -.admin API.-> KC_Container

Flujo OAuth2 + PKCE (móvil)

sequenceDiagram
    participant U as Usuario
    participant App as Flutter App
(Android/iOS) participant KC as Keycloak participant API as custodiam-api U->>App: Toca "Iniciar sesión" App->>App: Genera code_verifier + code_challenge
(_pendingGrant en memoria) App->>KC: Abre Custom Tab con
authorize?code_challenge=... KC-->>U: Pantalla de login U->>KC: Credenciales (usuario + password) KC-->>App: Redirige a
es.custodiam://callback?code=... Note over App: Deep link captura el code App->>KC: POST /token
code + code_verifier KC-->>App: access_token + refresh_token App->>App: Guarda en flutter_secure_storage App->>API: GET /me
Authorization: Bearer API->>API: Valida JWT localmente con PyJWT
(usa JWKS cacheado) API-->>App: 200 OK + datos del voluntario

Flujo OAuth2 + PKCE (web)

sequenceDiagram
    participant U as Usuario
    participant Web as PWA Web
(Flutter Web) participant SS as sessionStorage participant KC as Keycloak participant API as custodiam-api U->>Web: Click "Iniciar sesión" Web->>Web: Genera code_verifier + code_challenge Web->>SS: Persiste code_verifier
(clave: custodiam.oauth.code_verifier) Web->>KC: window.location = authorize?code_challenge=...
(webOnlyWindowName: '_self') Note over Web: ⚠️ Recarga completa del navegador KC-->>U: Pantalla de login U->>KC: Credenciales KC-->>Web: Redirige a
https://app.custodiam.es/callback?code=... Note over Web: GoRouter (PathUrlStrategy) matcha /callback Web->>SS: Lee code_verifier Web->>KC: POST /token
code + code_verifier KC-->>Web: access_token + refresh_token Web->>SS: Limpia code_verifier
Guarda tokens en sessionStorage Web->>API: GET /me
Authorization: Bearer API-->>Web: 200 OK + datos del voluntario

Asimetría móvil/web — ADR-023

La diferencia clave entre móvil y web: en móvil la app sobrevive la redirección al IdP (deep link vuelve a la misma instancia con el _pendingGrant intacto en memoria); en web, la navegación a Keycloak recarga la PWA completa, perdiendo el estado en memoria. Por eso web necesita persistir el code_verifier en sessionStorage antes de redirigir. Se implementa con dos AuthService distintos seleccionados vía kIsWeb. Detalle completo en [ADR-023 del repo privado].

Flujo de notificación de emergencia

sequenceDiagram
    participant Coord as Coordinador
(usuario) participant App as App Coord participant API as custodiam-api participant FCM as Firebase FCM participant NTFY as ntfy participant Volunt as App Voluntario
(N dispositivos) Coord->>App: Crea convocatoria de emergencia App->>API: POST /servicios
tipo=emergencia, voluntarios_filtrados API->>API: Filtra voluntarios disponibles + permisos API->>FCM: Envía push a tokens FCM
(canal principal) alt FCM responde 200 FCM-->>Volunt: Push notification
(notificación + datos del servicio) Note over Volunt: Voluntario toca → abre app en pantalla del servicio else FCM falla o timeout > 5s API->>NTFY: Publica al canal /custodiam-emergencia
(fallback ADR-005) NTFY-->>Volunt: Push via ntfy
(canal secundario) Note over API: Log de fallo de FCM
+ telemetría para análisis end Volunt->>API: PATCH /servicios/{id}/voluntarios/{me}
{respuesta: "acepto" | "rechazo"} API-->>App: Notifica al coordinador
(via FCM tambien)

Despliegue del book de documentación

flowchart LR
    Dev["Dev (commit + push)"]
    GH["GitHub Actions
workflow deploy.yml"] Pages["GitHub Pages
branch gh-pages"] DNS["Cloudflare DNS
CNAME docs → custodiam.github.io
(DNS only, sin proxy)"] User((Usuario)) Dev -->|push a main| GH GH -->|uv sync + mkdocs build| GH GH -->|peaceiris/actions-gh-pages@v4| Pages User -->|https://docs.custodiam.es| DNS DNS -->|resuelve a IP de GitHub Pages| Pages User -.fallback.->|custodiam.github.io/custodiam-book/| Pages

Resiliencia vendor-lock-free

El sitio sigue siendo accesible en https://custodiam.github.io/custodiam-book/ aunque Cloudflare desaparezca: solo se perdería el dominio "bonito" docs.custodiam.es. Cloudflare se reserva como CDN opcional futuro (toggle Proxied reversible). Decisión en [ADR-027].

Referencias

  • Stack técnico — tecnologías concretas con versiones.
  • ADRs — decisiones arquitectónicas que sostienen estos diagramas.