Tema 10 - Projecte Final: MiniSuite Bioinformàtica

Família de gens: Opsines

📖 Sobre les Opsines

Les opsines són proteïnes transmembrana que actuen com a fotoreceptors. S'uneixen a un cromòfor (retinal) per formar pigments visuals (rodopsina, fotopsines). Estan presents en bastons i cons de la retina, i són essencials per a la visió en animals.

Tipus principals:

Exercici 1: Funció crear_proteina()

Implementa la funció crear_proteina() que reservi memòria dinàmica per a una nova proteïna i inicialitzi tots els seus camps.

#include <stdio.h>   /* Biblioteca estàndard d'entrada/sortida */
#include <stdlib.h>  /* Biblioteca per a memòria dinàmica: malloc(), free() */
#include <string.h>  /* Biblioteca per a funcions de cadenes: strlen(), strcpy() */

/* ============================================ */
/* DEFINICIÓ DE L'ESTRUCTURA PRINCIPAL          */
/* ============================================ */

/* Estructura que representa una proteïna completa */
/* Agrupa totes les dades rellevants d'una seqüència biològica */
typedef struct {
    char *nom;              /* Punter al nom/identificador (memòria dinàmica) */
                            /* Ex: "Rodopsina", "OPN1SW", etc. */

    char *capcalera;        /* Punter a la capçalera FASTA completa (memòria dinàmica) */
                            /* Ex: ">sp|P08100|OPSD_HUMAN Rodopsina" */

    char *sequencia_adn;    /* Punter a la seqüència d'ADN original (memòria dinàmica) */
                            /* Emmagatzemada en majúscules */

    int longitud_adn;       /* Longitud de la seqüència d'ADN en nucleòtids */

    char *sequencia_aa;     /* Punter a la seqüència d'aminoàcids traduïda (memòria dinàmica) */
                            /* Resultat de la traducció de l'ORF més llarg */

    int longitud_aa;        /* Longitud de la seqüència d'aminoàcids */

    float pes_molecular;    /* Pes molecular aproximat en Daltons (Da) */
                            /* Calculat sumant els pesos de cada aminoàcid */

    float pct_gc;           /* Percentatge GC de la seqüència d'ADN */

    int composicio_aa[26];  /* Array per comptar cada aminoàcid (índex 0='A', 1='C', etc.) */
                            /* Mida 26: un comptador per a cada lletra de l'alfabet */
                            /* Permet calcular la composició aminoacídica */

} Proteina;  /* 'Proteina' és ara un nom de tipus vàlid gràcies a typedef */


/* ============================================ */
/* FUNCIÓ: crear_proteina()                     */
/* ============================================ */

/**
 * Crea una nova proteïna reservant memòria dinàmica
 * i inicialitzant tots els seus camps a valors per defecte.
 * 
 * @return Punter a la nova Proteina creada, o NULL si error de memòria
 * 
 * Nota: El programador és responsable d'alliberar la memòria
 * amb la funció alliberar_proteina() quan ja no es necessiti.
 */
Proteina* crear_proteina(void){  /* 'void' indica que no rep paràmetres */
                                  /* Retorna un punter a Proteina (Proteina*) */

    /* Reserva memòria per a l'estructura Proteina */
    Proteina *prot = (Proteina*)malloc(sizeof(Proteina));
    /* malloc() demana un bloc de memòria al sistema operatiu */
    /* sizeof(Proteina) calcula la mida total de l'estructura en bytes */
    /* (Proteina*) converteix el punter genèric void* al tipus correcte */

    /* Verifica que la reserva de memòria ha tingut èxit */
    if(prot == NULL){  /* Si malloc() retorna NULL, no hi ha memòria disponible */
        printf("ERROR: No s'ha pogut reservar memoria per a la proteina.\n");
        return NULL;  /* Retorna NULL per indicar error al programador que crida */
    }

    /* Inicialitza TOTS els punters a NULL */
    /* NULL indica "no apunta a cap adreça vàlida" */
    /* Això evita punters salvatges (wild pointers) que podrien causar errors */
    prot->nom = NULL;              /* El nom encara no s'ha assignat */
    prot->capcalera = NULL;        /* La capçalera FASTA no s'ha llegit */
    prot->sequencia_adn = NULL;    /* La seqüència d'ADN no s'ha carregat */
    prot->sequencia_aa = NULL;     /* La seqüència d'AA no s'ha calculat */

    /* Inicialitza els valors numèrics a 0 */
    prot->longitud_adn = 0;        /* Encara no hi ha seqüència d'ADN */
    prot->longitud_aa = 0;         /* Encara no hi ha seqüència d'AA */
    prot->pes_molecular = 0.0f;    /* Pes molecular a 0 (la 'f' indica float) */
    prot->pct_gc = 0.0f;           /* Percentatge GC a 0 */

    /* Inicialitza l'array de composició aminoacídica a zeros */
    for(int i = 0; i < 26; i++){   /* Recorre les 26 posicions (A-Z) */
        prot->composicio_aa[i] = 0; /* Cada comptador comença a 0 */
    }

    return prot;  /* Retorna el punter a la proteïna creada i inicialitzada */
}


/* ============================================ */
/* FUNCIÓ: alliberar_proteina()                 */
/* ============================================ */

/**
 * Allibera tota la memòria dinàmica associada a una proteïna.
 * 
 * @param prot Punter a la Proteina a alliberar
 * 
 * Nota: Després de cridar aquesta funció, el punter 'prot'
 * ja no és vàlid i no s'ha d'utilitzar.
 */
void alliberar_proteina(Proteina *prot){  /* Rep un punter a Proteina */
                                          /* 'void' indica que no retorna res */

    if(prot == NULL)  /* Verifica que el punter no sigui NULL */
        return;       /* Si és NULL, no hi ha res a alliberar */

    /* Allibera cada camp de memòria dinàmica individualment */
    /* free() allibera la memòria reservada amb malloc() */
    /* És segur cridar free(NULL): no fa res */

    free(prot->nom);              /* Allibera la memòria del nom */
    prot->nom = NULL;             /* Assigna NULL per evitar punters penjants */

    free(prot->capcalera);        /* Allibera la capçalera FASTA */
    prot->capcalera = NULL;

    free(prot->sequencia_adn);    /* Allibera la seqüència d'ADN */
    prot->sequencia_adn = NULL;

    free(prot->sequencia_aa);     /* Allibera la seqüència d'aminoàcids */
    prot->sequencia_aa = NULL;

    /* Finalment, allibera la pròpia estructura */
    free(prot);  /* Allibera la memòria de l'estructura Proteina */
    /* Nota: prot ja no és vàlid després d'aquesta línia */
}


