C01 · Analiza antes de programar: la tabla Entrada – Proceso – Salida
ENUNCIADO
Taquilla del cine. Un formulario envía por POST la
edad(número), eldia(unselectcon los siete días de la semana) ycarnet_joven(un checkbox). La entrada cuesta 9 €. Los miércoles cuesta 5,50 €. Los menores de 12 años y los mayores de 65 pagan la mitad. Con carnet joven se resta 1 €, salvo los miércoles. La página muestra el precio final y la lista de descuentos aplicados.
Paso 1. Entrada, Proceso y Salida.
Parte A · Distingue entre Entrada, Proceso y Salida.
Escribe en el cuaderno los números del 1 al 12 y, al lado de cada uno, una E, una P o una S:
- Comprobar que la edad es un entero entre 0 y 120.
- El precio final, con dos decimales y el símbolo €.
- El valor de
diaque llega por POST. - Restar 1 € si se ha marcado el carnet joven y no es miércoles.
- El precio general de 9 €.
- El mensaje «La edad debe ser un número entre 0 y 120».
- Comprobar que
diaes uno de los siete días permitidos. - El valor de
carnet_joven, que solo llega si se marca la casilla. - Aplicar el 50 % de descuento si la edad es menor que 12 o mayor que 65.
- La lista con los descuentos que se han aplicado.
- El valor de
edadque llega por POST. - Volver a mostrar el formulario con lo que escribió el usuario.
Antes de hacer la tabla: dos huecos del enunciado
Al subrayar el enunciado (paso 1) aparecen dos dudas que hay que resolver antes de programar:
- ¿En qué orden se aplican el 50 % y el euro del carnet? No da lo mismo. Con 70 años y carnet joven, un viernes:
- Si primero se aplica la mitad: 9 / 2 − 1 = 3,50 €.
- Si primero se resta el euro: (9 − 1) / 2 = 4,00 €.
- ¿El 50 % por edad se aplica también los miércoles? El enunciado solo excluye el carnet joven los miércoles,pero nososotros lo interpretamos que tampoco se aplica, así que la edad no cuenta: un niño paga 5,50 € los miercoles.
Decisiones:
- Primero se fija el precio del día, si es miercoles, se aplica el descuento y finalizamos .
- Si no es miercoles, se aplica el descuento por edad.
- Si no es miercoles, por último se resta el euro del carnet joven.
- La edad válida va de 0 a 120: un bebé tiene 0 años.
La tabla
| Entrada (¿qué tengo?) | Proceso (¿qué hago?) | Salida (¿qué muestro?) |
|---|---|---|
El método de la petición ($_SERVER['REQUEST_METHOD']): dice si se ha enviado el formulario |
Saber si se ha enviado el formulario | El formulario persistente: la edad escrita, el día elegido y la casilla marcada |
dia (POST): llega como cadena |
|
|
edad (POST): llega como cadena |
|
|
carnet_joven (POST): llega como cadena o no llega. |
|
|
| Datos fijos: 9 €; 5,50 € los miércoles; límites de edad 12 y 65; 50 %; 1 € del carnet; lista de los 7 días; rango de edad 0–120 | Si no hay errores, fijar el precio base: 5,50 € si es miércoles y 9 € si no | Precio final de la entrada |
Errores habituales al construir esta tabla
- Poner acciones en la columna Salida. «Aplicar el 50 %» o «quitar 1 €» son proceso. En Salida solo va lo que se ve: el precio, la lista y los errores.
- Olvidar los datos fijos. El 9 €, el 5,50 €, el 12 y el 65 se usan en los cálculos. Si no están en Entrada, acabarás escribiendo números «mágicos» en mitad del código.
- Aplicar descuentos el miércoles. El miércoles tiene su propio precio y ahí termina el cálculo: no cuentan ni la edad ni el carnet.
- Cambiar el orden de los descuentos. Primero va la edad y después el carnet. Al revés, el precio sale distinto.
- Validar solo la edad. El
selectse puede manipular con F12, así quediatambién se valida (patrón G). - Olvidar el formulario persistente. Es la salida 12 de la parte A.
- Copiar mal los datos del enunciado. El precio de los miércoles es 5,50 €, no 5 €.
- Confundir el botón con un dato. El botón de enviar no hace falta para saber si se ha enviado el formulario: se mira el método de la petición (patrón B).
Paso 3. Pasos para resolver
Rellena en el cuaderno los pasos a resolver tomando como referencia los valores .
Paso 4. Pseudocódigo y traza
4.1. Pseudocódigo
DATOS FIJOS
precio_general ← 9 precio_miercoles ← 5,50
edad_infantil ← 12 edad_mayor ← 65
descuento_carnet ← 1
dias ← lunes, martes, miércoles, jueves, viernes, sábado, domingo
errores ← lista vacía precio ← nada descuentos ← lista vacía
SI se ha enviado el formulario ENTONCES
edad ← dato "edad" sin espacios al principio ni al final
dia ← dato "dia"
carnet ← ¿ha llegado "carnet_joven"?
SI edad no es un entero entre 0 y 120 ENTONCES
errores['edad'] ← "La edad debe ser un número entre 0 y 120."
FIN SI
SI dia no está en dias ENTONCES
errores['dia'] ← "Elige un día de la semana."
FIN SI
SI errores está vacía ENTONCES
SI dia = miércoles ENTONCES
precio ← precio_miercoles
añadir "Precio de miércoles (5,50 €)" a descuentos
SI NO
precio ← precio_general
SI edad < edad_infantil O edad > edad_mayor ENTONCES
precio ← precio / 2
añadir "Descuento por edad (50 %)" a descuentos
FIN SI
SI carnet ENTONCES
precio ← precio − descuento_carnet
añadir "Carnet joven (−1 €)" a descuentos
FIN SI
FIN SI
FIN SI
FIN SI
mostrar el formulario con edad, dia y carnet
SI hay errores ENTONCES mostrar cada error junto a su campo
SI hay precio ENTONCES mostrar el precio con 2 decimales y €, y la lista de descuentos
Fíjate en dos detalles:
- Los descuentos están dentro del
SI NOdel miércoles. Por eso, si es miércoles, no se llega a comprobarlos y ya no hace falta preguntar «Y dia ≠ miércoles» en el carnet. - Dentro del
SI NO, los dosSIvan uno detrás de otro, no conSI NO, porque una persona puede tener los dos descuentos. El orden en que se escriben es el orden en que se aplican: primero la edad y después el carnet.
4.2. Traza
Sigue el pseudocódigo a mano y apunta el precio después de cada SI:
| Caso | edad |
dia |
carnet |
Precio base | Tras la edad | Tras el carnet | Descuentos | Se ve |
|---|---|---|---|---|---|---|---|---|
| Normal | 30 | lunes | No | 9 | 9 | 9 | ninguno | 9,00 €, «Sin descuentos» |
| Carnet en miércoles | 30 | miércoles | Sí | 5,50 | — | — | miércoles | 5,50 € (no se comprueba el carnet) |
| Niño en miércoles | 8 | miércoles | No | 5,50 | — | — | miércoles | 5,50 € (no se comprueba la edad) |
| Todo a la vez | 70 | viernes | Sí | 9 | 4,50 | 3,50 | edad, carnet | 3,50 € |
| Límite | 12 | martes | Sí | 9 | 9 | 8 | carnet | 8,00 € (12 no es menor que 12) |
| Límite | 0 | sábado | No | 9 | 4,50 | 4,50 | edad | 4,50 € |
| Erróneo | abc |
lunes | — | — | — | — | — | Error en la edad |
| Erróneo | 30 | (vacío) | — | — | — | — | — | Error en el día |
Si tu traza da estos resultados, el algoritmo está bien y solo queda traducirlo a PHP.
Paso 5. Programa poco a poco
Estos son los trozos en el orden en que se van a programar. Cada uno se prueba antes de pasar al siguiente:
- Trozo 1. Los datos fijos y el formulario, con el
selectgenerado por un bucle. - Trozo 2. Saber si se ha enviado el formulario y recoger los datos.
- Trozo 3. Validar la edad y el día.
- Trozo 4. Calcular el precio y la lista de descuentos.
- Trozo 5. Mostrar el formulario persistente, los errores y el resultado.
Trozo 1 · Datos fijos y formulario
Los datos fijos de la columna Entrada se escriben al principio, como constantes. Así, si mañana la entrada sube a 9,50 €, solo cambias una línea.
Los días van en un array asociativo:
- la clave es el
valueque viaja en el formulario, sin tildes; - el valor es el texto que ve el usuario.
Con ese array se generan las <option> con un foreach, en lugar de escribirlas a mano.
<?php
// ── Datos fijos ──────────────────────────────────────────────
const PRECIO_GENERAL = 9.00;
const PRECIO_MIERCOLES = 5.50;
const DESCUENTO_CARNET = 1.00;
const EDAD_INFANTIL = 12; // los menores de 12 pagan la mitad
const EDAD_MAYOR = 65; // los mayores de 65 pagan la mitad
// Días permitidos: clave = value del formulario, valor = texto visible
$dias = [
'lunes' => 'Lunes',
'martes' => 'Martes',
'miercoles' => 'Miércoles',
'jueves' => 'Jueves',
'viernes' => 'Viernes',
'sabado' => 'Sábado',
'domingo' => 'Domingo',
];
?>
<form method="post">
<input type="text" name="edad">
<select name="dia">
<option value="">Elige un día</option>
<?php foreach ($dias as $valor => $nombre): ?>
<option value="<?= $valor ?>"><?= $nombre ?></option>
<?php endforeach; ?>
</select>
<label><input type="checkbox" name="carnet_joven" value="si"> Tengo carnet joven</label>
<button type="submit">Calcular precio</button>
</form>
Abre el inspector (F12) y comprueba que hay 8 <option>: la vacía y los 7 días, cada uno con su value sin tilde.
git add ut03c01.php
git commit -m "C01: datos fijos y formulario de la taquilla"
Trozo 2 · ¿Se ha enviado? Recoger los datos
Entre los datos fijos y el ?>, añade la zona de lógica (patrón A). Empieza por las variables que usará el HTML, con los valores de la primera visita:
// ── Variables que usa el HTML (valores de la primera visita) ──
$edadTexto = ''; // lo que escribió el usuario
$dia = ''; // el día elegido
$carnet = false; // ¿se marcó la casilla?
$errores = []; // errores por campo
$precio = null; // precio final, si todo es válido
$descuentos = []; // textos de los descuentos aplicados
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$edadTexto = trim($_POST['edad'] ?? '');
$dia = $_POST['dia'] ?? '';
$carnet = isset($_POST['carnet_joven']);
echo '<pre>'; // depuración provisional
var_dump($_POST, $carnet);
echo '</pre>';
}
Envía 70, Viernes y la casilla marcada. Después repite sin marcar la casilla:
array(3) { array(2) {
["edad"]=> string(2) "70" ["edad"]=> string(2) "70"
["dia"]=> string(7) "viernes" ["dia"]=> string(7) "viernes"
["carnet_joven"]=> string(2) "si" }
} bool(false)
bool(true)
Con la casilla sin marcar, la clave carnet_joven ni siquiera aparece en $_POST. Por eso no se pregunta por su valor, sino si existe, con isset(). Fíjate también en que la edad llega como string, aunque escribas un número.
Trozo 3 · Validar
Sustituye el var_dump() por la validación. Cada error se guarda con el nombre de su campo como clave (patrón C), para mostrarlo después debajo de ese campo:
// Validar la edad: entero entre 0 y 120
$edad = filter_var($edadTexto, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 0, 'max_range' => 120],
]);
if ($edad === false) {
$errores['edad'] = 'La edad debe ser un número entre 0 y 120.';
}
// Validar el día con lista blanca (patrón G)
if (!array_key_exists($dia, $dias)) {
$errores['dia'] = 'Elige un día de la semana.';
}
Pruébalo con un var_dump($edad, $errores) provisional:
| Escribes | $edad |
$errores |
|---|---|---|
30, Lunes |
int(30) |
vacío |
0, Lunes |
int(0) |
vacío |
abc, Lunes |
bool(false) |
clave edad |
121, Lunes |
bool(false) |
clave edad |
30, Elige un día |
int(30) |
clave dia |
30 y, con F12, cambias un value a festivo |
int(30) |
clave dia |
Aquí === false es imprescindible
Un bebé tiene 0 años y filter_var() devuelve int(0), que es válido. Si escribieras if (!$edad), el 0 se evaluaría como falso y la página diría que la edad no es válida.
Trozo 4 · Calcular
Debajo de la validación, y solo si no hay errores, se traduce el cálculo del pseudocódigo con la misma estructura: un if … else para el miércoles y, dentro del else, dos if seguidos:
// Calcular el precio solo si todo es válido
if (empty($errores)) {
if ($dia === 'miercoles') {
// Miércoles: precio especial y ningún descuento más
$precio = PRECIO_MIERCOLES;
$descuentos[] = 'Precio de miércoles (5,50 €)';
} else {
// Resto de días: precio general y descuentos en orden
$precio = PRECIO_GENERAL;
// 1. Descuento por edad
if ($edad < EDAD_INFANTIL || $edad > EDAD_MAYOR) {
$precio = $precio / 2;
$descuentos[] = 'Descuento por edad (50 %)';
}
// 2. Carnet joven, después del descuento por edad
if ($carnet) {
$precio -= DESCUENTO_CARNET;
$descuentos[] = 'Carnet joven (−1 €)';
}
}
}
Los comentarios // 1. y // 2. recuerdan que el orden de los dos if no se puede cambiar: si los intercambias, el caso de 70 años con carnet da 4,00 € en lugar de 3,50 €.
Comprueba con un var_dump($precio, $descuentos) provisional los casos de la traza. Por ejemplo, con 70, Viernes y carnet:
float(3.5)
array(2) {
[0]=> string(25) "Descuento por edad (50 %)"
[1]=> string(23) "Carnet joven (−1 €)"
}
Si cada caso coincide con la traza del paso 4, borra el var_dump() y guarda:
git commit -am "C01: validación y cálculo del precio"
Trozo 5 · Mostrar
La zona de presentación solo muestra lo que ya está calculado:
- Edad persistente (patrones D y E):
valuecon$edadTexto, escapado conhtmlspecialchars(). - Día persistente: dentro del
foreach, se añadeselecteda la opción cuyovaluecoincide con$dia. - Casilla persistente: se añade
checkedsi$carnetestrue. - Errores: cada campo pregunta si existe su clave en
$errores. Si existe, se pone en rojo (is-danger) y muestra el mensaje debajo. - Resultado: solo si
$preciono esnull. El precio se formatea connumber_format()usando coma decimal. Esto es dar formato al dato para mostrarlo, no calcular. Si no hay descuentos, se muestra «Sin descuentos».
git commit -am "C01: formulario persistente, errores y resultado"
Vuelve a pasar todos los casos de la traza en el navegador y comprueba que coinciden.
Código completo
ut03c01.php
<?php
/**
* C01 · Taquilla del cine
* Página autoprocesada: calcula el precio de la entrada según el día,
* la edad y el carnet joven, y muestra los descuentos aplicados.
*/
// ── Datos fijos ──────────────────────────────────────────────
const PRECIO_GENERAL = 9.00;
const PRECIO_MIERCOLES = 5.50;
const DESCUENTO_CARNET = 1.00;
const EDAD_INFANTIL = 12; // los menores de 12 pagan la mitad
const EDAD_MAYOR = 65; // los mayores de 65 pagan la mitad
// Días permitidos: clave = value del formulario, valor = texto visible
$dias = [
'lunes' => 'Lunes',
'martes' => 'Martes',
'miercoles' => 'Miércoles',
'jueves' => 'Jueves',
'viernes' => 'Viernes',
'sabado' => 'Sábado',
'domingo' => 'Domingo',
];
// ── Variables que usa el HTML (valores de la primera visita) ──
$edadTexto = ''; // lo que escribió el usuario
$dia = ''; // el día elegido
$carnet = false; // ¿se marcó la casilla?
$errores = []; // errores por campo
$precio = null; // precio final, si todo es válido
$descuentos = []; // textos de los descuentos aplicados
// ── Zona de lógica ───────────────────────────────────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Recoger los datos
$edadTexto = trim($_POST['edad'] ?? '');
$dia = $_POST['dia'] ?? '';
$carnet = isset($_POST['carnet_joven']); // el checkbox solo llega si se marca
// Validar la edad: entero entre 0 y 120
$edad = filter_var($edadTexto, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 0, 'max_range' => 120],
]);
if ($edad === false) {
$errores['edad'] = 'La edad debe ser un número entre 0 y 120.';
}
// Validar el día con lista blanca
if (!array_key_exists($dia, $dias)) {
$errores['dia'] = 'Elige un día de la semana.';
}
// Calcular el precio solo si todo es válido
if (empty($errores)) {
if ($dia === 'miercoles') {
// Miércoles: precio especial y ningún descuento más
$precio = PRECIO_MIERCOLES;
$descuentos[] = 'Precio de miércoles (5,50 €)';
} else {
// Resto de días: precio general y descuentos en orden
$precio = PRECIO_GENERAL;
// 1. Descuento por edad
if ($edad < EDAD_INFANTIL || $edad > EDAD_MAYOR) {
$precio = $precio / 2;
$descuentos[] = 'Descuento por edad (50 %)';
}
// 2. Carnet joven, después del descuento por edad
if ($carnet) {
$precio -= DESCUENTO_CARNET;
$descuentos[] = 'Carnet joven (−1 €)';
}
}
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>C01 · Taquilla del cine</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css">
</head>
<body>
<section class="section">
<div class="container" style="max-width: 32rem;">
<h1 class="title">Taquilla del cine</h1>
<!-- Sin "action": el formulario se envía a esta misma página -->
<form method="post">
<div class="field">
<label class="label" for="edad">Edad</label>
<div class="control">
<input class="input<?= isset($errores['edad']) ? ' is-danger' : '' ?>"
type="text" id="edad" name="edad" inputmode="numeric"
value="<?= htmlspecialchars($edadTexto) ?>">
</div>
<?php if (isset($errores['edad'])): ?>
<p class="help is-danger"><?= $errores['edad'] ?></p>
<?php endif; ?>
</div>
<div class="field">
<label class="label" for="dia">Día</label>
<div class="control">
<div class="select<?= isset($errores['dia']) ? ' is-danger' : '' ?>">
<select id="dia" name="dia">
<option value="">Elige un día</option>
<?php foreach ($dias as $valor => $nombre): ?>
<option value="<?= $valor ?>"<?= $valor === $dia ? ' selected' : '' ?>><?= $nombre ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<?php if (isset($errores['dia'])): ?>
<p class="help is-danger"><?= $errores['dia'] ?></p>
<?php endif; ?>
</div>
<div class="field">
<label class="checkbox">
<input type="checkbox" name="carnet_joven" value="si"<?= $carnet ? ' checked' : '' ?>>
Tengo carnet joven
</label>
</div>
<div class="field">
<button type="submit" class="button is-primary">Calcular precio</button>
</div>
</form>
<?php if ($precio !== null): ?>
<div class="notification is-success content mt-5">
<p>Precio final: <strong><?= number_format($precio, 2, ',', '.') ?> €</strong></p>
<?php if (empty($descuentos)): ?>
<p>Sin descuentos.</p>
<?php else: ?>
<p>Descuentos aplicados:</p>
<ul>
<?php foreach ($descuentos as $descuento): ?>
<li><?= $descuento ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</section>
</body>
</html>
Los errores, los nombres de los días y los textos de los descuentos se muestran sin htmlspecialchars() porque los escribimos nosotros en el código. $edadTexto sí viene del usuario, y por eso se escapa.
$dia también viene del usuario, pero nunca se muestra directamente: solo se compara con las claves de $dias.