🟒 – AutomatitzaciΓ³

SMX Β· Repte Capa LΓ²gica

VerificaciΓ³ AutomΓ tica del Protocol de la Capa LΓ²gica

Script que automatitza les comprovacions OSI capes 3–7, implementat en Bash i Python.

Autor
G. Martin
Web
gmartin.inscastellbisbal.net
Llenguatges
Bash Β· Python 3
Sistema
Linux Ubuntu/Debian
01 β€” DescripciΓ³

L’objectiu Γ©s automatitzar les comprovacions del protocol de verificaciΓ³ de la capa lΓ²gica del model OSI. El script comprova connectivitat, ports TCP, resoluciΓ³ DNS, HTTP/HTTPS i certificats SSL/TLS.

Comprovacions: Ping ICMP Β· Ports TCP Β· DNS A i PTR Β· HTTP/HTTPS Β· SSL/TLS Β· AutocomprovaciΓ³
Bloc 1 Β· Capa 3
Connectivitat
Ping, latència
Bloc 2 Β· Capa 4
Ports TCP
Estat, banner
Bloc 3 Β· Capa 7
DNS
Registres A i PTR
Bloc 4 Β· Capa 7
HTTP/HTTPS
Codis, capΓ§aleres
Bloc 5 Β· Capa 6
SSL/TLS
VersiΓ³, certificat
Bloc 6 Β· Meta
Autocheck
Eines, permisos
02 β€” Bash vs Python
Criteri Bash Python
Disponibilitat Linuxβœ” Natiuβœ” Natiu
Portabilitat Windows⚠ Cal WSLβœ” Directe
GestiΓ³ d’errors⚠ BΓ sicaβœ” try/except
Llegibilitat⚠ Limitadaβœ” ExcelΒ·lent
SSL/TLS nadiu⚠ Via opensslβœ” MΓ²dul ssl
03 β€” Script Python (recomanat)
verificacio_logica.py
#!/usr/bin/env python3
"""
=============================================================================
SCRIPT: verificacio_logica.py
DESCRIPCIΓ“: Automatitza les comprovacions del protocol de verificaciΓ³
            de la capa lΓ²gica (OSI Capa 4-7 / TCP-UDP / DNS / HTTP / SSL)
NIVELL:     SMX - Sistemes MicroinformΓ tics i Xarxes
VERSIΓ“:     2.0
ÚS:         python3 verificacio_logica.py [-H host] [-p 80,443,22] [-t timeout] [-o informe.html]
=============================================================================
"""

import socket
import ssl
import subprocess
import argparse
import sys
import os
import json
import time
from datetime import datetime
from urllib.request import urlopen, Request
from urllib.error import URLError, HTTPError

# ─── COLORS ANSI PER TERMINAL ──────────────────────────────────────────────────
class Color:
    RED    = "\033[0;31m";  GREEN  = "\033[0;32m"
    YELLOW = "\033[1;33m";  BLUE   = "\033[0;34m"
    CYAN   = "\033[0;36m";  BOLD   = "\033[1m";  NC = "\033[0m"

# ─── VALORS PER DEFECTE ────────────────────────────────────────────────────────
HOST_DEFECTE    = "8.8.8.8"
PORTS_DEFECTE   = "80,443,22,53"
TIMEOUT_DEFECTE = 5
SORTIDA_HTML    = "/home/claude/informe_capa_logica.html"