/* ============================================ */
/* FUNCIÓ PRINCIPAL DE DEMOSTRACIÓ              */
/* ============================================ */
int main(){  /* Punt d'entrada del programa */

    printf("========================================\n");
    printf("  DEMOSTRACIO: crear_proteina()\n");
    printf("========================================\n\n");

    /* Crea una nova proteïna */
    printf("Creant una nova proteina...\n");
    Proteina *opsina = crear_proteina();  /* Crida a la funció */
                                          /* 'opsina' és un punter a Proteina */

    /* Verifica que s'ha creat correctament */
    if(opsina == NULL){  /* Si la funció ha retornat NULL */
        printf("Error en crear la proteina. Finalitzant.\n");
        return 1;  /* Retorna 1 per indicar error */
    }

    /* Mostra l'estat inicial de la proteïna */
    printf("Proteina creada correctament!\n\n");
    printf("--- ESTAT INICIAL DE LA PROTEINA ---\n");
    printf("  Nom:              %s\n", opsina->nom ? opsina->nom : "(no assignat)");
    /* Operador ternari: si opsina->nom és NULL, mostra "(no assignat)" */
    printf("  Capcalera:        %s\n", opsina->capcalera ? opsina->capcalera : "(no assignada)");
    printf("  Sequencia ADN:    %s\n", opsina->sequencia_adn ? opsina->sequencia_adn : "(no assignada)");
    printf("  Longitud ADN:     %d\n", opsina->longitud_adn);
    printf("  Sequencia AA:     %s\n", opsina->sequencia_aa ? opsina->sequencia_aa : "(no assignada)");
    printf("  Longitud AA:      %d\n", opsina->longitud_aa);
    printf("  Pes molecular:    %.2f Da\n", opsina->pes_molecular);
    printf("  %%GC:              %.2f%%\n", opsina->pct_gc);
    printf("  Composicio AA:    (buida)\n\n");

    /* Demostració: assignar valors a la proteïna */
    printf("--- ASSIGNANT VALORS D'EXEMPLE ---\n");

    /* Assigna el nom (reservant memòria) */
    opsina->nom = (char*)malloc(50 * sizeof(char));  /* Reserva 50 bytes */
    if(opsina->nom != NULL)
        strcpy(opsina->nom, "Rodopsina");  /* Copia el nom */

    /* Assigna la capçalera FASTA */
    opsina->capcalera = (char*)malloc(100 * sizeof(char));
    if(opsina->capcalera != NULL)
        strcpy(opsina->capcalera, ">sp|P08100|OPSD_HUMAN Rodopsina OS=Homo sapiens");

    /* Assigna una seqüència d'ADN d'exemple */
    opsina->sequencia_adn = (char*)malloc(200 * sizeof(char));
    if(opsina->sequencia_adn != NULL){
        strcpy(opsina->sequencia_adn, "ATGAATGGCACAGAAGGCCCTAACTTCTACGTCCCCTTCTCCAATAAAACCGGTGTGGTG");
        opsina->longitud_adn = strlen(opsina->sequencia_adn);
    }

    /* Mostra l'estat actualitzat */
    printf("Valors assignats correctament!\n\n");
    printf("--- ESTAT ACTUALITZAT ---\n");
    printf("  Nom:           %s\n", opsina->nom);
    printf("  Capcalera:     %s\n", opsina->capcalera);
    printf("  Sequencia ADN: %.50s...\n", opsina->sequencia_adn);  /* Primers 50 caràcters */
    printf("  Longitud ADN:  %d nucleotids\n", opsina->longitud_adn);

    /* Allibera la memòria */
    printf("\nAlliberant memoria...\n");
    alliberar_proteina(opsina);  /* Allibera tota la memòria */
    printf("Memoria alliberada correctament. Programa finalitzat.\n");

    return 0;  /* Finalitza amb èxit */
}

Exercici 2: Mòdul analisi.c

Escriu el mòdul analisi.c amb funcions per calcular GC, freqüències i traduir ADN a proteïna utilitzant una taula completa del codi genètic.

/* ============================================ */
/* FITXER: analisi.h - Capçalera del mòdul      */
/* ============================================ */

#ifndef ANALISI_H  /* Guarda d'inclusió: evita múltiples inclusions */
#define ANALISI_H  /* Defineix la macro per a la guarda */

#include "proteina.h"  /* Inclou la definició de l'estructura Proteina */

/* Prototips de les funcions del mòdul d'anàlisi */

/**
 * Calcula el percentatge GC d'una seqüència d'ADN.
 * @param adn Cadena amb la seqüència d'ADN
 * @return Percentatge GC (0.0 a 100.0)
 */
float calcular_gc(const char *adn);

/**
 * Compta la freqüència de cada nucleòtid en una seqüència d'ADN.
 * @param adn Cadena amb la seqüència d'ADN
 * @param a Punter per emmagatzemar el comptador d'A
 * @param c Punter per emmagatzemar el comptador de C
 * @param g Punter per emmagatzemar el comptador de G
 * @param t Punter per emmagatzemar el comptador de T
 */
void comptar_nucleotids(const char *adn, int *a, int *c, int *g, int *t);

/**
 * Tradueix una seqüència d'ADN a proteïna en un marc de lectura específic.
 * @param adn Seqüència d'ADN a traduir
 * @param marc Marc de lectura (0, 1 o 2)
 * @return Punter a la seqüència d'aminoàcids traduïda (memòria dinàmica),
 *         o NULL si error. El programador ha d'alliberar amb free().
 */
char* traduir_adn_a_proteina(const char *adn, int marc);

/**
 * Troba l'ORF (Open Reading Frame) més llarg en una seqüència d'ADN.
 * Un ORF comença amb ATG i acaba amb un codó STOP (TAA, TAG, TGA).
 * @param adn Seqüència d'ADN
 * @param marc Punter per emmagatzemar el marc de lectura (0,1,2)
 * @param inici Punter per emmagatzemar la posició d'inici (0-based)
 * @param longitud Punter per emmagatzemar la longitud en nucleòtids
 * @return Punter a la seqüència d'aminoàcids de l'ORF més llarg,
 *         o NULL si no es troba cap ORF
 */
char* trobar_orf_mes_llarg(const char *adn, int *marc, int *inici, int *longitud);

/**
 * Calcula el pes molecular aproximat d'una seqüència d'aminoàcids.
 * @param aa Seqüència d'aminoàcids (codi d'una lletra)
 * @return Pes molecular en Daltons (Da)
 */
float calcular_pes_molecular(const char *aa);

/**
 * Compta la composició aminoacídica d'una seqüència.
 * @param aa Seqüència d'aminoàcids
 * @param composicio Array de 26 posicions a omplir amb els comptatges
 */
void comptar_aminoacids(const char *aa, int composicio[26]);

/**
 * Calcula totes les estadístiques d'una proteïna.
 * @param prot Punter a la Proteina a analitzar (es modifiquen els seus camps)
 */
void calcular_estadistiques(Proteina *prot);

