Un práctico para hacer en clases, al final de la unidad. Junta todo lo visto: variables, condicionales, ciclos, arreglos, include y formularios. Se construye por pasos; cada uno indica qué parte de la unidad usa y termina con un commit.

El encargo

El Cine del Estrecho, un cine de Punta Arenas con dos salas, quiere un sitio con dos vistas: la cartelera de hoy y un formulario para comprar entradas. No tiene base de datos: las películas viven en un arreglo de PHP.

Archivos del proyecto

cine-del-estrecho/
|-- index.php            <- cartelera: una tarjeta por película
|-- comprar.php          <- formulario de compra y resumen
|-- datos.php            <- los arreglos $peliculas y $precios
`-- partes/
    |-- cabecera.php     <- <head>, Tailwind y navbar con la página activa
    `-- pie.php          <- footer con el año actual

Cómo se navega

flowchart LR
    A["index.php<br/>cartelera"] -->|"navbar"| B["comprar.php<br/>formulario"]
    B -->|"POST con errores"| B
    B -->|"POST correcto"| C["comprar.php<br/>resumen de la compra"]

Crea un repositorio llamado cine-del-estrecho, clónalo y trabaja dentro de esa carpeta. Para verlo en el navegador: php -S localhost:8000.

Los datos

Copia este archivo tal cual. Cada película es una función (una sala y una hora).

<?php
// datos.php
$peliculas = [
    ["titulo" => "El faro del fin del mundo", "genero" => "Drama", "duracion" => 128, "clasificacion" => "+14", "sala" => 1, "hora" => "18:30", "cupos" => 24],
    ["titulo" => "Pingüinos de Magdalena", "genero" => "Documental", "duracion" => 95, "clasificacion" => "TE", "sala" => 2, "hora" => "16:00", "cupos" => 40],
    ["titulo" => "Viento blanco", "genero" => "Suspenso", "duracion" => 110, "clasificacion" => "+18", "sala" => 1, "hora" => "22:00", "cupos" => 0],
    ["titulo" => "La última barcaza", "genero" => "Aventura", "duracion" => 120, "clasificacion" => "+7", "sala" => 2, "hora" => "19:15", "cupos" => 3],
    ["titulo" => "Noche en Porvenir", "genero" => "Comedia", "duracion" => 102, "clasificacion" => "+14", "sala" => 1, "hora" => "20:45", "cupos" => 18],
    ["titulo" => "Estrecho", "genero" => "Drama", "duracion" => 141, "clasificacion" => "+7", "sala" => 2, "hora" => "21:30", "cupos" => 0],
];
 
$precios = [
    "general" => 5500,
    "estudiante" => 3500,
    "adulto-mayor" => 3000,
];
  • duracion está en minutos.
  • clasificacion es "TE", "+7", "+14" o "+18".
  • Una película con "cupos" => 0 está agotada.
  • $precios es un arreglo asociativo: la clave es el tipo de entrada y el valor, su precio.

Paso 1 · Partes compartidas

Parte 5: include y require.

  • partes/cabecera.php con el <!DOCTYPE html>, el <head> (con la meta viewport y Tailwind por CDN) y una navbar con dos enlaces: Cartelera y Comprar
  • La navbar marca la vista actual con una clase distinta, según una variable $pagina definida en cada vista
  • El <title> de cada vista sale de una variable $titulo
  • partes/pie.php con un footer que muestra el año actual con date("Y") y la zona horaria America/Punta_Arenas
  • index.php y comprar.php cargan datos.php con require_once e incluyen la cabecera y el pie con include, usando __DIR__

Prueba de este paso

Las dos vistas tienen la misma navbar y el mismo pie, y en cada una aparece marcado el enlace correcto.

Commit: partes compartidas y navbar activa.

Paso 2 · La cartelera

Parte 2, Parte 3, Parte 5: tablas desde una lista de registros.

En index.php, una tarjeta por película, generada con foreach:

  • Grilla responsiva con Tailwind: una columna en el teléfono, dos en la tablet y tres en el computador
  • Cada tarjeta muestra el título, el género, la duración, la clasificación, la sala y la hora
  • La duración en horas y minutos: 128 se muestra como 2 h 8 min, y 120 como 2 h (sin 0 min)
  • La clasificación es una insignia de color, con la clase elegida con match: verde para TE, celeste para +7, ámbar para +14 y rojo para +18
  • El estado de los cupos: Agotada si son 0, Últimos N cupos si quedan menos de 5, y N cupos en otro caso
  • Sobre la grilla, cuántas películas hay en cartelera, con count()
  • Si $peliculas está vacío, en vez de la grilla aparece No hay películas esta semana.

Pistas

  • Horas y minutos salen de intdiv($duracion, 60) y $duracion % 60 (Operadores).
  • Calcula la duración y el color al comienzo de cada vuelta, en un bloque <?php … ?>, y en el HTML solo imprímelos.
  • El match devuelve las clases completas, como en Clases de Tailwind: siempre completas.

Lo que se ve en dos de las tarjetas:

TítuloDatosClasificaciónEstado
El faro del fin del mundoDrama · 2 h 8 min · Sala 1 · 18:30+14 (ámbar)24 cupos
La última barcazaAventura · 2 h · Sala 2 · 19:15+7 (celeste)Últimos 3 cupos

Commit: cartelera generada desde los datos.

Paso 3 · El formulario de compra

Parte 4: ciclos que generan HTML, Parte 5: arreglos y foreach.

En comprar.php, un formulario method="post" que se envía a la misma página, con cada campo y su <label for>:

  • Nombre y correo
  • Película: un <select> generado con foreach que muestra solo las películas con cupos, con el título y la hora. El value de cada opción es la posición de la película en $peliculas
  • Cantidad: un <select> del 1 al 10 generado con for
  • Tipo de entrada: un <select> generado con foreach ($precios as $tipo => $precio), que muestra el tipo y su precio (por ejemplo, estudiante · $3.500)
  • Cada <select> empieza con una opción vacía que pide elegir (Elige una película), salvo la cantidad

Pista: la posición como value

Con foreach ($peliculas as $posicion => $pelicula), $posicion vale 0, 1, 2… Si la película de la posición 2 está agotada, simplemente no se imprime su <option>: el if va dentro del foreach.

Commit: formulario de compra.

Paso 4 · Validar en el servidor

Parte 6: Validar en el servidor.

Al enviar, PHP revisa cada campo y guarda los errores en $errores, con el name del campo como clave:

  • Nombre: obligatorio, de al menos 3 letras (mb_strlen)
  • Correo: con formato válido (FILTER_VALIDATE_EMAIL)
  • Película: tiene que existir en $peliculas y tener cupos
  • Cantidad: un entero entre 1 y 10, que además no supere los cupos de esa película (Solo quedan 3 cupos para esa función.)
  • Tipo: tiene que ser una de las claves de $precios
  • Cada error aparece debajo de su campo, en texto
  • Si hay errores, el formulario conserva lo escrito y lo elegido en cada <select>
  • En la primera visita (sin enviar) no aparece ningún error ni ningún Warning

Pistas

  • Todo llega como texto: la película 3 llega como "3". isset($peliculas[$elegida]) dice si existe esa posición, y isset($precios[$tipo]) si existe ese tipo: no hace falta recorrer nada.
  • Para la cantidad, convierte con (int) antes de comparar.
  • Revisa los cupos solo si la película es válida: si no existe, no se puede leer $peliculas[$elegida]["cupos"].
  • Para conservar lo elegido, compara el value de cada opción con lo que llegó, ambos como texto: (string) $posicion === $elegida.

Commit: validacion de la compra en el servidor.

Paso 5 · El resumen de la compra

Si no hay errores, en vez del formulario se muestra el resumen:

  • Un saludo con el nombre y el correo al que se envían las entradas
  • La película, la sala y la hora
  • La cantidad, el tipo y el precio unitario
  • Si se compran 5 o más entradas, un 10% de descuento sobre el subtotal
  • El total, con punto de miles
  • El nombre y el correo se imprimen con htmlspecialchars()

Por ejemplo, 5 entradas de estudiante para Pingüinos de Magdalena:

ConceptoMonto
5 entradas de estudiante × $3.500$17.500
Descuento por 5 o más entradas-$1.750
Total$15.750

Los cupos no bajan, y está bien

Sin base de datos, el sitio no puede recordar una compra: si recargas, los cupos vuelven a su valor original. Guardar las compras es lo que se agrega en la Unidad 3.

Commit: resumen de la compra con descuento.

Antes de terminar

Recorre el sitio completo y revisa cada caso:

  • La cartelera muestra las 6 películas, con 2 h para La última barcaza y Agotada en Viento blanco y Estrecho
  • Con $peliculas = []; aparece el mensaje de cartelera vacía (luego vuelve a dejar los datos)
  • Las películas agotadas no aparecen en el <select> de la compra
  • El formulario enviado vacío muestra un error en cada campo y ningún Warning
  • 5 entradas para La última barcaza dan el error de cupos y conservan lo elegido
  • Con F12, cambia el value de una película a 2 (Viento blanco) y el de un tipo a vip: PHP rechaza los dos
  • Un nombre como <b>Ana</b> se ve en el resumen tal cual, sin negrita
  • La compra del ejemplo da un total de $15.750
  • El sitio se ve bien en el teléfono (F12 → vista de dispositivo)

Cero mensajes de PHP

Ninguna vista puede mostrar un Warning, un Deprecated o un Fatal error, en ningún caso: tampoco con el formulario vacío ni con valores cambiados con F12.

Si terminas antes

  • Resumen de la cartelera: bajo el título, cuántas películas están agotadas y cuántos cupos quedan en total, calculados con un acumulador en un foreach.
  • Tabla de precios: en comprar.php, una tabla con el total de 1 a 10 entradas generales, con el descuento desde 5, como en los ejercicios de ciclos.
  • Botón por película: en cada tarjeta con cupos, un enlace Comprar a comprar.php.