16 - Ejemplo practico 5: soporte tecnico multinivel

Administramos tickets de soporte en tres niveles (basico, intermedio y experto) usando colas independientes y reglas de escalamiento manual.

16.1 Planteo del problema

  • Registrar tickets con cliente, resumen y nivel deseado.
  • Atender siempre el nivel mas alto disponible.
  • Escalar tickets desde niveles inferiores cuando un agente decide transferirlos.
  • Generar un reporte final por nivel.

16.2 Modelo de datos

#define CAP_TICKETS 40
#define MAX_CLIENTE 40
#define MAX_NOTA 64

typedef struct {
  char cliente[MAX_CLIENTE];
  char nota[MAX_NOTA];
  int nivel;
} Ticket;

typedef struct {
  Ticket datos[CAP_TICKETS];
  int frente;
  int final;
  int cantidad;
} ColaTickets;

void cola_init(ColaTickets *cola) {
  cola->frente = 0;
  cola->final = 0;
  cola->cantidad = 0;
}

int cola_push(ColaTickets *cola, Ticket t) {
  if (cola->cantidad == CAP_TICKETS) {
    return 0;
  }
  cola->datos[cola->final] = t;
  cola->final = (cola->final + 1) % CAP_TICKETS;
  cola->cantidad++;
  return 1;
}

int cola_pop(ColaTickets *cola, Ticket *t) {
  if (cola->cantidad == 0) {
    return 0;
  }
  *t = cola->datos[cola->frente];
  cola->frente = (cola->frente + 1) % CAP_TICKETS;
  cola->cantidad--;
  return 1;
}

16.3 Operaciones principales

typedef struct {
  ColaTickets basic;
  ColaTickets mid;
  ColaTickets expert;
  int atendidos[3];
} CentroSoporte;

void soporte_init(CentroSoporte *c) {
  cola_init(&c->basic);
  cola_init(&c->mid);
  cola_init(&c->expert);
  c->atendidos[0] = c->atendidos[1] = c->atendidos[2] = 0;
}

int registrarTicket(CentroSoporte *c, const char *cliente, const char *nota, int nivel) {
  Ticket t;
  strncpy(t.cliente, cliente, MAX_CLIENTE - 1);
  t.cliente[MAX_CLIENTE - 1] = '\0';
  strncpy(t.nota, nota, MAX_NOTA - 1);
  t.nota[MAX_NOTA - 1] = '\0';
  t.nivel = nivel;
  ColaTickets *cola = nivel == 3 ? &c->expert : (nivel == 2 ? &c->mid : &c->basic);
  return cola_push(cola, t);
}

int atenderTicket(CentroSoporte *c, Ticket *t) {
  ColaTickets *colas[3] = { &c->expert, &c->mid, &c->basic };
  for (int i = 0; i < 3; ++i) {
    if (cola_pop(colas[i], t)) {
      c->atendidos[2 - i]++;
      return 1;
    }
  }
  return 0;
}

int escalarTicket(CentroSoporte *c, int origen, int destino) {
  ColaTickets *colas[3] = { &c->basic, &c->mid, &c->expert };
  if (origen < 1 || origen > 3 || destino < 1 || destino > 3 || destino <= origen) {
    return 0;
  }
  if (colas[origen - 1]->cantidad == 0) {
    return 0;
  }
  Ticket t;
  cola_pop(colas[origen - 1], &t);
  t.nivel = destino;
  return cola_push(colas[destino - 1], t);
}

void mostrarEstado(const CentroSoporte *c) {
  printf("Experto: %d pendientes\n", c->expert.cantidad);
  printf("Intermedio: %d pendientes\n", c->mid.cantidad);
  printf("Basico: %d pendientes\n", c->basic.cantidad);
}

16.4 Interfaz de consola

void limpiarEntrada(void) {
  int c;
  while ((c = getchar()) != '\n' && c != EOF) {}
}

