estat-servei.sh ja distingeix procés, port i aplicació, però es queda a la porta: sap que /salud retorna un 200, no què diu. La veloz-api respon JSON —estat de la base de dades, enviaments a la cua, versió desplegada, mètriques—, i aquí el toolkit deixa de llegir fitxers que algú va deixar al disc per integrar-se amb la resta del sistema. Aquesta lliçó cobreix curl a fons per parlar amb APIs i jq per manejar JSON de debò: extreure'l, filtrar-lo, convertir-lo en columnes i també construir-lo de forma segura, perquè al final informe-diari.sh publicarà el seu resum diari en JSON.

Contingut

  1. curl als scripts: el trio -sSf
  2. Mètodes, capçaleres i cossos
  3. Fitxers, redireccions i temps d'espera
  4. Credencials sense deixar rastre
  5. Codis HTTP: què ha de distingir un script
  6. Cos i codi d'estat en una sola crida
  7. Per què NO s'analitza JSON amb grep o sed
  8. jq: el model de filtres
  9. Seleccionar, filtrar i agregar
  10. Controlar la sortida: -r, -c, -e i @tsv
  11. Construir JSON de forma segura
  12. Llegir JSON a Bash, i JSON com a configuració
  13. Aplicació: el toolkit parla amb l'API i publica JSON

  1. curl als scripts: el trio -sSf

Per defecte curl està pensat per a una persona en un terminal: escriu una barra de progrés i, si el servidor contesta un error, imprimeix la pàgina d'error i retorna 0. En un script això és exactament el contrari del que vols. Tres opcions ho arreglen:

Opció Efecte Per què
-s Silenciós: sense barra de progrés ni missatges La barra embruta la sortida i els registres
-S Però que mostra els errors de curl Amb -s a seques, una fallada de xarxa seria muda
-f Falla (codi 22) davant de respostes HTTP 4xx i 5xx Sense això, $? val 0 encara que el servidor contesti 500
curl -sSf http://localhost:8080/salud || veloz_morir 1 "l'API no contesta correctament"

Memoritza -sSf com un bloc: silenciós, però que es queixi, i que falli de debò. És l'error més estès als scripts que parlen amb APIs, i produeix la pitjor de les fallades possibles: una de silenciosa que fa creure que tot va bé.

  1. Mètodes, capçaleres i cossos

Per a APIs REST calen quatre opcions més:

curl -sSf -X POST -H 'Content-Type: application/json' -H "Authorization: Bearer $VELOZ_TOKEN" \
     -d '{"ciutat":"Madrid","estat":"incidencia"}' http://localhost:8080/envios

-X fixa el mètode (GET, POST, PUT, DELETE); no cal amb -d, que ja implica POST. -H afegeix una capçalera i es repeteix tantes vegades com calgui. -d envia el cos —si comença per @, el llegeix d'un fitxer: -d @cos.json—. Per a paràmetres amb caràcters especials existeix --data-urlencode, que codifica el valor: curl -sSfG --data-urlencode "ciudad=Palma de Mallorca" http://localhost:8080/envios. Aquest -G converteix les dades en paràmetres de l'URL en lloc de cos, així que el resultat és /envios?ciudad=Palma%20de%20Mallorca. Escriure aquest URL a mà amb una variable de Bash és una fallada esperant a passar: un espai, un & o un accent al valor i la petició canvia de significat.

  1. Fitxers, redireccions i temps d'espera

Opció Què fa
-o fitxer Desa el cos en aquest fitxer (-o /dev/null per llençar-lo)
-O Desa amb el nom que té a l'URL
-L Segueix les redireccions 301/302 (no ho fa per defecte)
--connect-timeout N Màxim per establir la connexió
--max-time N Màxim per a tota l'operació

Els dos temps d'espera són diferents i cal posar-los tots dos. --connect-timeout 5 talla ràpid quan la màquina no hi és; --max-time 30 protegeix del servidor que accepta la connexió i després es queda pensant indefinidament. Un curl sense --max-time en un script automàtic és una bomba de rellotgeria: el dia que el servei s'encalli, la teva tasca de cron es quedarà penjada per sempre i bloquejarà les següents (07-01).

  1. Credencials sense deixar rastre

-u usuari:clau fa autenticació bàsica, però escrita així la contrasenya apareix a ps, a l'historial (02-06) i a qualsevol registre de l'ordre. Les dues formes correctes:

curl -sSf --netrc-file ~/.netrc https://api.veloz.example/envios     # credencials en fitxer 600
curl -sSf -H "Authorization: Bearer $VELOZ_TOKEN" http://localhost:8080/metricas

--netrc-file llegeix màquina, usuari i clau d'un fitxer amb permisos 600, igual que veloz-ops.conf (05-06). La segona opció fa servir una variable d'entorn carregada des d'aquest fitxer de configuració: no queda a l'historial, encara que sí que és visible a /proc/<pid>/environ per al mateix usuari i per a root. El que no s'ha de fer mai és incrustar la credencial a l'URL, com ja es va veure a 06-04.

  1. Codis HTTP: què ha de distingir un script

Família Significat Què ha de fer l'script
2xx Correcte Continuar
3xx Redirecció Seguir-la amb -L, o tractar-la com a configuració incorrecta
4xx Tu t'has equivocat (401, 403, 404, 422) Fallar i no reintentar: reintentar no ho arreglarà
5xx El servidor ha fallat (500, 502, 503) Reintentar amb retrocés (06-04); pot ser temporal
Sense resposta Xarxa, DNS, temps esgotat Distingir-ho d'un 5xx al missatge

Aquesta distinció entre 4xx i 5xx és la que fa útil un script d'integració: reintentar un 404 és perdre el temps, i no reintentar un 503 és donar per caigut un servei que s'estava reiniciant. I recorda el punt de partida: sense -f, curl retorna 0 amb qualsevol codi, així que sense ell ni tan sols te n'assabentes.

  1. Cos i codi d'estat en una sola crida

-f et diu que hi ha hagut error, però descarta el cos, que sol contenir l'explicació. Per tenir totes dues coses sense fer dues peticions, es demana el codi amb -w al final i se separa després:

resposta=$(curl -sS -w '\n%{http_code}' --max-time 10 "http://localhost:8080/envios?ciudad=Madrid")
codi="${resposta##*$'\n'}"           # ultima linia: el codi
cos="${resposta%$'\n'*}"             # tot l'anterior: el cos
case "$codi" in
    2??) veloz_log_info "consulta correcta" ;;
    4??) veloz_morir 1 "peticio incorrecta ($codi): $cos" ;;
    5??) veloz_log_error "error del servidor ($codi), reintentant"; return 1 ;;
    *)   veloz_morir 1 "sense resposta de l'API" ;;
esac

