#!/usr/bin/env bash # Pruebas del backend 13-oct: arranca app.py con carpetas temporales y ejercita cada endpoint nuevo. # Uso: bash tests-1013.sh [puerto] set -u REPO=${1:?ruta a hbox-docs}; PORT=${2:-8098} T=$(mktemp -d); export ACTAS_DIR=$T/actas PORT mkdir -p $ACTAS_DIR python3 $REPO/backend/app.py > $T/app.log 2>&1 & APP=$!; trap "kill $APP 2>/dev/null" EXIT; sleep 1.5 U="http://127.0.0.1:$PORT" LEO=(-s -H "X-Remote-User: leo" -H "Content-Type: application/json") ALAN=(-s -H "X-Remote-User: alan" -H "Content-Type: application/json") ok=0; fail=0 check() { # check "nombre" "esperado" "obtenido" if [[ "$3" == *"$2"* ]]; then ok=$((ok+1)); echo " ✓ $1"; else fail=$((fail+1)); echo " ✕ $1 — esperaba «$2», obtuve: ${3:0:160}"; fi; } echo "## salud y endpoints viejos" check "salud" '"servicio":"actas-hbox"' "$(curl -s $U/api/salud)" check "whoami leo admin" '"admin":true' "$(curl "${LEO[@]}" $U/api/whoami)" check "actas vacías" '"total":0' "$(curl -s $U/api/actas)" echo "## tareas" check "sin usuario → 403" "No puedo identificarte" "$(curl -s -H 'Content-Type: application/json' -X POST $U/api/tareas -d '{"titulo":"x"}')" check "POST (alan)" '"id":"t-a"' "$(curl "${ALAN[@]}" -X POST $U/api/tareas -d '{"id":"t-a","titulo":"Pendiente A","pista":"ingenieria","responsable":"michelle","fecha_limite":"2026-10-01","version_tarjeta":"V5.6","enlaces":[{"titulo":"ECO","url":"/hardware/v56-eco/"}]}')" check "POST id duplicado → 409" '"existe":true' "$(curl "${ALAN[@]}" -X POST $U/api/tareas -d '{"id":"t-a","titulo":"dup"}')" check "POST pista inválida → 400" "Pista inv" "$(curl "${ALAN[@]}" -X POST $U/api/tareas -d '{"titulo":"x","pista":"ventas"}')" check "POST fecha inválida → 400" "AAAA-MM-DD" "$(curl "${ALAN[@]}" -X POST $U/api/tareas -d '{"titulo":"x","fecha_limite":"13/10/2026"}')" curl "${LEO[@]}" -X POST $U/api/tareas -d '{"id":"t-b","titulo":"Pendiente B","pista":"compras","bloqueada_por":["t-a"]}' > /dev/null check "GET filtro pista" '"total":1' "$(curl -s "$U/api/tareas?pista=compras")" check "GET filtro responsable" '"total":1' "$(curl -s "$U/api/tareas?responsable=mich")" check "vencida calculada" '"vencida":true' "$(curl -s "$U/api/tareas?pista=ingenieria")" check "PATCH mover (alan) → historial con quién" '"quien":"alan"' "$(curl "${ALAN[@]}" -X PATCH "$U/api/tarea?id=t-a" -d '{"estado":"en-curso","orden":0}')" check "PATCH sin cambios" '"sin_cambios":true' "$(curl "${ALAN[@]}" -X PATCH "$U/api/tarea?id=t-a" -d '{"estado":"en-curso"}')" check "PATCH estado inválido → 400" "Estado inv" "$(curl "${ALAN[@]}" -X PATCH "$U/api/tarea?id=t-a" -d '{"estado":"listo"}')" check "PATCH inexistente → 404" "No existe" "$(curl "${ALAN[@]}" -X PATCH "$U/api/tarea?id=nada" -d '{"titulo":"x"}')" check "POST orden (renumera columna: t-b cambia de columna, t-a de posición)" '"actualizadas":2' "$(curl "${ALAN[@]}" -X POST $U/api/tareas/orden -d '{"estado":"en-curso","ids":["t-b","t-a"]}')" check "resumen" '"en-curso": 1' "$(curl -s $U/api/tareas/resumen | python3 -c 'import sys,json; print(json.dumps(json.load(sys.stdin)["por_pista"]["ingenieria"]))')" check "resumen vencidas" '"id":"t-a"' "$(curl -s $U/api/tareas/resumen)" check "DELETE alan → 403" "Solo un administrador" "$(curl "${ALAN[@]}" -X DELETE "$U/api/tarea?id=t-b")" check "DELETE leo → ok" '"borrado":"t-b"' "$(curl "${LEO[@]}" -X DELETE "$U/api/tarea?id=t-b")" check "índice CSV escrito" "t-a" "$(cat $T/tareas/_indice.csv)" echo "## plan" check "GET sin archivo → por omisión" '"_default":true' "$(curl -s $U/api/plan)" check "PUT alan → 403" "Solo un administrador" "$(curl "${ALAN[@]}" -X PUT $U/api/plan -d '{"pedido_total":570}')" check "PUT leo" '"actualizado_por":"leo"' "$(curl "${LEO[@]}" -X PUT $U/api/plan -d '{"pedido_total":570,"versiones":[{"version":"V5.6","objetivo":113,"serie_prefijo":"V56","fecha":"2026-11-30"},{"version":"V5.7","objetivo":327},{"version":"v6","objetivo":130}]}')" check "PUT conserva etapas" '"clave":"instalacion"' "$(curl -s $U/api/plan)" check "PUT sin versiones → 400" "al menos una versi" "$(curl "${LEO[@]}" -X PUT $U/api/plan -d '{"versiones":[]}')" echo "## inventario" check "POST ítem (alan)" '"id":"EB-V56"' "$(curl "${ALAN[@]}" -X POST $U/api/inventario -d '{"codigo":"EB-V56","descripcion":"EinarBoard V5.6","categoria":"tarjeta","aplica_a":["V5.6"],"minimo":5}')" check "POST código duplicado → 409" '"existe":true' "$(curl "${ALAN[@]}" -X POST $U/api/inventario -d '{"codigo":"eb-v56","descripcion":"dup"}')" check "POST categoría inválida → 400" "inv\\u00e1lida: tornillo" "$(curl "${ALAN[@]}" -X POST $U/api/inventario -d '{"codigo":"X1","descripcion":"x","categoria":"tornillo"}')" check "POST con stock_inicial → entrada" '"stock": 7' "$(curl "${ALAN[@]}" -X POST $U/api/inventario -d '{"codigo":"SD-15B-05","descripcion":"Mean Well","categoria":"fuente","minimo":113,"stock_inicial":7}' | python3 -c 'import sys,json; print(json.dumps(json.load(sys.stdin)["item"]))')" check "salida sin stock → 409" "No hay stock suficiente" "$(curl "${ALAN[@]}" -X POST $U/api/inventario/movimiento -d '{"item_id":"EB-V56","tipo":"salida","cantidad":3,"motivo":"ensamble"}')" check "entrada" '"stock_despues":10' "$(curl "${ALAN[@]}" -X POST $U/api/inventario/movimiento -d '{"item_id":"EB-V56","tipo":"entrada","cantidad":10,"motivo":"llegó pedido","referencia":"OC-1"}')" check "salida" '"stock_despues":7' "$(curl "${ALAN[@]}" -X POST $U/api/inventario/movimiento -d '{"item_id":"EB-V56","tipo":"salida","cantidad":3,"motivo":"ensamble","referencia":"HBOX5-E01-A001-R0001"}')" check "ajuste (conteo 4 → delta −3)" '"delta":-3' "$(curl "${ALAN[@]}" -X POST $U/api/inventario/movimiento -d '{"item_id":"EB-V56","tipo":"ajuste","cantidad":4,"motivo":"conteo físico"}')" check "sin motivo → 400" "Escribe el motivo" "$(curl "${ALAN[@]}" -X POST $U/api/inventario/movimiento -d '{"item_id":"EB-V56","tipo":"entrada","cantidad":1}')" check "alerta = stock < mínimo" '"alerta": true' "$(curl -s "$U/api/inventario?alertas=1" | python3 -c 'import sys,json; j=json.load(sys.stdin); print(json.dumps([i for i in j["items"] if i["id"]=="EB-V56"]))')" check "PATCH stock a mano → 400" "no se edita a mano" "$(curl "${ALAN[@]}" -X PATCH "$U/api/inventario/item?id=EB-V56" -d '{"stock":99}')" check "PATCH ubicación → historial" "ubicacion:" "$(curl "${ALAN[@]}" -X PATCH "$U/api/inventario/item?id=EB-V56" -d '{"ubicacion":"Anaquel A2","minimo":3}')" check "movimientos del ítem" '"total":3' "$(curl -s "$U/api/inventario/movimientos?item_id=EB-V56")" check "export CSV" 'codigo,descripcion' "$(curl -s $U/api/inventario/export.csv)" check "export CSV cabecera" 'text/csv' "$(curl -s -D - -o /dev/null $U/api/inventario/export.csv)" echo "## sitios" check "POST alan → 403" "Solo un administrador" "$(curl "${ALAN[@]}" -X POST $U/api/sitios -d '{"id":"icave","nombre":"ICAVE"}')" check "POST leo" '"id":"icave"' "$(curl "${LEO[@]}" -X POST $U/api/sitios -d '{"id":"icave","nombre":"ICAVE","cliente":"Hutchison Ports","ubicacion":"Veracruz","alias":["ICAVE (Veracruz)"],"contactos":[{"nombre":"Abraham","rol":"supervisor"}]}')" check "POST duplicado → 409" '"existe":true' "$(curl "${LEO[@]}" -X POST $U/api/sitios -d '{"id":"icave","nombre":"ICAVE"}')" check "PATCH alan → 403" "Solo un administrador" "$(curl "${ALAN[@]}" -X PATCH "$U/api/sitio?id=icave" -d '{"ubicacion":"x"}')" check "PATCH leo → historial" "ubicacion: Veracruz" "$(curl "${LEO[@]}" -X PATCH "$U/api/sitio?id=icave" -d '{"ubicacion":"Veracruz, Ver."}')" check "GET sitios" '"nombre":"ICAVE"' "$(curl -s $U/api/sitios)" curl "${ALAN[@]}" -X POST $U/api/actas -d '{"tipo":"instalacion-campo","serie":"HBOX5-E01-A001-R0001","tecnico":"Alan","conforme":true,"resultado":"OK","fecha":"2026-10-10","sitio":"icave","maquina_id":"FL-JUN-060","accion":"Instalada y reportando"}' > /dev/null curl "${ALAN[@]}" -X POST $U/api/actas -d '{"tipo":"diagnostico-campo","serie":"RTK-014","tecnico":"Alan","conforme":true,"resultado":"FALLA","fecha":"2026-10-11","terminal":"ICAVE","maquina_id":"FL-JUN-060"}' > /dev/null curl "${ALAN[@]}" -X POST $U/api/actas -d '{"tipo":"dano","serie":"RTK-014","tecnico":"Alan","conforme":true,"resultado":"REGISTRADO","fecha":"2026-10-11","sitio":"icave","clasificacion":"Agua","accion":"Marcada como dañada","resumen":"agua adentro"}' > /dev/null curl "${ALAN[@]}" -X POST $U/api/actas -d '{"tipo":"envio","serie":"HBOX5-E02-A002-R0002","tecnico":"Alan","conforme":true,"resultado":"ENVIADA","fecha":"2026-10-12","clasificacion":"ICAVE (Veracruz)","accion":"Enviada"}' > /dev/null R=$(curl -s "$U/api/sitio/unidades?id=icave") check "unidades del sitio" '"total_unidades":3' "$R" check "coincidencia por slug" '"coincidencia":"sitio"' "$R" check "coincidencia por texto (terminal/destino)" '"coincidencia":"texto"' "$R" check "instalada con fecha" '"instalada":"2026-10-10"' "$R" check "marca de daño en la unidad" '"marca":"DANADA"' "$R" check "sitio inexistente → 404" "No existe el sitio" "$(curl -s "$U/api/sitio/unidades?id=nada")" check "/api/unidad trae sitio" '"sitio":"icave"' "$(curl -s "$U/api/unidad?serie=HBOX5-E01-A001-R0001")" check "/api/actas trae sitio" '"sitio":"icave"' "$(curl -s "$U/api/actas")" echo "## siembra (idempotente)" S1=$(python3 $REPO/backend/seed_1013.py --usuario leo 2>&1) check "primera corrida crea" "pendientes (seed_1013): 12 creados" "$S1" check "sitios: 3 nuevos (icave ya existía)" "sitios: 3 creados, 1 ya existían" "$S1" S2=$(python3 $REPO/backend/seed_1013.py --usuario leo 2>&1) check "segunda corrida no duplica" "12 ya existían" "$S2" check "inventario no duplica" "0 ítems creados" "$S2" echo '[{"id":"t-notion-1","titulo":"Importado de Notion","pista":"ingenieria","estado":"hecho"}]' > $T/extra.json check "--extra importa" "extra.json): 1 creados" "$(python3 $REPO/backend/seed_1013.py --solo nada --extra $T/extra.json 2>&1)" check "resumen tras siembra (1 + 12 sembrados + 1 extra)" '"total":14' "$(curl -s $U/api/tareas/resumen)" echo; echo "RESULTADO: $ok correctas · $fail fallidas · datos en $T" [ $fail -eq 0 ]