#endif /* ANALISI_H */
/* ============================================ */
/* FITXER: analisi.c - Implementació del mòdul  */
/* ============================================ */

#include <stdio.h>   /* Per a printf() */
#include <stdlib.h>  /* Per a malloc(), free() */
#include <string.h>  /* Per a strlen(), strcpy(), strncpy(), strcmp() */
#include <ctype.h>   /* Per a toupper() */
#include "analisi.h"  /* La nostra capçalera */

/* ============================================ */
/* TAULA COMPLETA DEL CODI GENÈTIC              */
/* ============================================ */

/* Estructura per a una entrada de la taula genètica */
typedef struct {
    char codo[4];      /* El triplet de nucleòtids (3 + '\0') */
    char aminoacid;    /* L'aminoàcid en codi d'una lletra */
    const char *nom;   /* Nom complet de l'aminoàcid */
} EntradaCodiGenetic;

/* Taula completa del codi genètic (64 codons) */
/* Inclou TOTS els codons possibles */
static const EntradaCodiGenetic TAULA_GENETICA[] = {
    /* Codons que codifiquen per aminoàcids (61 codons) */
    {"GCA", 'A', "Alanina"}, {"GCC", 'A', "Alanina"}, 
    {"GCG", 'A', "Alanina"}, {"GCT", 'A', "Alanina"},
    {"TGC", 'C', "Cisteina"}, {"TGT", 'C', "Cisteina"},
    {"GAC", 'D', "Aspartat"}, {"GAT", 'D', "Aspartat"},
    {"GAA", 'E', "Glutamat"}, {"GAG", 'E', "Glutamat"},
    {"TTC", 'F', "Fenilalanina"}, {"TTT", 'F', "Fenilalanina"},
    {"GGA", 'G', "Glicina"}, {"GGC", 'G', "Glicina"}, 
    {"GGG", 'G', "Glicina"}, {"GGT", 'G', "Glicina"},
    {"CAC", 'H', "Histidina"}, {"CAT", 'H', "Histidina"},
    {"ATA", 'I', "Isoleucina"}, {"ATC", 'I', "Isoleucina"}, {"ATT", 'I', "Isoleucina"},
    {"AAA", 'K', "Lisina"}, {"AAG", 'K', "Lisina"},
    {"TTA", 'L', "Leucina"}, {"TTG", 'L', "Leucina"},
    {"CTA", 'L', "Leucina"}, {"CTC", 'L', "Leucina"}, 
    {"CTG", 'L', "Leucina"}, {"CTT", 'L', "Leucina"},
    {"ATG", 'M', "Metionina"},
    {"AAC", 'N', "Asparagina"}, {"AAT", 'N', "Asparagina"},
    {"CCA", 'P', "Prolina"}, {"CCC", 'P', "Prolina"}, 
    {"CCG", 'P', "Prolina"}, {"CCT", 'P', "Prolina"},
    {"CAA", 'Q', "Glutamina"}, {"CAG", 'Q', "Glutamina"},
    {"AGA", 'R', "Arginina"}, {"AGG", 'R', "Arginina"},
    {"CGA", 'R', "Arginina"}, {"CGC", 'R', "Arginina"}, 
    {"CGG", 'R', "Arginina"}, {"CGT", 'R', "Arginina"},
    {"TCA", 'S', "Serina"}, {"TCC", 'S', "Serina"}, {"TCG", 'S', "Serina"}, {"TCT", 'S', "Serina"},
    {"AGC", 'S', "Serina"}, {"AGT", 'S', "Serina"},
    {"ACA", 'T', "Treonina"}, {"ACC", 'T', "Treonina"}, 
    {"ACG", 'T', "Treonina"}, {"ACT", 'T', "Treonina"},
    {"GTA", 'V', "Valina"}, {"GTC", 'V', "Valina"}, 
    {"GTG", 'V', "Valina"}, {"GTT", 'V', "Valina"},
    {"TGG", 'W', "Triptofan"},
    {"TAC", 'Y', "Tirosina"}, {"TAT", 'Y', "Tirosina"},
    
    /* Codons de parada (STOP) - 3 codons */
    {"TAA", '*', "STOP (Ocre)"},
    {"TAG", '*', "STOP (Ambre)"},
    {"TGA", '*', "STOP (Opal)"}
};

/* Nombre d'entrades a la taula */
#define NUM_CODONS (sizeof(TAULA_GENETICA) / sizeof(TAULA_GENETICA[0]))

/* ============================================ */
/* IMPLEMENTACIÓ DE LES FUNCIONS                */
/* ============================================ */

float calcular_gc(const char *adn){
    if(adn == NULL) return 0.0f;  /* Verifica que el punter no sigui NULL */

    int gc = 0;     /* Comptador de G + C */
    int total = 0;  /* Comptador de bases vàlides */

    for(int i = 0; adn[i] != '\0'; i++){  /* Recorre la seqüència */
        char base = toupper(adn[i]);  /* Converteix a majúscula */
        if(base == 'G' || base == 'C'){
            gc++;     /* Incrementa GC */
            total++;  /* Incrementa total */
        } else if(base == 'A' || base == 'T'){
            total++;  /* Només incrementa total */
        }
    }

    if(total == 0) return 0.0f;  /* Evita divisió per zero */
    return (float)gc / total * 100.0f;  /* Calcula percentatge */
}

void comptar_nucleotids(const char *adn, int *a, int *c, int *g, int *t){
    /* Inicialitza els comptadors a 0 */
    *a = 0; *c = 0; *g = 0; *t = 0;  /* Desreferencia els punters per modificar els valors */

    if(adn == NULL) return;  /* Si no hi ha seqüència, surt */

    for(int i = 0; adn[i] != '\0'; i++){  /* Recorre la seqüència */
        switch(toupper(adn[i])){  /* Avalua cada nucleòtid en majúscula */
            case 'A': (*a)++; break;  /* Incrementa A */
            case 'C': (*c)++; break;  /* Incrementa C */
            case 'G': (*g)++; break;  /* Incrementa G */
            case 'T': (*t)++; break;  /* Incrementa T */
        }
    }
}

/**
 * Busca un codó a la taula genètica i retorna l'aminoàcid corresponent.
 * @param codo Cadena de 3 caràcters amb el codó
 * @return L'aminoàcid (codi d'una lletra) o 'X' si no es troba
 */
static char buscar_aminoacid(const char *codo){  /* 'static' limita l'àmbit a aquest fitxer */
    for(int i = 0; i < NUM_CODONS; i++){  /* Cerca lineal a la taula */
        if(strcmp(codo, TAULA_GENETICA[i].codo) == 0)  /* Compara triplets */
            return TAULA_GENETICA[i].aminoacid;  /* Retorna l'aminoàcid */
    }
    return 'X';  /* 'X' per a codons no reconeguts */
}

