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 ejemplokeplertraza.sbip.cl). Ejemplos de cuentas:traza,logisticasbip. - Sin prefijo
/apiglobal. Los prefijos son por blueprint:/api/v1/toay/api/scv. - Zonas (
src/config.py):metropolitana,centro,norte,sur. En las rutas legacy con<zona>en el path,metroes alias demetropolitana.
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:
mandantedebe ser una clave registrada ensrc/utils/mandante_adapters.zonadebe estar enZONES(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
200concomparison(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 deToaOrderChange(detected_at DESC).page ≥ 1;per_pagedefault 50, tope 200;only_activedefaulttrue.order_changes/<orden_trabajo>: una orden activa por OT.404si 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.fechafiltra 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.200conPelo,Orden_de_Trabajo,Direccion,Access ID;200 {"error":"Rut encontrado pero no tiene pelo"};400zona inválida;404sin archivo o rut no encontrado.get_pelo_db: busca la orden activa más reciente del rut en la base.Pelose resuelve desdefull_data["Pelo"]oniveles_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 (oAuthorization: Bearer <api_key>de panel) y no se documentan aquí. - Los blueprints
navixy,geovictoria,telegram,integrations,authyadminfueron eliminados endevelop-paneles(commit4fd3014, "isolate panel-only functionality"); no son parte de la superficie actual.