12 - Ejemplo práctico 1: simulador de turnos

Este es el primero de una serie de ejemplos. Comenzamos con un escenario sencillo que consolida los conceptos básicos antes de avanzar hacia proyectos más complejos en temas posteriores.

12.1 Planteo del problema

Necesitamos un simulador pequeño para una sucursal que atiende clientes en orden de llegada. La cola debe registrar el número de ticket y un nombre breve. El objetivo es permitir tres operaciones desde consola:

  • Agregar un nuevo cliente con ticket incremental.
  • Llamar al siguiente cliente disponible.
  • Mostrar el estado actual de la cola.

Este ejemplo se centra en la cola circular de tamaño fijo, ideal para comenzar porque garantiza O(1) y evita asignaciones dinámicas.

Representación de la cola de turnos en memoria circular

La estructura circular reutiliza casilleros liberados y mantiene el orden de llegada de cada ticket.

12.2 Modelo de datos

Usaremos un arreglo de estructuras con tres campos: ticket, nombre y un flag de ocupación. La cola mantiene punteros frente y final, además de un contador para detectar estados vacío/lleno.

#define CAP_TURNOS 16
#define MAX_NOMBRE 32

typedef struct {
  int ticket;
  char nombre[MAX_NOMBRE];
} Cliente;

typedef struct {
  Cliente datos[CAP_TURNOS];
  int frente;
  int final;
  int cantidad;
  int siguienteTicket;
} ColaTurnos;

void inicializar(ColaTurnos *cola) {
  cola->frente = 0;
  cola->final = 0;
  cola->cantidad = 0;
  cola->siguienteTicket = 1;
}

12.3 Operaciones principales

Las funciones agregarCliente, llamarCliente y mostrarCola encapsulan el flujo. Cada una devuelve un código para informar éxito o error (cola llena/vacía).

int agregarCliente(ColaTurnos *cola, const char *nombre) {
  if (cola->cantidad == CAP_TURNOS) {
    return 0;
  }
  Cliente *slot = &cola->datos[cola->final];
  slot->ticket = cola->siguienteTicket++;
  strncpy(slot->nombre, nombre, MAX_NOMBRE - 1);
  slot->nombre[MAX_NOMBRE - 1] = '\0';
  cola->final = (cola->final + 1) % CAP_TURNOS;
  cola->cantidad++;
  return slot->ticket;
}

int llamarCliente(ColaTurnos *cola, Cliente *cliente) {
  if (cola->cantidad == 0) {
    return 0;
  }
  *cliente = cola->datos[cola->frente];
  cola->frente = (cola->frente + 1) % CAP_TURNOS;
  cola->cantidad--;
  return 1;
}

void mostrarCola(const ColaTurnos *cola) {
  printf("Turnos pendientes (%d):\n", cola->cantidad);
  for (int i = 0, idx = cola->frente; i < cola->cantidad; ++i, idx = (idx + 1) % CAP_TURNOS) {
    printf("  #%d - %s\n", cola->datos[idx].ticket, cola->datos[idx].nombre);
  }
}

12.4 Interfaz de consola

El programa principal muestra un pequeño menú y ejecuta las operaciones según la entrada del usuario. Las entradas se validan para evitar desbordes.

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

int main(void) {
  ColaTurnos cola;
  inicializar(&cola);

  int opcion;
  char nombre[MAX_NOMBRE];
  Cliente cliente;

  do {
    puts("\n1) Agregar cliente");
    puts("2) Llamar siguiente");
    puts("3) Mostrar cola");
    puts("0) Salir");
    printf("Opcion: ");
    if (scanf("%d", &opcion) != 1) {
      puts("Entrada invalida");
      limpiarEntrada();
      continue;
    }
    limpiarEntrada();
    switch (opcion) {
      case 1:
        printf("Nombre: ");
        if (!fgets(nombre, sizeof nombre, stdin)) break;
        nombre[strcspn(nombre, "\n")] = '\0';
        int ticket;
        ticket = agregarCliente(&cola, nombre);
        if (ticket == 0) {
          puts("Cola llena, intenta mas tarde.");
        } else {
          printf("Ticket asignado: #%d\n", ticket);
        }
        break;
      case 2:
        if (llamarCliente(&cola, &cliente)) {
          printf("Atendiendo #%d - %s\n", cliente.ticket, cliente.nombre);
        } else {
          puts("No hay clientes esperando.");
        }
        break;
      case 3:
        mostrarCola(&cola);
        break;
      case 0:
        puts("Hasta luego");
        break;
      default:
        puts("Opcion no valida");
    }
  } while (opcion != 0);

  return 0;
}

