Skip to content

API Kepler (externa)

Kepler es la aplicación Flask que sirve el panel de operaciones TOA. Este documento cubre los endpoints pensados para ser llamados por sistemas externos y scripts (no los del panel de operadores, que requieren sesión de navegador).

Branch fuente: develop-paneles del repo kepler.

Cómo conectarse

  • Hosting: PythonAnywhere. Cada instancia vive en https://<usuario>.pythonanywhere.com (algunas con un CNAME *.sbip.cl, por ejemplo keplertraza.sbip.cl). Ejemplos de cuentas: traza, logisticasbip.
  • Sin prefijo /api global. Los prefijos son por blueprint: /api/v1/toa y /api/scv.
  • Zonas (src/config.py): metropolitana, centro, norte, sur. En las rutas legacy con <zona> en el path, metro es alias de metropolitana.

Autenticación

Cada endpoint usa uno de estos mecanismos (no hay un token global):

Mecanismo Header Variables de entorno Dónde
HTTP Basic Authorization: Basic <base64(user:pass)> BASIC_AUTH_USERNAME / BASIC_AUTH_PASSWORD ingest de TOA (register-token, order_changes*)
HTTP Basic (access) Authorization: Basic <base64(user:pass)> ACCESS_AUTH_USERNAME / ACCESS_AUTH_PASSWORD POST /panel/access (credenciales separadas, menor privilegio)
API Key X-API-Key: <key> COMMAND_CENTER_API_KEY GET /api/scv/healthcheck
Público cargas de sabana, pelo, search_orders, fotos

Los POST de carga de archivos están exentos de CSRF (@csrf.exempt) para que scripts externos puedan llamarlos.