int main(void) {
  CentroSoporte centro;
  soporte_init(¢ro);

  int opcion;
  char cliente[MAX_CLIENTE];
  char nota[MAX_NOTA];
  Ticket ticket;
  int origen, destino;

  do {
    puts("\n1) Registrar ticket basico");
    puts("2) Registrar ticket intermedio");
    puts("3) Registrar ticket experto");
    puts("4) Atender siguiente ticket");
    puts("5) Escalar ticket");
    puts("6) Mostrar estado");
    puts("0) Cerrar turno");
    printf("Opcion: ");
    if (scanf("%d", &opcion) != 1) {
      puts("Entrada invalida");
      limpiarEntrada();
      continue;
    }
    limpiarEntrada();
    switch (opcion) {
      case 1:
      case 2:
      case 3:
        printf("Cliente: ");
        if (!fgets(cliente, sizeof cliente, stdin)) break;
        cliente[strcspn(cliente, "\n")] = '\0';
        printf("Resumen: ");
        if (!fgets(nota, sizeof nota, stdin)) break;
        nota[strcspn(nota, "\n")] = '\0';
        if (registrarTicket(¢ro, cliente, nota, opcion)) {
          puts("Ticket registrado.");
        } else {
          puts("Cola correspondiente llena.");
        }
        break;
      case 4:
        if (atenderTicket(¢ro, &ticket)) {
          printf("Atendiendo [%d] %s - %s\n", ticket.nivel, ticket.cliente, ticket.nota);
        } else {
          puts("No hay tickets pendientes.");
        }
        break;
      case 5:
        printf("Nivel origen (1-3): ");
        scanf("%d", &origen);
        printf("Nivel destino (1-3): ");
        scanf("%d", &destino);
        limpiarEntrada();
        if (escalarTicket(¢ro, origen, destino)) {
          puts("Escalamiento realizado.");
        } else {
          puts("No se pudo escalar (verifica niveles y disponibilidad).");
        }
        break;
      case 6:
        mostrarEstado(¢ro);
        break;
      case 0:
        puts("Turno cerrado.");
        mostrarEstado(¢ro);
        printf("Atendidos experto: %d\n", centro.atendidos[2]);
        printf("Atendidos intermedio: %d\n", centro.atendidos[1]);
        printf("Atendidos basico: %d\n", centro.atendidos[0]);
        break;
      default:
        puts("Opcion no valida.");
    }
  } while (opcion != 0);

  return 0;
}

16.5 Codigo completo para CLion

Usa este archivo para compilar el ejemplo de punta a punta.

#include <stdio.h>
#include <string.h>

#define CAP_TICKETS 40
#define MAX_CLIENTE 40
#define MAX_NOTA 64

typedef struct {
  char cliente[MAX_CLIENTE];
  char nota[MAX_NOTA];
  int nivel;
} Ticket;

typedef struct {
  Ticket datos[CAP_TICKETS];
  int frente;
  int final;
  int cantidad;
} ColaTickets;

typedef struct {
  ColaTickets basic;
  ColaTickets mid;
  ColaTickets expert;
  int atendidos[3];
} CentroSoporte;

void cola_init(ColaTickets *cola) { cola->frente = cola->final = cola->cantidad = 0; }

int cola_push(ColaTickets *cola, Ticket t) {
  if (cola->cantidad == CAP_TICKETS) return 0;
  cola->datos[cola->final] = t;
  cola->final = (cola->final + 1) % CAP_TICKETS;
  cola->cantidad++;
  return 1;
}

int cola_pop(ColaTickets *cola, Ticket *t) {
  if (cola->cantidad == 0) return 0;
  *t = cola->datos[cola->frente];
  cola->frente = (cola->frente + 1) % CAP_TICKETS;
  cola->cantidad--;
  return 1;
}

void soporte_init(CentroSoporte *c) {
  cola_init(&c->basic);
  cola_init(&c->mid);
  cola_init(&c->expert);
  c->atendidos[0] = c->atendidos[1] = c->atendidos[2] = 0;
}

int registrarTicket(CentroSoporte *c, const char *cliente, const char *nota, int nivel) {
  Ticket t;
  strncpy(t.cliente, cliente, MAX_CLIENTE - 1);
  t.cliente[MAX_CLIENTE - 1] = '\0';
  strncpy(t.nota, nota, MAX_NOTA - 1);
  t.nota[MAX_NOTA - 1] = '\0';
  t.nivel = nivel;
  ColaTickets *cola = nivel == 3 ? &c->expert : (nivel == 2 ? &c->mid : &c->basic);
  return cola_push(cola, t);
}