char* traduir_adn_a_proteina(const char *adn, int marc){
    if(adn == NULL || marc < 0 || marc > 2) return NULL;  /* Validació de paràmetres */

    int len = strlen(adn);
    /* Calcula quants codons complets hi ha en aquest marc */
    int num_codons = (len - marc) / 3;
    if(num_codons <= 0) return NULL;  /* Si no hi ha cap codó complet */

    /* Reserva memòria per a la seqüència d'aminoàcids */
    char *proteina = (char*)malloc((num_codons + 1) * sizeof(char));
    /* +1 per al caràcter nul '\0' */
    if(proteina == NULL) return NULL;  /* Error de memòria */

    int aa_idx = 0;  /* Índex per a la seqüència d'aminoàcids */
    char codo[4];    /* Buffer per al codó actual */
    codo[3] = '\0';  /* El caràcter nul sempre al final */

    /* Tradueix codó a codó */
    for(int i = marc; i + 2 < len; i += 3){  /* Avança de 3 en 3 */
        /* Extreu el codó actual */
        codo[0] = toupper(adn[i]);
        codo[1] = toupper(adn[i+1]);
        codo[2] = toupper(adn[i+2]);

        char aa = buscar_aminoacid(codo);  /* Tradueix el codó */
        proteina[aa_idx++] = aa;  /* Afegeix l'aminoàcid a la seqüència */

        if(aa == '*') break;  /* Si és STOP, acaba la traducció */
    }

    proteina[aa_idx] = '\0';  /* Finalitza la cadena */

    /* Si la seqüència és molt curta, considera que no és vàlida */
    if(aa_idx < 10){  /* Mínim 10 aminoàcids */
        free(proteina);
        return NULL;
    }

    return proteina;  /* Retorna la seqüència traduïda */
}

char* trobar_orf_mes_llarg(const char *adn, int *marc, int *inici, int *longitud){
    if(adn == NULL) return NULL;

    int len = strlen(adn);
    char *millor_aa = NULL;  /* Millor seqüència trobada fins ara */
    int millor_longitud = 0;  /* Longitud del millor ORF */
    int millor_marc = -1;     /* Marc del millor ORF */
    int millor_inici = -1;    /* Posició d'inici del millor ORF */

    /* Prova els 3 marcs de lectura */
    for(int m = 0; m < 3; m++){
        int pos = m;  /* Comença a la posició del marc */

        while(pos + 5 < len){  /* Necessitem almenys ATG + 3 bases + STOP */
            char codo_inici[4];
            codo_inici[0] = toupper(adn[pos]);
            codo_inici[1] = toupper(adn[pos+1]);
            codo_inici[2] = toupper(adn[pos+2]);
            codo_inici[3] = '\0';

            /* Si no és ATG, avança 3 posicions */
            if(strcmp(codo_inici, "ATG") != 0){
                pos += 3;
                continue;  /* Torna a l'inici del bucle while */
            }

            /* Ha trobat un ATG: busca el STOP */
            int pos_stop = -1;
            for(int j = pos + 3; j + 2 < len; j += 3){
                char codo_stop[4];
                codo_stop[0] = toupper(adn[j]);
                codo_stop[1] = toupper(adn[j+1]);
                codo_stop[2] = toupper(adn[j+2]);
                codo_stop[3] = '\0';

                if(strcmp(codo_stop, "TAA") == 0 ||
                   strcmp(codo_stop, "TAG") == 0 ||
                   strcmp(codo_stop, "TGA") == 0){
                    pos_stop = j;  /* Ha trobat un STOP */
                    break;
                }
            }

            /* Si ha trobat un STOP, calcula la longitud de l'ORF */
            if(pos_stop != -1){
                int long_orf = pos_stop - pos + 3;  /* Inclou el codó STOP */

                /* Si és l'ORF més llarg trobat, el guarda */
                if(long_orf > millor_longitud){
                    /* Allibera la seqüència anterior si n'hi havia */
                    free(millor_aa);

                    /* Tradueix aquest ORF */
                    char *temp_adn = (char*)malloc(long_orf + 1);
                    if(temp_adn){
                        strncpy(temp_adn, adn + pos, long_orf);
                        temp_adn[long_orf] = '\0';
                        millor_aa = traduir_adn_a_proteina(temp_adn, 0);
                        free(temp_adn);
                    }

                    millor_longitud = long_orf;
                    millor_marc = m;
                    millor_inici = pos;
                }

                pos = pos_stop + 3;  /* Continua després del STOP */
            } else {
                pos += 3;  /* No ha trobat STOP, avança */
            }
        }
    }

    /* Retorna els resultats a través dels punters */
    if(marc) *marc = millor_marc;
    if(inici) *inici = millor_inici;
    if(longitud) *longitud = millor_longitud;

    return millor_aa;  /* Retorna la millor seqüència (o NULL si no n'hi ha cap) */
}

float calcular_pes_molecular(const char *aa){
    if(aa == NULL) return 0.0f;

    /* Pesos moleculars dels 20 aminoàcids estàndard (en Daltons) */
    static const float pesos[26] = {
        ['A'-'A'] = 89.1f,   /* Alanina */
        ['C'-'A'] = 121.2f,  /* Cisteina */
        ['D'-'A'] = 133.1f,  /* Aspartat */
        ['E'-'A'] = 147.1f,  /* Glutamat */
        ['F'-'A'] = 165.2f,  /* Fenilalanina */
        ['G'-'A'] = 75.1f,   /* Glicina */
        ['H'-'A'] = 155.2f,  /* Histidina */
        ['I'-'A'] = 131.2f,  /* Isoleucina */
        ['K'-'A'] = 146.2f,  /* Lisina */
        ['L'-'A'] = 131.2f,  /* Leucina */
        ['M'-'A'] = 149.2f,  /* Metionina */
        ['N'-'A'] = 132.1f,  /* Asparagina */
        ['P'-'A'] = 115.1f,  /* Prolina */
        ['Q'-'A'] = 146.2f,  /* Glutamina */
        ['R'-'A'] = 174.2f,  /* Arginina */
        ['S'-'A'] = 105.1f,  /* Serina */
        ['T'-'A'] = 119.1f,  /* Treonina */
        ['V'-'A'] = 117.1f,  /* Valina */
        ['W'-'A'] = 204.2f,  /* Triptòfan */
        ['Y'-'A'] = 181.2f,  /* Tirosina */
    };
    /* Inicialització designada: pesos['C'-'A'] = pesos[2] */

    float pes = 0.0f;
    for(int i = 0; aa[i] != '\0'; i++){
        if(aa[i] == '*') break;  /* STOP no contribueix al pes */
        int idx = toupper(aa[i]) - 'A';  /* Converteix lletra a índex (0-25) */
        if(idx >= 0 && idx < 26){
            pes += pesos[idx];  /* Suma el pes de l'aminoàcid */
        }
    }

    /* Resta aigua per cada enllaç peptídic (18.015 Da per enllaç) */
    int num_enllacos = strlen(aa) - 1;
    if(num_enllacos > 0)
        pes -= num_enllacos * 18.015f;

    return pes;
}