Aquí es juxtaposen tres coses de mòduls anteriors: -w '\n%{http_code}' afegeix el codi en una línia nova al final, les expansions ${var##*} i ${var%*} de 04-04 el separen del cos sense llançar processos, i el case amb globs de 04-05 classifica per família amb 2??. Fixa't que aquí no es fa servir -f: volem el cos de l'error, així que la decisió la pren el case.

  1. Per què NO s'analitza JSON amb grep o sed

La temptació és enorme i el resultat sempre acaba malament. Per treure l'estat de /salud, el que tothom escriu la primera vegada és curl -s $API/salud | grep -o '"estat":"[^"]*"' | cut -d'"' -f4. Funciona fins que deixa de fer-ho, i les raons no són rebuscades: el JSON pot venir compacte o amb salts de línia (la teva regex depèn del format, que el servidor pot canviar sense avisar); l'ordre de les claus no està garantit; pot aparèixer un objecte imbricat amb una altra clau estat i el teu grep agafarà la primera que trobi; un valor pot contenir cometes escapades ("missatge":"error \"greu\"") que destrossen [^"]*; i null, els números i els booleans no porten cometes, així que el patró ni els veu. En resum: JSON no és un format de línies, i les eines de línies no el poden entendre amb fiabilitat. El que fa falta és un analitzador, i aquest és jq.

  1. jq: el model de filtres

jq és un llenguatge de filtres: rep un document JSON, li aplica una expressió i emet JSON. La idea central és que tot filtre transforma una entrada en zero, una o diverses sortides, i s'encadenen amb | com les canonades del shell.

Suposem que /salud retorna {"estat":"ok","versio":"2.4.1","bd":{"connectada":true,"latencia_ms":12},"cua":[3,7,2]}:

Filtre Resultat Què fa
. Tot el document formatat Identitat; útil per llegir-lo
.estat "ok" Accedeix a una clau
.bd.latencia_ms 12 Camí imbricat
.cua[0] 3 Índex d'un array
.cua[1:] [7,2] Llesca
.cua[] 3, 7, 2 Itera: emet tres sortides separades
.cua | length 3 Longitud d'array, objecte o cadena
keys ["bd","cua","estat","versio"] Claus ordenades
has("estat") true Existeix aquesta clau?
.inexistent null Una clau inexistent no és un error
.estat // "desconegut" "ok" Valor per defecte si és null o false
.bd.x? (res) ? suprimeix l'error si el tipus no encaixa

Les dues últimes files són les que eviten la meitat dels ensurts: // dona un valor per defecte quan l'API omet un camp, i ? impedeix que un error de tipus avorti tot el filtre. La diferència entre .cua (un array) i .cua[] (tres sortides) és el concepte que cal interioritzar: la majoria dels filtres interessants treballen sobre fluxos de valors, no sobre un de sol.

  1. Seleccionar, filtrar i agregar

Sobre una resposta de /envios?ciudad=Madrid, que retorna un array d'objectes amb id, estat i import:

curl -sSf "$API/envios?ciudad=Madrid" | jq '.[] | select(.estat == "incidencia")'
curl -sSf "$API/envios?ciudad=Madrid" | jq '[.[] | .import] | add'
Filtre Què fa
select(cond) Deixa passar només els elements que compleixen la condició
map(f) Aplica f a cada element d'un array ([.[] | f] abreujat)
add Suma els elements d'un array (o els concatena)
length Nombre d'elements, claus o caràcters
sort_by(.camp) Ordena un array per aquest camp
min / max / min_by(.c) Extrems
group_by(.camp) Agrupa en un array d'arrays (cal ordenar pel mateix camp)
to_entries Converteix {"a":1} en [{"key":"a","value":1}] per poder iterar objectes

Els claudàtors de [.[] | .import] són importants: .[] | .import emet tres valors solts, i add necessita un array, així que cal recollir-los. Confondre «flux de valors» amb «array» és l'error número u amb jq, i s'arregla sempre amb [ ] o amb map(). Veuràs que group_by + map és la mateixa agregació que feies amb arrays associatius a awk (06-01), només que sobre JSON.

  1. Controlar la sortida: -r, -c, -e i @tsv

Per defecte jq emet JSON, així que una cadena surt entre cometes: "ok", no ok. En assignar-la a una variable de Bash, aquestes cometes hi viatgen i espatllen qualsevol comparació posterior.

Opció Efecte
-r Raw: emet les cadenes sense cometes
-c Compacte: cada resultat en una sola línia
-e El codi de sortida reflecteix el resultat: 1 si ha estat null o false
-n No llegeix entrada; construeix des de zero (secció 11)
estat=$(curl -sSf "$API/salud" | jq -r '.estat')     # "ok" -> ok
[[ "$estat" == ok ]] || veloz_log_error "API en estat $estat"
curl -sSf "$API/salud" | jq -e '.bd.connectada' >/dev/null || veloz_log_error "BD desconnectada"

-e converteix una consulta JSON en una condició de shell, sense variables intermèdies. I per tornar al terreny d'awk, @tsv i @csv converteixen arrays en columnes:

curl -sSf "$API/envios?ciudad=Madrid" | jq -r '.[] | [.id, .estat, .import] | @tsv' |
    awk -F'\t' '{ s[$2] += $3 } END { for (e in s) printf "%-12s %8.2f\n", e, s[e] }'

Aquest encadenament és el pont entre els dos mons: jq entén l'estructura i l'aplana a columnes; awk agrega. @csv posa els camps entre cometes segons les regles de CSV, i tots dos necessiten -r perquè no surti tot com una cadena JSON escapada.

  1. Construir JSON de forma segura

Generar JSON a mà —printf '{"ciutat":"%s","nota":"%s"}\n' "$ciutat" "$nota"— funciona fins al primer valor amb una cometa, una barra invertida o un salt de línia, i llavors produeix JSON invàlid o, pitjor, JSON vàlid amb el contingut canviat. La forma correcta és jq -n, que construeix des de zero, amb --arg per a cadenes i --argjson per a valors que ja són JSON (números, booleans, objectes):

jq -n --arg data "$DATA" --arg host "$(hostname)" \
      --argjson total "$total" --argjson taxa "$taxa" \
      '{data: $data, host: $host, total: $total, taxa_lliurament: $taxa}'
# -> {"data":"2026-08-03","host":"srv-veloz-01","total":1001,"taxa_lliurament":87.3}

jq s'encarrega de l'escapada: una cometa dins de $nota surt com a \", un salt de línia com a \n i els accents es codifiquen correctament. La distinció entre --arg i --argjson és el parany habitual: --arg total 1001 produeix "1001" (cadena) i --argjson total 1001 produeix 1001 (número). Si el valor pot venir buit, --argjson fallarà —jq no accepta JSON invàlid—, així que convé un ${total:-0}. I com amb awk -v a 06-01, el principi és el mateix: els valors de Bash entren com a paràmetres, mai interpolats dins del filtre, perquè interpolar-los és injecció de codi.

  1. Llegir JSON a Bash, i JSON com a configuració

Per recórrer una resposta a Bash, la combinació és jq -r '.[] | @tsv' amb el bucle de 04-01:

while IFS=$'\t' read -r id estat import; do
    [[ "$estat" == incidencia ]] && veloz_log_error "enviament $id amb incidencia ($import EUR)"
done < <(curl -sSf "$API/envios?ciudad=Madrid" | jq -r '.[] | [.id, .estat, .import] | @tsv')

@tsv és preferible a separar per espais perquè els valors en poden contenir, i a més escapa els tabuladors i els salts de línia que hi hagués dins d'un camp. La substitució de processos < <( ) de 05-05 manté el bucle al shell actual. Quan el que necessites és un array de Bash amb una sola columna, mapfile -t ids < <(jq -r '.[].id' <<< "$json") (04-03) és més directe.

JSON també serveix com a format de configuració, i jq el llegeix amb les mateixes regles: jq -r '.llindars.disc // 85' etc/veloz.json retorna el valor o el defecte si falta. Davant del clau=valor de veloz-ops.conf (05-06), guanya en estructura imbricada i tipus, i perd en què no es pot carregar amb source i no admet comentaris. Per a YAML —el format dels fitxers de CI i de Kubernetes— existeix yq, que replica la sintaxi de jq sobre YAML i fins i tot converteix entre tots dos amb yq -o=json. Si ja saps jq, saps yq.

  1. Aplicació: el toolkit parla amb l'API i publica JSON

Primer, estat-servei.sh consulta l'API de debò i distingeix caiguda d'error d'aplicació, que és la diferència entre reiniciar el servei i avisar l'equip de desenvolupament:

comprovar_api_json() {
    local api="http://localhost:8080" cos codi sortida
    sortida=$(curl -sS -w '\n%{http_code}' --connect-timeout 3 --max-time 10 "$api/salud") || {
        veloz_log_error "veloz-api: sense resposta (xarxa, DNS o servei caigut)"; return 1; }
    codi="${sortida##*$'\n'}"; cos="${sortida%$'\n'*}"
    [[ "$codi" == 2?? ]] || {
        veloz_log_error "veloz-api: HTTP $codi — $(jq -r '.missatge // "sense detall"' <<< "$cos")"
        return 1; }
    jq -e '.estat == "ok"' <<< "$cos" >/dev/null ||
        veloz_log_error "veloz-api: viva pero degradada ($(jq -r '.estat' <<< "$cos"))"
    veloz_log_info "veloz-api $(jq -r '.versio' <<< "$cos") — BD $(jq -r 'if .bd.connectada then "ok" else "KO" end' <<< "$cos")"
}

Els tres desenllaços són diferents a propòsit: sense resposta (el curl falla), resposta amb error HTTP (s'extreu .missatge del cos per a l'avís) i resposta correcta però amb estat degradat. jq -e fa servir el JSON com a condició i l'if ... then ... else ... end de jq formata el booleà per al missatge. Fixa't en el <<< "$cos", la cadena-document de 05-05: evita rellegir la resposta de la xarxa cada vegada.

I informe-diari.sh tanca el cercle publicant el seu resum com a JSON, construït amb jq -n:

publicar_resum() {
    local desti="$BASE_DIR/logs/resum-$(date +%F).json" tmp
    tmp=$(mktemp) && trap 'rm -f "$tmp"' RETURN
    jq -n --arg data "$(date +%F)" --arg host "$(hostname -f)" \
          --argjson total "${total:-0}" --argjson taxa "${taxa:-0}" \
          '{generat: (now | todate), data: $data, host: $host,
            resum: {enviaments: $total, taxa_lliurament: $taxa}}' > "$tmp" && mv "$tmp" "$desti"
    veloz_log_info "resum publicat a $desti"
}