int atenderTicket(CentroSoporte *c, Ticket *t) {
  if (cola_pop(&c->expert, t)) {
    c->atendidos[2]++;
    return 1;
  }
  if (cola_pop(&c->mid, t)) {
    c->atendidos[1]++;
    return 1;
  }
  if (cola_pop(&c->basic, t)) {
    c->atendidos[0]++;
    return 1;
  }
  return 0;
}

int escalarTicket(CentroSoporte *c, int origen, int destino) {
  ColaTickets *colas[3] = { &c->basic, &c->mid, &c->expert };
  if (origen < 1 || origen > 3 || destino < 1 || destino > 3 || destino <= origen) {
    return 0;
  }
  if (colas[origen - 1]->cantidad == 0) {
    return 0;
  }
  Ticket t;
  cola_pop(colas[origen - 1], &t);
  t.nivel = destino;
  return cola_push(colas[destino - 1], t);
}

void mostrarEstado(const CentroSoporte *c) {
  printf("Experto: %d pendientes\n", c->expert.cantidad);
  printf("Intermedio: %d pendientes\n", c->mid.cantidad);
  printf("Basico: %d pendientes\n", c->basic.cantidad);
}

void limpiarEntrada(void) {
  int c;
  while ((c = getchar()) != '\n' && c != EOF) {}
}

int main(void) {
  CentroSoporte centro;
  soporte_init(¢ro);

  int opcion;
  char cliente[MAX_CLIENTE];
  char nota[MAX_NOTA];
  Ticket ticket;
  int origen, destino;

  do {
    puts("\n1) Registrar ticket basico");
    puts("2) Registrar ticket intermedio");
    puts("3) Registrar ticket experto");
    puts("4) Atender siguiente ticket");
    puts("5) Escalar ticket");
    puts("6) Mostrar estado");
    puts("0) Cerrar turno");
    printf("Opcion: ");
    if (scanf("%d", &opcion) != 1) {
      puts("Entrada invalida");
      limpiarEntrada();
      continue;
    }
    limpiarEntrada();
    switch (opcion) {
      case 1:
      case 2:
      case 3:
        printf("Cliente: ");
        if (!fgets(cliente, sizeof cliente, stdin)) break;
        cliente[strcspn(cliente, "\n")] = '\0';
        printf("Resumen: ");
        if (!fgets(nota, sizeof nota, stdin)) break;
        nota[strcspn(nota, "\n")] = '\0';
        if (registrarTicket(¢ro, cliente, nota, opcion)) {
          puts("Ticket registrado.");
        } else {
          puts("Cola correspondiente llena.");
        }
        break;
      case 4:
        if (atenderTicket(¢ro, &ticket)) {
          printf("Atendiendo [%d] %s - %s\n", ticket.nivel, ticket.cliente, ticket.nota);
        } else {
          puts("No hay tickets pendientes.");
        }
        break;
      case 5:
        printf("Nivel origen (1-3): ");
        scanf("%d", &origen);
        printf("Nivel destino (1-3): ");
        scanf("%d", &destino);
        limpiarEntrada();
        if (escalarTicket(¢ro, origen, destino)) {
          puts("Escalamiento realizado.");
        } else {
          puts("No se pudo escalar (verifica niveles y disponibilidad).");
        }
        break;
      case 6:
        mostrarEstado(¢ro);
        break;
      case 0:
        puts("Turno cerrado.");
        mostrarEstado(¢ro);
        printf("Atendidos experto: %d\n", centro.atendidos[2]);
        printf("Atendidos intermedio: %d\n", centro.atendidos[1]);
        printf("Atendidos basico: %d\n", centro.atendidos[0]);
        break;
      default:
        puts("Opcion no valida.");
    }
  } while (opcion != 0);

  return 0;
}

16.6 Pruebas sugeridas

  • Simular un turno con tickets desbalanceados (muchos expertos, pocos basicos) para validar la prioridad.
  • Escalar multiples tickets consecutivos y verificar que las colas destino los reciben en orden.
  • Agregar un limite por SLA (por ejemplo, responder al menos 5 tickets basicos) para extender el ejemplo.

Una version futura incluirá persistencia de tickets y estadisticas semanales.