# ─── CLASSE PRINCIPAL ──────────────────────────────────────────────────────────
class VerificacioCapa:
    """Gestiona totes les comprovacions del protocol de la capa lΓ²gica."""

    def __init__(self, host: str, ports: list[int], timeout: int, sortida: str):
        self.host       = host
        self.ports      = ports
        self.timeout    = timeout
        self.sortida    = sortida
        self.timestamp  = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

        # Comptadors
        self.total       = 0
        self.passats     = 0
        self.fallats     = 0
        self.advertencies = 0

        # Llista de resultats en ordre d'execuciΓ³
        self.resultats: list[dict] = []

    # ─── MÈTODE: Imprimir i desar resultat ─────────────────────────────────────
    def _resultat(self, nom: str, estat: str, detall: str, bloc: str) -> None:
        """Registra un resultat i el mostra per terminal."""
        self.total += 1
        if estat == "PASS":
            icona = f"{Color.GREEN}βœ” PASS{Color.NC}"
            self.passats += 1
        elif estat == "WARN":
            icona = f"{Color.YELLOW}⚠ WARN{Color.NC}"
            self.advertencies += 1
        else:
            icona = f"{Color.RED}✘ FAIL{Color.NC}"
            self.fallats += 1

        print(f"  {icona}  {Color.BOLD}{nom}{Color.NC} β€” {detall}")
        self.resultats.append({"nom": nom, "estat": estat, "detall": detall, "bloc": bloc})

    # ═══════════════════════════════════════════════════════════════════════════
    # BLOC 1: CONNECTIVITAT BΓ€SICA (ping via subprocess)
    # ═══════════════════════════════════════════════════════════════════════════
    def comprovar_connectivitat(self) -> None:
        print(f"\n{Color.BLUE}{Color.BOLD}━━━ BLOC 1: CONNECTIVITAT BΓ€SICA ━━━{Color.NC}")
        bloc = "Bloc 1: Connectivitat BΓ sica"

        # 1.1 Ping
        try:
            resultat = subprocess.run(
                ["ping", "-c", "3", "-W", str(self.timeout), self.host],
                capture_output=True, text=True, timeout=self.timeout + 5
            )
            if resultat.returncode == 0:
                # Extreure latència de la línia de resum
                latencia = "N/A"
                for linia in resultat.stdout.splitlines():
                    if "avg" in linia or "rtt" in linia:
                        parts = linia.split("/")
                        if len(parts) >= 5:
                            latencia = f"{parts[4]}ms"
                self._resultat(f"Ping → {self.host}", "PASS", f"Latència mitja: {latencia}", bloc)
            else:
                self._resultat(f"Ping β†’ {self.host}", "FAIL", "Host inaccessible o ICMP bloquejat", bloc)
        except (subprocess.TimeoutExpired, FileNotFoundError) as e:
            self._resultat(f"Ping β†’ {self.host}", "FAIL", f"Error: {e}", bloc)

        # 1.2 ResoluciΓ³ DNS del host (si Γ©s nom de domini)
        try:
            ip = socket.gethostbyname(self.host)
            if ip != self.host:
                self._resultat(f"ResoluciΓ³ β†’ {self.host}", "PASS", f"Resolt a {ip}", bloc)
            else:
                self._resultat(f"Host Γ©s IP directa", "PASS", f"AdreΓ§a: {ip}", bloc)
        except socket.gaierror as e:
            self._resultat(f"ResoluciΓ³ β†’ {self.host}", "FAIL", f"Error DNS: {e}", bloc)

    # ═══════════════════════════════════════════════════════════════════════════
    # BLOC 2: COMPROVACIΓ“ DE PORTS TCP
    # ═══════════════════════════════════════════════════════════════════════════
    def comprovar_ports(self) -> None:
        print(f"\n{Color.BLUE}{Color.BOLD}━━━ BLOC 2: PORTS TCP ━━━{Color.NC}")
        bloc = "Bloc 2: Ports TCP"

        # Diccionari de serveis coneguts
        serveis = {
            21: "FTP", 22: "SSH", 23: "Telnet", 25: "SMTP",
            53: "DNS", 80: "HTTP", 110: "POP3", 143: "IMAP",
            443: "HTTPS", 3306: "MySQL", 5432: "PostgreSQL", 8080: "HTTP-Alt"
        }

        for port in self.ports:
            servei = serveis.get(port, "Desconegut")
            try:
                with socket.create_connection((self.host, port), timeout=self.timeout) as s:
                    # Intentar llegir banner del servei
                    s.settimeout(1)
                    try:
                        banner = s.recv(64).decode("utf-8", errors="ignore").strip()[:50]
                        info = f"Obert Β· Banner: {banner}" if banner else "Obert i accessible"
                    except Exception:
                        info = "Obert i accessible"
                self._resultat(f"Port TCP {port} ({servei})", "PASS", info, bloc)
            except (ConnectionRefusedError, OSError):
                self._resultat(f"Port TCP {port} ({servei})", "FAIL", "Tancat o filtrat", bloc)
            except socket.timeout:
                self._resultat(f"Port TCP {port} ({servei})", "WARN", f"Timeout ({self.timeout}s) β€” possible filtre", bloc)

    # ═══════════════════════════════════════════════════════════════════════════
    # BLOC 3: RESOLUCIΓ“ DNS
    # ═══════════════════════════════════════════════════════════════════════════
    def comprovar_dns(self) -> None:
        print(f"\n{Color.BLUE}{Color.BOLD}━━━ BLOC 3: RESOLUCIΓ“ DNS ━━━{Color.NC}")
        bloc = "Bloc 3: ResoluciΓ³ DNS"

        dominis_prova = ["google.com", "github.com", "cloudflare.com"]

        # 3.1 ResoluciΓ³ directa (socket.getaddrinfo)
        for domini in dominis_prova:
            try:
                infos = socket.getaddrinfo(domini, None, socket.AF_INET)
                if infos:
                    ip = infos[0][4][0]
                    self._resultat(f"DNS A β†’ {domini}", "PASS", f"Resolt a {ip}", bloc)
                else:
                    self._resultat(f"DNS A β†’ {domini}", "FAIL", "Cap resultat DNS", bloc)
            except socket.gaierror as e:
                self._resultat(f"DNS A β†’ {domini}", "FAIL", f"Error: {e}", bloc)

        # 3.2 ResoluciΓ³ inversa PTR
        try:
            nom_invers, _, _ = socket.gethostbyaddr(self.host)
            self._resultat(f"DNS PTR β†’ {self.host}", "PASS", f"Resolt a {nom_invers}", bloc)
        except socket.herror:
            self._resultat(f"DNS PTR β†’ {self.host}", "WARN", "Sense registre PTR (pot ser normal)", bloc)
        except socket.gaierror as e:
            self._resultat(f"DNS PTR β†’ {self.host}", "FAIL", f"Error: {e}", bloc)

    # ═══════════════════════════════════════════════════════════════════════════
    # BLOC 4: HTTP / HTTPS
    # ═══════════════════════════════════════════════════════════════════════════
    def comprovar_http(self) -> None:
        print(f"\n{Color.BLUE}{Color.BOLD}━━━ BLOC 4: HTTP / HTTPS ━━━{Color.NC}")
        bloc = "Bloc 4: HTTP / HTTPS"

        # 4.1 HTTP
        try:
            req = Request(f"http://{self.host}", headers={"User-Agent": "SMX-VerificadorCapa/2.0"})
            with urlopen(req, timeout=self.timeout) as resp:
                codi = resp.getcode()
                self._resultat(f"HTTP β†’ {self.host}", "PASS", f"Codi: {codi}", bloc)
        except HTTPError as e:
            estat = "WARN" if e.code in (301, 302, 403) else "FAIL"
            self._resultat(f"HTTP β†’ {self.host}", estat, f"Codi HTTP: {e.code}", bloc)
        except URLError as e:
            self._resultat(f"HTTP β†’ {self.host}", "FAIL", f"Error: {e.reason}", bloc)

        # 4.2 HTTPS (sense verificaciΓ³ cert per mΓ xima compatibilitat)
        try:
            ctx = ssl.create_default_context()
            ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE
            req = Request(f"https://{self.host}", headers={"User-Agent": "SMX-VerificadorCapa/2.0"})
            with urlopen(req, timeout=self.timeout, context=ctx) as resp:
                codi = resp.getcode()
                self._resultat(f"HTTPS β†’ {self.host}", "PASS", f"Codi: {codi}", bloc)
        except HTTPError as e:
            self._resultat(f"HTTPS β†’ {self.host}", "WARN", f"Codi: {e.code}", bloc)
        except Exception as e:
            self._resultat(f"HTTPS β†’ {self.host}", "FAIL", f"Error HTTPS: {str(e)[:80]}", bloc)

    # ═══════════════════════════════════════════════════════════════════════════
    # BLOC 5: SSL / TLS
    # ═══════════════════════════════════════════════════════════════════════════
    def comprovar_ssl(self) -> None:
        print(f"\n{Color.BLUE}{Color.BOLD}━━━ BLOC 5: SSL / TLS ━━━{Color.NC}")
        bloc = "Bloc 5: SSL / TLS"

        try:
            ctx = ssl.create_default_context()
            ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE

            with socket.create_connection((self.host, 443), timeout=self.timeout) as raw_sock:
                with ctx.wrap_socket(raw_sock, server_hostname=self.host) as s_ssl:
                    # VersiΓ³ TLS
                    versio = s_ssl.version() or "Desconeguda"
                    cipher, proto, _ = s_ssl.cipher()
                    self._resultat(f"SSL ConnexiΓ³ β†’ {self.host}:443", "PASS",
                                   f"VersiΓ³: {versio} Β· Cipher: {cipher}", bloc)

                    # Certificat
                    cert = s_ssl.getpeercert()
                    if cert:
                        # Data d'expiraciΓ³
                        not_after = cert.get("notAfter", "Desconegut")
                        try:
                            exp_dt = datetime.strptime(not_after, "%b %d %H:%M:%S %Y %Z")
                            dies_restants = (exp_dt - datetime.utcnow()).days
                            if dies_restants > 30:
                                self._resultat("Certificat SSL", "PASS",
                                               f"Caduca: {not_after} ({dies_restants} dies)", bloc)
                            elif dies_restants > 0:
                                self._resultat("Certificat SSL", "WARN",
                                               f"Caduca AVIAT: {not_after} ({dies_restants} dies!)", bloc)
                            else:
                                self._resultat("Certificat SSL", "FAIL", f"CADUCAT: {not_after}", bloc)
                        except ValueError:
                            self._resultat("Certificat SSL", "WARN", f"Caduca: {not_after}", bloc)

                        # Subject / Common Name
                        subj = dict(x[0] for x in cert.get("subject", []))
                        cn = subj.get("commonName", "N/A")
                        self._resultat("CN Certificat", "PASS", f"CommonName: {cn}", bloc)

        except ConnectionRefusedError:
            self._resultat(f"SSL β†’ {self.host}:443", "FAIL", "Port 443 tancat", bloc)
        except socket.timeout:
            self._resultat(f"SSL β†’ {self.host}:443", "FAIL", "Timeout en connexiΓ³ SSL", bloc)
        except Exception as e:
            self._resultat(f"SSL β†’ {self.host}:443", "FAIL", f"Error SSL: {str(e)[:80]}", bloc)

    # ═══════════════════════════════════════════════════════════════════════════
    # BLOC 6: AUTOCOMPROVACIΓ“
    # ═══════════════════════════════════════════════════════════════════════════
    def autocomprovacio(self) -> None:
        print(f"\n{Color.BLUE}{Color.BOLD}━━━ BLOC 6: AUTOCOMPROVACIΓ“ ━━━{Color.NC}")
        bloc = "Bloc 6: AutocomprovaciΓ³"

        # Test 1: MΓ²duls Python necessaris
        moduls = ["socket", "ssl", "subprocess", "urllib.request"]
        for modul in moduls:
            try:
                __import__(modul)
                self._resultat(f"MΓ²dul Python: {modul}", "PASS", "Disponible", bloc)
            except ImportError:
                self._resultat(f"MΓ²dul Python: {modul}", "FAIL", "No disponible!", bloc)

        # Test 2: Permisos d'escriptura
        dir_sortida = os.path.dirname(self.sortida) or "."
        if os.access(dir_sortida, os.W_OK):
            self._resultat(f"Permisos escriptura β†’ {dir_sortida}", "PASS", "Directori accessible", bloc)
        else:
            self._resultat(f"Permisos escriptura β†’ {dir_sortida}", "FAIL", "Sense permisos d'escriptura", bloc)

        # Test 3: ValidaciΓ³ de parΓ metres
        if self.timeout > 0 and self.timeout <= 60:
            self._resultat("ParΓ metre timeout", "PASS", f"Valor vΓ lid: {self.timeout}s", bloc)
        else:
            self._resultat("ParΓ metre timeout", "WARN", f"Valor inusual: {self.timeout}s", bloc)

        # Test 4: Connectivitat bΓ sica de Python (DNS del sistema)
        try:
            socket.setdefaulttimeout(self.timeout)
            socket.gethostbyname("google.com")
            self._resultat("DNS Sistema (Python)", "PASS", "ResoluciΓ³ DNS bΓ sica funciona", bloc)
        except socket.gaierror:
            self._resultat("DNS Sistema (Python)", "FAIL", "Sense resoluciΓ³ DNS!", bloc)

    # ═══════════════════════════════════════════════════════════════════════════
    # GENERACIΓ“ D'INFORME HTML
    # ═══════════════════════════════════════════════════════════════════════════
    def generar_html(self) -> None:
        """Genera l'informe HTML amb tots els resultats."""
        pct = (self.passats * 100 // self.total) if self.total > 0 else 0
        color_pct = "var(--ok)" if pct >= 80 else ("var(--warn)" if pct >= 50 else "var(--fail)")

        # Agrupar resultats per bloc
        blocs: dict[str, list[dict]] = {}
        for r in self.resultats:
            blocs.setdefault(r["bloc"], []).append(r)

        # Generar fileres HTML
        blocs_html = ""
        for nom_bloc, items in blocs.items():
            fileres = ""
            for item in items:
                classe = "ok" if item["estat"] == "PASS" else item["estat"].lower()
                fileres += f"""
      <div class="fila">
        <span class="pill {classe}">{item['estat']}</span>
        <span class="nom">{item['nom']}</span>
        <span class="detall">{item['detall']}</span>
      </div>"""
            blocs_html += f"""
  <div class="bloc">
    <div class="bloc-header">{nom_bloc}</div>{fileres}
  </div>"""

        html = f"""<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Informe VerificaciΓ³ Capa LΓ²gica β€” {self.host}</title>
<style>
  :root {{
    --bg:#0f1117; --surface:#1a1d2e; --surface2:#252840;
    --ok:#22d3a0; --fail:#ff4d6d; --warn:#fbbf24;
    --text:#e2e8f0; --muted:#64748b; --accent:#6366f1;
    --border:rgba(99,102,241,0.2);
    --font:'JetBrains Mono','Fira Code','Courier New',monospace;
  }}
  * {{ margin:0; padding:0; box-sizing:border-box; }}
  body {{ background:var(--bg); color:var(--text); font-family:var(--font); font-size:13px; line-height:1.6; }}
  .container {{ max-width:960px; margin:0 auto; padding:2rem; }}
  .header {{ text-align:center; padding:3rem 0 2rem; border-bottom:1px solid var(--border); }}
  .badge {{ display:inline-block; background:var(--accent); color:#fff; font-size:10px; padding:3px 10px; border-radius:20px; letter-spacing:2px; text-transform:uppercase; margin-bottom:1rem; }}
  h1 {{ font-size:2rem; font-weight:700; color:#fff; }}
  h1 span {{ color:var(--accent); }}
  .meta {{ color:var(--muted); font-size:11px; margin-top:.5rem; }}
  .resum {{ display:grid; grid-template-columns:repeat(4,1fr); gap:1rem; margin:2rem 0; }}
  .resum-card {{ background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:1.2rem; text-align:center; }}
  .resum-card .num {{ font-size:2.2rem; font-weight:700; }}
  .resum-card .etiq {{ font-size:10px; text-transform:uppercase; color:var(--muted); letter-spacing:1px; margin-top:4px; }}
  .resum-card.ok .num {{ color:var(--ok); }} .resum-card.fail .num {{ color:var(--fail); }}
  .resum-card.warn .num {{ color:var(--warn); }} .resum-card.total .num {{ color:var(--accent); }}
  .progres-wrap {{ background:var(--surface); border-radius:8px; padding:1.5rem; margin-bottom:2rem; border:1px solid var(--border); }}
  .progres-label {{ display:flex; justify-content:space-between; margin-bottom:8px; }}
  .pct {{ font-size:1.4rem; font-weight:700; color:{color_pct}; }}
  .barra {{ height:8px; background:var(--surface2); border-radius:4px; overflow:hidden; }}
  .barra-fill {{ height:100%; width:{pct}%; background:linear-gradient(90deg,var(--accent),var(--ok)); border-radius:4px; }}
  .info-host {{ background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:1.2rem 1.5rem; margin-bottom:2rem; display:flex; gap:2rem; flex-wrap:wrap; }}
  .info-item {{ display:flex; flex-direction:column; }}
  .info-item .key {{ font-size:10px; color:var(--muted); text-transform:uppercase; letter-spacing:1px; }}
  .info-item .val {{ color:var(--accent); font-weight:600; }}
  .bloc {{ background:var(--surface); border:1px solid var(--border); border-radius:8px; margin-bottom:1.5rem; overflow:hidden; }}
  .bloc-header {{ background:var(--surface2); padding:.8rem 1.2rem; font-weight:700; font-size:11px; letter-spacing:1px; text-transform:uppercase; color:var(--accent); border-bottom:1px solid var(--border); }}
  .fila {{ display:flex; align-items:center; padding:.7rem 1.2rem; border-bottom:1px solid rgba(99,102,241,.08); gap:.8rem; }}
  .fila:last-child {{ border-bottom:none; }}
  .fila:hover {{ background:rgba(99,102,241,.05); }}
  .pill {{ font-size:10px; font-weight:700; padding:2px 8px; border-radius:12px; min-width:44px; text-align:center; flex-shrink:0; }}
  .pill.ok {{ background:rgba(34,211,160,.15); color:var(--ok); }}
  .pill.fail {{ background:rgba(255,77,109,.15); color:var(--fail); }}
  .pill.warn {{ background:rgba(251,191,36,.15); color:var(--warn); }}
  .nom {{ font-weight:600; color:#fff; min-width:220px; }}
  .detall {{ color:var(--muted); font-size:12px; }}
  footer {{ text-align:center; color:var(--muted); font-size:11px; padding:2rem 0; border-top:1px solid var(--border); margin-top:2rem; }}
  footer span {{ color:var(--accent); }}
</style>
</head>
<body>
<div class="container">
  <div class="header">
    <div class="badge">SMX Β· Capa LΓ²gica Β· Python</div>
    <h1>Informe de <span>VerificaciΓ³</span><br>Protocol Capa LΓ²gica</h1>
    <div class="meta">Generat: {self.timestamp} Β· Host: {self.host} Β· Timeout: {self.timeout}s</div>
  </div>
  <div class="resum" style="margin-top:2rem;">
    <div class="resum-card total"><div class="num">{self.total}</div><div class="etiq">Total proves</div></div>
    <div class="resum-card ok"><div class="num">{self.passats}</div><div class="etiq">Correctes</div></div>
    <div class="resum-card fail"><div class="num">{self.fallats}</div><div class="etiq">Fallades</div></div>
    <div class="resum-card warn"><div class="num">{self.advertencies}</div><div class="etiq">Advertències</div></div>
  </div>
  <div class="progres-wrap">
    <div class="progres-label"><span>Resultat global</span><span class="pct">{pct}% correcte</span></div>
    <div class="barra"><div class="barra-fill"></div></div>
  </div>
  <div class="info-host">
    <div class="info-item"><span class="key">Host Objectiu</span><span class="val">{self.host}</span></div>
    <div class="info-item"><span class="key">Ports Analitzats</span><span class="val">{','.join(map(str,self.ports))}</span></div>
    <div class="info-item"><span class="key">Timeout</span><span class="val">{self.timeout}s</span></div>
    <div class="info-item"><span class="key">Plataforma</span><span class="val">Python {sys.version.split()[0]}</span></div>
  </div>
  {blocs_html}
  <footer>
    Generat per <span>verificacio_logica.py v2.0</span> Β· SMX Capa LΓ²gica Β· {self.timestamp}
  </footer>
</div>
</body>
</html>"""

        with open(self.sortida, "w", encoding="utf-8") as f:
            f.write(html)
        print(f"\n{Color.GREEN}{Color.BOLD}βœ” Informe HTML generat:{Color.NC} {self.sortida}")

    # ═══════════════════════════════════════════════════════════════════════════
    # EXECUCIΓ“ COMPLETA
    # ═══════════════════════════════════════════════════════════════════════════
    def executar(self) -> None:
        """Punt d'entrada: executa tots els blocs de comprovaciΓ³."""
        print(f"{Color.BOLD}{Color.CYAN}")
        print("╔══════════════════════════════════════════════════════════╗")
        print("β•‘   VERIFICACIΓ“ PROTOCOL CAPA LΓ’GICA  β€” SMX v2.0 (Python) β•‘")
        print(f"β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•{Color.NC}")
        print(f"  Host: {Color.CYAN}{self.host}{Color.NC} | Timeout: {Color.CYAN}{self.timeout}s{Color.NC}")
        print(f"  Inici: {Color.CYAN}{self.timestamp}{Color.NC}\n")

        # Ordre d'execuciΓ³ dels blocs
        self.autocomprovacio()
        self.comprovar_connectivitat()
        self.comprovar_ports()
        self.comprovar_dns()
        self.comprovar_http()
        self.comprovar_ssl()

        # Resum final
        pct = (self.passats * 100 // self.total) if self.total > 0 else 0
        print(f"\n{Color.BOLD}{Color.CYAN}━━━ RESUM FINAL ━━━{Color.NC}")
        print(f"  Total: {self.total} | {Color.GREEN}Passats: {self.passats}{Color.NC} "
              f"| {Color.RED}Fallats: {self.fallats}{Color.NC} "
              f"| {Color.YELLOW}Advertències: {self.advertencies}{Color.NC}")
        print(f"  PuntuaciΓ³ global: {Color.BOLD}{pct}%{Color.NC}")

        # Generar informe
        self.generar_html()
        print(f"{Color.BOLD}{Color.CYAN}══════════════════════════════════════════════════════════{Color.NC}\n")


# ─── PUNT D'ENTRADA ────────────────────────────────────────────────────────────
def main():
    parser = argparse.ArgumentParser(
        description="VerificaciΓ³ automΓ tica del protocol de la capa lΓ²gica β€” SMX",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="Exemples:\n  python3 verificacio_logica.py\n  python3 verificacio_logica.py -H example.com -p 80,443 -t 3"
    )
    parser.add_argument("-H", "--host",    default=HOST_DEFECTE,    help=f"Host objectiu (defecte: {HOST_DEFECTE})")
    parser.add_argument("-p", "--ports",   default=PORTS_DEFECTE,   help=f"Ports TCP (defecte: {PORTS_DEFECTE})")
    parser.add_argument("-t", "--timeout", default=TIMEOUT_DEFECTE, type=int, help=f"Timeout en segons (defecte: {TIMEOUT_DEFECTE})")
    parser.add_argument("-o", "--output",  default=SORTIDA_HTML,    help=f"Fitxer HTML de sortida")
    args = parser.parse_args()

    # Parsejar llista de ports
    try:
        llista_ports = [int(p.strip()) for p in args.ports.split(",") if p.strip().isdigit()]
    except ValueError:
        print(f"{Color.RED}Error: Format de ports invΓ lid. Usa: 80,443,22{Color.NC}")
        sys.exit(1)

    # Crear i executar verificaciΓ³
    verificador = VerificacioCapa(
        host=args.host,
        ports=llista_ports,
        timeout=args.timeout,
        sortida=args.output
    )
    verificador.executar()


if __name__ == "__main__":
    main()
04 β€” Script Bash
verificacio_logica.sh
#!/usr/bin/env bash
# =============================================================================
# SCRIPT: verificacio_logica.sh
# DESCRIPCIΓ“: Automatitza les comprovacions del protocol de verificaciΓ³
#             de la capa lΓ²gica (OSI Capa 4-7 / TCP-UDP / DNS / HTTP)
# NIVELL: SMX - Sistemes MicroinformΓ tics i Xarxes
# AUTOR: VerificaciΓ³ AutomΓ tica SMX
# VERSIΓ“: 2.0
# ÚS: ./verificacio_logica.sh [-H host] [-p ports] [-t timeout] [-o informe.html]
# =============================================================================

# ─── COLORS ────────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'

# ─── VALORS PER DEFECTE (parΓ metres configurables) ─────────────────────────────
HOST_DEFECTE="8.8.8.8"
PORTS_DEFECTE="80,443,22,53"
TIMEOUT_DEFECTE=5
SORTIDA_HTML="/home/claude/informe_capa_logica.html"
LOG_FILE="/tmp/verificacio_$(date +%Y%m%d_%H%M%S).log"

# ─── VARIABLES GLOBALS ─────────────────────────────────────────────────────────
TOTAL=0; PASSATS=0; FALLATS=0; ADVERTENCIES=0
declare -A RESULTATS   # Diccionari per guardar resultats
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

# ─── FUNCIΓ“: Ús del script ──────────────────────────────────────────────────────
us() {
    echo -e "${BOLD}ÚS:${NC} $0 [opcions]"
    echo -e "  ${CYAN}-H${NC} <host>      Host o IP objectiu  (defecte: $HOST_DEFECTE)"
    echo -e "  ${CYAN}-p${NC} <ports>     Ports separats per comes (defecte: $PORTS_DEFECTE)"
    echo -e "  ${CYAN}-t${NC} <segons>    Timeout per connexiΓ³ (defecte: ${TIMEOUT_DEFECTE}s)"
    echo -e "  ${CYAN}-o${NC} <fitxer>    Fitxer de sortida HTML (defecte: $SORTIDA_HTML)"
    echo -e "  ${CYAN}-h${NC}             Mostra aquesta ajuda"
    exit 0
}

# ─── FUNCIΓ“: Log ───────────────────────────────────────────────────────────────
log() {
    local nivell="$1"; local missatge="$2"
    echo "[$TIMESTAMP][$nivell] $missatge" >> "$LOG_FILE"
}

# ─── FUNCIΓ“: Imprimir resultat ─────────────────────────────────────────────────
resultat() {
    local nom="$1"; local estat="$2"; local detall="$3"
    TOTAL=$((TOTAL + 1))
    if [[ "$estat" == "OK" ]]; then
        echo -e "  ${GREEN}βœ” PASS${NC}  ${BOLD}$nom${NC} β€” $detall"
        PASSATS=$((PASSATS + 1))
        RESULTATS["$nom"]="PASS|$detall"
        log "PASS" "$nom: $detall"
    elif [[ "$estat" == "WARN" ]]; then
        echo -e "  ${YELLOW}⚠ WARN${NC}  ${BOLD}$nom${NC} β€” $detall"
        ADVERTENCIES=$((ADVERTENCIES + 1))
        RESULTATS["$nom"]="WARN|$detall"
        log "WARN" "$nom: $detall"
    else
        echo -e "  ${RED}✘ FAIL${NC}  ${BOLD}$nom${NC} β€” $detall"
        FALLATS=$((FALLATS + 1))
        RESULTATS["$nom"]="FAIL|$detall"
        log "FAIL" "$nom: $detall"
    fi
}

# ═══════════════════════════════════════════════════════════════════════════════
# BLOC 1: COMPROVACIΓ“ DE CONNECTIVITAT (Capa de Xarxa + Transport)
# ═══════════════════════════════════════════════════════════════════════════════
comprovar_connectivitat() {
    echo -e "\n${BLUE}${BOLD}━━━ BLOC 1: CONNECTIVITAT BΓ€SICA ━━━${NC}"

    # 1.1 Ping al host objectiu
    if ping -c 3 -W "$TIMEOUT" "$HOST" &>/dev/null; then
        local latencia
        latencia=$(ping -c 3 -W "$TIMEOUT" "$HOST" 2>/dev/null | tail -1 | awk -F'/' '{print $5}' 2>/dev/null || echo "N/A")
        resultat "Ping → $HOST" "OK" "Latència mitjana: ${latencia}ms"
    else
        resultat "Ping β†’ $HOST" "FAIL" "Host inaccessible o ICMP bloquejat"
    fi

    # 1.2 ComprovaciΓ³ de ruta (traceroute si disponible)
    if command -v traceroute &>/dev/null; then
        local salts
        salts=$(traceroute -m 10 -w 2 "$HOST" 2>/dev/null | wc -l)
        resultat "Traceroute β†’ $HOST" "OK" "Ruta amb ~$salts salts detectats"
    else
        resultat "Traceroute β†’ $HOST" "WARN" "Eina 'traceroute' no instalΒ·lada"
    fi
}

# ═══════════════════════════════════════════════════════════════════════════════
# BLOC 2: COMPROVACIΓ“ DE PORTS TCP (Capa Transport)
# ═══════════════════════════════════════════════════════════════════════════════
comprovar_ports() {
    echo -e "\n${BLUE}${BOLD}━━━ BLOC 2: PORTS TCP ━━━${NC}"

    # Noms de serveis coneguts
    declare -A SERVEIS=(
        [21]="FTP" [22]="SSH" [23]="Telnet" [25]="SMTP"
        [53]="DNS" [80]="HTTP" [110]="POP3" [143]="IMAP"
        [443]="HTTPS" [3306]="MySQL" [5432]="PostgreSQL" [8080]="HTTP-Alt"
    )

    IFS=',' read -ra LLISTA_PORTS <<< "$PORTS"
    for port in "${LLISTA_PORTS[@]}"; do
        port=$(echo "$port" | tr -d ' ')  # Eliminar espais
        local servei="${SERVEIS[$port]:-Desconegut}"

        # Intentar connexiΓ³ TCP amb timeout
        if timeout "$TIMEOUT" bash -c "echo >/dev/tcp/$HOST/$port" 2>/dev/null; then
            resultat "Port TCP $port ($servei)" "OK" "Obert i accessible"
        else
            resultat "Port TCP $port ($servei)" "FAIL" "Tancat o filtrat"
        fi
    done
}

# ═══════════════════════════════════════════════════════════════════════════════
# BLOC 3: RESOLUCIΓ“ DNS (Capa AplicaciΓ³)
# ═══════════════════════════════════════════════════════════════════════════════
comprovar_dns() {
    echo -e "\n${BLUE}${BOLD}━━━ BLOC 3: RESOLUCIΓ“ DNS ━━━${NC}"

    local DOMINIS=("google.com" "github.com" "cloudflare.com")

    # 3.1 ResoluciΓ³ directa (A record)
    for domini in "${DOMINIS[@]}"; do
        if command -v nslookup &>/dev/null; then
            local ip
            ip=$(nslookup "$domini" 2>/dev/null | awk '/^Address: / {print $2; exit}')
            if [[ -n "$ip" ]]; then
                resultat "DNS A β†’ $domini" "OK" "Resolt a $ip"
            else
                resultat "DNS A β†’ $domini" "FAIL" "No s'ha pogut resoldre"
            fi
        elif command -v dig &>/dev/null; then
            local ip
            ip=$(dig +short "$domini" A 2>/dev/null | head -1)
            if [[ -n "$ip" ]]; then
                resultat "DNS A β†’ $domini" "OK" "Resolt a $ip"
            else
                resultat "DNS A β†’ $domini" "FAIL" "No s'ha pogut resoldre"
            fi
        else
            resultat "DNS β†’ $domini" "WARN" "Ni 'nslookup' ni 'dig' disponibles"
            break
        fi
    done

    # 3.2 ResoluciΓ³ inversa (PTR)
    if command -v dig &>/dev/null; then
        local ptr
        ptr=$(dig +short -x "$HOST" 2>/dev/null | head -1)
        if [[ -n "$ptr" ]]; then
            resultat "DNS PTR β†’ $HOST" "OK" "Resolt a $ptr"
        else
            resultat "DNS PTR β†’ $HOST" "WARN" "Sense registre PTR (pot ser normal)"
        fi
    fi
}

# ═══════════════════════════════════════════════════════════════════════════════
# BLOC 4: COMPROVACIΓ“ HTTP/HTTPS (Capa AplicaciΓ³)
# ═══════════════════════════════════════════════════════════════════════════════
comprovar_http() {
    echo -e "\n${BLUE}${BOLD}━━━ BLOC 4: HTTP / HTTPS ━━━${NC}"

    if ! command -v curl &>/dev/null; then
        resultat "HTTP/HTTPS" "WARN" "curl no disponible β€” salt de comprovacions HTTP"
        return
    fi

    # 4.1 HTTP bΓ sic
    local codi_http
    codi_http=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$TIMEOUT" "http://$HOST" 2>/dev/null)
    if [[ "$codi_http" =~ ^[23] ]]; then
        resultat "HTTP β†’ $HOST" "OK" "Codi de resposta: $codi_http"
    elif [[ "$codi_http" == "301" || "$codi_http" == "302" ]]; then
        resultat "HTTP β†’ $HOST" "WARN" "RedirecciΓ³ detectada ($codi_http)"
    else
        resultat "HTTP β†’ $HOST" "FAIL" "Codi: $codi_http (o sense resposta)"
    fi

    # 4.2 HTTPS amb verificaciΓ³ SSL
    local codi_https
    codi_https=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$TIMEOUT" "https://$HOST" 2>/dev/null)
    if [[ "$codi_https" =~ ^[23] ]]; then
        resultat "HTTPS β†’ $HOST" "OK" "Codi de resposta: $codi_https (SSL vΓ lid)"
    elif [[ "$codi_https" == "000" ]]; then
        resultat "HTTPS β†’ $HOST" "FAIL" "Sense resposta HTTPS o SSL invΓ lid"
    else
        resultat "HTTPS β†’ $HOST" "WARN" "Codi inesperat: $codi_https"
    fi

    # 4.3 CapΓ§aleres de seguretat HTTP
    local headers
    headers=$(curl -s -I --max-time "$TIMEOUT" "https://$HOST" 2>/dev/null)
    if echo "$headers" | grep -qi "strict-transport-security"; then
        resultat "HSTS Header" "OK" "CapΓ§alera Strict-Transport-Security present"
    else
        resultat "HSTS Header" "WARN" "CapΓ§alera HSTS absent (recomanada)"
    fi
}

# ═══════════════════════════════════════════════════════════════════════════════
# BLOC 5: COMPROVACIΓ“ SSL/TLS
# ═══════════════════════════════════════════════════════════════════════════════
comprovar_ssl() {
    echo -e "\n${BLUE}${BOLD}━━━ BLOC 5: SSL / TLS ━━━${NC}"

    if ! command -v openssl &>/dev/null; then
        resultat "SSL/TLS" "WARN" "openssl no disponible"
        return
    fi

    # 5.1 ConnexiΓ³ SSL bΓ sica
    local ssl_info
    ssl_info=$(echo | timeout "$TIMEOUT" openssl s_client -connect "$HOST:443" 2>/dev/null)

    if echo "$ssl_info" | grep -q "CONNECTED"; then
        # Extreure versiΓ³ TLS
        local versio_tls
        versio_tls=$(echo "$ssl_info" | grep "Protocol" | awk '{print $3}' | head -1)
        resultat "SSL ConnexiΓ³ β†’ $HOST:443" "OK" "VersiΓ³: ${versio_tls:-TLS detectat}"

        # Verificar certificat
        local data_exp
        data_exp=$(echo "$ssl_info" | grep "notAfter" | cut -d= -f2)
        if [[ -n "$data_exp" ]]; then
            resultat "Certificat SSL Caducitat" "OK" "Caduca: $data_exp"
        fi

        # Verificar TLS 1.0/1.1 (protocols obsolets)
        if echo | timeout "$TIMEOUT" openssl s_client -connect "$HOST:443" -tls1 2>/dev/null | grep -q "CONNECTED"; then
            resultat "TLS 1.0 (obsolet)" "WARN" "TLS 1.0 acceptat β€” considera desactivar-lo"
        else
            resultat "TLS 1.0 desactivat" "OK" "Protocol TLS 1.0 rebutjat correctament"
        fi
    else
        resultat "SSL ConnexiΓ³ β†’ $HOST:443" "FAIL" "No s'ha pogut establir connexiΓ³ SSL"
    fi
}

# ═══════════════════════════════════════════════════════════════════════════════
# BLOC 6: TEST D'AUTOCOMPROVACIΓ“ DEL SCRIPT
# ═══════════════════════════════════════════════════════════════════════════════
autocomprovacio() {
    echo -e "\n${BLUE}${BOLD}━━━ BLOC 6: AUTOCOMPROVACIΓ“ DEL SCRIPT ━━━${NC}"

    # Test 1: Eines necessΓ ries
    local eines=("ping" "curl" "openssl" "nslookup")
    for eina in "${eines[@]}"; do
        if command -v "$eina" &>/dev/null; then
            resultat "Eina: $eina" "OK" "Disponible al sistema"
        else
            resultat "Eina: $eina" "WARN" "No instalΒ·lada β€” algunes comprovacions es saltaran"
        fi
    done

    # Test 2: Permisos d'escriptura per a l'informe
    local dir_sortida
    dir_sortida=$(dirname "$SORTIDA_HTML")
    if [[ -w "$dir_sortida" ]]; then
        resultat "Permisos escriptura β†’ $dir_sortida" "OK" "Directori accessible"
    else
        resultat "Permisos escriptura β†’ $dir_sortida" "FAIL" "Sense permisos d'escriptura"
    fi

    # Test 3: ValidaciΓ³ de parΓ metres
    if [[ "$TIMEOUT" =~ ^[0-9]+$ ]] && [[ "$TIMEOUT" -gt 0 ]]; then
        resultat "ParΓ metre timeout" "OK" "Valor vΓ lid: ${TIMEOUT}s"
    else
        resultat "ParΓ metre timeout" "FAIL" "Valor invΓ lid: $TIMEOUT"
    fi
}

# ═══════════════════════════════════════════════════════════════════════════════
# GENERACIΓ“ D'INFORME HTML
# ═══════════════════════════════════════════════════════════════════════════════
generar_html() {
    local percentatge=0
    [[ "$TOTAL" -gt 0 ]] && percentatge=$(( (PASSATS * 100) / TOTAL ))

    # Color del resum
    local color_resum="var(--warn)"
    [[ "$percentatge" -ge 80 ]] && color_resum="var(--ok)"
    [[ "$percentatge" -lt 50 ]] && color_resum="var(--fail)"

    cat > "$SORTIDA_HTML" << HTMLEOF
<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Informe VerificaciΓ³ Capa LΓ²gica β€” $HOST</title>
<style>
  :root {
    --bg: #0f1117; --surface: #1a1d2e; --surface2: #252840;
    --ok: #22d3a0; --fail: #ff4d6d; --warn: #fbbf24;
    --text: #e2e8f0; --muted: #64748b; --accent: #6366f1;
    --border: rgba(99,102,241,0.2);
    --font: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
  }
  * { margin:0; padding:0; box-sizing:border-box; }
  body { background:var(--bg); color:var(--text); font-family:var(--font); font-size:13px; line-height:1.6; }
  .container { max-width:960px; margin:0 auto; padding:2rem; }

  /* HEADER */
  .header { text-align:center; padding:3rem 0 2rem; border-bottom:1px solid var(--border); }
  .header .badge { display:inline-block; background:var(--accent); color:#fff; font-size:10px; padding:3px 10px; border-radius:20px; letter-spacing:2px; text-transform:uppercase; margin-bottom:1rem; }
  .header h1 { font-size:2rem; font-weight:700; letter-spacing:-0.5px; color:#fff; }
  .header h1 span { color:var(--accent); }
  .header .meta { color:var(--muted); font-size:11px; margin-top:0.5rem; }

  /* RESUM */
  .resum { display:grid; grid-template-columns:repeat(4,1fr); gap:1rem; margin:2rem 0; }
  .resum-card { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:1.2rem; text-align:center; }
  .resum-card .num { font-size:2.2rem; font-weight:700; }
  .resum-card .etiq { font-size:10px; text-transform:uppercase; color:var(--muted); letter-spacing:1px; margin-top:4px; }
  .resum-card.ok .num { color:var(--ok); }
  .resum-card.fail .num { color:var(--fail); }
  .resum-card.warn .num { color:var(--warn); }
  .resum-card.total .num { color:var(--accent); }

  /* BARRA PROGRÉS */
  .progres-wrap { background:var(--surface); border-radius:8px; padding:1.5rem; margin-bottom:2rem; border:1px solid var(--border); }
  .progres-label { display:flex; justify-content:space-between; margin-bottom:8px; }
  .progres-label .pct { font-size:1.4rem; font-weight:700; color:${color_resum}; }
  .barra { height:8px; background:var(--surface2); border-radius:4px; overflow:hidden; }
  .barra-fill { height:100%; width:${percentatge}%; background:linear-gradient(90deg,var(--accent),var(--ok)); border-radius:4px; transition:width 1s ease; }

  /* INFO HOST */
  .info-host { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:1.2rem 1.5rem; margin-bottom:2rem; display:flex; gap:2rem; flex-wrap:wrap; }
  .info-item { display:flex; flex-direction:column; }
  .info-item .key { font-size:10px; color:var(--muted); text-transform:uppercase; letter-spacing:1px; }
  .info-item .val { color:var(--accent); font-weight:600; }

  /* BLOC DE RESULTATS */
  .bloc { background:var(--surface); border:1px solid var(--border); border-radius:8px; margin-bottom:1.5rem; overflow:hidden; }
  .bloc-header { background:var(--surface2); padding:0.8rem 1.2rem; font-weight:700; font-size:11px; letter-spacing:1px; text-transform:uppercase; color:var(--accent); border-bottom:1px solid var(--border); }
  .fila { display:flex; align-items:center; padding:0.7rem 1.2rem; border-bottom:1px solid rgba(99,102,241,0.08); gap:0.8rem; }
  .fila:last-child { border-bottom:none; }
  .fila:hover { background:rgba(99,102,241,0.05); }
  .pill { font-size:10px; font-weight:700; padding:2px 8px; border-radius:12px; min-width:44px; text-align:center; flex-shrink:0; }
  .pill.ok { background:rgba(34,211,160,0.15); color:var(--ok); }
  .pill.fail { background:rgba(255,77,109,0.15); color:var(--fail); }
  .pill.warn { background:rgba(251,191,36,0.15); color:var(--warn); }
  .nom { font-weight:600; color:#fff; min-width:220px; }
  .detall { color:var(--muted); font-size:12px; }

  /* FOOTER */
  footer { text-align:center; color:var(--muted); font-size:11px; padding:2rem 0; border-top:1px solid var(--border); margin-top:2rem; }
  footer span { color:var(--accent); }
</style>
</head>
<body>
<div class="container">

  <div class="header">
    <div class="badge">SMX Β· Capa LΓ²gica</div>
    <h1>Informe de <span>VerificaciΓ³</span><br>Protocol Capa LΓ²gica</h1>
    <div class="meta">Generat: $TIMESTAMP Β· Host: $HOST Β· Timeout: ${TIMEOUT}s</div>
  </div>

  <div class="resum" style="margin-top:2rem;">
    <div class="resum-card total"><div class="num">$TOTAL</div><div class="etiq">Total proves</div></div>
    <div class="resum-card ok"><div class="num">$PASSATS</div><div class="etiq">Correctes</div></div>
    <div class="resum-card fail"><div class="num">$FALLATS</div><div class="etiq">Fallades</div></div>
    <div class="resum-card warn"><div class="num">$ADVERTENCIES</div><div class="etiq">Advertències</div></div>
  </div>

  <div class="progres-wrap">
    <div class="progres-label">
      <span>Resultat global</span>
      <span class="pct">${percentatge}% correcte</span>
    </div>
    <div class="barra"><div class="barra-fill"></div></div>
  </div>

  <div class="info-host">
    <div class="info-item"><span class="key">Host Objectiu</span><span class="val">$HOST</span></div>
    <div class="info-item"><span class="key">Ports Analitzats</span><span class="val">$PORTS</span></div>
    <div class="info-item"><span class="key">Timeout</span><span class="val">${TIMEOUT}s</span></div>
    <div class="info-item"><span class="key">Sistema</span><span class="val">$(uname -s) $(uname -r | cut -d- -f1)</span></div>
    <div class="info-item"><span class="key">Log</span><span class="val">$LOG_FILE</span></div>
  </div>

HTMLEOF

    # Afegir resultats per blocs
    local bloc_actual=""
    for nom_clau in "${!RESULTATS[@]}"; do
        local valor="${RESULTATS[$nom_clau]}"
        local estat="${valor%%|*}"; local det="${valor#*|}"
        local classe_pill="${estat,,}"
        [[ "$classe_pill" == "pass" ]] && classe_pill="ok"

        # Detectar nou bloc
        local bloc_nou
        if echo "$nom_clau" | grep -qi "ping\|traceroute"; then bloc_nou="Bloc 1: Connectivitat BΓ sica"
        elif echo "$nom_clau" | grep -qi "port"; then bloc_nou="Bloc 2: Ports TCP"
        elif echo "$nom_clau" | grep -qi "dns"; then bloc_nou="Bloc 3: ResoluciΓ³ DNS"
        elif echo "$nom_clau" | grep -qi "http\|hsts"; then bloc_nou="Bloc 4: HTTP / HTTPS"
        elif echo "$nom_clau" | grep -qi "ssl\|tls\|certif"; then bloc_nou="Bloc 5: SSL / TLS"
        else bloc_nou="Bloc 6: AutocomprovaciΓ³"
        fi

        if [[ "$bloc_nou" != "$bloc_actual" ]]; then
            [[ -n "$bloc_actual" ]] && echo "</div>" >> "$SORTIDA_HTML"
            echo "<div class=\"bloc\"><div class=\"bloc-header\">$bloc_nou</div>" >> "$SORTIDA_HTML"
            bloc_actual="$bloc_nou"
        fi

        cat >> "$SORTIDA_HTML" << ROWEOF
    <div class="fila">
      <span class="pill $classe_pill">$estat</span>
      <span class="nom">$nom_clau</span>
      <span class="detall">$det</span>
    </div>
ROWEOF
    done

    [[ -n "$bloc_actual" ]] && echo "</div>" >> "$SORTIDA_HTML"

    cat >> "$SORTIDA_HTML" << FOOTEREOF

  <footer>
    Generat per <span>verificacio_logica.sh v2.0</span> Β· SMX Capa LΓ²gica Β· $TIMESTAMP
  </footer>
</div>
</body>
</html>
FOOTEREOF

    echo -e "\n${GREEN}${BOLD}βœ” Informe HTML generat:${NC} $SORTIDA_HTML"
}

# ═══════════════════════════════════════════════════════════════════════════════
# PARSEO D'ARGUMENTS
# ═══════════════════════════════════════════════════════════════════════════════
HOST="$HOST_DEFECTE"
PORTS="$PORTS_DEFECTE"
TIMEOUT="$TIMEOUT_DEFECTE"

while getopts "H:p:t:o:h" opt; do
    case "$opt" in
        H) HOST="$OPTARG" ;;
        p) PORTS="$OPTARG" ;;
        t) TIMEOUT="$OPTARG" ;;
        o) SORTIDA_HTML="$OPTARG" ;;
        h) us ;;
        *) us ;;
    esac