El mktemp amb trap de 05-01 i el mv final garanteixen que ningú no llegeixi mai un fitxer a mig escriure: o existeix el resum complet o existeix el del dia anterior. now | todate genera la marca de temps en UTC ISO-8601 sense cridar date. I ara l'informe ja no és només un text per llegir: és una dada que un altre programa —un panell, una alerta, el projecte 09-05— pot consumir sense tornar a analitzar res.

Errors Habituals i Consells

  • curl sense -f (o sense comprovar el codi). Retorna 0 amb un 500 i l'script continua com si res. Fes servir -sSf, o -w '%{http_code}' i un case.
  • curl sense --max-time. Un servei encallat penja la tasca de cron indefinidament.
  • Analitzar JSON amb grep/sed. Falla amb el format compacte, l'ordre de claus, els imbricats i les cometes escapades. Fes servir jq.
  • Oblidar -r. estat=$(jq '.estat') desa "ok" amb cometes i [[ $estat == ok ]] falla.
  • Confondre flux amb array. .[] | .import emet valors solts; add necessita [.[] | .import].
  • --arg per a números. Produeix "1001" en comptes de 1001; per a números i booleans, --argjson.
  • Interpolar variables de Bash dins del filtre jq. Mateix risc d'injecció que a awk: fes servir --arg.
  • Reintentar un 4xx. No s'arreglarà sol. Reintenta els 5xx i les fallades de xarxa, amb retrocés (06-04).
  • Consell: construeix els filtres per passos (jq '.', després jq '.camp'…) contra una resposta d'exemple desada en un fitxer: és més ràpid que cridar l'API, funciona sense connexió i jq es depura molt millor en increments que d'una tirada.

