chore(ti/licenciamiento): etapa oneshot con dos jobs manuales para...
chore(ti/licenciamiento): etapa oneshot con dos jobs manuales para materializar en qa la hoja que la vista dejo de leer en vivo
QUE ES ESTO, EN UNA LINEA
Un boton en el pipeline que escribe en ti_licencias de QA exactamente los
mismos datos que la vista de Licenciamiento ya venia mostrando — no una fuente
nueva, no un sync recurrente, no un cambio de contrato.
POR QUE HACE FALTA
Hasta 375ec704 ("la vista lee la tabla y gana captura; se retira la hoja") el
endpoint de Licenciamiento leia la pestaña "Licencias" del libro "Data KPIS
Infraestructura" EN VIVO, en cada request, y agregaba en memoria. Ese commit
invirtio la direccion: la verdad pasa a vivir en la tabla ti_licencias y la
hoja se abandona, para que el modulo gane captura propia (CRUD + RBAC) en vez de
depender de que alguien edite un Google Sheet.
La consecuencia operativa no se cubrio. Base.metadata.create_all crea
ti_licencias VACIA al arrancar el backend, y nada en .gitlab-ci.yml la
puebla: el deploy solo corre backend/migrations/run.sh y
scripts/backfill_canonicals.py. En QA eso deja
GET /api/ti/licencias/dashboard/stats respondiendo 200 con total: 0 —
verificado en Network: la pantalla monta bien, el RBAC pasa, la ruta existe, y
no hay un solo renglon que pintar. En local la misma carga si se hizo y la tabla
tiene 1,252 filas, todas origen='sheet'.
QUE NO ES
No introduce una dependencia nueva de Google. Es el MISMO spreadsheet id
(1YbiNC5lAKDhf2wJWBNwg7Q8Oj0HU_Frljp78h79Ce4I), la MISMA cuenta de servicio
(backend/service_account.json, que deploy_qa ya preserva via
git clean -e), el MISMO scope de solo lectura y las MISMAS filas que el
endpoint viejo leia en cada request. Lo unico que cambia es CUANDO se lee — una
vez, a mano, en vez de en cada peticion — y DONDE aterriza: una tabla
persistente en vez de un dict que moria con la respuesta HTTP.
Tampoco toca el esquema. Cero DDL, cero migracion, cero cambio de modelo, cero
cambio de router o de front. El unico archivo tocado es .gitlab-ci.yml.
POR QUE MANUAL Y NO UN PASO DE deploy_qa
La carga es one-shot por diseño. Colgarla del deploy la convertiria en un sync
que re-lee Google en cada despliegue, que es exactamente lo contrario de lo que
375ec704 decidio. El operador aprieta el boton una vez y despues el bloque se
borra.
POR QUE LA ETAPA oneshot Y NO LA ETAPA deploy
Un job en la misma etapa que deploy_qa correria en paralelo con el. La carga
tiene que ocurrir DESPUES de que docker compose up --build levanto el backend
nuevo, porque es ese arranque el que garantiza —via create_all— que la tabla
destino existe. Una etapa posterior serializa eso sin depender de needs:.
POR QUE SIN LA LLAVE environment:
El job no despliega: no cambia la version corriendo, no debe aparecer en el
historial de deployments del entorno qa ni marcarlo como re-desplegado. Como
efecto lateral util, omitirla lo saca del gate de Protected Environments, que
solo deja disparar a usuarios con rol de deployer.
IDEMPOTENCIA — POR CONSTRUCCION, NO POR GUARDA EXTERNA
backend/scripts/import_licencias_sheet.py conserva como PK el Id de la
columna A de la hoja (entero denso y unico, 1..1252). Re-correrlo ACTUALIZA la
fila existente en vez de duplicarla. Ademas:
- Nunca pisa una fila con
origen='portal'ni una dada de baja: si la hoja y el portal difieren, gana el portal, que es la fuente de verdad desde la carga. - El cruce de empresa se resuelve EN QA contra
empresas_grupo_caabsaviaservices/empresas_catalogo_lookup.build_canon_index. No se copianempresa_idde otro entorno — seria correcto por casualidad o incorrecto en silencio. Lo que no cruza se queda comoempresa_textoy se reporta aparte. - La columna PORTAFOLIO de la hoja se ignora a proposito: trae taxonomia CCC y la vista usa la del catalogo de /admin.
- Al terminar reposiciona la secuencia de
ti_licencias.idal maximo + 1, sin lo cual la primera alta desde el formulario chocaria con la PK.
Por eso apply no lleva guarda de "solo si la tabla esta vacia": una guarda asi
impediria un reintento legitimo si la primera corrida fallara a la mitad, y no
aporta seguridad que el script no de ya.
LOS DOS JOBS
licencias_import_qa_dryrun — allow_failure: true. Imprime el conteo previo
y corre el script con --dry-run: reporta altas, actualizaciones, cuantas
filas cruzan contra el catalogo de QA y cuales traen campos criticos
vacios. NO escribe. Puede fallar sin ensuciar el pipeline.
licencias_import_qa_apply — allow_failure: false. Conteo previo, carga, y
conteo posterior agrupado por origen para que el log deje constancia del
antes y el despues en vez de terminar mudo.
Ambos con when: manual bajo rules: y acotados a $CI_COMMIT_BRANCH == "qa",
asi que no existen en pipelines de merge request ni en main.
RETIRO
Estos dos jobs, la etapa oneshot y el script se borran en el commit siguiente
a la carga, como pide el docstring del propio script. Ese mismo commit lleva el
bloque equivalente para deploy_prod —donde el contenedor es caabsa_postgres
y el compose es docker-compose.yml—, porque main sigue en c85a6b6a (20 de
agosto) y va a toparse con la tabla vacia en cuanto qa llegue ahi.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com