void comptar_aminoacids(const char *aa, int composicio[26]){
    /* Inicialitza l'array a zeros */
    for(int i = 0; i < 26; i++)
        composicio[i] = 0;

    if(aa == NULL) return;

    for(int i = 0; aa[i] != '\0'; i++){
        if(aa[i] == '*') break;  /* No compta STOP */
        int idx = toupper(aa[i]) - 'A';  /* Converteix a índex */
        if(idx >= 0 && idx < 26)
            composicio[idx]++;  /* Incrementa el comptador */
    }
}

void calcular_estadistiques(Proteina *prot){
    if(prot == NULL || prot->sequencia_adn == NULL) return;

    /* Calcula el percentatge GC */
    prot->pct_gc = calcular_gc(prot->sequencia_adn);

    /* Troba l'ORF més llarg i el tradueix */
    int marc, inici, long_orf;
    char *millor_aa = trobar_orf_mes_llarg(prot->sequencia_adn, &marc, &inici, &long_orf);

    if(millor_aa != NULL){
        /* Allibera la seqüència anterior si n'hi havia */
        free(prot->sequencia_aa);

        /* Assigna la nova seqüència */
        prot->sequencia_aa = millor_aa;
        prot->longitud_aa = strlen(millor_aa);

        /* Calcula el pes molecular */
        prot->pes_molecular = calcular_pes_molecular(millor_aa);

        /* Compta la composició aminoacídica */
        comptar_aminoacids(millor_aa, prot->composicio_aa);
    }
}

Exercici 3: Disseny del format de l'informe de sortida

Dissenya el format de l'informe de sortida: quines dades ha d'incloure i en quin ordre.

/*
 * ============================================
 * FORMAT DE L'INFORME DE SORTIDA
 * ============================================
 * 
 * L'informe es genera en format text pla i segueix
 * l'estructura següent:
 * 
 * 1. CAPÇALERA
 *    - Títol: "MINISUITE BIOINFORMÀTICA - INFORME D'ANÀLISI"
 *    - Data i hora de generació
 *    - Família gènica analitzada: Opsines
 *    - Nombre de seqüències processades
 * 
 * 2. RESUM GENERAL
 *    - Taula amb: nom, longitud ADN, longitud AA, %GC, pes molecular
 * 
 * 3. DETALL PER SEQÜÈNCIA (per a cada proteïna)
 *    3.1. Informació general
 *         - Nom i capçalera FASTA
 *         - Longitud de la seqüència d'ADN
 *         - Longitud de la proteïna traduïda
 *    
 *    3.2. Composició nucleotídica
 *         - Taula: A, C, G, T amb recomptes i percentatges
 *         - Percentatge GC
 *    
 *    3.3. Seqüència traduïda
 *         - ORF més llarg (marc de lectura, posició inicial)
 *         - Seqüència d'aminoàcids en blocs de 60
 *    
 *    3.4. Composició aminoacídica
 *         - Taula amb tots els aminoàcids trobats
 *         - Recompte i percentatge de cada un
 *         - Classificació: hidrofòbics, polars, carregats
 *    
 *    3.5. Pes molecular
 *         - Pes molecular calculat en Daltons (Da)
 *         - Pes en kiloDaltons (kDa)
 * 
 * 4. COMPARACIÓ DE SEQÜÈNCIES (si n'hi ha més d'una)
 *    4.1. Matriu d'identitat
 *         - Taula NxN amb percentatges d'identitat
 *    
 *    4.2. Per a cada parella comparada:
 *         - Percentatge d'identitat
 *         - Percentatge de similitud
 *         - Alineament visual (primers 100 aminoàcids)
 *         - Llista de mutacions/diferències
 * 
 * 5. PEU D'INFORME
 *    - Resum final
 *    - Llegenda de símbols
 * 
 * EXEMPLE DE FORMAT:
 * 
 * ========================================
 *   MINISUITE BIOINFORMÀTICA - INFORME
 *   Família: Opsines
 *   Data: 2024-01-15 14:30:00
 *   Seqüències analitzades: 5
 * ========================================
 * 
 * --- RESUM GENERAL ---
 * NOM              LONG_ADN  LONG_AA  %GC     PES_MOLEC
 * Rodopsina        1047      348      52.3    39245.67 Da
 * Opsina_blau      1044      347      48.9    38901.23 Da
 * ...
 * 
 * --- DETALL: Rodopsina ---
 * Capçalera: >sp|P08100|OPSD_HUMAN
 * Longitud ADN: 1047 nt
 * Longitud AA:  348 aa
 * 
 * Composició nucleotídica:
 *   A: 250 (23.9%)  C: 298 (28.5%)  G: 250 (23.9%)  T: 249 (23.8%)
 *   %GC: 52.3%
 * 
 * ORF més llarg: marc 0, posició 0, longitud 1047 nt
 * Seqüència AA:
 *   MNGTEGPNFYVPFSNKTGVVRSPFEYPQYYLAEPWQFSMLAAYMFLLIVLGFP
 *   INFLTLYVTVQHKKLRTPLNYILLNLAVADLFMVLGGFTSTLYTSLHGYFVFG
 *   ...
 * 
 * Composició aminoacídica:
 *   A:12  C:8  D:10  E:15  F:22  G:18  H:6  I:14  K:10  L:35
 *   M:10  N:10  P:12  Q:8  R:12  S:25  T:18  V:28  W:5  Y:14
 *   Hidrofòbics: 156 (44.8%)
 *   Polars:      98 (28.2%)
 *   Carregats:   47 (13.5%)
 * 
 * Pes molecular: 39245.67 Da (39.25 kDa)
 * 
 * ========================================
 */

🏆 Projecte Final – MiniSuite Bioinformàtica (Opsines)

Construeix la suite completa amb tots els requisits.

/* ============================================ */
/* FITXER: proteina.h - Estructura de dades     */
/* ============================================ */

#ifndef PROTEINA_H
#define PROTEINA_H

/* Estructura principal per a una proteïna */
typedef struct {
    char *nom;              /* Nom de la proteïna */
    char *capcalera;        /* Capçalera FASTA completa */
    char *sequencia_adn;    /* Seqüència d'ADN */
    int longitud_adn;       /* Longitud de l'ADN */
    char *sequencia_aa;     /* Seqüència d'aminoàcids */
    int longitud_aa;        /* Longitud d'aminoàcids */
    float pes_molecular;    /* Pes molecular (Da) */
    float pct_gc;           /* Percentatge GC */
    int composicio_aa[26];  /* Composició aminoacídica */
} Proteina;