Exercicis

Exercici 1. Escriu una funció veloz_api_get per a lib/comu.sh que faci GET a un camí de l'API, retorni el cos per la sortida estàndard, distingeixi xarxa / 4xx / 5xx amb missatges diferents i codis de sortida diferents, i no es pugui penjar.

Exercici 2. Amb /metricas retornant {"peticions":48210,"errors":37,"latencia_p95_ms":210,"enviaments_cua":14}, escriu una comprovació que avisi si la taxa d'errors supera el 0,1 % o si la latència p95 passa de 500 ms, tractant els camps que faltin com a 0.

Exercici 3. Converteix la sortida de /envios?ciudad=Madrid (array d'objectes amb id, repartidor, estat, import) en un resum per repartidor amb nombre d'enviaments i import total, ordenat de major a menor, fent servir només jq.

Solucions

Solució 1.

# veloz_api_get — GET a l'API. Us: veloz_api_get <cami>. Retorna el cos per stdout.
veloz_api_get() {
    local cami="${1:?falta el cami}" base="${VELOZ_API:-http://localhost:8080}" sortida codi
    sortida=$(curl -sS -w '\n%{http_code}' --connect-timeout 3 --max-time 15 "$base$cami") ||
        { veloz_log_error "API: sense resposta a $cami"; return 69; }
    codi="${sortida##*$'\n'}"
    case "$codi" in
        2??) printf '%s\n' "${sortida%$'\n'*}" ;;
        4??) veloz_log_error "API: peticio incorrecta ($codi) a $cami"; return 64 ;;
        *)   veloz_log_error "API: error del servidor ($codi) a $cami"; return 75 ;;
    esac
}

Els tres codis són deliberats i segueixen la taula de 05-03: 69 (EX_UNAVAILABLE) quan no hi ha resposta, 64 (EX_USAGE) per a un 4xx —l'error és nostre— i 75 (EX_TEMPFAIL) per a un 5xx, que és precisament el codi que diu a qui la crida «això és temporal, pots reintentar». El cos surt net per stdout i els diagnòstics per stderr (02-04), de manera que dades=$(veloz_api_get /salud) captura només el que interessa.

Solució 2.