done

# ═══════════════════════════════════════════════════════════════════════════════
# EXECUCIΓ“ PRINCIPAL
# ═══════════════════════════════════════════════════════════════════════════════
clear
echo -e "${BOLD}${CYAN}"
echo "╔══════════════════════════════════════════════════════════╗"
echo "β•‘   VERIFICACIΓ“ PROTOCOL CAPA LΓ’GICA  β€” SMX v2.0          β•‘"
echo "β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•${NC}"
echo -e "  Host: ${CYAN}$HOST${NC} | Ports: ${CYAN}$PORTS${NC} | Timeout: ${CYAN}${TIMEOUT}s${NC}"
echo -e "  Inici: ${CYAN}$TIMESTAMP${NC}\n"

# Executar tots els blocs de comprovaciΓ³
autocomprovacio
comprovar_connectivitat
comprovar_ports
comprovar_dns
comprovar_http
comprovar_ssl

# ─── RESUM FINAL ───────────────────────────────────────────────────────────────
echo -e "\n${BOLD}${CYAN}━━━ RESUM FINAL ━━━${NC}"
echo -e "  Total: $TOTAL | ${GREEN}Passats: $PASSATS${NC} | ${RED}Fallats: $FALLATS${NC} | ${YELLOW}Advertències: $ADVERTENCIES${NC}"