12.5 Código completo para CLion

Compila el siguiente archivo en CLion (o cualquier IDE) para probar el flujo sin necesidad de copiar fragmentos por separado:

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

#define CAP_TURNOS 16
#define MAX_NOMBRE 32

typedef struct {
  int ticket;
  char nombre[MAX_NOMBRE];
} Cliente;

typedef struct {
  Cliente datos[CAP_TURNOS];
  int frente;
  int final;
  int cantidad;
  int siguienteTicket;
} ColaTurnos;

void inicializar(ColaTurnos *cola) {
  cola->frente = 0;
  cola->final = 0;
  cola->cantidad = 0;
  cola->siguienteTicket = 1;
}

int agregarCliente(ColaTurnos *cola, const char *nombre) {
  if (cola->cantidad == CAP_TURNOS) {
    return 0;
  }
  Cliente *slot = &cola->datos[cola->final];
  slot->ticket = cola->siguienteTicket++;
  strncpy(slot->nombre, nombre, MAX_NOMBRE - 1);
  slot->nombre[MAX_NOMBRE - 1] = '\0';
  cola->final = (cola->final + 1) % CAP_TURNOS;
  cola->cantidad++;
  return slot->ticket;
}

int llamarCliente(ColaTurnos *cola, Cliente *cliente) {
  if (cola->cantidad == 0) {
    return 0;
  }
  *cliente = cola->datos[cola->frente];
  cola->frente = (cola->frente + 1) % CAP_TURNOS;
  cola->cantidad--;
  return 1;
}

void mostrarCola(const ColaTurnos *cola) {
  printf("Turnos pendientes (%d):\n", cola->cantidad);
  for (int i = 0, idx = cola->frente; i < cola->cantidad; ++i, idx = (idx + 1) % CAP_TURNOS) {
    printf("  #%d - %s\n", cola->datos[idx].ticket, cola->datos[idx].nombre);
  }
}

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

int main(void) {
  ColaTurnos cola;
  inicializar(&cola);

  int opcion;
  char nombre[MAX_NOMBRE];
  Cliente cliente;

  do {
    puts("\n1) Agregar cliente");
    puts("2) Llamar siguiente");
    puts("3) Mostrar cola");
    puts("0) Salir");
    printf("Opcion: ");
    if (scanf("%d", &opcion) != 1) {
      puts("Entrada invalida");
      limpiarEntrada();
      continue;
    }
    limpiarEntrada();
    switch (opcion) {
      case 1: {
        printf("Nombre: ");
        if (!fgets(nombre, sizeof nombre, stdin)) break;
        nombre[strcspn(nombre, "\n")] = '\0';
        int ticket = agregarCliente(&cola, nombre);
        if (ticket == 0) {
          puts("Cola llena, intenta mas tarde.");
        } else {
          printf("Ticket asignado: #%d\n", ticket);
        }
        break;
      }
      case 2:
        if (llamarCliente(&cola, &cliente)) {
          printf("Atendiendo #%d - %s\n", cliente.ticket, cliente.nombre);
        } else {
          puts("No hay clientes esperando.");
        }
        break;
      case 3:
        mostrarCola(&cola);
        break;
      case 0:
        puts("Hasta luego");
        break;
      default:
        puts("Opcion no valida");
    }
  } while (opcion != 0);

  return 0;
}

El programa es autocontenible y puede usarse como plantilla para futuros ejemplos con mayor complejidad.

Flujo general del simulador de turnos con cola circular

La cola circular facilita mantener los tickets en orden incluso cuando la cantidad de clientes alcanza el límite.

12.6 Pruebas sugeridas

  • Ingresar más de 16 clientes para verificar el manejo de cola llena.
  • Atender todos los clientes y comprobar que el contador vuelve a cero.
  • Alternar altas y bajas rápidamente para asegurar que el movimiento circular de punteros funciona.

En los siguientes ejemplos prácticos aumentaremos la complejidad incorporando hilos, prioridades y almacenamiento persistente.