Appearance
Manual Técnico
Subsistema informático de soporte para el proceso de selección de postulantes al curso de formación y especialización judicial.
Exportar a PDF
Esta página reúne todo el manual. Use Ctrl + P (Cmd + P en macOS) y elija Guardar como PDF.
Guía de Integración OAuth2 y OpenID Connect
Esta sección está orientada a desarrolladores que necesitan implementar login con el proveedor de identidad:
- Aplicaciones web (frontend + backend).
- Aplicaciones backend que consumen APIs protegidas.
- Integraciones que requieren identidad de usuario mediante OIDC.
Qué resuelve esta guía
- Cómo implementar el flujo de autorización recomendado (
authorization_code+ PKCE). - Cómo intercambiar
codepor tokens y consultaruserinfo. - Cómo validar, revocar y cerrar sesión de forma segura.
- Cómo consumir la API de directorio con token OAuth2.
Estándares de referencia
- OAuth 2.0 (RFC 6749): modelo de autorización y tipos de grant.
- PKCE (RFC 7636): protección del flujo de código de autorización.
- OpenID Connect Core 1.0: capa de identidad sobre OAuth2.
- OpenID Connect Discovery 1.0: descubrimiento automático de metadatos.
- OAuth 2.0 Token Introspection (RFC 7662): validación de tokens.
- OAuth 2.0 Token Revocation (RFC 7009): revocación de tokens.
- JSON Web Key (RFC 7517) y JWT (RFC 7519): verificación de firmas y estructura de tokens.
Ruta sugerida de lectura
- Introducción y decisiones de arquitectura.
- Discovery OIDC y configuración de cliente.
- Flujo de login paso a paso con ejemplos HTTP.
- Consumo de endpoints de negocio (
/api/v1/directory/users/*).
Introducción
Base URL por entorno
En este módulo se usa BASE_URL_IDP como referencia común para todos los endpoints.
- Entorno de test:
https://backend.idp-test.eje.gob.bo - Entorno de producción:
https://backend.idp.eje.gob.bo
Regla de uso:
- Reemplazar
BASE_URL_IDPpor la URL del entorno objetivo (test o producción).
Esta guía explica cómo integrar autenticación de usuarios con OAuth2 y OpenID Connect (OIDC) contra:
- Discovery OIDC:
BASE_URL_IDP/.well-known/openid-configuration - Documentación API:
BASE_URL_IDP/api/docs
Cuándo usar OAuth2 y cuándo OIDC
- Usa OAuth2 cuando tu objetivo principal es autorización para consumir una API con
access_token. - Usa OIDC cuando, además de autorización, necesitas identidad de usuario autenticado (
id_tokeny/ouserinfo).
En login de aplicaciones web, normalmente se usan ambos: OAuth2 para tokens de acceso y OIDC para identidad.
Flujo recomendado para login
Para aplicaciones web y SPA con backend, se recomienda:
authorization_code.- PKCE (
S256). - Intercambio de código en backend o BFF (evitar exponer secretos en frontend).
- Sesión de aplicación basada en cookie segura (HttpOnly, Secure, SameSite).
Roles del protocolo
- Authorization Server / IdP: emite tokens y autentica usuarios.
- Client: tu aplicación (frontend, backend o BFF).
- Resource Server: APIs protegidas que validan tokens.
- Resource Owner: usuario final.
Datos que debe definir el equipo integrador
scopemínimo requerido (openid,profile,emaily, si aplica, scopes de directorio oficiales).
Referencias normativas
- OAuth 2.0: RFC 6749.
- PKCE: RFC 7636.
- OpenID Connect Core 1.0.
- OpenID Connect Discovery 1.0.
Discovery OAuth2/OIDC
Fuente: BASE_URL_IDP/.well-known/openid-configuration
El discovery permite que el cliente no tenga endpoints hardcodeados y obtenga metadatos oficiales del proveedor.
Metadatos principales
issuer:BASE_URL_IDPauthorization_endpoint:BASE_URL_IDP/auth/public/authorize/token_endpoint:BASE_URL_IDP/oauth2/tokenuserinfo_endpoint:BASE_URL_IDP/oidc/userinfojwks_uri:BASE_URL_IDP/.well-known/jwks.jsonintrospection_endpoint:BASE_URL_IDP/oauth2/introspectrevocation_endpoint:BASE_URL_IDP/oauth2/revokeend_session_endpoint:BASE_URL_IDP/auth/logout/client
Cómo consumir discovery en runtime
- Cargar el documento de discovery al iniciar la aplicación o en bootstrap de autenticación.
- Cachear metadatos en memoria con TTL corto (por ejemplo, 5 a 15 minutos).
- Usar exclusivamente endpoints publicados por discovery para reducir desalineaciones por cambios del proveedor.
Ejemplo:
bash
curl -sS 'BASE_URL_IDP/.well-known/openid-configuration'Capacidades declaradas
response_types_supported:coderesponse_modes_supported:querygrant_types_supported:authorization_coderefresh_tokenclient_credentials
code_challenge_methods_supported:S256id_token_signing_alg_values_supported:RS256token_endpoint_auth_methods_supported:client_secret_postclient_secret_basic
Interpretación práctica:
response_types_supported: codeconfirma que el login debe implementarse con código de autorización.code_challenge_methods_supported: S256confirma uso de PKCE en modo seguro.id_token_signing_alg_values_supported: RS256indica que la verificación de firma usa clave pública RSA (JWKS).token_endpoint_auth_methods_supporteddefine cómo autenticar cliente en/oauth2/token(según tipo de cliente).
Scopes soportados
openidprofileemaildirectory:all:users:readdirectory:external:users:readdirectory:internal:users:readdirectory:admin:users:read
Recomendación:
- Solicitar solo scopes mínimos por principio de mínimo privilegio.
- Para login base, comenzar con:
openid profile email. - Agregar scopes de directorio solo cuando el caso de uso lo requiera.
- El sistema puede definir scopes adicionales para el acceso a datos específicos de Identidad, como los datos de ciudadanía.
- El sistema puede definir scopes dinámicos para la incorporación de nuevos servidores de recursos que deleguen autorización a la plataforma de identidad.
- Para consultar la existencia y forma de uso de scopes adicionales consultar al equipo de informática de la Escuela de Jueces del Estado.
Claims soportados
subnamegiven_namefamily_nameemailnro_documento_identidadtipo_documento_identidadamracrauth_timenonceat_hashiatexpissaudtipo_usuariois_eje_sysadmin
Validación de tokens y firma
Para id_token o JWT firmados con RS256:
- Obtener llaves en
jwks_uri. - Buscar la llave por
kiddel header del token. - Verificar firma y validar claims críticos:
iss,aud,exp,iat,nonce(si aplica).
Esto sigue OIDC Core y los RFC 7517/7519.
Endpoints OAuth2/OIDC
Los siguientes endpoints provienen del discovery OIDC del proveedor de identidad.
Menú de rutas
| Grupo | Método | Ruta | Uso principal |
|---|---|---|---|
| Autorización | GET | /auth/public/authorize/ | Iniciar flujo OAuth2/OIDC (authorization code). |
| Tokens | POST | /oauth2/token | Intercambiar code por tokens o renovar con refresh_token. |
| UserInfo | GET | /oidc/userinfo | Obtener claims del usuario autenticado. |
| Introspección | POST | /oauth2/introspect | Validar estado y metadatos de un access token. |
| Revocación | POST | /oauth2/revoke | Revocar access token o refresh token. |
| Claves públicas | GET | /.well-known/jwks.json | Obtener llaves públicas para verificación de JWT. |
| Cierre de sesión | GET/POST | /auth/logout/client | Finalizar sesión en el proveedor de identidad. |
Flujo de integración de login (Authorization Code + PKCE)
Paso 1: redirección a autorización
Endpoint: GET /auth/public/authorize/
Parámetros mínimos:
response_type=codeclient_id=<CLIENT_ID>redirect_uri=<URI_REGISTRADA>scope=openid profile emailstate=<ALEATORIO>code_challenge=<PKCE_CHALLENGE>code_challenge_method=S256
Ejemplo:
text
BASE_URL_IDP/auth/public/authorize/?response_type=code&client_id=mi_cliente&redirect_uri=https%3A%2F%2Fapp.midominio.com%2Fauth%2Fcallback&scope=openid%20profile%20email&state=RANDOM123&code_challenge=abc123...&code_challenge_method=S256Paso 2: intercambio de código por tokens
Endpoint: POST /oauth2/token
Content-Type esperado: application/x-www-form-urlencoded
Payload base:
grant_type=authorization_codecode=<AUTHORIZATION_CODE>redirect_uri=<URI_REGISTRADA>client_id=<CLIENT_ID>code_verifier=<PKCE_VERIFIER>
Ejemplo:
bash
curl -X POST 'BASE_URL_IDP/oauth2/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=mi_cliente' \
--data-urlencode 'code=AUTH_CODE' \
--data-urlencode 'redirect_uri=https://app.midominio.com/auth/callback' \
--data-urlencode 'code_verifier=PKCE_VERIFIER'Paso 3: obtención de identidad de usuario
Endpoint: GET /oidc/userinfo
Header:
Authorization: Bearer <access_token>
Ejemplo:
bash
curl 'BASE_URL_IDP/oidc/userinfo' \
-H 'Authorization: Bearer ACCESS_TOKEN'Paso 4: renovación de token
Endpoint: POST /oauth2/token
Payload:
grant_type=refresh_tokenrefresh_token=<REFRESH_TOKEN>client_id=<CLIENT_ID>
Paso 5: introspección (backend a backend)
Endpoint: POST /oauth2/introspect
Uso recomendado:
- Validar tokens cuando no se usa validación local JWT.
- Obtener estado
activey metadatos del token.
Paso 6: revocación y logout
- Revocación de token:
POST /oauth2/revoke - Logout del proveedor:
/auth/logout/client
Recomendación de cierre de sesión:
- Invalidar sesión local en tu aplicación.
- Revocar token/refresh token cuando aplique.
- Redirigir a endpoint de logout del IdP.
Rutas absolutas
BASE_URL_IDP/auth/public/authorize/BASE_URL_IDP/oauth2/tokenBASE_URL_IDP/oidc/userinfoBASE_URL_IDP/oauth2/introspectBASE_URL_IDP/oauth2/revokeBASE_URL_IDP/.well-known/jwks.jsonBASE_URL_IDP/auth/logout/client
Flujo recomendado
- Implementar
authorization_codecon PKCE. - Mantener secretos y refresh tokens fuera del frontend público.
- Validar
stateynoncepara mitigar replay/CSRF. - Verificar firma y claims de tokens cuando aplique.
- Aplicar rotación y revocación de tokens en logout.
Tutorial rápido con oauth2c en Linux
oauth2c permite probar flujos OAuth2/OIDC desde terminal sin escribir una aplicación de prueba completa. En este módulo puede usarse para validar los dos grant types que expone el discovery:
authorization_codeclient_credentials
Instalación en Linux
bash
go install github.com/cloudentity/oauth2c@latestVariables de entorno sugeridas
bash
export BASE_URL_IDP='https://backend.idp-test.eje.gob.bo'
export CLIENT_ID='mi_cliente'
export CLIENT_SECRET='mi_secreto'Flujo 1: authorization_code
Usa este flujo para probar login interactivo de usuario con navegador.
Requisitos previos:
- El
redirect_uridebe estar registrado para el cliente. - Para pruebas locales, una opción común es
http://localhost:9876/callback.
bash
oauth2c "$BASE_URL_IDP" \
--client-id "$CLIENT_ID" \
--client-secret "$CLIENT_SECRET" \
--response-types code \
--response-mode query \
--grant-type authorization_code \
--auth-method client_secret_basic \
--redirect-url http://localhost:9876/callback \
--scopes openid,profile,emailQué validar:
- Que se abra el navegador y el IdP complete el login.
- Que
oauth2creciba el callback y muestre elaccess_token. - Que el token permita consultar
GET /oidc/userinfoo las APIs que correspondan al scope solicitado.
Flujo 2: client_credentials
Usa este flujo para probar integración máquina a máquina sin usuario final.
bash
oauth2c "$BASE_URL_IDP" \
--client-id "$CLIENT_ID" \
--client-secret "$CLIENT_SECRET" \
--grant-type client_credentials \
--auth-method client_secret_basic \
--scopes <SCOPES_AUTORIZADOS_PARA_ESE_CLIENTE>Qué validar:
- Que el proveedor emita un
access_tokensin interacción de usuario. - Que el token sea aceptado por el recurso para el cual fue emitido.
- Que los scopes del cliente coincidan con los permitidos por la configuración del IdP.
Recomendaciones prácticas
- Empieza con
authorization_codesi quieres simular el recorrido de un usuario. - Usa
client_credentialssolo cuando estés validando un cliente de servicio. - Si el cliente no está registrado o el
redirect_urino coincide, el flujo fallará antes de obtener tokens. - Si necesitas un entorno distinto, cambia solo
BASE_URL_IDP.
API REST de Directorio de Identidades
Fuente de contrato OpenAPI (schema): https://auth-backend-eje.app.com.bo/api/schema/
Base URL de integración para este capítulo: BASE_URL_IDP (definida en la Introducción del módulo).
Documentación API: BASE_URL_IDP/api/docs/
Prerrequisitos
Antes de consumir estos endpoints, el cliente debe:
- Autenticar usuario con OAuth2/OIDC.
- Obtener
access_tokenválido. - Enviar
Authorization: Bearer <token>en cada request. - Solicitar scopes suficientes para cada operación.
Esquema de seguridad del API:
BearerAuth(HTTP Bearer JWT).
Scopes oficiales para API de directorio:
directory:all:users:readdirectory:external:users:readdirectory:internal:users:readdirectory:admin:users:read
Reglas de acceso por alcance:
directory:all:users:read: permite leer usuarios internos y externos.directory:external:users:read: limita lectura a usuarios externos.directory:internal:users:read: limita lectura a usuarios internos.directory:admin:users:read: habilita visualizar datos de usuarios administradores de sistema.
Menú de rutas de Directorio
| Tag | Método | Ruta | Summary |
|---|---|---|---|
| Directory | GET | /api/v1/directory/users/ | Listar identidades |
| Directory | GET | /api/v1/directory/users/{public_id}/ | Detalle de identidad |
Endpoint de schema
El contrato OpenAPI se expone en:
GET /api/schema/
Parámetros opcionales soportados por el endpoint de schema:
format:jsonoyamllang: localización de etiquetas/descripciones del schema
Rutas absolutas
BASE_URL_IDP/api/v1/directory/users/BASE_URL_IDP/api/v1/directory/users/{public_id}/
GET /api/v1/directory/users/
Retorna el directorio de identidades registradas en el IdP. Cada item de resultado sigue un formato equivalente al payload de userinfo OIDC.
Query params
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
is_active | boolean | No | Filtrar por estado activo. |
is_eje_sysadmin | boolean | No | Filtrar por rol administrador. Requiere scope directory:admin:users:read. |
page | integer | No | Número de página. |
page_size | integer | No | Resultados por página (máximo 100). |
termino | string | No | Búsqueda por término clave. |
tipo_usuario | string | No | Tipo de usuario: USUARIO_EXTERNO o USUARIO_INTERNO. |
Respuesta 200
application/json con estructura paginada:
json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"sub": "abc123",
"name": "Juan Perez",
"given_name": "Juan",
"family_name": "Perez",
"preferred_username": "juan.perez",
"email": "juan@example.com",
"email_verified": true,
"is_active": true,
"tipo_usuario": "USUARIO_INTERNO",
"nro_documento_identidad": "12345678",
"nro_documento_identidad_verified": true,
"fecha_nacimiento": "01/01/1990",
"celular": "70000000"
}
]
}Respuestas de error
400 Bad Request: parámetros inválidos.401 Unauthorized: token ausente, expirado o inválido.403 Forbidden: sin permisos/scopes suficientes.
Scopes requeridos
Se evalúan los scopes del token según el tipo de usuario consultado:
- Lectura general de internos y externos:
directory:all:users:read. - Lectura restringida a externos:
directory:external:users:read. - Lectura restringida a internos:
directory:internal:users:read. - Para ver datos de usuarios administradores de sistema:
directory:admin:users:read.
GET /api/v1/directory/users/{public_id}/
Retorna el detalle de una identidad por su public_id. La respuesta también es equivalente al formato de claims de userinfo OIDC.
Path params
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
public_id | string | Sí | Identificador público de la identidad. |
Respuesta 200
application/json:
json
{
"sub": "abc123",
"name": "Juan Perez",
"given_name": "Juan",
"family_name": "Perez",
"preferred_username": "juan.perez",
"email": "juan@example.com",
"email_verified": true,
"is_active": true,
"nro_documento_identidad": "12345678",
"nro_documento_identidad_verified": true
}Respuestas de error
401 Unauthorized: token ausente, expirado o inválido.403 Forbidden: token válido sin scope/permisos suficientes.404 Not Found: no existe identidad para esepublic_id.
Scopes requeridos
Para consultar un public_id, el token debe contar con scope compatible con el tipo de usuario objetivo (all, internal o external).
Si la identidad objetivo corresponde a un usuario administrador de sistema, además se requiere directory:admin:users:read.
Esquemas de respuesta
DirectoryIdentity
Campos reportados por identidad (el backend puede incluir claims adicionales):
sub: stringname: stringgiven_name: stringfamily_name: stringpreferred_username: stringemail: string (email)email_verified: booleanis_active: booleantipo_usuario: stringnro_documento_identidad: stringnro_documento_identidad_verified: booleanfecha_nacimiento: stringcelular: string
PaginatedDirectoryIdentity
count: integernext: string | nullprevious: string | nullresults: DirectoryIdentity[]
ApiErrorResponse
Formato estándar de error del directorio:
json
{
"error_code": "insufficient_scope",
"error_message": "El token no tiene permisos para acceder a este recurso.",
"detail": "El token no tiene permisos para acceder a este recurso."
}Campos:
error_code(requerido)error_message(requerido)detail(opcional)
Ejemplos de consumo
Listar identidades:
bash
curl 'BASE_URL_IDP/api/v1/directory/users/' \
-H 'Authorization: Bearer ACCESS_TOKEN'Listar con filtros y paginación:
bash
curl 'BASE_URL_IDP/api/v1/directory/users/?is_active=true&tipo_usuario=USUARIO_INTERNO&page=1&page_size=20&termino=juan' \
-H 'Authorization: Bearer ACCESS_TOKEN'Detalle de identidad:
bash
curl 'BASE_URL_IDP/api/v1/directory/users/8f9f9d10-xxxx-xxxx-xxxx-xxxxxxxxxxxx/' \
-H 'Authorization: Bearer ACCESS_TOKEN'Manejo de errores recomendado
401 Unauthorized: token ausente, expirado o inválido.403 Forbidden: token válido pero sin scope/permisos suficientes.400 Bad Request: filtros o parámetros inválidos.404 Not Found:public_idinexistente.
Recomendaciones de seguridad
- El token de acceso es de corta duración y debe obtenerse uno nuevo para cada petición
- En caso de tokens obtenidos por interacción de usuario, usar
refresh_tokenpara renovar el token de acceso. - Separar clientes confidenciales (backend) de clientes públicos (frontend SPA o mobile) y usar flujos de autorización distintos para cada caso.
Consideraciones de integración
- Para obtener listados, usar filtros y paginación según parámetros que exponga la implementación.
- Para detalle, reemplazar
{public_id}por el identificador público de la identidad. - Gestionar autorización con token OAuth2 válido y scopes acordes al caso de uso:
directory:all:users:read,directory:external:users:read,directory:internal:users:readydirectory:admin:users:read. - El filtro
is_eje_sysadminy el acceso a datos de administradores de sistema requierendirectory:admin:users:read. - El campo
resultspuede incluir claims OIDC adicionales definidos por el IdP.
Relación con estándares
- Portador de token (
Bearer) conforme a RFC 6750. - Scopes y autorización conforme a RFC 6749.
- Claims de identidad alineados al modelo de
userinfode OpenID Connect.
Seguridad y Buenas Prácticas
Checklist de implementación
- Usar siempre
authorization_code+ PKCE (S256) para login de usuarios. - Validar
stateen callback para prevenir CSRF. - Validar
noncecuando se utiliceid_token. - No almacenar
client_secreten frontend público. - Evitar guardar tokens en
localStoragecuando se pueda usar sesión HttpOnly en backend/BFF. - Aplicar expiración corta para
access_tokeny rotación derefresh_token. - Implementar revocación en logout (
/oauth2/revoke) y cierre en IdP (/auth/logout/client). - Registrar auditoría de eventos de autenticación y errores de autorización.
Validaciones mínimas del token
Si validas tokens localmente:
- Verificar firma con
jwks_uri. - Verificar
issigual al issuer esperado. - Verificar
audcontiene tuclient_id. - Verificar vigencia (
exp,iat) y tolerancia de reloj controlada. - Verificar claims de contexto (
acr,amr) si tu negocio lo exige.
Arquitectura recomendada
Para aplicaciones web:
- Frontend redirige a login.
- Backend/BFF gestiona intercambio de código por tokens.
- Backend mantiene sesión segura y llama APIs de negocio.
Beneficio: reduce exposición de tokens y simplifica controles de seguridad.
Referencias de estándar
- OAuth 2.0: https://www.rfc-editor.org/rfc/rfc6749
- Bearer Token Usage: https://www.rfc-editor.org/rfc/rfc6750
- PKCE: https://www.rfc-editor.org/rfc/rfc7636
- Token Revocation: https://www.rfc-editor.org/rfc/rfc7009
- Token Introspection: https://www.rfc-editor.org/rfc/rfc7662
- JWT: https://www.rfc-editor.org/rfc/rfc7519
- JWK: https://www.rfc-editor.org/rfc/rfc7517
- OpenID Connect Core 1.0: https://openid.net/specs/openid-connect-core-1_0.html
- OpenID Connect Discovery 1.0: https://openid.net/specs/openid-connect-discovery-1_0.html
APIs de los backends
Esta sección describe las APIs que exponen los dos backends del sistema:
| Backend | Sistema | API REST (/api/v1/) | GraphQL |
|---|---|---|---|
auth-backend | Sistema Único de Identificación (IDP) | Directorio, aprobación de documentos y notificaciones | admin-schema, main-schema, public-schema |
backend-seleccion-postulantes | Sistema de Procesos de Selección (SPS) | Interoperabilidad (convocatorias, admitidos, postulaciones) | admin-schema, comision-schema, postulante-schema, estacion-examen-schema |
Tipos de API
- REST (
/api/v1/): APIs de interoperabilidad para sistemas externos. Están versionadas, protegidas con tokens OAuth2 emitidos por el IDP y documentadas en OpenAPI. - GraphQL: APIs internas consumidas por las interfaces del propio sistema. No están pensadas para integraciones externas y pueden cambiar sin aviso.
Ruta sugerida de lectura
- Introducción: URLs base, autenticación y documentación OpenAPI.
- API REST del IDP.
- API REST del SPS.
- APIs GraphQL internas.
TIP
Para obtener tokens (flujos authorization_code + PKCE y client_credentials) consulte la Guía de Integración OAuth2 y OpenID Connect.
Introducción
URLs base por entorno
| Variable | Test | Producción |
|---|---|---|
BASE_URL_IDP | https://backend.idp-test.eje.gob.bo | https://backend.idp.eje.gob.bo |
BASE_URL_SPS | https://backend.sps-test.eje.gob.bo | https://backend.sps.eje.gob.bo |
En las páginas siguientes, reemplace BASE_URL_IDP o BASE_URL_SPS por la URL del entorno objetivo.
Documentación OpenAPI
Ambos backends generan su especificación OpenAPI con drf-spectacular y la publican en las mismas rutas:
| Ruta | Contenido |
|---|---|
/api/schema/ | Especificación OpenAPI (YAML; JSON con ?format=json) |
/api/docs/ | Swagger UI |
/api/redoc/ | ReDoc |
Ejemplo: BASE_URL_SPS/api/docs/.
La especificación OpenAPI es la referencia completa de cada endpoint (campos de request y response, códigos de estado y ejemplos). Esta sección resume los endpoints disponibles y los requisitos para consumirlos.
Autenticación
Todas las APIs REST de /api/v1/ requieren un access token emitido por el IDP, enviado en la cabecera:
http
Authorization: Bearer <access_token>- La audiencia del token es
eje-idp-api. - El cliente OAuth2 debe tener asignados los scopes que exige cada endpoint y solicitarlos al pedir el token.
- Para integraciones sistema a sistema se usa
client_credentials; para acciones en nombre de un usuario,authorization_code+ PKCE.
Cómo valida el token cada backend:
| Backend | Validación |
|---|---|
| IDP | Contra su base de tokens emitidos: verifica que exista, que no esté revocado ni expirado y que tenga la audiencia esperada. |
| SPS | Localmente como JWT: firma con las claves del jwks_uri del IDP, issuer del discovery, audiencia eje-idp-api, claims obligatorios exp, iss, aud, sub y token_use=access_token. Los scopes se leen del claim scope. |
INFO
Como el SPS valida el JWT localmente, un token revocado en el IDP sigue siendo aceptado por el SPS hasta que expira.
Respuestas de error
| Código | Significado |
|---|---|
400 | Parámetros o cuerpo inválidos |
401 | Token ausente, inválido o expirado |
403 | El token no tiene el scope requerido |
404 | Recurso no encontrado (o no visible para el cliente) |
El formato del cuerpo de error difiere entre backends; se detalla en la página de cada uno.
Raíz de la API
Cada grupo de recursos expone en su prefijo una vista raíz (generada por el DefaultRouter de Django REST Framework) que lista los recursos disponibles. Por ejemplo, GET BASE_URL_SPS/api/v1/.
API REST del IDP
Backend: auth-backend. URL base: BASE_URL_IDP/api/v1/.
Convenciones
Autenticación:
Authorization: Bearer <access_token>(ver Introducción).Aislamiento por cliente: en aprobación de documentos y notificaciones, cada cliente OAuth2 solo ve los recursos que él mismo registró; el cliente se asigna automáticamente al crear el recurso.
Identificador: los recursos se consultan por su
codigo.Paginación: por número de página.
Parámetro Default Máximo page1 — page_size10 100 Formato de error:
json{ "error_code": "...", "error_message": "...", "detail": "..." }
Directorio de identidades
| Método | Ruta | Descripción |
|---|---|---|
GET | /api/v1/directory/users/ | Lista identidades del directorio |
GET | /api/v1/directory/users/{public_id}/ | Obtiene una identidad por public_id |
Scopes: directory:all:users:read, directory:external:users:read, directory:internal:users:read y directory:admin:users:read, según el tipo de usuarios a consultar.
La documentación detallada (parámetros, respuestas y ejemplos) está en API REST de Directorio de Identidades.
Aprobación de documentos
Prefijo: /api/v1/aprobacion-documentos/.
| Método | Ruta | Descripción | Scope |
|---|---|---|---|
GET | documentos-oficiales/ | Listar documentos oficiales | approvaldocs:officialdocuments:read |
POST | documentos-oficiales/ | Registrar documento oficial | approvaldocs:officialdocuments:create |
GET | documentos-oficiales/{codigo}/ | Obtener documento oficial | approvaldocs:officialdocuments:read |
GET | solicitudes-aprobacion/ | Listar solicitudes de aprobación | approvaldocs:approvalrequests:read |
POST | solicitudes-aprobacion/ | Registrar solicitud de aprobación | approvaldocs:approvalrequests:create |
GET | solicitudes-aprobacion/{codigo}/ | Obtener solicitud de aprobación | approvaldocs:approvalrequests:read |
El registro de documentos oficiales recibe el archivo como multipart/form-data.
Notificaciones
Prefijo: /api/v1/notificaciones/.
| Método | Ruta | Descripción | Scope |
|---|---|---|---|
GET | solicitudes-notificacion/ | Listar solicitudes de notificación | notifications:requests:read |
POST | solicitudes-notificacion/ | Registrar solicitud de notificación | notifications:requests:create |
GET | solicitudes-notificacion/{codigo}/ | Obtener solicitud de notificación | notifications:requests:read |
POST | solicitudes-notificacion/{codigo}/reemplazar-formulario/ | Reemplazar el formulario de notificación | notifications:requests:create |
Reemplazar formulario
Reemplaza la solicitud de aprobación del formulario de notificación sin crear una nueva solicitud de notificación:
- Se permite en cualquier estado (pendiente, en proceso, cancelada o fallida), salvo si la solicitud ya fue completada.
- Si no se adjunta un nuevo
formulario_notificacion, se reutiliza el documento oficial anterior con una nueva solicitud de aprobación. - Si la solicitud original no tenía formulario (no notificaba por Ciudadanía Digital), el endpoint agrega ese canal:
formulario_notificacionpasa a ser obligatorio y se crea un envío en espera por cada destinatario ya registrado.
Referencia completa
Campos de cada request/response: BASE_URL_IDP/api/docs/.
API REST del SPS
Backend: backend-seleccion-postulantes (app interoperabilidad). URL base: BASE_URL_SPS/api/v1/.
API de solo lectura para que sistemas externos consulten información de los procesos de selección. No expone operaciones de creación, modificación ni eliminación.
Convenciones
Autenticación:
Authorization: Bearer <access_token>emitido por el IDP (ver Introducción). Se aceptan tokens obtenidos conclient_credentialsoauthorization_code+ PKCE.Alcance de los datos: solo se exponen convocatorias con resultados publicados.
Paginación: por desplazamiento (
limit/offset).Recurso Default limitMáximo General 20 100 postulaciones10 50 historial-postulacionesSin paginación — Formato de error (Django REST Framework):
json{ "detail": "..." }Para parámetros requeridos faltantes:
{ "<parametro>": "Parámetro requerido." }.
Endpoints
| Método | Ruta | Descripción | Parámetros | Scope |
|---|---|---|---|---|
GET | convocatorias/ | Lista convocatorias con resultados publicados | limit, offset | seleccion:convocatorias:read |
GET | convocatorias/{codigo}/ | Obtiene una convocatoria | codigo (path) | seleccion:convocatorias:read |
GET | ofertas-departamentales/ | Plazas ofertadas por departamento | convocatoria (requerido), limit, offset | seleccion:convocatorias:read |
GET | postulantes-admitidos/ | Postulantes admitidos de una convocatoria | convocatoria (requerido), limit, offset | seleccion:admitidos:read |
GET | postulaciones/ | Historial de postulaciones de una persona | usuario_base (requerido, idp_sub), limit, offset | seleccion:postulaciones:read |
GET | usuarios-base/{idp_sub}/ | Datos de identidad y contacto de un postulante | idp_sub (path) | seleccion:postulaciones:read |
GET | historial-postulaciones/ | Búsqueda en postulaciones actuales e históricas | termino (requerido) | seleccion:historialpostulaciones:read |
Notas:
convocatorias/es el punto de entrada: de allí se obtienen loscodigoque usan los demás recursos. Elcodigopuede contener/.historial-postulaciones/:terminoacepta un número de documento o un nombre de al menos 3 caracteres.usuarios-base/no tiene listado; solo consulta poridp_sub(elsubdel usuario en el IDP). Es el identificador para correlacionar a una persona entre sistemas.postulantes-admitidos/ypostulaciones/incluyen el usuario anidado con suidp_sub,emailycelular.
Flujo típico de consulta
Referencia completa
Campos de cada respuesta: BASE_URL_SPS/api/docs/.
APIs GraphQL internas
Las interfaces web del sistema se comunican con los backends mediante GraphQL. Estas APIs son internas: su contrato sigue la evolución de los frontends y no se garantiza compatibilidad para integraciones externas. Para integraciones use las APIs REST.
Características comunes
- Vista:
FileUploadGraphQLView(graphene-file-upload). Acepta peticiones JSON estándar ymultipart/form-datapara subir archivos según la especificación GraphQL multipart request. - Implementación:
graphene-django+django-graphbox. Cada schema se arma combinando las queries y mutations de varias apps de Django. - Método:
POSTal path del schema (por ejemploBASE_URL_SPS/admin-schema/). - Cliente en los frontends: Apollo Client. La URL se arma como
VITE_API_ENDPOINT + VITE_API_ENDPOINT_PATH(ver Imágenes Docker). - GraphiQL: disponible en el mismo path solo si el backend corre con
DEBUG=True.
Autenticación y sesión
Los schemas GraphQL no usan tokens OAuth2 en la cabecera: trabajan con una sesión por cookie que el backend crea al completar el inicio de sesión.
- CSRF: las peticiones
POSTrequieren la cabeceraX-CSRFToken. El token se obtiene en:- IDP:
BASE_URL_IDP/auth/public/csrf-token/ - SPS:
BASE_URL_SPS/iam-core/csrf-token/
- IDP:
- Sesión: el backend guarda el identificador de sesión hasheado en base de datos y lo valida en cada petición.
- Autorización por portal: en el SPS cada schema tiene su gestor de sesión (
admin_session_manager,comision_session_manager,postulante_session_manager,estacion_examen_session_manager), que verifica que el usuario esté activo y tenga acceso vigente a ese portal (y al grupo o rol que exige la operación). - Modo standby: con
SYSTEM_STATUS=False,StandbyMiddlewareresponde503({"error": "STANDBY", ...}) a toda petición excepto/health/, en ambos backends.
Schemas del IDP
Backend: auth-backend. URL base: BASE_URL_IDP.
| Path | Frontend | Audiencia | Apps incluidas |
|---|---|---|---|
/admin-schema/ | auth-frontend-admin | Administradores del IDP | iam_core, oauth2_eje, oidc, aprobacion_documentos, notificaciones |
/main-schema/ | auth-authorization-frontend | Registro de cuentas en el portal de autorización | iam_core |
/public-schema/ | frontend-public-auth | Usuarios gestionando su cuenta | iam_core, aprobacion_documentos |
Schemas del SPS
Backend: backend-seleccion-postulantes. URL base: BASE_URL_SPS.
| Path | Frontend | Audiencia |
|---|---|---|
/admin-schema/ | frontend-portal-admin-seleccion-postulantes | Personal administrativo |
/comision-schema/ | frontend-portal-comisiones-seleccion-postulantes | Miembros de comisiones |
/postulante-schema/ | frontend-portal-postulantes-seleccion-postulantes | Postulantes |
/estacion-examen-schema/ | frontend-estacion-examen | Estaciones de examen en línea |
Apps incluidas en cada schema:
| Schema | Apps |
|---|---|
admin | catalogos, iam_core, document_engine, analitica_planillas, sgc, acciones_previas, custodio_integration, convocatorias, difusion, comisiones, resoluciones, trabajo_comisiones, conclusion_proceso, impugnaciones, reportes, interoperabilidad, datos_historicos, notificaciones, consultas, seeders |
comision | iam_core, document_engine, sgc, convocatorias, postulaciones, comisiones, trabajo_comisiones, impugnaciones, interoperabilidad, reportes |
postulante | catalogos, iam_core, document_engine, sgc, convocatorias, difusion, postulaciones, trabajo_comisiones, impugnaciones, consultas |
estacion_examen | trabajo_comisiones |
Estaciones de examen
Las estaciones no inician sesión con usuario: se registran subiendo un archivo de credencial JWT generado desde el sistema para el recinto. Estos endpoints REST están exentos de CSRF.
| Método | Ruta | Descripción |
|---|---|---|
POST | /trabajo-comisiones/registrar-estacion-examen/ | Recibe el archivo credential_jwt (multipart), lo valida y crea la cookie de sesión de la estación |
POST | /trabajo-comisiones/sustituir-estacion-examen/ | Sustituye una estación registrada usando una credencial JWT |
Con esa sesión, la estación opera contra /estacion-examen-schema/.
Actualización automática de estados
El SPS incluye CierreFaseAutomaticoMiddleware: antes de procesar cualquier petición a los cuatro schemas GraphQL, cierra las fases de convocatoria vencidas y marca como finalizadas las rendiciones de examen cuyo tiempo terminó. Por eso los estados pueden cambiar sin una acción explícita del usuario.
Explorar un schema
Con DEBUG=True (entorno local), abra el path del schema en el navegador (por ejemplo http://localhost:8000/postulante-schema/) para usar GraphiQL, que incluye el explorador de documentación del schema.
Para auditar qué campos de un schema usa su frontend, vea el script auditar_types_graphql en Scripts útiles de los backends.
Infraestructura y despliegue
Consulte la documentación de infraestructura y despliegue para obtener información sobre cómo configurar y desplegar los servicios y aplicaciones de la Escuela de Jueces del Estado.
Entorno Testing
Sistema Único de Identificación de la Escuela de Jueces del Estado (IDP)
(*.idp-test.eje.gob.bo)
| Repo | Nombre | Descripción | Subdominio | Puerto |
|---|---|---|---|---|
auth-backend | IDP Backend | API OAuth2/OIDC, gestión de identidad y autenticación | backend.idp-test.eje.gob.bo | 8500 |
auth-authorization-frontend | Portal de Autorización | Pantalla de login y consentimiento OIDC para todos los sistemas | auth.idp-test.eje.gob.bo | 8300 |
auth-frontend-admin | Admin IDP | Gestión administrativa de usuarios, clientes OAuth2 y permisos | admin.idp-test.eje.gob.bo | 8301 |
frontend-public-auth | Portal de Cuenta | Autogestión de cuenta para cada usuario (perfil, contraseña, 2FA) | cuenta.idp-test.eje.gob.bo | 8302 |
Sistema de Procesos de Selección (SPS)
(*.sps-test.eje.gob.bo)
| Repo | Nombre | Descripción | Subdominio | Puerto |
|---|---|---|---|---|
backend-seleccion-postulantes | Seleccionador Backend | APIs del proceso de selección de postulantes | backend.sps-test.eje.gob.bo | 8501 |
frontend-portal-admin-seleccion-postulantes | Admin Seleccionador | Gestión administrativa del proceso de selección y convocatorias | admin.sps-test.eje.gob.bo | 8303 |
frontend-portal-comisiones-seleccion-postulantes | Portal de Comisiones | Portal de acceso para las comisiones | comisiones.sps-test.eje.gob.bo | 8304 |
frontend-portal-postulantes-seleccion-postulantes | Portal de Postulantes | Portal público para que los postulantes apliquen a convocatorias | postulantes.sps-test.eje.gob.bo | 8305 |
frontend-estacion-examen | Estación de Examen | Estación de rendición de exámenes presenciales en línea | estacion.sps-test.eje.gob.bo | 8306 |
Documentación
(*.docs-test.eje.gob.bo)
| Repo | Nombre | Descripción | Subdominio | Puerto |
|---|---|---|---|---|
frontend-manual-tecnico | Manual Técnico | Documentación técnica para desarrolladores de ambos sistemas | tecnico.docs-test.eje.gob.bo | 8401 |
frontend-manual-de-usuario | Manual de Usuario | Guía de uso para usuarios finales de ambos sistemas | usuario.docs-test.eje.gob.bo | 8402 |
Entorno Producción
Sistema Único de Identificación de la Escuela de Jueces del Estado (IDP)
(*.idp.eje.gob.bo)
| Repo | Nombre | Descripción | Subdominio | Puerto |
|---|---|---|---|---|
auth-backend | IDP Backend | API OAuth2/OIDC, gestión de identidad y autenticación | backend.idp.eje.gob.bo | 8500 |
auth-authorization-frontend | Portal de Autorización | Pantalla de login y consentimiento OIDC para todos los sistemas | auth.idp.eje.gob.bo | 8300 |
auth-frontend-admin | Admin IDP | Gestión administrativa de usuarios, clientes OAuth2 y permisos | admin.idp.eje.gob.bo | 8301 |
frontend-public-auth | Portal de Cuenta | Autogestión de cuenta para cada usuario (perfil, contraseña, 2FA) | cuenta.idp.eje.gob.bo | 8302 |
Sistema de Procesos de Selección (SPS)
(*.sps.eje.gob.bo)
| Repo | Nombre | Descripción | Subdominio | Puerto |
|---|---|---|---|---|
backend-seleccion-postulantes | Seleccionador Backend | APIs del proceso de selección de postulantes | backend.sps.eje.gob.bo | 8501 |
frontend-portal-admin-seleccion-postulantes | Admin Seleccionador | Gestión administrativa del proceso de selección y convocatorias | admin.sps.eje.gob.bo | 8303 |
frontend-portal-comisiones-seleccion-postulantes | Portal de Comisiones | Portal de acceso para las comisiones | comisiones.sps.eje.gob.bo | 8304 |
frontend-portal-postulantes-seleccion-postulantes | Portal de Postulantes | Portal público para que los postulantes apliquen a convocatorias | postulantes.sps.eje.gob.bo | 8305 |
frontend-estacion-examen | Estación de Examen | Estación de rendición de exámenes presenciales en línea | estacion.sps.eje.gob.bo | 8306 |
Documentación
(*.docs.eje.gob.bo)
| Repo | Nombre | Descripción | Subdominio | Puerto |
|---|---|---|---|---|
frontend-manual-tecnico | Manual Técnico | Documentación técnica para desarrolladores de ambos sistemas | tecnico.docs.eje.gob.bo | 8401 |
frontend-manual-de-usuario | Manual de Usuario | Guía de uso para usuarios finales de ambos sistemas | usuario.docs.eje.gob.bo | 8402 |
Arquitectura de despliegue
Visión general
Todos los servicios web se ejecutan como contenedores Docker en un host por entorno (test y producción). El despliegue está centralizado en el repositorio infra-deploy, que actúa como orquestador: contiene los docker-compose.yml de cada sistema y el pipeline que los aplica en el host vía SSH.
| Entorno | Rama Git | Tag de imagen | Variables CI/CD |
|---|---|---|---|
| Test | develop | develop-latest | DEV_* |
| Producción | main | main-latest | PROD_* |
Repositorio infra-deploy
infra-deploy/
├── .gitlab-ci.yml # Pipeline orquestador (job deploy-orquestador)
├── idp/docker-compose.yml # Stack del IDP
├── seleccionador/docker-compose.yml # Stack del SPS
└── docs/docker-compose.yml # Stack de documentaciónCada docker-compose.yml define un proyecto Compose independiente (name:), con su propia red y sus propios servicios.
Stacks
IDP (name: idp)
Red: idp-net (bridge).
| Servicio | Puerto host | Volúmenes | env_file |
|---|---|---|---|
auth-backend | 8500 | Sí | back.env |
auth-authorization-frontend | 8300 | No | — |
auth-frontend-admin | 8301 | No | — |
frontend-public-auth | 8302 | No | — |
Seleccionador (name: seleccionador)
Red: seleccionador-net (bridge).
| Servicio | Puerto host | Volúmenes | env_file |
|---|---|---|---|
backend-seleccion-postulantes | 8501 | Sí | back.env |
frontend-portal-admin-seleccion-postulantes | 8303 | No | — |
frontend-portal-comisiones-seleccion-postulantes | 8304 | No | — |
frontend-portal-postulantes-seleccion-postulantes | 8305 | No | — |
frontend-estacion-examen | 8306 | No | — |
Documentación (name: docs)
Red: docs-net (bridge).
| Servicio | Puerto host |
|---|---|
frontend-manual-tecnico | 8401 |
frontend-manual-de-usuario | 8402 |
INFO
Todos los contenedores escuchan en el puerto 80 interno. Los subdominios públicos de cada servicio se detallan en Mapas de subdominios y puertos.
Configuración común de los servicios
Todos los servicios comparten la misma configuración base en Compose:
yaml
image: "git.eje.gob.bo:5050/proceso-seleccion/<servicio>:${IMAGE_TAG}"
container_name: <servicio>
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost/health/"]
interval: 30s
timeout: 5s # backends | 3s frontends
retries: 3
start_period: 20s # backends | 5s frontends
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"IMAGE_TAGse lee del archivo.envdel stack, que el pipeline genera en cada despliegue (develop-latestomain-latest).restart: unless-stoppedreinicia el contenedor ante caídas, salvo que se haya detenido manualmente.- Los logs rotan a 10 MB con un máximo de 5 archivos por contenedor.
Backends
Los backends montan adicionalmente:
yaml
env_file:
- back.env
volumes:
- ./storage/public:/app/public_files
- ./storage/private:/app/private_files
- ./back.env:/app/.env:ro| Ruta en host | Ruta en contenedor | Uso |
|---|---|---|
storage/public/ | /app/public_files | Archivos públicos, servidos por nginx en /files/ |
storage/private/ | /app/private_files | Archivos privados, solo accesibles vía X-Accel-Redirect |
back.env | /app/.env (ro) | Variables de entorno de Django |
Estructura en el host
El pipeline crea y mantiene esta estructura bajo DEPLOY_BASE_PATH (variable distinta por entorno):
${DEPLOY_BASE_PATH}/
├── idp/
│ ├── docker-compose.yml # copiado desde infra-deploy/idp
│ ├── .env # IMAGE_TAG=<develop|main>-latest
│ ├── back.env # variables + secretos de auth-backend
│ └── storage/
│ ├── public/
│ └── private/
├── seleccionador/
│ ├── docker-compose.yml
│ ├── .env
│ ├── back.env # variables + secretos de backend-seleccion-postulantes
│ └── storage/
│ ├── public/
│ └── private/
└── docs/
├── docker-compose.yml
└── .envWARNING
docker-compose.yml, .env y back.env se sobrescriben en cada despliegue. Cualquier cambio manual en el host se pierde; los cambios deben hacerse en infra-deploy o en las variables CI/CD de GitLab.
Custodio
custodio no se despliega en el host: es una aplicación de escritorio (Python + Tkinter) que valida, consolida y cifra los bancos de preguntas para la comisión de elaboración. Su pipeline genera ejecutables para Linux y Windows y los publica en el Package Registry de GitLab. Ver Pipelines CI/CD.
Imágenes Docker
Todas las imágenes usan build multi-etapa: una etapa de construcción con las herramientas de compilación y una etapa final mínima con solo lo necesario para ejecutar.
Las imágenes se publican en el registry de GitLab:
git.eje.gob.bo:5050/proceso-seleccion/<repositorio>:<tag>Backends (Django)
Aplica a auth-backend y backend-seleccion-postulantes.
Etapas
| Etapa | Base | Contenido |
|---|---|---|
builder | python:3.11-slim | Instala requirements.prod.txt en /install (pip --prefix) |
| runtime | python:3.11-slim | Copia /install a /usr/local, el código fuente, nginx y entrypoint.sh |
Paquetes de sistema en runtime:
auth-backend:nginx,curl.backend-seleccion-postulantes:nginx,curl,ghostscripty librerías nativas que WeasyPrint y OpenCV cargan en tiempo de ejecución (libpango,libpangocairo,libgdk-pixbuf,libcairo2,libffi8,libglib2.0-0,libgl1,libsm6,libxext6,libxrender1,shared-mime-info, fuentesliberationydejavu). La etapabuilderinstala además las librerías de desarrollo necesarias para compilar estas dependencias.
Usuario y permisos
El contenedor corre como appuser (no root). nginx (www-data) y gunicorn (appuser) comparten acceso a los volúmenes:
dockerfile
RUN useradd -m appuser \
&& mkdir -p /app/public_files /app/private_files \
&& chown -R appuser:www-data /app \
&& chmod -R 775 /app/public_files /app/private_filesTIP
Si en el host los directorios storage/public o storage/private tienen otro propietario, el backend no podrá escribir archivos. Deben ser escribibles por el UID de appuser dentro del contenedor.
Procesos dentro del contenedor
Cada contenedor de backend ejecuta nginx + gunicorn:
- gunicorn: 2 workers, timeout 60 s, logs de acceso y error a stdout/stderr.
- nginx:
proxy_read_timeout 60sy cabecerasHost,X-Real-IP,X-Forwarded-For,X-Forwarded-Protohacia gunicorn. - La ruta
/private_files/esinternal: solo se sirve cuando Django responde con la cabeceraX-Accel-Redirecttras validar el acceso.
Health check
dockerfile
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD curl -fsS http://localhost/health/ || exit 1El endpoint /health/ está definido en Django (auth_interop_backend/urls.py y seleccion_backend/urls.py).
entrypoint.sh
Se ejecuta en cada arranque del contenedor.
auth-backend
sh
python manage.py migrate --noinput
python manage.py collectstatic --noinput --clear -v 0
python manage.py rotate_signing_keys
python manage.py create_default_scopes
python manage.py create_claim_definitions
nginx -g "daemon off;" &
exec gunicorn auth_interop_backend.wsgi:application --bind 127.0.0.1:8000 ...backend-seleccion-postulantes
sh
python manage.py migrate --noinput
python manage.py collectstatic --noinput --clear -v 0
# Bucle en segundo plano: cada 30 min
python manage.py limpiar_segmentos_huerfanos
nginx -g "daemon off;" &
exec gunicorn seleccion_backend.wsgi:application --bind 127.0.0.1:8000 ...INFO
Las migraciones se aplican automáticamente al desplegar. Los comandos de arranque se describen en Scripts útiles de los backends.
Frontends (Vue + Vite)
Aplica a los portales del IDP, del SPS y a la estación de examen.
| Etapa | Base | Contenido |
|---|---|---|
builder | node:24-alpine | pnpm@11.3.0 vía corepack, pnpm install --frozen-lockfile, pnpm run build |
| runtime | nginx:alpine | Copia /app/dist a /usr/share/nginx/html y nginx.conf |
Variables de build (VITE_*)
Las variables VITE_* se incrustan en el bundle durante el build (se pasan como --build-arg). No pueden cambiarse en runtime: cualquier cambio requiere un nuevo build.
| Repositorio | Build args |
|---|---|
auth-authorization-frontend | VITE_TITLE_APP, VITE_API_ENDPOINT, VITE_API_ENDPOINT_FILES, VITE_PUBLIC_AUTH_PORTAL, VITE_ADMIN_AUTH_PORTAL |
auth-frontend-admin | VITE_TITLE_APP, VITE_API_ENDPOINT, VITE_API_ENDPOINT_PATH, VITE_API_ENDPOINT_FILES, VITE_LOGIN_REDIRECT_URL, VITE_MANUAL_TECNICO_URL, VITE_MANUAL_USUARIO_URL |
frontend-public-auth | VITE_TITLE_APP, VITE_API_ENDPOINT, VITE_API_ENDPOINT_PATH, VITE_API_ENDPOINT_FILES, VITE_API_FILES_PRIVATE, VITE_LOGIN_REDIRECT_URL, VITE_JACOBITUS_API_URL |
frontend-portal-admin-seleccion-postulantes | VITE_TITLE_APP, VITE_API_ENDPOINT, VITE_API_ENDPOINT_PATH, VITE_API_ENDPOINT_FILES, VITE_AUTH_ORIGIN_PORTAL, VITE_API_FILES_PRIVATE, VITE_MANUAL_USUARIO_URL, VITE_MANUAL_TECNICO_URL, VITE_AUTH_PUBLIC_URL |
frontend-portal-comisiones-seleccion-postulantes | VITE_TITLE_APP, VITE_API_ENDPOINT, VITE_API_ENDPOINT_PATH, VITE_API_ENDPOINT_FILES, VITE_API_FILES_PRIVATE, VITE_AUTH_ORIGIN_PORTAL, VITE_AUTH_PUBLIC_URL |
frontend-portal-postulantes-seleccion-postulantes | VITE_TITLE_APP, VITE_API_ENDPOINT, VITE_API_ENDPOINT_PATH, VITE_API_ENDPOINT_FILES, VITE_API_FILES_PRIVATE, VITE_LOGIN_REDIRECT_URL, VITE_AUTH_ORIGIN_PORTAL, VITE_DOMAIN_PUBLIC_URL |
frontend-estacion-examen | VITE_TITLE_APP, VITE_API_ENDPOINT, VITE_API_ENDPOINT_PATH, VITE_API_FILES_PRIVATE |
Los valores se toman de las variables CI/CD DEV_VITE_* o PROD_VITE_* según la rama. Ver Variables y secretos de CI/CD.
nginx de los frontends
nginx
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
location /health {
access_log off;
default_type text/plain;
return 200 'OK';
}
# SPA: rutas del router resuelven a index.html
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(js|css|png|jpg|svg|ico|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}Manuales (VitePress)
Aplica a frontend-manual-tecnico y frontend-manual-de-usuario.
Misma estructura que los frontends, con estas diferencias:
- El build ejecuta
pnpm run docs:build. - Se copia
/app/.vitepress/disten lugar de/app/dist. - No reciben build args.
- Usan la misma configuración de nginx que los frontends.
Construcción local
Para validar una imagen antes de subir cambios:
sh
# Backend
docker build -t auth-backend:local ./auth-backend
# Frontend (los build args son obligatorios para un bundle funcional)
docker build \
--build-arg VITE_TITLE_APP="..." \
--build-arg VITE_API_ENDPOINT="..." \
-t frontend-portal-admin:local ./frontend-portal-admin-seleccion-postulantesPipelines CI/CD
El despliegue se realiza en dos niveles de pipelines de GitLab:
- Pipeline de cada repositorio de servicio: construye la imagen Docker, la publica en el registry y dispara el orquestador.
- Pipeline del orquestador (
infra-deploy): se conecta por SSH al host del entorno y actualiza los contenedores.
| Rama | Entorno | Tag latest | Variables usadas |
|---|---|---|---|
develop | Test | develop-latest | DEV_* |
main | Producción | main-latest | PROD_* |
Todos los jobs se ejecutan en runners con el tag test (excepto el build de Windows de Custodio, que usa el tag windows).
Pipeline de los repositorios de servicio
Aplica a todos los backends, frontends y manuales.
Stages
| Stage | Job | Imagen del job |
|---|---|---|
deploy | build | docker:26 + servicio docker:26-dind |
trigger | trigger-infra-deploy | curlimages/curl:8.8.0 |
Job build
Se ejecuta en develop y main:
yaml
build:
stage: deploy
image: docker:26
services:
- docker:26-dind
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
- if: '$CI_COMMIT_BRANCH == "main"'
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- export TAG="${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}"
- docker build -t "$IMAGE:$TAG" -t "$IMAGE:${CI_COMMIT_REF_SLUG}-latest" .
- docker push "$IMAGE:$TAG"
- docker push "$IMAGE:${CI_COMMIT_REF_SLUG}-latest"Cada build publica dos tags:
| Tag | Ejemplo | Uso |
|---|---|---|
<rama>-<sha corto> | main-a1b2c3d4 | Versión inmutable; permite rollback |
<rama>-latest | main-latest | Tag que usa docker-compose.yml en el host |
Frontends: variables por rama
En los frontends, cada regla asigna las variables VITE_* desde su versión DEV_ o PROD_ y se pasan como --build-arg:
yaml
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
variables:
VITE_TITLE_APP: $DEV_VITE_TITLE_APP
VITE_API_ENDPOINT: $DEV_VITE_API_ENDPOINT
# ...
- if: '$CI_COMMIT_BRANCH == "main"'
variables:
VITE_TITLE_APP: $PROD_VITE_TITLE_APP
VITE_API_ENDPOINT: $PROD_VITE_API_ENDPOINT
# ...
script:
- |
docker build \
--build-arg VITE_TITLE_APP="${VITE_TITLE_APP}" \
--build-arg VITE_API_ENDPOINT="${VITE_API_ENDPOINT}" \
# ...
-t "$IMAGE:$TAG" -t "$IMAGE:${CI_COMMIT_REF_SLUG}-latest" .La lista de build args de cada frontend está en Imágenes Docker.
Job trigger-infra-deploy
Se ejecuta después de build (needs: build) y dispara el pipeline de infra-deploy en la misma rama, indicando qué servicio desplegar:
yaml
trigger-infra-deploy:
stage: trigger
image: curlimages/curl:8.8.0
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
- if: '$CI_COMMIT_BRANCH == "main"'
needs:
- job: build
script:
- |
curl -fsSk --retry 3 -X POST \
--form token=$INFRA_DEPLOY_TRIGGER_TOKEN \
--form ref=$CI_COMMIT_BRANCH \
--form "variables[SERVICE_NAME]=<nombre-del-servicio>" \
"https://git.eje.gob.bo/api/v4/projects/13/trigger/pipeline"refdetermina el entorno:develop→ test,main→ producción.SERVICE_NAMEdebe coincidir con el nombre del servicio en eldocker-compose.yml(igual al nombre del repositorio).
Pipeline orquestador (infra-deploy)
Un único job, deploy-orquestador (stage deploy, imagen alpine:3.19), que se ejecuta en develop y main. Según la rama, mapea las variables DEV_* o PROD_* a nombres genéricos:
yaml
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
variables:
DEPLOY_USER: $DEV_DEPLOY_USER
DEPLOY_HOST: $DEV_DEPLOY_HOST
SSH_PRIVATE_KEY: $DEV_SSH_PRIVATE_KEY
IMAGE_TAG: develop-latest
IDP_DEPLOY_PATH: $DEV_DEPLOY_BASE_PATH/idp
SELECCIONADOR_DEPLOY_PATH: $DEV_DEPLOY_BASE_PATH/seleccionador
DOCS_DEPLOY_PATH: $DEV_DEPLOY_BASE_PATH/docs
# ... BACK_ENV y secretos de cada backend
- if: '$CI_COMMIT_BRANCH == "main"'
variables:
# ... mismas variables con prefijo PROD_ e IMAGE_TAG: main-latestPasos del despliegue
Conexión SSH: instala
openssh-client, carga la clave enssh-agenty registra el host enknown_hostsconssh-keyscan.Directorios: crea en el host
storage/publicystorage/privatede IDP y Seleccionador, y el directorio de docs.Archivos de configuración: copia por
scp:.envconIMAGE_TAG=<rama>-latesta los tres stacks.idp/,seleccionador/ydocs/docker-compose.yml.- El archivo de variables de cada backend como
back.env.tmp.
Composición de
back.env(en el host): por cada backend concatenaback.env.tmpcon los secretos individuales y elimina el temporal. Se regenera completo en cada ejecución:shcat back.env.tmp > back.env echo "SECRET_KEY=${IDP_SECRET_KEY}" >> back.env echo "SIGNING_KEY_MASTER_SECRET=${IDP_SIGNING_KEY_MASTER_SECRET}" >> back.env echo "DATABASE_PASS=${IDP_DATABASE_PASS}" >> back.env rm back.env.tmpLogin al registry con el deploy token del grupo (
REGISTRY_DEPLOY_TOKEN_USER/REGISTRY_DEPLOY_TOKEN_PASSWORD).Despliegue:
Con
SERVICE_NAME(disparado por un repositorio): ubica el stack al que pertenece el servicio y ejecuta solo para él:shdocker compose pull "$TARGET_SERVICE" docker compose up -d --no-deps "$TARGET_SERVICE"Si el valor no es un servicio conocido, el job falla.
Sin
SERVICE_NAME(ejecución manual del pipeline): hacepullyup -d --no-depsde todos los servicios de los tres stacks.
Verificación: espera hasta 120 s (compartidos entre los tres stacks) a que todos los contenedores estén
runningy, si tienen health check,healthy. Si un backend no terminó de iniciar, imprime sus últimas 200 líneas de log.Resumen: imprime una tabla con proyecto, servicio, estado, salud y puertos publicados.
TIP
El paso 7 no hace fallar el job: si un servicio no queda saludable, el pipeline termina igual pero muestra el aviso y los logs. Revise siempre la salida del job tras un despliegue.
Valores válidos de SERVICE_NAME
| Stack | Servicios |
|---|---|
idp | auth-backend, auth-authorization-frontend, auth-frontend-admin, frontend-public-auth |
seleccionador | backend-seleccion-postulantes, frontend-portal-admin-seleccion-postulantes, frontend-portal-comisiones-seleccion-postulantes, frontend-portal-postulantes-seleccion-postulantes, frontend-estacion-examen |
docs | frontend-manual-tecnico, frontend-manual-de-usuario |
Ejecución manual
Despliegue completo de un entorno (todos los servicios), desde una máquina con acceso a GitLab:
sh
curl -fsSk --retry 3 -X POST \
--form token=$INFRA_DEPLOY_TRIGGER_TOKEN \
--form ref=develop \
"https://git.eje.gob.bo/api/v4/projects/13/trigger/pipeline"Despliegue de un solo servicio:
sh
curl -fsSk --retry 3 -X POST \
--form token=$INFRA_DEPLOY_TRIGGER_TOKEN \
--form ref=main \
--form "variables[SERVICE_NAME]=auth-backend" \
"https://git.eje.gob.bo/api/v4/projects/13/trigger/pipeline"También puede ejecutarse desde GitLab: Build → Pipelines → Run pipeline en infra-deploy, eligiendo la rama y, opcionalmente, la variable SERVICE_NAME.
Agregar un nuevo servicio
- Crear el
Dockerfile(conEXPOSE 80y endpoint/health/) en el repositorio del servicio. - Agregar los jobs
buildytrigger-infra-deploycon suSERVICE_NAME. - Agregar el servicio al
docker-compose.ymldel stack correspondiente eninfra-deploy, con un puerto libre. - Agregar el nombre en el
casedel despliegue selectivo y en el bloque de despliegue completo de.gitlab-ci.ymldeinfra-deploy. - Actualizar el mapa de subdominios y puertos.
Custodio
custodio tiene un pipeline independiente que no despliega en servidores: genera ejecutables portables y los publica en el Package Registry de GitLab. Se dispara al crear un tag Git.
| Stage | Job | Runner | Descripción |
|---|---|---|---|
test | test | test | pytest tests/ sobre la lógica de negocio (python:3.12-slim) |
test | test:gui-import | test | Smoke test: inicializa la GUI Tkinter bajo Xvfb |
build | build:linux | test | PyInstaller con Custodio.spec en debian:bookworm-slim → Custodio-linux-<tag> |
build | build:windows | windows | PyInstaller en runner Windows nativo → Custodio-windows-<tag>.exe |
verify | checksums | test | Genera CHECKSUMS.txt (SHA-256 de ambos ejecutables) |
publish | publish | test | Manual. Sube ejecutables y checksums a packages/generic/custodio/<tag>/ |
- Los jobs de
testcorren automáticamente con tags; en merge requests y enmainquedan como manuales. - Los artefactos se conservan 6 meses.
publishes un paso manual deliberado para controlar qué versión se distribuye.
Variables y secretos de CI/CD
Las variables se configuran en GitLab en Settings → CI/CD → Variables de cada proyecto. Ningún secreto se guarda en los repositorios.
Convención de prefijos:
DEV_*: entorno de test (ramadevelop).PROD_*: entorno de producción (ramamain).
WARNING
Las variables PROD_* deben marcarse como Protected (solo disponibles en ramas protegidas, como main) y los secretos como Masked para que no aparezcan en los logs de los jobs.
Variables predefinidas de GitLab
Usadas por los jobs build; no requieren configuración:
| Variable | Uso |
|---|---|
CI_REGISTRY, CI_REGISTRY_IMAGE | Registry y ruta de la imagen del proyecto |
CI_REGISTRY_USER, CI_REGISTRY_PASSWORD | Login al registry desde el job |
CI_COMMIT_BRANCH, CI_COMMIT_REF_SLUG | Rama y su versión normalizada para tags |
CI_COMMIT_SHORT_SHA | SHA corto para el tag inmutable |
CI_COMMIT_TAG, CI_JOB_TOKEN, CI_API_V4_URL | Pipeline de Custodio |
Repositorios de servicio
Todos
| Variable | Tipo | Descripción |
|---|---|---|
INFRA_DEPLOY_TRIGGER_TOKEN | Variable, Masked | Token de trigger del proyecto infra-deploy |
TIP
Es recomendable definir INFRA_DEPLOY_TRIGGER_TOKEN a nivel del grupo proceso-seleccion para no repetirla en cada repositorio.
Frontends
Cada frontend requiere las variables de build de su tabla en Imágenes Docker, en ambas versiones:
DEV_VITE_<NOMBRE> → build de develop (test)
PROD_VITE_<NOMBRE> → build de main (producción)Ejemplo para frontend-portal-admin-seleccion-postulantes:
| Test | Producción |
|---|---|
DEV_VITE_TITLE_APP | PROD_VITE_TITLE_APP |
DEV_VITE_API_ENDPOINT | PROD_VITE_API_ENDPOINT |
DEV_VITE_API_ENDPOINT_PATH | PROD_VITE_API_ENDPOINT_PATH |
DEV_VITE_API_ENDPOINT_FILES | PROD_VITE_API_ENDPOINT_FILES |
DEV_VITE_AUTH_ORIGIN_PORTAL | PROD_VITE_AUTH_ORIGIN_PORTAL |
DEV_VITE_API_FILES_PRIVATE | PROD_VITE_API_FILES_PRIVATE |
DEV_VITE_MANUAL_USUARIO_URL | PROD_VITE_MANUAL_USUARIO_URL |
DEV_VITE_MANUAL_TECNICO_URL | PROD_VITE_MANUAL_TECNICO_URL |
DEV_VITE_AUTH_PUBLIC_URL | PROD_VITE_AUTH_PUBLIC_URL |
Estas variables no son secretas: terminan incrustadas en el JavaScript público.
Orquestador (infra-deploy)
Todas existen con prefijo DEV_ y PROD_, salvo las del registry.
Host de despliegue
| Variable (sin prefijo) | Tipo | Descripción |
|---|---|---|
DEPLOY_HOST | Variable | IP o nombre del host del entorno |
DEPLOY_USER | Variable | Usuario SSH en el host |
SSH_PRIVATE_KEY | Variable, Protected | Contenido de la clave privada SSH (se carga con ssh-add) |
DEPLOY_BASE_PATH | Variable | Directorio base de los stacks en el host |
El usuario de despliegue debe poder ejecutar docker sin sudo en el host.
IDP (auth-backend)
| Variable (sin prefijo) | Tipo | Se escribe en back.env como |
|---|---|---|
IDP_BACK_ENV | File, Protected | Base del archivo (variables no secretas) |
IDP_SECRET_KEY | Variable, Masked+Protected | SECRET_KEY |
IDP_SIGNING_KEY_MASTER_SECRET | Variable, Masked+Protected | SIGNING_KEY_MASTER_SECRET |
IDP_DATABASE_PASS | Variable, Masked+Protected | DATABASE_PASS |
Seleccionador (backend-seleccion-postulantes)
| Variable (sin prefijo) | Tipo | Se escribe en back.env como |
|---|---|---|
SELECCIONADOR_BACK_ENV | File, Protected | Base del archivo (variables no secretas) |
SELECCIONADOR_SECRET_KEY | Variable, Masked+Protected | SECRET_KEY |
SELECCIONADOR_SIGNING_KEY_MASTER_SECRET | Variable, Masked+Protected | SIGNING_KEY_MASTER_SECRET |
SELECCIONADOR_DATABASE_PASS | Variable, Masked+Protected | DATABASE_PASS |
Registry (compartidas por ambos entornos)
| Variable | Tipo | Descripción |
|---|---|---|
REGISTRY_DEPLOY_TOKEN_USER | Variable | Usuario del deploy token del grupo |
REGISTRY_DEPLOY_TOKEN_PASSWORD | Variable, Masked | Token con permiso read_registry |
Variable de trigger
| Variable | Origen | Descripción |
|---|---|---|
SERVICE_NAME | Enviada por trigger-infra-deploy o manualmente | Servicio a desplegar. Si se omite, se despliegan todos |
Archivo back.env
El archivo final que reciben los backends es la concatenación de <SISTEMA>_BACK_ENV + los tres secretos individuales. Se monta en el contenedor como env_file y como /app/.env (solo lectura).
- Para cambiar una variable no secreta: editar la variable de tipo File
<PREFIJO>_<SISTEMA>_BACK_ENVen GitLab y volver a desplegar. - Para cambiar un secreto: editar la variable individual y volver a desplegar.
- Los cambios hechos directamente en el host se pierden en el siguiente despliegue.
WARNING
back.env debe incluir SYSTEM_STATUS=True. Con SYSTEM_STATUS en False (valor por defecto en manage.py), los comandos de gestión distintos de check, runserver, help, version y shell se omiten, incluidas las migraciones del arranque. Ver Scripts útiles de los backends.
Custodio
No requiere variables personalizadas: usa CI_JOB_TOKEN para publicar en el Package Registry del propio proyecto. Requiere un runner Windows registrado con el tag windows y Python con Tkinter.
Scripts útiles de los backends
Comandos de gestión y scripts incluidos en auth-backend y backend-seleccion-postulantes. Se agrupan según su propósito:
- Arranque y configuración: se ejecutan en cada despliegue o una única vez.
- Datos de prueba y emulaciones: solo para el entorno de test.
- Importación / exportación: migración de datos y configuración.
- Mantenimiento y servicios: tareas periódicas.
- Utilidades de desarrollo: diagramas y auditorías.
Ejecución en los contenedores
Desde el directorio del stack en el host (${DEPLOY_BASE_PATH}/idp o ${DEPLOY_BASE_PATH}/seleccionador):
sh
# Comando de gestión de Django
docker compose exec auth-backend python manage.py <comando> [opciones]
docker compose exec backend-seleccion-postulantes python manage.py <comando> [opciones]
# Script de la carpeta scripts/ (django-extensions runscript)
docker compose exec backend-seleccion-postulantes \
python manage.py runscript <script> [--script-args <arg1> <arg2> ...]Los scripts de scripts/ definen una función run() y se ejecutan con runscript (django-extensions, incluido en requirements.prod.txt); no funcionan con python scripts/<script>.py.
Para copiar archivos generados entre el contenedor y el host:
sh
docker compose cp backend-seleccion-postulantes:/app/<ruta> ./<destino>
docker compose cp ./<origen> backend-seleccion-postulantes:/app/<ruta>Requisito: SYSTEM_STATUS
En ambos backends, manage.py lee SYSTEM_STATUS del .env (por defecto False). Si es False, solo se permiten check, runserver, help, version y shell; cualquier otro comando (incluidos migrate, runscript y todos los de esta página) imprime [STANDBY] '<comando>' omitido y termina sin hacer nada.
auth-backend
Arranque y configuración
Los tres primeros se ejecutan automáticamente en entrypoint.sh en cada arranque del contenedor.
| Comando | Argumentos | Descripción |
|---|---|---|
rotate_signing_keys | — | Ejecuta la rotación del ciclo de vida de las claves de firma de tokens. Transaccional. |
create_default_scopes | — | Crea los scopes OAuth2 definidos en DEFAULT_SCOPES que no existan. No modifica los existentes. |
create_claim_definitions | — | Crea/activa las definiciones de claims de ENABLED_PRESET_CLAIM_DEFINITIONS y desactiva las que no estén en esa lista. |
create_super_admin | Ver abajo | Crea un usuario administrador interno. Se usa una vez al inicializar un entorno. |
create_super_admin
sh
docker compose exec auth-backend python manage.py create_super_admin \
--username <usuario> \
--password '<contraseña>' \
--first_name <nombre> \
--last_name <apellido>| Opción | Requerido | Descripción |
|---|---|---|
--username | Uno de --username o --email | Identificador de login |
--email | Uno de --username o --email | Identificador de login |
--password | Sí | Debe cumplir la política de contraseñas |
--first_name | Sí | Nombre |
--last_name | Sí | Apellido |
- Crea
IdentityUser(is_admin=True, usuario interno),LoginIdentifier,UserCredentialyPasswordCredential. - Si el identificador ya existe, no crea nada.
WARNING
La contraseña queda en el historial de la shell. Cámbiela tras el primer ingreso o limpie el historial.
Datos de prueba
create_postulantes_prueba
Solo para entornos de prueba. Crea N cuentas de postulante externo en el IDP y escribe un JSON con sus datos (incluida la contraseña en texto plano), que sirve como fuente de identidades para el seeder de postulaciones del backend seleccionador.
sh
docker compose exec auth-backend python manage.py create_postulantes_prueba 500 \
--output /app/private_files/seeders/postulantes_prueba.json \
--password-fija 'Prueba123!' \
--confirmo-entorno-de-prueba| Argumento / opción | Requerido | Default | Descripción |
|---|---|---|---|
cantidad (posicional) | Sí | — | Cantidad de cuentas a crear (> 0) |
--output | Sí | — | Ruta del JSON de salida (crea el directorio si no existe) |
--prefijo | No | postulante.prueba | Prefijo del username (<prefijo>.<0001>.<hex6>) |
--seed | No | — | Semilla de Faker/random para reproducir el lote |
--password-fija | No | — | Misma contraseña para todo el lote (mín. 8, una mayúscula, un dígito y uno de !@#$%*) |
--confirmo-entorno-de-prueba | Sí | — | Confirmación explícita; sin ella el comando falla |
Crea por cada cuenta: IdentityUser, LoginIdentifier (username), atributos de documento de identidad, tipo de documento y fecha de nacimiento, UserCredential y PasswordCredential. El email generado usa el dominio postulante-prueba.eje.gob.bo.
TIP
Escriba el JSON dentro de /app/private_files/ para que quede en el volumen storage/private del host; fuera de los volúmenes se pierde al recrear el contenedor.
Mantenimiento y servicios
Envío de notificaciones pendientes (incluye seguimiento de Ciudadanía Digital) mediante EnvioNotificacionDispatcher:
| Comando | Opciones | Modo de uso |
|---|---|---|
notificacion_sender_cron | — | Procesa una vez y termina. Pensado para cron / programador de tareas. |
notification_sender_service | --interval N (default 30 s) | Bucle continuo; termina ordenadamente con SIGTERM/SIGINT. Pensado como servicio de larga duración. |
sh
docker compose exec auth-backend python manage.py notificacion_sender_cronbackend-seleccion-postulantes
Arranque
entrypoint.sh lanza en segundo plano un bucle que ejecuta cada 30 minutos:
limpiar_segmentos_huerfanos
Elimina los segmentos de subidas de documentos abandonadas a mitad de carga (usuario que cierra la pestaña o cancela): selecciona DocumentoUsuario sin archivo final cuyo último segmento tenga más de 3 horas y borra sus segmentos. Sin argumentos.
sh
docker compose exec backend-seleccion-postulantes python manage.py limpiar_segmentos_huerfanosDatos de prueba y emulaciones
Flujo típico para preparar una convocatoria completa en el entorno de test:
Los emuladores procesan lo que esté pendiente en el momento en que se ejecutan, por lo que la convocatoria debe estar en la fase correspondiente, con comisión conformada y sesión activa.
create_empty_convocatoria (runscript)
Crea una convocatoria de prueba usando seeders/convocatorias/main.py (crear_convocatoria_prueba).
sh
docker compose exec backend-seleccion-postulantes \
python manage.py runscript create_empty_convocatoria
# Con argumentos opcionales (posicionales)
docker compose exec backend-seleccion-postulantes \
python manage.py runscript create_empty_convocatoria \
--script-args <area_jurisdiccional> <caracter> <no_usado> <forma_presentacion>Sin argumentos usa los valores por defecto de crear_convocatoria_prueba.
seeder
Genera postulaciones de prueba (usuarios, perfiles, CV, documentos y postulaciones) para una convocatoria existente.
sh
docker compose exec backend-seleccion-postulantes \
python manage.py seeder --convocatoria 109 --cantidad 50| Opción | Default | Descripción |
|---|---|---|
--convocatoria | PostulacionSeederConfig.CONVOCATORIA_ID_DEFAULT | ID de la convocatoria (debe existir) |
--cantidad | 10 | Cantidad de postulaciones |
--no-transaction | desactivado | Sin transacción; ante error quedan datos parciales (depuración) |
--quiet | desactivado | Sin logs detallados |
--resumen | desactivado | Muestra el resumen de postulaciones existentes y termina |
Requisitos:
- Catálogos cargados (provincias, municipios, etc.) y la convocatoria creada.
- Archivo de identidades
seeders_data/test_500_1.json(relativo a/app). Para usar cuentas generadas concreate_postulantes_prueba, copie el JSON al contenedor en esa ruta.
Salida:
- Exporta un XLSX de credenciales en
seeders_data/credenciales_conv{ID}.xlsxdentro del almacenamiento público (PUBLIC_MEDIA_ROOT). Se sobrescribe en cada ejecución para la misma convocatoria.
DANGER
El XLSX contiene contraseñas en texto plano y se guarda en el almacenamiento público, que nginx sirve en /files/. No ejecute el seeder en producción y elimine el archivo cuando ya no sea necesario.
emular_dmh
Emula la Revisión de Documentación Mínima Habilitante de una comisión.
sh
docker compose exec backend-seleccion-postulantes \
python manage.py emular_dmh --comision <ID>Por cada miembro responsable (o subcomisión) con postulaciones asignadas en la sesión activa:
- Inicia la revisión individual de cada postulación pendiente.
- Marca aleatoriamente (≈1 de cada 3) un respaldo requerido como "no cumple".
- Finaliza la revisión y solicita la aprobación de los miembros participantes.
Las aprobaciones usan un cliente simulado (FakeIDPApprovalClient) que no llama a servicios externos y las da por aprobadas.
emular_cme
Emula la Calificación de Méritos de una comisión. Mismo flujo que emular_dmh: por cada postulación pendiente califica las acreditaciones curriculares considerando la cantidad declarada y descartando aleatoriamente algunas (≈1 de cada 3), finaliza la calificación y solicita aprobaciones con el cliente simulado.
sh
docker compose exec backend-seleccion-postulantes \
python manage.py emular_cme --comision <ID>emular_rendicion_examen
Emula la rendición del examen de admisión en línea.
sh
docker compose exec backend-seleccion-postulantes \
python manage.py emular_rendicion_examen --comision <ID>Requisitos: la comisión debe ser de recepción de examen de admisión y la programación del examen debe estar en modalidad en línea; de lo contrario el comando falla.
Por cada recinto habilitado con miembro responsable:
- Asegura estaciones de examen habilitadas para toda la capacidad del recinto.
- Inicia cada sesión programada pendiente.
- Registra asistencia (probabilidad 90 % por postulante).
- Rinde el examen de cada asistente eligiendo opciones al azar.
- Fuerza el fin de la rendición, actualiza las rendiciones finalizadas y cierra la sesión.
No pasa por el registro JWT de estaciones ni por sesiones de comisión.
Mutaciones GraphQL de seeders
El esquema de administración expone seedConvocatoriaPrueba y seedPostulacionesPrueba(convocatoria, cantidad), que reutilizan la misma lógica. Solo funcionan con DEBUG=True y no requieren sesión. seedPostulacionesPrueba se ejecuta en un hilo en segundo plano y devuelve de inmediato la URL del XLSX de credenciales.
WARNING
Mantenga DEBUG=False en producción: con DEBUG=True estas mutaciones quedan accesibles sin autenticación.
Correcciones de datos (runscript)
| Script | Descripción |
|---|---|
simular_periodos_impugnacion | Para cada informe de cierre de fase con resultados publicados (en fases que admiten impugnación) sin periodo de impugnación, crea uno con recepción abierta hasta 30 días desde ahora. Completa la fecha de publicación si falta. |
set_origenes_impugnados | Marca como impugnado el origen de cada resultado de fase que tenga una impugnación asociada. |
clean_variables_plantilla | Elimina las variables de plantillas SGC cuya clave no esté en CLAVES_VAR_CHOICES. |
sh
docker compose exec backend-seleccion-postulantes \
python manage.py runscript simular_periodos_impugnacionImportación / exportación
Configuración SGC
Exporta e importa la configuración del Sistema de Gestión de Calidad (procedimientos, registros/plantillas, formatos de CV, versiones, plazos, comisiones y parámetros). Útil para trasladar la configuración entre entornos.
sh
# Exportar
docker compose exec backend-seleccion-postulantes \
python manage.py exportar_configuracion_sgc /app/private_files/sgc_export
# Importar (probar primero con --dry-run)
docker compose exec backend-seleccion-postulantes \
python manage.py importar_configuracion_sgc /app/private_files/sgc_export \
--usuario <email> --dry-run| Comando | Argumentos |
|---|---|
exportar_configuracion_sgc | carpeta (posicional): carpeta de salida |
importar_configuracion_sgc | carpeta (posicional); --usuario <email> (requerido, UsuarioBase que registra la importación); --dry-run (revierte todo al final) |
La importación es transaccional: crea versiones nuevas y pasa las vigentes al historial. En --dry-run o ante error, elimina además los archivos subidos (el rollback de base de datos no cubre el almacenamiento).
Para mover la exportación entre entornos, cópiela al host con docker compose cp (o tómela de storage/private/ si se escribió en /app/private_files).
Datos históricos
Migran convocatorias de sistemas anteriores desde backups SQL Server (configuración de conexión en datos_historicos/services/conexion_backups).
| Comando | Opciones | Descripción |
|---|---|---|
comparar_esquemas | — | Analiza los esquemas de las bases históricas y escribe el resultado en datos_historicos/analisis_esquema |
exportar_datos_historicos | --familia F (por defecto ambas), --anio A (requiere --familia), --salida DIR (default private_files/exports_json) | Lee las bases de backup y escribe un JSON por convocatoria. No modifica la base de Django |
importar_datos_historicos | --archivo FILE, --carpeta DIR (default private_files/exports_json), --reemplazar | Importa los JSON. Sin --reemplazar omite convocatorias ya importadas |
DANGER
importar_datos_historicos --reemplazar borra en cascada la convocatoria existente antes de importarla.
Utilidades de desarrollo
Pensadas para el entorno local de desarrollo, no para los contenedores.
| Script (runscript) | Backend | Descripción |
|---|---|---|
generar_graficas | Ambos | Genera diagramas de modelos (graph_models) por app y globales en diagramas/. Instala graphviz con sudo apt-get, por lo que no funciona dentro del contenedor. |
auditar_types_graphql | Seleccionador | Compara lo que expone un schema GraphQL con lo que consulta su frontend y genera un reporte Markdown. |
sh
python manage.py runscript auditar_types_graphql \
--script-args <schema> <carpeta_front> <salida.md><schema>: admin, comision, postulante o estacion_examen.
Operación en el host
Comandos para inspeccionar y operar los servicios directamente en el host de un entorno. Todos se ejecutan desde el directorio del stack:
sh
cd ${DEPLOY_BASE_PATH}/idp # auth-backend y frontends del IDP
cd ${DEPLOY_BASE_PATH}/seleccionador # backend y frontends del SPS
cd ${DEPLOY_BASE_PATH}/docs # manualesINFO
La forma normal de desplegar es mediante los pipelines. Las operaciones manuales de esta página son para diagnóstico y contingencias; el siguiente despliegue vuelve a aplicar la configuración de infra-deploy.
Estado de los servicios
sh
docker compose psColumna STATUS:
| Estado | Significado |
|---|---|
Up (healthy) | Contenedor en ejecución y /health/ responde |
Up (health: starting) | Dentro del start_period (20 s backends, 5 s frontends) |
Up (unhealthy) | En ejecución, pero el health check falló 3 veces seguidas |
Restarting | El proceso termina y Docker lo reinicia (restart: unless-stopped) |
Detalle del último health check:
sh
docker inspect --format '{{json .State.Health}}' auth-backendUso de recursos:
sh
docker stats --no-streamLogs
sh
# Últimas 200 líneas
docker compose logs --tail 200 backend-seleccion-postulantes
# En vivo
docker compose logs -f backend-seleccion-postulantes
# Desde hace 30 minutos
docker compose logs --since 30m auth-backendEn los backends, la salida incluye los logs de acceso y error de gunicorn y la salida del entrypoint.sh (migraciones, collectstatic y comandos de arranque). Los logs rotan a 10 MB con 5 archivos por contenedor.
Reiniciar o recrear un servicio
sh
# Reiniciar el proceso (misma imagen y configuración)
docker compose restart auth-backend
# Recrear el contenedor (relee back.env y .env)
docker compose up -d --force-recreate --no-deps auth-backendTIP
restart no vuelve a leer env_file. Si cambió back.env, use up -d --force-recreate.
Actualizar a la última imagen
Equivale a lo que hace el pipeline para un servicio:
sh
docker compose pull backend-seleccion-postulantes
docker compose up -d --no-deps backend-seleccion-postulantesRequiere sesión iniciada en el registry (docker login git.eje.gob.bo:5050) con el deploy token del grupo.
Rollback a una versión anterior
Cada build publica un tag inmutable <rama>-<sha corto>. Para volver a una versión anterior de un servicio:
Identificar el tag en GitLab: Deploy → Container Registry del repositorio, o el SHA del commit en el historial de la rama.
Desplegar ese tag solo para el servicio afectado, sobrescribiendo
IMAGE_TAGen la línea de comandos (tiene prioridad sobre.env):shIMAGE_TAG=main-a1b2c3d4 docker compose pull auth-backend IMAGE_TAG=main-a1b2c3d4 docker compose up -d --no-deps auth-backendVerificar con
docker compose psy los logs.
Para revertir el rollback, vuelva a desplegar con latest:
sh
docker compose pull auth-backend
docker compose up -d --no-deps auth-backendWARNING
- El siguiente despliegue del servicio por pipeline vuelve a
<rama>-latest. Para que el rollback sea definitivo, revierta el commit en la rama y deje que el pipeline publique una nueva imagen. - En los backends,
entrypoint.shaplica migraciones al arrancar, pero volver a una imagen anterior no revierte migraciones. Si la versión nueva incluía migraciones incompatibles, evalúe revertirlas conpython manage.py migrate <app> <migracion>antes del rollback.
Ejecutar comandos dentro de un contenedor
sh
# Shell interactiva (los backends corren como appuser)
docker compose exec backend-seleccion-postulantes bash
# Comando puntual
docker compose exec auth-backend python manage.py showmigrationsLos frontends usan nginx:alpine, que solo incluye sh:
sh
docker compose exec frontend-portal-admin-seleccion-postulantes shLos comandos de gestión disponibles se describen en Scripts útiles de los backends.
Archivos de los backends
Los archivos de los backends persisten en el host, fuera de los contenedores:
${DEPLOY_BASE_PATH}/idp/storage/{public,private}
${DEPLOY_BASE_PATH}/seleccionador/storage/{public,private}Recrear o eliminar un contenedor no afecta estos directorios. Inclúyalos en el respaldo del entorno junto con las bases de datos.
Detener servicios
sh
# Detener un servicio (no se reinicia automáticamente)
docker compose stop frontend-estacion-examen
# Volver a iniciarlo
docker compose start frontend-estacion-examenDANGER
Evite docker compose down -v en los stacks: aunque los volúmenes actuales son directorios del host (bind mounts), el comando elimina contenedores y redes del stack y deja todos los servicios fuera de línea hasta un nuevo despliegue.
Limpieza de imágenes
Cada despliegue descarga nuevas imágenes; las anteriores quedan sin etiqueta y ocupan disco:
sh
docker image prune -fPara eliminar también las imágenes no usadas por ningún contenedor (incluye tags antiguos que podrían servir para rollback):
sh
docker image prune -a -fRepositorios del sistema
El sistema está compuesto por repositorios independientes alojados en el grupo proceso-seleccion de GitLab (git.eje.gob.bo).
Inventario
| Repositorio | Tipo | Tecnología principal | Descripción |
|---|---|---|---|
auth-backend | Backend | Python 3.11 · Django 5.2 | Proveedor de identidad: OAuth2/OIDC, cuentas, aprobación de documentos y notificaciones |
auth-authorization-frontend | Frontend | Vue 3 · Vite | Login, registro y consentimiento OIDC |
auth-frontend-admin | Frontend | Vue 3 · Vite | Administración del IDP |
frontend-public-auth | Frontend | Vue 3 · Vite | Autogestión de la cuenta del usuario |
backend-seleccion-postulantes | Backend | Python 3.11 · Django 5.2 | Lógica del proceso de selección de postulantes |
frontend-portal-admin-seleccion-postulantes | Frontend | Vue 3 · Vite | Portal administrativo del proceso de selección |
frontend-portal-comisiones-seleccion-postulantes | Frontend | Vue 3 · Vite | Portal de las comisiones |
frontend-portal-postulantes-seleccion-postulantes | Frontend | Vue 3 · Vite | Portal de los postulantes |
frontend-estacion-examen | Frontend | Vue 3 · Vite | Estación de rendición del examen en línea |
frontend-manual-tecnico | Documentación | VitePress | Este manual |
frontend-manual-de-usuario | Documentación | VitePress | Manual de usuario |
custodio | Aplicación escritorio | Python 3.12 · Tkinter | Validación, consolidación y cifrado de bancos de preguntas |
infra-deploy | Infraestructura | Docker Compose · GitLab CI | Orquestación del despliegue (ver Infraestructura y despliegue) |
Herramientas para el entorno de desarrollo
| Herramienta | Versión | Uso |
|---|---|---|
| Git | — | Control de versiones |
| Python | 3.11 (backends), 3.12 (Custodio) | Backends y Custodio |
| Node.js | 22.x o 24.x | Frontends y manuales |
| pnpm | 11.3.0 (vía Corepack) | Gestor de paquetes de los proyectos Node |
| PostgreSQL | — | Base de datos de los backends |
| Docker + Compose | — | Construcción y prueba de imágenes |
| Graphviz | — | Generación de diagramas de modelos (opcional) |
Activación de pnpm (incluido en Node.js mediante Corepack):
sh
corepack enable pnpmLa versión exacta de pnpm queda fijada por el campo packageManager de cada package.json.
Backends: estructura
Aplica a auth-backend (IDP) y backend-seleccion-postulantes (SPS). Ambos son proyectos Django 5.2 con la misma organización.
Organización general
text
<backend>/
├── <proyecto>/ # Configuración del proyecto Django
│ ├── settings.py # Configuración (lee variables de .env)
│ ├── urls.py # Rutas raíz: schemas GraphQL, /api/v1/, docs, health
│ ├── *_schema.py # Composición de cada schema GraphQL
│ ├── api/ | api_router.py # Router de la API REST /api/v1/
│ ├── middleware/ # StandbyMiddleware y otros
│ └── wsgi.py
├── <app>/ # Una carpeta por módulo funcional (app Django)
├── scripts/ # Scripts para manage.py runscript
├── diagramas/ # Diagramas de modelos generados
├── manage.py # CLI de Django (con control SYSTEM_STATUS)
├── requirements.txt # Dependencias de desarrollo
├── requirements.prod.txt # Dependencias de la imagen Docker
├── .env.example # Plantilla de variables de entorno
├── Dockerfile · entrypoint.sh · nginx.conf| Backend | Carpeta del proyecto |
|---|---|
auth-backend | auth_interop_backend/ |
backend-seleccion-postulantes | seleccion_backend/ |
Estructura de una app
Cada app separa sus modelos, sus servicios y lo que expone en cada schema GraphQL:
text
<app>/
├── models.py # Modelos (tablas)
├── migrations/ # Migraciones de base de datos
├── services/ # Lógica de negocio (managers, conectores, utilidades)
├── admin_schema/ # Queries/mutations del schema administrativo
├── comision_schema/ # ... del schema de comisiones (SPS)
├── postulante_schema/ # ... del schema de postulantes (SPS)
├── estacion_examen_schema/ # ... del schema de estaciones (SPS)
├── main_schema/ · public_schema/ # ... schemas del IDP
├── api/ # API REST (IDP: views, serializers, openapi)
├── management/commands/ # Comandos de manage.py
├── templates/ # Plantillas (correos, documentos)
├── urls.py · views.py # Endpoints HTTP no GraphQL
└── admin.pySolo existen las carpetas de los schemas en los que participa la app. La lógica de negocio vive en services/; los resolvers GraphQL y las vistas REST la invocan.
Apps del IDP (auth-backend)
| App | Responsabilidad |
|---|---|
iam_core | Identidades: usuarios, identificadores de login, credenciales (contraseña, TOTP), proveedores de identidad federados, claims personalizados, registro |
authentication | Flujo de inicio de sesión (intentos de login, vinculación de identidades federadas) |
oauth2_eje | Servidor OAuth2: clientes, scopes, códigos de autorización, consentimientos, tokens y claves de firma |
oidc | OpenID Connect: discovery, JWKS, userinfo y definición de claims por scope |
aprobacion_documentos | Aprobación de documentos oficiales con firma digital o Ciudadanía Digital |
notificaciones | Solicitudes de notificación, destinatarios y envíos |
serve_private_files | Entrega controlada de archivos privados |
Apps del SPS (backend-seleccion-postulantes)
| App | Responsabilidad |
|---|---|
iam_core | Usuario local vinculado al IDP (UsuarioBase), accesos por portal, privilegios y sesiones |
catalogos | Catálogos geográficos (provincias, municipios) |
analitica_planillas | Planillas del Órgano Judicial y acefalías de jueces |
acciones_previas | Informes técnicos previos, analítica de acefalías y propuesta de plazas |
sgc | Sistema de Gestión de Calidad: procedimientos, registros, plantillas, formatos de CV, plazos y parámetros |
convocatorias | Convocatorias, ofertas departamentales, requisitos, méritos, fases y programación del examen |
resoluciones | Resoluciones administrativas y ampliaciones de plazo |
difusion | Avisos y archivos publicados en el sitio web institucional |
postulaciones | Datos del postulante, currículum, acreditaciones y postulaciones |
comisiones | Comisiones, subcomisiones, miembros, excusas y veedores |
trabajo_comisiones | Trabajo de las comisiones: sesiones, revisión DMH, calificación de méritos, examen de admisión y resultados por fase |
impugnaciones | Periodos de impugnación e impugnaciones de resultados |
conclusion_proceso | Informe final y postulantes admitidos |
reportes | Certificados de participación y reportes |
document_engine | Documentos de usuario, subida por segmentos y tokens de acceso a documentos |
aprobacion_documentos | Integración con la aprobación de documentos del IDP |
notificaciones | Integración con las notificaciones del IDP |
custodio_integration | Versiones de Custodio y cifrado/descifrado de bancos de preguntas |
interoperabilidad | API REST externa y servicios interoperables |
datos_historicos | Convocatorias y postulaciones de sistemas anteriores |
consultas | Preguntas frecuentes y casos de ayuda |
seeders | Datos de prueba y emuladores (solo entornos de prueba) |
Paquetes de soporte (no son apps con modelos):
| Paquete | Uso |
|---|---|
idp_api_client | Cliente de las APIs REST del IDP (directorio, aprobación, notificaciones) |
website_api | Cliente de la API del sitio web institucional (avisos y archivos) |
serve_private_files | Entrega controlada de archivos privados |
fixtures | Datos iniciales (servicios de interoperabilidad externa) |
El detalle de las tablas de cada app se describe en la sección de modelado de datos.
Backends: dependencias y configuración
Dependencias
Versiones de requirements.prod.txt (las que se instalan en la imagen Docker).
Comunes a ambos backends
| Paquete | Versión | Uso |
|---|---|---|
django | 5.2.14 (IDP) · 5.2.16 (SPS) | Framework web |
graphene-django | 3.2.2 | APIs GraphQL sobre los modelos de Django |
graphene-file-upload | 1.2.2 | Subida de archivos en GraphQL (multipart) |
django-graphbox | 1.5.14 | Construcción de schemas GraphQL y middleware de autenticación |
djangorestframework | 3.17.1 | API REST /api/v1/ |
drf-spectacular | 0.29.0 | Generación de OpenAPI, Swagger UI y ReDoc |
django-extensions | 3.1.3 | runscript, graph_models y utilidades de desarrollo |
django-environ | 0.7.0 | Lectura de variables desde .env |
wsgicors | 0.7.0 | Cabeceras CORS a nivel WSGI |
psycopg[binary] | 3.3.4 | Driver de PostgreSQL |
gunicorn | 26.0.0 | Servidor WSGI en producción |
cryptography | 48.0.0 | Primitivas criptográficas |
PyJWT | 2.10.1 | Emisión y validación de JWT |
pyotp | 2.9.0 | Códigos TOTP (segundo factor) |
requests | 2.34.2 | Cliente HTTP para servicios externos |
Pillow | 9.5.0 | Procesamiento de imágenes |
qrcode | 8.2 | Generación de códigos QR |
pypdf | 6.14.2 | Lectura y manipulación de PDF |
Faker | 37.11.0 (IDP) · ≥18.0.0 (SPS) | Datos ficticios para comandos de prueba |
Solo IDP (auth-backend)
| Paquete | Versión | Uso |
|---|---|---|
captcha | 0.7.1 | Captcha de imagen en el flujo de login |
pyHanko | 0.35.2 | Firmas digitales en PDF. Declarado en requirements; no se encontró uso directo en el código |
Solo SPS (backend-seleccion-postulantes)
| Paquete | Versión | Uso |
|---|---|---|
weasyprint | 69.0 | Generación de PDF desde HTML |
Jinja2 | 3.1.6 | Plantillas de documentos |
tiptapy | 0.21.0 | Conversión de contenido del editor Tiptap a HTML |
beautifulsoup4 | 4.9.3 | Procesamiento de HTML |
openpyxl | 3.1.5 | Lectura y escritura de Excel |
pdf2image | 1.16.0 | Conversión de PDF a imagen |
opencv-contrib-python | 4.8.1.78 | Lectura OMR de hojas de respuestas y marcadores ArUco en documentos |
numpy | 1.24.3 | Cálculo numérico (requerido por OpenCV) |
PyNaCl | 1.6.2 | Cifrado de bancos de preguntas (compatible con Custodio) |
zipstream-ng | 1.9.3 | Descarga de archivos ZIP en streaming |
pycountry | 26.2.16 | Catálogo de países |
num2words | 0.5.14 | Números en letras para documentos |
Dependencias de sistema
La imagen Docker instala además:
- Ambos:
nginx,curl. - SPS:
ghostscripty librerías nativas de WeasyPrint y OpenCV (Pango, Cairo, GDK-PixBuf, libffi, libGL, fuentes Liberation y DejaVu). Ver Imágenes Docker.
requirements.txt frente a requirements.prod.txt
requirements.txt es para desarrollo local: no incluye gunicorn y agrega mysqlclient (driver MySQL, requiere las librerías de desarrollo de MySQL en el sistema). En el SPS también fija Pillow>=13.0.0. La imagen Docker usa solo requirements.prod.txt.
Variables de entorno
Los backends leen su configuración de un archivo .env en la raíz del proyecto (django-environ). La plantilla es .env.example. En los servidores, este archivo es el back.env que genera el pipeline (ver Variables y secretos de CI/CD).
Comunes
| Variable | Descripción |
|---|---|
SECRET_KEY | Clave secreta de Django |
DEBUG | Modo depuración. Debe ser False en servidores |
SYSTEM_STATUS | True para operar; False activa el modo standby (503) y bloquea comandos de manage.py |
ALLOWED_HOSTS | Dominios aceptados (lista separada por comas) |
ALLOWED_CORS | Orígenes de los frontends permitidos (CORS y CSRF) |
MAIN_DOMAIN | Dominio base para las cookies (sin subdominio) |
DATABASE_ENGINE | Motor de Django (django.db.backends.postgresql) |
DATABASE_NAME · DATABASE_USER · DATABASE_PASS · DATABASE_HOST · DATABASE_PORT | Conexión a la base de datos |
SIGNING_KEY_MASTER_SECRET | Clave maestra de cifrado de datos sensibles |
STATIC_ROOT | Destino de collectstatic (por defecto assets/) |
PUBLIC_MEDIA_ROOT | Raíz de archivos públicos (por defecto files/; en Docker /app/public_files) |
PRIVATE_MEDIA_ROOT | Raíz de archivos privados (por defecto private_files/; en Docker /app/private_files) |
PRIVATE_MEDIA_SERVE_BACKEND | Cómo se entregan los archivos privados: django, apache o nginx (por defecto django) |
PRIVATE_MEDIA_NGINX_HEADER · PRIVATE_MEDIA_NGINX_INTERNAL_PREFIX | Cabecera (X-Accel-Redirect) y prefijo interno (/private_files/) para nginx |
PRIVATE_MEDIA_APACHE_HEADER | Cabecera para Apache (X-Sendfile) |
PRIVATE_MEDIA_URL | URL de archivos privados cuando los sirve Django |
TIP
En los contenedores, nginx está configurado para X-Accel-Redirect sobre /private_files/, por lo que corresponde PRIVATE_MEDIA_SERVE_BACKEND=nginx.
Solo IDP
| Variable | Descripción |
|---|---|
AUTHSERVER_ISSUER | Issuer del servidor de autorización (tokens y discovery OIDC) |
PUBLIC_LOGIN_URL | URL pública del portal de login |
IDP_CALLBACK_URL | Callback del login con proveedores de identidad externos |
IDP_LINKING_CALLBACK_URL | Callback de vinculación de proveedores externos con una cuenta existente |
IDP_LOGIN_CD_FOR_APPROVAL_CALLBACK_URL | Callback del login con Ciudadanía Digital para aprobar documentos |
CD_AGETIC_ISSUER | Issuer de Ciudadanía Digital (AGETIC) |
BASE_SERVE_PRIVATE_FILES | URL base para construir enlaces a archivos privados |
SSO_COOKIE_SESSION_KEY | Nombre de la cookie de sesión SSO |
Solo SPS
| Variable | Descripción |
|---|---|
DATABASE_CONN_MAX_AGE | Persistencia de conexiones en segundos (por defecto 900) |
IDP_OIDC_DISCOVERY_URL | Discovery OIDC del IDP |
IDP_OIDC_CLIENT_ID · IDP_OIDC_CLIENT_SECRET | Cliente OIDC del SPS para el login de los portales |
IDP_OIDC_SCOPES | Scopes solicitados en el login |
IDP_OIDC_ADMIN_REDIRECT_URI · IDP_OIDC_ADMIN_LOGOUT_REDIRECT_URI | Redirecciones de login/logout del portal administrativo |
IDP_OIDC_COMISIONES_REDIRECT_URI · IDP_OIDC_COMISIONES_LOGOUT_REDIRECT_URI | Ídem, portal de comisiones |
IDP_OIDC_POSTULANTE_REDIRECT_URI · IDP_OIDC_POSTULANTE_LOGOUT_REDIRECT_URI | Ídem, portal de postulantes |
IDP_API_CLIENT_ID · IDP_API_CLIENT_SECRET | Cliente client_credentials para consumir las APIs REST del IDP |
IDP_API_SCOPES | Scopes de ese cliente |
IDP_API_BASE_URL | URL base de las APIs del IDP |
COOKIE_SESSION_KEY | Nombre de la cookie de sesión de los portales |
COOKIE_ESTACION_SESSION_KEY | Nombre de la cookie de sesión de las estaciones de examen |
CREDENTIALS_KEY_EXAMEN_VIRTUAL | Clave de las credenciales JWT de estaciones de examen |
APPROVALDOCS_APPROVAL_URL | URL del IDP donde el usuario aprueba documentos |
POST_APPROVALDOCS_POSTULACION_REDIRECT_URI | Redirección tras aprobar documentos de una postulación |
PUBLIC_DOCS_VERIFICATION_BASE_URL | URL pública de verificación de documentos emitidos (códigos QR) |
WEBSITE_API_BASE_URL · WEBSITE_API_KEY | API del sitio web institucional (publicación de avisos) |
MIGRATION_MSSQL_CONN | Conexión a los backups SQL Server de datos históricos (opcional) |
Ejecución local
sh
# 1. Entorno virtual con Python 3.11
python3.11 -m venv env
source env/bin/activate
# 2. Dependencias
pip install -r requirements.txt
# 3. Configuración
cp .env.example .env # completar valores; SYSTEM_STATUS=True
# 4. Base de datos
python manage.py migrate
# 5. Servidor de desarrollo
python manage.py runserverEn el IDP, después de migrar ejecute los comandos de inicialización que en Docker corren en el arranque (rotate_signing_keys, create_default_scopes, create_claim_definitions) y cree un administrador con create_super_admin. Ver Scripts útiles de los backends.
En el SPS, WeasyPrint requiere las librerías nativas en el sistema (en Debian/Ubuntu: libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf2.0-0 libffi-dev libcairo2 shared-mime-info fonts-liberation fonts-dejavu-core).
Frontends: estructura
Los siete frontends son aplicaciones de página única (SPA) con Vue 3, empaquetadas con Vite y con la misma organización base.
Organización general
text
<frontend>/
├── public/ # Archivos estáticos copiados tal cual al build
├── src/
│ ├── main.js # Punto de entrada: crea la app, registra Pinia y el router
│ ├── App.vue # Componente raíz
│ ├── assets/ # CSS (Tailwind), imágenes y fuentes
│ ├── components/ # Componentes reutilizables
│ │ └── ui/ # Componentes base (shadcn-vue / Reka UI), alias @ui
│ ├── composables/ # Lógica reutilizable (Composition API)
│ ├── configs/ # Variables de entorno, cliente Apollo y constantes
│ ├── graphql/ # Queries y mutations GraphQL
│ ├── lib/ # Utilidades (por ejemplo combinación de clases CSS)
│ ├── router/ # Rutas y guards de Vue Router
│ ├── shared/ # Utilidades y estado compartido entre módulos
│ ├── stores/ # Stores globales (Pinia)
│ └── views/ # Vistas asociadas a rutas, una carpeta por módulo
├── index.html # HTML base donde Vite inyecta la aplicación
├── vite.config.js # Plugins, alias y configuración de build
├── components.json # Configuración de shadcn-vue
├── eslint.config.js · jsconfig.json
├── .env.example # Plantilla de variables VITE_*
├── Dockerfile · nginx.conf
└── package.json · pnpm-lock.yaml · pnpm-workspace.yamlFlujo de arranque
index.html carga src/main.js, que crea la aplicación con createApp, registra Pinia y el router, y monta App.vue. El router renderiza la vista de views/ que corresponde a la URL.
Carpetas propias de algunos proyectos
| Carpeta | Proyectos | Uso |
|---|---|---|
src/api/ | auth-authorization-frontend | Cliente HTTP (Fetch) de los endpoints REST del flujo de login |
src/services/ | auth-authorization-frontend | Orquestación de llamadas del flujo de login |
src/auth/ | Portales del SPS, estación, frontend-public-auth | Inicio de sesión vía IDP y manejo de sesión |
src/jacobitus/ | frontend-public-auth | Integración con Jacobitus (firma digital con token) para aprobar documentos |
cypress/ | auth-frontend-admin | Pruebas end-to-end |
Convenciones de build
vite.config.js define los alias @ (src/), @ui (src/components/ui/) y, en todos salvo el portal de postulantes, @pages (src/components/pages/, reservado; la carpeta no existe actualmente). La salida del build se organiza en js/, assets/img/ y assets/font/ con hash en el nombre.
Módulos por proyecto
Carpetas de src/views/ de cada frontend:
| Frontend | API que consume | Módulos (views/) |
|---|---|---|
auth-authorization-frontend | REST auth/flow/* + main-schema | login-identifier, login-password, login-totp, login-register, login-recovery, login-consent, login-complete, auth-callback, logout |
auth-frontend-admin | admin-schema (IDP) | home, identidad-usuarios, interoperabilidad, configuracion |
frontend-public-auth | public-schema (IDP) | perfil, aprobacion-documentos, aprobacion-detalle, aprobacion-cd, federated |
frontend-portal-admin-seleccion-postulantes | admin-schema (SPS) | home, login, login-callback, convocatorias, convocatoria, convocatoria-historica, postulacion, postulacion-historica, historial-postulaciones, informes-previos, planillas, sgc, usuarios, accesos, custodio, interoperabilidad, provincias-municipios, preguntas-frecuentes, casos-ayuda |
frontend-portal-comisiones-seleccion-postulantes | comision-schema | home, login, login-callback, convocatorias, comision, sesion |
frontend-portal-postulantes-seleccion-postulantes | postulante-schema | home, login-callback, dashboard, convocatorias-vigentes, convocatoria-detalle, postulacion-proceso, aviso-detalle, perfil, verificacion, preguntas-frecuentes |
frontend-estacion-examen | estacion-examen-schema | home |
Las APIs se describen en APIs de los backends.
Frontends: dependencias y configuración
Dependencias comunes
Presentes en los siete frontends, con la misma versión.
| Paquete | Versión | Uso |
|---|---|---|
vue | 3.5.32 | Framework de interfaz |
vue-router | 5.0.4 | Enrutamiento |
pinia | 3.0.4 | Estado global |
@vueuse/core | 14.3.0 | Composables utilitarios |
tailwindcss | 4.3.0 | Estilos por clases utilitarias |
@tailwindcss/vite | 4.3.0 | Integración de Tailwind con Vite |
reka-ui | 2.9.8 | Componentes accesibles sin estilo (base de shadcn-vue) |
class-variance-authority | 0.7.1 | Variantes de estilos de componentes |
clsx · tailwind-merge | 2.1.1 · 3.6.0 | Combinación de clases CSS |
@lucide/vue | 1.16.0 | Iconos |
Dependencias por función
| Paquete | Versión | Uso | Proyectos |
|---|---|---|---|
@apollo/client · @vue/apollo-composable · @vue/apollo-util | 3.11.8 · 4.2.1 · 4.2.2 | Cliente GraphQL | Todos excepto auth-authorization-frontend |
graphql · graphql-tag | 16.12.0 · 2.12.6 | Definición de operaciones GraphQL | Ídem |
apollo-upload-client | 18.0.1 | Subida de archivos por GraphQL | Ídem |
date-fns | 4.3.0 | Manejo de fechas | Ídem |
vee-validate · @vee-validate/zod · zod | 4.15.1 · 4.15.1 · 4.4.3 | Validación de formularios | Todos excepto auth-authorization-frontend y la estación |
@embedpdf/* (core, engines y plugins) | 2.14.4 | Visor de PDF embebido | frontend-public-auth y los tres portales del SPS |
js-sha1 | 0.7.0 | Hash SHA-1 en el cliente | frontend-public-auth y los tres portales del SPS |
@tiptap/* (vue-3, starter-kit, pm y extensiones) | 3.26–3.27 | Editor de texto enriquecido | Los tres portales del SPS |
@tiptap/extension-mention | 3.27.1 | Menciones en el editor | Portales administrativo y de comisiones |
tiptap-pagination-plus | 3.1.0 | Paginación en el editor | Portal administrativo |
echarts | 6.1.0 | Gráficos | Portales administrativo y de comisiones |
auth-authorization-frontend no usa GraphQL de forma general: consume los endpoints REST del flujo de login con Fetch.
Herramientas de desarrollo
| Paquete | Versión | Uso |
|---|---|---|
vite | ^8.0.8 | Servidor de desarrollo y empaquetado |
@vitejs/plugin-vue | ^6.0.6 | Soporte de componentes .vue |
vite-plugin-vue-devtools | ^8.1.1 | Vue DevTools integradas en desarrollo |
eslint · @eslint/js · eslint-plugin-vue · globals | ^10.2.1 · ^10.0.1 · ~10.8.0 · ^17.5.0 | Linter |
oxlint · eslint-plugin-oxlint | ~1.60.0 | Linter rápido (se ejecuta antes de ESLint) |
prettier · eslint-config-prettier | 3.8.3 · ^10.1.8 | Formato de código |
npm-run-all2 | ^8.0.4 | Ejecución de scripts lint:* en serie |
tw-animate-css | ^1.4.0 | Animaciones para Tailwind |
@types/node | ^25.9.1 | Tipos de Node para el editor |
cypress · eslint-plugin-cypress · start-server-and-test | ^15.16.0 · ^6.3.1 · ^3.0.5 | Pruebas end-to-end (solo auth-frontend-admin) |
Componentes de interfaz (shadcn-vue)
Los componentes de src/components/ui/ siguen shadcn-vue: su código fuente vive en el repositorio (no es una librería instalada) y se construye sobre Reka UI y Tailwind. La configuración está en components.json (estilo new-york, iconos lucide, CSS en src/assets/css/tailwind.css).
Variables de entorno
Las variables VITE_* se definen en .env (plantilla .env.example) y se incrustan en el bundle al compilar. La lista por proyecto está en Imágenes Docker.
| Variable | Descripción |
|---|---|
VITE_TITLE_APP | Título de la aplicación |
VITE_API_ENDPOINT | URL base del backend |
VITE_API_ENDPOINT_PATH | Path del schema GraphQL (por ejemplo /admin-schema/) |
VITE_API_ENDPOINT_FILES | URL base de archivos públicos |
VITE_API_FILES_PRIVATE | URL base de archivos privados |
VITE_AUTH_ORIGIN_PORTAL | Portal de origen enviado al iniciar sesión (por ejemplo PORTAL_ADMINISTRATIVO) |
VITE_AUTH_PUBLIC_URL | URL del portal de cuenta del IDP ("Mi cuenta") |
VITE_LOGIN_REDIRECT_URL | URL del portal de login del IDP (el logout usa <url>/logout) |
VITE_PUBLIC_AUTH_PORTAL · VITE_ADMIN_AUTH_PORTAL | URLs de los portales del IDP |
VITE_DOMAIN_PUBLIC_URL | Se recibe en el build; sin uso en el código actual |
VITE_JACOBITUS_API_URL | URL del servicio Jacobitus (firma digital con token) |
VITE_MANUAL_TECNICO_URL · VITE_MANUAL_USUARIO_URL | Enlaces a los manuales |
Scripts y ejecución local
sh
corepack enable pnpm
pnpm install
cp .env.example .env # completar valores
pnpm run dev| Script | Acción |
|---|---|
pnpm run dev | Servidor de desarrollo |
pnpm run build | Build de producción en dist/ |
pnpm run preview | Sirve el build localmente |
pnpm run lint | Oxlint + ESLint con corrección automática |
pnpm run format | Prettier sobre src/ |
pnpm run test:e2e | Pruebas Cypress (solo auth-frontend-admin) |
Puertos y host local de desarrollo (package.json y server.allowedHosts de vite.config.js):
| Frontend | Puerto | Host local |
|---|---|---|
auth-authorization-frontend | 8080 | login-auth-eje-local.app.com.bo |
auth-frontend-admin | 7070 | admin-auth-eje-local.app.com.bo, auth-admin-eje-local.app.com.bo |
frontend-public-auth | 9092 | auth-public-eje-local.app.com.bo |
frontend-portal-admin-seleccion-postulantes | 9090 | admin-portal-seleccion-eje-local.app.com.bo |
frontend-portal-postulantes-seleccion-postulantes | 9091 | postulantes-portal-seleccion-eje-local.app.com.bo |
frontend-portal-comisiones-seleccion-postulantes | 9093 | comisiones-portal-seleccion-eje-local.app.com.bo |
frontend-estacion-examen | 9094 | estacion-examen-eje-local.app.com.bo |
Manuales (VitePress)
Aplica a frontend-manual-tecnico y frontend-manual-de-usuario. Ambos son sitios estáticos generados con VitePress a partir de archivos Markdown.
Estructura
text
<manual>/
├── .vitepress/
│ ├── config.mjs # Configuración del sitio: título, búsqueda, nav, sidebar
│ ├── rutas/ | sidebars/ # Sidebar de cada sección (un archivo .js por sección)
│ └── theme/ # Tema: extiende el tema por defecto y agrega CSS (pantalla e impresión)
├── docs/ # Contenido (srcDir)
│ ├── index.md # Página de inicio
│ ├── <seccion>/ # Una carpeta por sección: index.md + páginas numeradas
│ ├── consolidado/ # Páginas que reúnen secciones para exportar a PDF
│ └── public/ # Archivos servidos tal cual (logo, imágenes)
├── Dockerfile · nginx.conf
└── package.json · pnpm-lock.yaml · pnpm-workspace.yaml| Manual | Carpeta de sidebars |
|---|---|
frontend-manual-tecnico | .vitepress/rutas/ |
frontend-manual-de-usuario | .vitepress/sidebars/ |
Dependencias
| Paquete | Versión | Uso |
|---|---|---|
vitepress | 2.0.0-alpha.17 | Generador del sitio |
mermaid | ^12.2.0 | Diagramas en bloques ```mermaid |
vitepress-plugin-mermaid | ^2.0.17 | Integración de Mermaid con VitePress (withMermaid en config.mjs) |
Agregar una sección (manual técnico)
Crear
docs/<seccion>/index.mdy las páginas numeradas (01-<tema>.md,02-<tema>.md, …).Crear
.vitepress/rutas/<seccion>.jsexportando el sidebar:jsconst PATH_BASE = '/<seccion>'; export const RUTAS_<SECCION> = [ { link: `${PATH_BASE}/`, text: 'Título de la sección', items: [{ text: 'Página 1', link: `${PATH_BASE}/01-<tema>` }], }, ];Importarlo en
.vitepress/config.mjsy registrarlo enthemeConfig.sidebarcon la clave'/<seccion>/'.Agregar la tarjeta en
featuresdedocs/index.md.Incluir las páginas en la exportación de
docs/consolidado/.
Ejecución local
sh
corepack enable pnpm
pnpm install
pnpm run docs:dev # servidor de desarrollo
pnpm run docs:build # genera .vitepress/dist
pnpm run docs:preview # sirve el buildVitePress valida los enlaces internos durante docs:build: un enlace roto hace fallar el build (y el pipeline).
Custodio
Aplicación de escritorio portable que usa la comisión de elaboración para validar, consolidar y cifrar los bancos de preguntas antes de entregarlos al sistema.
Flujo
- Cada área entrega un banco
.xlsxcon las hojasPreguntas,Respuestasy_metadata(generadas por el sistema). - Custodio valida que cada banco esté completo: todas las preguntas habilitadas con sus 4 incisos, respuesta correcta y justificación.
- Consolida los bancos válidos en
Preguntas.xlsxyRespuestas.xlsx(una hoja por área). - Cifra ambos archivos con la clave pública
key.pub. Solo el sistema, que tiene la clave privada, puede descifrarlos.
El ejecutable se ubica en la misma carpeta que key.pub y la carpeta de bancos. Las versiones distribuidas se registran en el portal administrativo del SPS (app custodio_integration).
Estructura
text
custodio/
├── custodio.py # Interfaz gráfica (Tkinter)
├── custodio_core.py # Lógica de negocio sin interfaz (validación, consolidación, cifrado)
├── generar_claves_sistema.py # Utilidad de referencia: genera un par de claves X25519 (lado sistema)
├── tests/ # Pruebas pytest de custodio_core
├── Custodio.spec # Configuración de PyInstaller
└── requirements.txtLa separación entre custodio.py y custodio_core.py permite probar la lógica en CI sin entorno gráfico.
Dependencias
| Paquete | Versión | Uso |
|---|---|---|
PyNaCl | 1.6.2 | Cifrado con clave pública (X25519) |
openpyxl | 3.1.5 | Lectura y escritura de los bancos Excel |
Pillow | 11.0.0 | Redimensionar las imágenes incluidas en las preguntas al consolidar |
pyinstaller | 6.22.0 | Empaquetado como ejecutable portable |
pytest | 9.1.1 | Pruebas |
Requiere Python 3.12 con Tkinter.
Ejecución y build
sh
pip install -r requirements.txt
# Ejecutar en desarrollo
python custodio.py
# Pruebas
python -m pytest tests/ -v
# Ejecutable (dist/Custodio o dist/Custodio.exe)
pyinstaller Custodio.spec --cleanPyInstaller no hace compilación cruzada: el ejecutable de Windows se genera en Windows. El pipeline de publicación se describe en Pipelines CI/CD.