local_pct=0
[[ "$TOTAL" -gt 0 ]] && local_pct=$(( (PASSATS * 100) / TOTAL ))
echo -e "  PuntuaciΓ³ global: ${BOLD}${local_pct}%${NC}"

# Generar informe HTML
generar_html

echo -e "\n  Log detallat: ${CYAN}$LOG_FILE${NC}"
echo -e "${BOLD}${CYAN}══════════════════════════════════════════════════════════${NC}\n"
05 β€” Resultats ExecuciΓ³ (8.8.8.8)
21
Total
15
Correctes
4
Fallades
2
Advertències
PuntuaciΓ³ global71%
Detall β€” Host: 8.8.8.8
PASSPort TCP 80 (HTTP)Obert i accessible
PASSPort TCP 443 (HTTPS)Obert i accessible
FAILPort TCP 22 (SSH)Tancat o filtrat
PASSDNS A β†’ google.com173.194.194.101
PASSDNS PTR β†’ 8.8.8.8dns.google
WARNHTTP β†’ 8.8.8.8Codi 403 (servidor respon)
PASSSSL β†’ :443TLSv1.3 Β· TLS_AES_256_GCM
VerificaciΓ³ Protocol Capa LΓ²gica gmartin.inscastellbisbal.net Β· SMX Β· 2026
πŸ§‘β€πŸ’»

Assistent Gerard Martin

En lΓ­nia

Hola! πŸ‘‹ SΓ³c l'assistent d'en Gerard Pregunta'm sobre els seus projectes, habilitats o experiΓ¨ncia.
Qui Γ©s en Gerard? Quins projectes tΓ©? Com contactar-lo?