/* Funcions de gestió de proteïnes */
Proteina* crear_proteina(void);
void alliberar_proteina(Proteina *prot);

#endif
/* ============================================ */
/* FITXER: main.c - Programa principal          */
/* MiniSuite Bioinformàtica - Família Opsines   */
/* ============================================ */

#include <stdio.h>   /* Entrada/sortida estàndard */
#include <stdlib.h>  /* Memòria dinàmica */
#include <string.h>  /* Funcions de cadenes */
#include <ctype.h>   /* Classificació de caràcters */
#include <time.h>    /* Per a data i hora */
#include "proteina.h" /* Estructura Proteina */
#include "analisi.h"  /* Funcions d'anàlisi */

/* Constants del programa */
#define MAX_SEQ 5000       /* Longitud màxima de seqüència */
#define MAX_LINIA 2000     /* Longitud màxima de línia */
#define MAX_PROTEINES 50   /* Nombre màxim de proteïnes */
#define MAX_NOM 200        /* Longitud màxima del nom */

/* Variables globals */
Proteina *proteines[MAX_PROTEINES];  /* Array de punters a proteïnes */
int total_proteines = 0;             /* Comptador de proteïnes carregades */

/* Prototips de funcions */
void mostrar_menu(void);
void importar_fasta(const char *nom_fitxer);
void mostrar_estadistiques(void);
void mostrar_traduccio(void);
void comparar_proteines(void);
void exportar_informe(const char *nom_fitxer);
void alliberar_totes(void);

/* Funció per crear un fitxer d'exemple d'opsines */
void crear_fitxer_exemple(const char *nom_fitxer){
    FILE *f = fopen(nom_fitxer, "w");
    if(f == NULL) return;

    fprintf(f, ">Rodopsina|OPSD_HUMAN|Homo_sapiens\n");
    fprintf(f, "ATGAATGGCACAGAAGGCCCTAACTTCTACGTCCCCTTCTCCAATAAAACCGGTGTGGTG\n");
    fprintf(f, "CGCAGCCCCTTCGAGTACCCACAGTACTACCTGGCTGAGCCATGGCAGTTCTCCATGCTG\n");
    fprintf(f, "GCCGCCTACATGTTCCTGCTGATCGTGCTGGGCTTCCCCATCAACTTCCTCACGCTCTAC\n");
    fprintf(f, "GTCACCGTCCAGCACAAGAAGCTGCGCACGCCTCTCAACTACATCCTGCTCAACCTAGCC\n");
    fprintf(f, "GTGGCTGACCTCTTCATGGTCCTAGGTGGCTTCACCAGCACCCTCTACACCTCTCTGCAT\n");
    fprintf(f, "GGATACTTCGTCTTCGGGCCCACAGGATGCAATTTGGAGGGCTTCTTTGCCACCCTGGGC\n");
    fprintf(f, "GGTGAAATTGCCCTGTGGTCCTTGGTGGTCCTGGCCATCGAGCGGTACGTGGTGGTGTGT\n");
    fprintf(f, "AAGCCCATGAGCAACTTCCGCTTCGGGGAGAACCATGCCATCATGGGCGTTGCCTTCACC\n");
    fprintf(f, "TGGGTCATGGCGCTGGCCTGCGCCCCACCCCTCGTCGGCTGGTCCAGGTACATCCCGGAG\n");
    fprintf(f, "GGCATGCAGTGCTCGTGTGGAGCCATCTACTACACGCTCAAGCCGGAGATCAACAACGAG\n");
    fprintf(f, "TCTTTCGTCATCTACATGTTCGTGGTCCACTTCACCATCCCCATGATTATCATCTTTTTC\n");
    fprintf(f, "TGCTATGGGCAGCTCGTCTTCACCGTCAAGGAGGCGGCTGCCCAGCAGCAGGAGTCAGCC\n");
    fprintf(f, "ACCACCCAGAAGGCAGAGAAGGAGGTGACCCGCATGGTCATCATCATGGTCATCGCTTTC\n");
    fprintf(f, "CTGATCTGCTGGGTGCCCTACGCCAGCGTGGCGTTCTACATCTTCACCCACCAGGGCAGC\n");
    fprintf(f, "GACTTCGGCCCGGTGTTCATGACCATCCCAGCGTTCTTTGCCAAGACCTCGGCGGTCTAC\n");
    fprintf(f, "AACCCCGTCATCTATATCATGATGAACAAGCAGTTCCGGAACTGCATGCTCACCACCATC\n");
    fprintf(f, "TGCTGCGGCAAGAACCCGCTGGGTGACGACGAGGCCTCTGCCACCGTGTCCAAGACGGAG\n");
    fprintf(f, "ACGTCCCAGGTGGCCCCGGCGTAG\n");

    fprintf(f, ">Opsina_blau|OPSB_HUMAN|Homo_sapiens\n");
    fprintf(f, "ATGCGGGCCCTGTGGAGCCGGAAGAGCTGGCTGTTCCTGTCCCAGCTGCTGGGGCTGGGG\n");
    fprintf(f, "GTGGTGGGCAGCTCCAGCATCTTCATGAGCCGCCGCGAGGAGAACACGCTGCCCATCCGC\n");
    fprintf(f, "CACGTCCGCGACCTGATGGCCGAGATGCGGCAGCCCGCGGAGGCCAGCGCCGCGGCCGCC\n");
    fprintf(f, "GTGGCCGCGGCCAACACCCTGGTGGCCGGCGGCATCACCAGCGTGGTGCCGTTCGCCGCC\n");
    fprintf(f, "GCCGCCAGCCACTACGCCTTCGAGTACACGCTGGCCAAGGTCCCCGCCTTCGAGATCCTG\n");
    fprintf(f, "TTCATGGTGTTTGCCACCCTGGTGGGCCTCAACGCGGTCTGGCTGCGCATGTACGCCGAG\n");
    fprintf(f, "AGCTACCTGAAGCCCTTCTTCGACACCAACTGCCTGCTGAAGCTGGCCGTGGCCGACTGG\n");
    fprintf(f, "GGCACCTTCTACTACGCCAACTGCGACGTGCTGCGCTTCGTGGTGGCCGTGCTCTACTAC\n");
    fprintf(f, "GCCCTGTGCTTCACCTACGCCTTCGCCGCCGCCATGGCCGCCACCGTGGCCGCCATCGCC\n");
    fprintf(f, "GCCTACCTGGGCGAGTTCTGCTCCAAGATGCGCTTCCCGCAGTTCTCCATCGTGGTGTTC\n");
    fprintf(f, "TACTACAAGCTGTTCGCCTGCTTCCCCGTCACCTTCCTGGTGGCCGCCTACGCCTTCCTC\n");
    fprintf(f, "TACGCCGCCTACGCCTGCGCCGCCTTCGCCGCCGCCTTCGCCGCCGCCTGGTCCGCCGCC\n");
    fprintf(f, "TGGGCCTCCGCCTGGGTGCCCGCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCC\n");
    fprintf(f, "GCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCC\n");
    fprintf(f, "GCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCC\n");
    fprintf(f, "GCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCC\n");
    fprintf(f, "GCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCC\n");
    fprintf(f, "GCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTCCGCCTAG\n");

    fclose(f);
}