En el resto del documento, <host> = la base de la instancia (p. ej. https://traza.pythonanywhere.com).


Healthchecks

GET  <host>/api/v1/toa/healthcheck     # público → {"status":"ok","platform":"toa"}
GET  <host>/api/scv/healthcheck         # requiere X-API-Key

/api/scv/healthcheck responde 200 {"status":"ok","project":"kepler", "instance":..., "status_detail":{"api":"ok","db":"ok"}}, 401 si falta o es inválido el X-API-Key, y 500 si la variable COMMAND_CENTER_API_KEY no está configurada (mal configurado, no 401).


Registro de dispositivo (notificaciones push)

POST  <host>/api/v1/toa/devices/register-token      [HTTP Basic]
{ "fcm_token": "string (<=512)", "rut": "string (<=20)", "platform": "android|ios" }

Registra o actualiza un token FCM para un técnico (upsert por fcm_token). platform es opcional. Si el fcm_token ya existe, se actualiza rut / platform / last_active_at en vez de insertar uno nuevo. Respuesta 200 con el dispositivo; 400 si faltan fcm_token/rut o exceden largo; 401 si Basic falla.


Carga de sabana (órdenes TOA)

Todas reciben multipart/form-data con el campo file (.json o .zip; un ZIP debe contener exactamente un JSON). Son públicas + @csrf.exempt.

POST /api/v1/toa/set_data_toa — carga canónica

mandante: str (default "vtr")   # selecciona adaptador
zona:     str (requerido)       # metropolitana | centro | norte | sur
file:     .json | .zip

Guarda el archivo, valida el JSON, hace diff contra la base, persiste órdenes nuevas y registra historial de estado.

Reglas:

  • mandante debe ser una clave registrada en src/utils/mandante_adapters.
  • zona debe estar en ZONES (case-insensitive, trim).
  • El JSON debe ser un arreglo o {"data": [...]}.
  • Se filtran órdenes por tipo_red_producto ∈ {NFTT, ONT} y por prefijos de técnico configurados.
  • Respuesta 200 con comparison (new_orders, removed_orders, flag_changes, cambio_pelo_changes + contadores); si no hay cambios, {"message": "No changes detected from previous file"}.

Otras empresas (por zona)

POST /api/v1/toa/set_data_toa_other_enterprise              # metropolitana
POST /api/v1/toa/set_data_toa_other_enterprise_north_zone   # norte
POST /api/v1/toa/set_data_toa_other_enterprise_south_zone   # sur
POST /api/v1/toa/set_data_toa_other_enterprise_center_zone  # centro

Mismo campo file. La zona metropolitana se auto-deriva del flag COMPANY_HAS_ZONES; las demás la traen fija. Misma envoltura de respuesta.

Deprecated (usan set_data_toa en su lugar)

POST /api/v1/toa/set_sabana, set_sabana_metro, set_sabana_centro,
              set_sabana_norte, set_sabana_sur

Escriben solo a la carpeta de sabana (sin historial de estado). Loguean un warning y delegan al mismo helper de carga.


Lectura de sabana

GET /api/v1/toa/get_data_toa_other_enterprise              # metrop., transformado a "iniciados"
GET /api/v1/toa/get_data_toa_other_enterprise_north_zone
GET /api/v1/toa/get_data_toa_other_enterprise_south_zone
GET /api/v1/toa/get_data_toa_other_enterprise_center_zone
GET /api/v1/toa/get_sabana_metro      # vista filtrada (sin transform legacy)
GET /api/v1/toa/get_sabana_centro     # JSON crudo reciente
GET /api/v1/toa/get_sabana_norte
GET /api/v1/toa/get_sabana_sur

Públicas. Devuelven el JSON más reciente de la carpeta correspondiente, opcionalmente transformado al formato legacy "iniciados". Respuesta 200 {"message":..., "data":[...]}; 404 {"error":"No file found"}.


Consulta de cambios de orden

Todos HTTP Basic (BASIC_AUTH_*).

GET  <host>/api/v1/toa/order_changes?page=1&per_page=50&only_active=true
GET  <host>/api/v1/toa/order_changes/<orden_trabajo>      # p. ej. 1-45874
GET  <host>/api/v1/toa/order_changes/resumen?page=1&fecha=2026-07-10
  • order_changes: lista paginada de ToaOrderChange (detected_at DESC). page ≥ 1; per_page default 50, tope 200; only_active default true.
  • order_changes/<orden_trabajo>: una orden activa por OT. 404 si no existe.
  • order_changes/resumen: resumen liviano (4 campos por fila: orden_de_trabajo, rut_tecnico, fecha_deteccion, ultimo_estado). Tamaño de página fijo en 10. fecha filtra por día calendario Chile (YYYY-MM-DD).

Búsqueda de órdenes

GET  <host>/api/v1/toa/search_orders?q=<texto>&only_alerts=false&zona=norte     # público

Búsqueda ILIKE sobre orden_de_trabajo. q requerido (mínimo 2 caracteres); only_alerts default false; zona opcional. Devuelve hasta 20 resultados: {"results":[...], "count": int}.


Pelo (puerto del técnico)

GET  <host>/api/v1/toa/get_pelo/<zona>/<rut>     # público; zona ∈ {sur,centro,norte,metro}
GET  <host>/api/v1/toa/get_pelo_db/<rut>         # público; lee desde la base
  • get_pelo: busca el pelo desde el JSON en memoria de la zona. 200 con Pelo, Orden_de_Trabajo, Direccion, Access ID; 200 {"error":"Rut encontrado pero no tiene pelo"}; 400 zona inválida; 404 sin archivo o rut no encontrado.
  • get_pelo_db: busca la orden activa más reciente del rut en la base. Pelo se resuelve desde full_data["Pelo"] o niveles_inicial["u_current_physical_port"].

Fotos

GET  <host>/api/v1/toa/photo/<int:photo_id>          # público; bytes de la imagen (image/jpeg)
GET  <host>/api/v1/toa/order_photos/<int:order_id>   # público; lista de fotos de la orden

order_photos devuelve {"success": true, "photos":[{..., "url": str}]}. Ambos 404 si no existen.


Archivos auxiliares (carga/lectura)

Cargas: públicas + @csrf.exempt, campo file (.json/.zip), renombrado con timestamp; lecturas públicas.

POST/GET /api/v1/toa/set_data_toa_estaticos | get_data_toa_estaticos
POST/GET /api/v1/toa/set_toa_equipos         | get_toa_equipos
POST/GET /api/v1/toa/set_reporte_90_dias_sabana | get_reporte_90_dias_sabana
POST/GET /api/v1/toa/set_reporte_calidad/<zona>  | get_reporte_calidad/<zona>
GET       /api/v1/toa/get_reporte_calidad/<zona>/solo-reiterados
GET       /api/v1/toa/get_sabana_filtrada/<zona>     # zona ∈ {metro,centro,norte,sur}

Envoltura estándar de carga: 200 {"message":"File uploaded successfully"}. Errores: 400 {"error":"No file provided" | "No selected file" | "El archivo debe ser un JSON o ZIP con nombre válido"}, 500 {"error":"Ocurrió un error inesperado"}.


Endpoints de test (smoke)

POST /api/v1/toa/set_sabana_metro_test | _centro_test | _norte_test | _sur_test
POST /api/v1/toa/set_sabana_test
POST /api/v1/toa/set_data_toa_other_enterprise_test | _south_zone_test | _north_zone_test | _center_zone_test

Públicos + @csrf.exempt (el decorador Basic está comentado). Sólo validan la extensión del archivo y devuelven {"success": true, "file_type": "json"|"zip"}no guardan ni procesan nada.


Kill-switch de instancia

POST  <host>/panel/access      [HTTP Basic con ACCESS_AUTH_* — credenciales separadas]
{ "enabled": true, "reason": "motivo del cambio" }

Activa/desactiva is_active para todos los usuarios no-superadmin (corte general de la instancia). Responde {"success": ..., ...} con la cantidad de usuarios afectados. Es el único endpoint externo fuera de los blueprints /api/*.


Notas

  • Esta doc cubre solo la superficie externa/programática. Los endpoints del panel de operadores (listados, resolución de alertas, monitoreo, exports Excel de reportes, y la API de superadmin bajo /dev/api/...) requieren sesión de navegador (o Authorization: Bearer <api_key> de panel) y no se documentan aquí.
  • Los blueprints navixy, geovictoria, telegram, integrations, auth y admin fueron eliminados en develop-paneles (commit 4fd3014, "isolate panel-only functionality"); no son parte de la superficie actual.