metriques=$(veloz_api_get /metricas) || exit $?
read -r peticions errors p95 < <(jq -r '[.peticions // 0, .errors // 0,
    .latencia_p95_ms // 0] | @tsv' <<< "$metriques")
awk -v e="$errors" -v p="$peticions" 'BEGIN { exit !(p > 0 && 100*e/p > 0.1) }' &&
    veloz_log_error "taxa d'errors per sobre del 0.1% ($errors de $peticions)"
(( p95 > 500 )) && veloz_log_error "latencia p95 de ${p95}ms per sobre del llindar"

El // 0 de cada camp evita que una mètrica absent es converteixi en null i trenqui els comptes. Els tres valors s'extreuen en una sola invocació de jq amb @tsv i un read, en comptes de cridar-lo tres vegades. La taxa d'errors és decimal, així que la comparació va a awk amb l'idioma exit !(...) de 06-03; la latència és entera i en té prou amb (( )).

Solució 3.

veloz_api_get "/envios?ciudad=Madrid" |
    jq -r 'group_by(.repartidor)
           | map({repartidor: .[0].repartidor, enviaments: length, total: (map(.import) | add)})
           | sort_by(-.total)
           | .[] | [.repartidor, .enviaments, (.total | tostring)] | @tsv' |
    column -t

group_by produeix un array d'arrays —un per repartidor—, i map el converteix en objectes: .[0].repartidor pren el nom de qualsevol element del grup, length compta i map(.import) | add suma. sort_by(-.total) ordena descendent negant el valor, més net que ordenar i després reverse. Al final, @tsv aplana a columnes i column -t (02-02) les alinea. És exactament l'agregació de la solució 1 de 06-01, però partint de JSON en comptes de CSV.

Conclusió

Parlar amb una API des d'un script són dues eines i unes quantes regles. De curl: -sSf com un bloc indivisible —silenciós, queixós davant de fallades de xarxa i fallant de debò davant d'un error HTTP, perquè sense -f retorna 0 amb un 500—; -X, -H, -d i --data-urlencode per construir la petició; -L, -o/-O per al cos; --connect-timeout i --max-time sempre, o el dia que el servei s'encalli penjaràs la tasca de cron; credencials per --netrc o capçalera des d'un fitxer 600, mai a l'URL; i -w '\n%{http_code}' per quedar-te amb cos i codi en una sola crida i classificar amb un case que distingeixi 4xx (no reintentar) de 5xx (reintentar amb retrocés). De jq: un llenguatge de filtres encadenats amb | on .camp, .a.b, .[], select, map, group_by, add, sort_by, length, to_entries, // i ? cobreixen gairebé tot; -r per treure cadenes sense cometes, -e per fer servir JSON com a condició, @tsv/@csv per tornar al terreny d'awk; i jq -n --arg/--argjson per construir JSON amb l'escapada resolta, passant els valors de Bash com a paràmetres i mai interpolats. I la regla que ho engloba tot: JSON no és un format de línies, així que grep i sed no serveixen per llegir-lo.

Amb això es tanca el Mòdul 6 i el toolkit de Veloz Envíos canvia de naturalesa. informe-diari.sh agrega en una sola passada amb awk i publica el seu resum en JSON; estat-servei.sh reuneix el context del sistema, comprova xarxa i port, interroga la veloz-api i distingeix una caiguda d'un error d'aplicació. Ja no és un lector de fitxers: és una peça integrada amb la resta del sistema. Però fixa't en el que tenen en comú totes les execucions d'aquest mòdul: has estat tu qui ha escrit l'ordre. L'informe diari només existeix si algú es recorda de llançar-lo, i una comprovació d'estat que s'executa quan ja sospites que alguna cosa va malament arriba tard per definició. Al Mòdul 7 això s'acaba: cron perquè les tasques s'executin soles (07-01), el disseny de tasques veritablement desateses (07-02), còpies de seguretat automàtiques (07-03), monitoratge i registre continus (07-04), serveis i temporitzadors de systemd (07-05) i automatització remota amb ssh (07-06). El toolkit deixa d'esperar les teves ordres.

Curs de Programació en Bash

Mòdul 1: Introducció a Bash

Mòdul 2: Ordres Bàsiques de Bash

Mòdul 3: Fonaments de Scripting

Mòdul 4: Scripting Intermedi

Mòdul 5: Tècniques Avançades de Scripting

Mòdul 6: Treballar amb Eines Externes

Mòdul 7: Automatització i Programació

Mòdul 8: Bones Pràctiques i Optimització

Mòdul 9: Projectes del Món Real

© Copyright 2026. Tots els drets reservats