int main(int argc, char *argv[]){
    int opcio;
    char nom_fitxer[200] = "opsines.fasta";

    printf("========================================\n");
    printf("  MINISUITE BIOINFORMÀTICA\n");
    printf("  Família gènica: OPSINES\n");
    printf("========================================\n\n");

    /* Si s'ha passat un fitxer com a argument, l'utilitza */
    if(argc > 1)
        strcpy(nom_fitxer, argv[1]);

    /* Crea fitxer d'exemple si no existeix */
    FILE *test = fopen(nom_fitxer, "r");
    if(test == NULL){
        printf("No s'ha trobat '%s'. Creant fitxer d'exemple...\n", nom_fitxer);
        crear_fitxer_exemple(nom_fitxer);
    } else {
        fclose(test);
    }

    do{
        mostrar_menu();
        printf("  Opcio: ");
        scanf("%d", &opcio);
        getchar();

        switch(opcio){
            case 1:
                importar_fasta(nom_fitxer);
                break;
            case 2:
                mostrar_estadistiques();
                break;
            case 3:
                mostrar_traduccio();
                break;
            case 4:
                comparar_proteines();
                break;
            case 5:
                exportar_informe("informe_opsines.txt");
                break;
            case 6:
                printf("Fitxer a importar: ");
                scanf("%199s", nom_fitxer);
                getchar();
                importar_fasta(nom_fitxer);
                break;
            case 0:
                printf("\nAlliberant memoria i sortint...\n");
                alliberar_totes();
                printf("Adeu!\n");
                break;
            default:
                printf("Opcio no valida.\n");
        }
    } while(opcio != 0);

    return 0;
}

void mostrar_menu(void){
    printf("\n--- MENU PRINCIPAL ---\n");
    printf("  Proteines carregades: %d\n", total_proteines);
    printf("  1. Importar fitxer FASTA (opsines.fasta)\n");
    printf("  2. Mostrar estadistiques de totes les opsines\n");
    printf("  3. Mostrar traduccio (ORF mes llarg)\n");
    printf("  4. Comparar dues proteines\n");
    printf("  5. Exportar informe complet\n");
    printf("  6. Canviar fitxer d'entrada\n");
    printf("  0. Sortir\n");
}

void importar_fasta(const char *nom_fitxer){
    FILE *f = fopen(nom_fitxer, "r");
    if(f == NULL){
        printf("ERROR: No s'ha pogut obrir '%s'.\n", nom_fitxer);
        return;
    }

    /* Allibera proteïnes anteriors */
    alliberar_totes();

    char linia[MAX_LINIA];
    char seq_temp[MAX_SEQ] = "";
    char capcalera_temp[MAX_NOM] = "";
    int dins_seq = 0;

    printf("Llegint fitxer '%s'...\n", nom_fitxer);

    while(fgets(linia, MAX_LINIA, f) != NULL){
        int len = strlen(linia);
        if(len > 0 && linia[len-1] == '\n') linia[len-1] = '\0';

        if(linia[0] == '>'){
            /* Guarda la proteïna anterior si existeix */
            if(dins_seq && strlen(seq_temp) > 0 && total_proteines < MAX_PROTEINES){
                Proteina *prot = crear_proteina();
                if(prot){
                    prot->capcalera = (char*)malloc(strlen(capcalera_temp) + 1);
                    strcpy(prot->capcalera, capcalera_temp);

                    /* Extreu el nom (text fins al primer '|' o espai) */
                    char *nom = capcalera_temp + 1;
                    char *sep = strchr(nom, '|');
                    if(sep) *sep = '\0';
                    prot->nom = (char*)malloc(strlen(nom) + 1);
                    strcpy(prot->nom, nom);

                    prot->sequencia_adn = (char*)malloc(strlen(seq_temp) + 1);
                    strcpy(prot->sequencia_adn, seq_temp);
                    prot->longitud_adn = strlen(seq_temp);

                    /* Calcula estadístiques */
                    calcular_estadistiques(prot);

                    proteines[total_proteines++] = prot;
                }
            }

            /* Nova proteïna */
            strcpy(capcalera_temp, linia);
            seq_temp[0] = '\0';
            dins_seq = 1;

        } else if(dins_seq){
            /* Converteix a majúscules i afegeix */
            for(int i = 0; linia[i]; i++)
                linia[i] = toupper(linia[i]);

            /* Filtra només bases vàlides */
            char filtra[MAX_SEQ] = "";
            int j = 0;
            for(int i = 0; linia[i]; i++)
                if(strchr("ACGT", linia[i])) filtra[j++] = linia[i];
            filtra[j] = '\0';

            strcat(seq_temp, filtra);
        }
    }

    /* Guarda l'última proteïna */
    if(dins_seq && strlen(seq_temp) > 0 && total_proteines < MAX_PROTEINES){
        Proteina *prot = crear_proteina();
        if(prot){
            prot->capcalera = (char*)malloc(strlen(capcalera_temp) + 1);
            strcpy(prot->capcalera, capcalera_temp);

            char *nom = capcalera_temp + 1;
            char *sep = strchr(nom, '|');
            if(sep) *sep = '\0';
            prot->nom = (char*)malloc(strlen(nom) + 1);
            strcpy(prot->nom, nom);

            prot->sequencia_adn = (char*)malloc(strlen(seq_temp) + 1);
            strcpy(prot->sequencia_adn, seq_temp);
            prot->longitud_adn = strlen(seq_temp);

            calcular_estadistiques(prot);
            proteines[total_proteines++] = prot;
        }
    }

    fclose(f);
    printf("Carregades %d proteines (opsines).\n", total_proteines);
}

void mostrar_estadistiques(void){
    if(total_proteines == 0){
        printf("No hi ha proteines carregades. Importa un fitxer FASTA primer.\n");
        return;
    }

    printf("\n=== ESTADISTIQUES DE LES OPSINES ===\n\n");
    printf("%-20s %8s %8s %8s %12s\n", "NOM", "LONG_ADN", "LONG_AA", "%GC", "PES (Da)");
    printf("--------------------------------------------------------------\n");

    for(int i = 0; i < total_proteines; i++){
        Proteina *p = proteines[i];
        printf("%-20s %8d %8d %7.1f%% %10.1f\n",
               p->nom, p->longitud_adn, p->longitud_aa, p->pct_gc, p->pes_molecular);
    }

    /* Mostra composició aminoacídica de la primera opsina */
    if(total_proteines > 0 && proteines[0]->sequencia_aa){
        printf("\n--- Composicio aminoacidica de %s ---\n", proteines[0]->nom);
        printf("Aminoacids: ");
        for(int i = 0; i < 26; i++){
            if(proteines[0]->composicio_aa[i] > 0)
                printf("%c:%d ", 'A' + i, proteines[0]->composicio_aa[i]);
        }
        printf("\n");
    }
}

void mostrar_traduccio(void){
    if(total_proteines == 0){
        printf("No hi ha proteines carregades.\n");
        return;
    }

    printf("\n=== TRADUCCIO (ORF MES LLARG) ===\n\n");

    for(int i = 0; i < total_proteines; i++){
        Proteina *p = proteines[i];
        printf("--- %s ---\n", p->nom);
        printf("Longitud ADN: %d nt\n", p->longitud_adn);

        if(p->sequencia_aa){
            printf("Longitud AA: %d aa\n", p->longitud_aa);
            printf("Pes molecular: %.1f Da (%.1f kDa)\n", p->pes_molecular, p->pes_molecular/1000);
            printf("Sequencia AA (primers 100 aa):\n  ");
            for(int j = 0; j < p->longitud_aa && j < 100; j++){
                printf("%c", p->sequencia_aa[j]);
                if((j + 1) % 60 == 0) printf("\n  ");
            }
            printf("\n");
        } else {
            printf("No s'ha pogut traduir (no s'ha trobat ORF valid).\n");
        }
        printf("\n");
    }
}

void comparar_proteines(void){
    if(total_proteines < 2){
        printf("Es necessiten almenys 2 proteines per comparar.\n");
        return;
    }

    printf("\nProteines disponibles:\n");
    for(int i = 0; i < total_proteines; i++)
        printf("  %d. %s\n", i + 1, proteines[i]->nom);

    int i1, i2;
    printf("Selecciona la primera proteina (1-%d): ", total_proteines);
    scanf("%d", &i1);
    printf("Selecciona la segona proteina (1-%d): ", total_proteines);
    scanf("%d", &i2);
    getchar();

    if(i1 < 1 || i1 > total_proteines || i2 < 1 || i2 > total_proteines){
        printf("Indexs no valids.\n");
        return;
    }

    Proteina *p1 = proteines[i1 - 1];
    Proteina *p2 = proteines[i2 - 1];

    printf("\n=== COMPARACIO: %s vs %s ===\n\n", p1->nom, p2->nom);

    if(p1->sequencia_aa && p2->sequencia_aa){
        int len1 = p1->longitud_aa;
        int len2 = p2->longitud_aa;
        int min_len = (len1 < len2) ? len1 : len2;
        int max_len = (len1 > len2) ? len1 : len2;

        int identiques = 0;
        for(int i = 0; i < min_len; i++)
            if(p1->sequencia_aa[i] == p2->sequencia_aa[i])
                identiques++;

        printf("Longitud: %s=%d aa, %s=%d aa\n", p1->nom, len1, p2->nom, len2);
        printf("Identitat: %d/%d (%.1f%%)\n", identiques, max_len, 
               (float)identiques/max_len*100);

        /* Alineament visual */
        printf("\nAlineament (primers 100 aa):\n");
        printf("%-10s ", p1->nom);
        for(int i = 0; i < min_len && i < 100; i++)
            printf("%c", p1->sequencia_aa[i]);
        printf("\n          ");
        for(int i = 0; i < min_len && i < 100; i++)
            printf("%c", (p1->sequencia_aa[i] == p2->sequencia_aa[i]) ? '|' : ' ');
        printf("\n%-10s ", p2->nom);
        for(int i = 0; i < min_len && i < 100; i++)
            printf("%c", p2->sequencia_aa[i]);
        printf("\n");

        /* Llista de diferències */
        printf("\nDiferencies (primeres 20):\n");
        int difs = 0;
        for(int i = 0; i < min_len && difs < 20; i++){
            if(p1->sequencia_aa[i] != p2->sequencia_aa[i]){
                printf("  Pos %d: %c -> %c\n", i + 1, p1->sequencia_aa[i], p2->sequencia_aa[i]);
                difs++;
            }
        }
    }
}

void exportar_informe(const char *nom_fitxer){
    if(total_proteines == 0){
        printf("No hi ha dades per exportar.\n");
        return;
    }

    FILE *f = fopen(nom_fitxer, "w");
    if(f == NULL){
        printf("ERROR: No s'ha pogut crear '%s'.\n", nom_fitxer);
        return;
    }

    time_t t = time(NULL);
    fprintf(f, "========================================\n");
    fprintf(f, "  MINISUITE BIOINFORMATICA - INFORME\n");
    fprintf(f, "  Familia: Opsines\n");
    fprintf(f, "  Data: %s", ctime(&t));
    fprintf(f, "  Sequencies analitzades: %d\n", total_proteines);
    fprintf(f, "========================================\n\n");

    /* Resum */
    fprintf(f, "--- RESUM GENERAL ---\n");
    fprintf(f, "%-20s %8s %8s %8s %12s\n", "NOM", "LONG_ADN", "LONG_AA", "%%GC", "PES (Da)");
    fprintf(f, "--------------------------------------------------------------\n");
    for(int i = 0; i < total_proteines; i++){
        Proteina *p = proteines[i];
        fprintf(f, "%-20s %8d %8d %7.1f %10.1f\n",
                p->nom, p->longitud_adn, p->longitud_aa, p->pct_gc, p->pes_molecular);
    }

    /* Detall per proteïna */
    for(int i = 0; i < total_proteines; i++){
        Proteina *p = proteines[i];
        fprintf(f, "\n--- DETALL: %s ---\n", p->nom);
        fprintf(f, "Capcalera: %s\n", p->capcalera);
        fprintf(f, "Longitud ADN: %d nt\n", p->longitud_adn);
        fprintf(f, "Longitud AA: %d aa\n", p->longitud_aa);
        fprintf(f, "%%GC: %.1f%%\n", p->pct_gc);
        fprintf(f, "Pes molecular: %.1f Da (%.1f kDa)\n", p->pes_molecular, p->pes_molecular/1000);

        if(p->sequencia_aa){
            fprintf(f, "Sequencia AA:\n  ");
            for(int j = 0; j < p->longitud_aa; j++){
                fprintf(f, "%c", p->sequencia_aa[j]);
                if((j + 1) % 60 == 0) fprintf(f, "\n  ");
            }
            fprintf(f, "\n");
        }
    }

    fclose(f);
    printf("Informe exportat a '%s'.\n", nom_fitxer);
}

void alliberar_totes(void){
    for(int i = 0; i < total_proteines; i++)
        alliberar_proteina(proteines[i]);
    total_proteines = 0;
}