Paso 1. El navegador no tiene una cookie todavía
En este primer paso, el navegador no tiene una cookie todavía, por ello al iniciar la comunicación no envía ningún dato adicional:
Va siendo hora de comenzar a dar pasos firmes en el desarrollo del nuevo proyecto, y en BK Programación ultiman los preparativos y se toman las últimas decisiones para establecer el método que seguirán en su desarrollo.
Mientras, Carlos revisa los conocimientos adquiridos y decide hacer una lista con todo lo aprendido hasta el momento y otra con todo lo que aún no sabe hacer. El saldo final es bastante positivo: ya sabe utilizar variables, hacer llamadas a funciones, estructurar el código, utilizar formularios web, trabajar con bases de datos… ¡Incluso ha diseñado una pequeña página para gestionar su colección de comics!
Entre lo que no sabe hacer todavía hay algo que le llama la atención: no sabe cómo mantener los datos entre las distintas páginas de una aplicación. Es decir, la única forma que conoce es utilizando un formulario web…, o bases de datos. Pero tiene que haber otras maneras más adecuadas. Algún modo sencillo de mantener información del usuario dentro de la aplicación web.
De nuevo tendrá que pedir consejo a Juan.
En esta unidad nos adentrarnos en un nuevo nivel en lo relativo al desarrollo de aplicaciones web. Si analizas como son hoy día la mayoría de los sitios que visitas de Internet te darás cuenta de que todos, o casi todos, implementan mecanismos de autentificación y autorización. Es decir, se provee de un mecanismo para que el usuario pueda iniciar sesión, realizar algún conjunto de acciones en el sitio web que involucran el uso de información propia y privada, y después, cuando el usuario estima oportuno o cuando se dan circunstancias concretas, se lleva a cabo el cierre de la sesión.
Este proceso, a priori sencillo, tiene repercusiones importantes:
En esta unidad abordaremos estos importantes aspectos de forma introductoria.
Creado con eXeLearning (Ventana nueva)
Tal y como Esteban les ha explicado, el objetivo principal del proyecto no es crear una página web pública, con información sobre la empresa. Necesitan una aplicación web con un objetivo más específico: permitir a clientes y a empleados conocer información sobre los productos de la empresa.
Juan sabe que con estas condiciones, uno de los puntos fundamentales con los que deberá tratar en el nuevo proyecto es el control de acceso a la aplicación web. Todos los usuarios que accedan habrán de identificarse para poder acceder a las páginas del sitio web. Además, en función de si el usuario es un cliente o un empleado, habrá que darle acceso a una o a otra información.
Juan nunca ha programado sitios web con autentificación de los usuarios. Además, se encuentra terminando otro proyecto, y no dispone de demasiado tiempo. Por este motivo, le pide a Carlos que se documente sobre el tema para poder decidir el camino a tomar.
¿Te imaginas una aplicación web que almacenando datos de usuarios, tales como direcciones o los pedidos que han realizado, permitiese que dichos datos fuesen accesibles sin autenticación? Obviamente, no es lógico y tampoco aceptable, aparte de que contraviene leyes como la LOPD.
En la web es importante verificar la identidad de los usuarios y controlar muy bien el acceso a los datos, especialmente los privados. Un tercero no debe tener visibilidad de datos que no le corresponden y eso es responsabilidad en gran parte del desarrollador web. Pero además, la autenticación es algo que está presente en los dos extremos de la comunicación. En el caso de una comunicación web, existen métodos para identificar tanto al servidor en el que se aloja el sitio web, como al usuario del navegador que se encuentra en el otro extremo.
Los sitios web que necesitan emplear identificación del servidor, como las tiendas o los bancos, utilizan el protocolo HTTPS. Este protocolo requiere de un certificado válido, firmado por una autoridad confiable, que es verificado por el navegador cuando se accede al sitio web. Además, HTTPS utiliza métodos de cifrado para crear un canal seguro entre el navegador y el servidor, de tal forma que no se pueda interceptar la información que se transmite por el mismo.
Para identificar a los usuarios que visitan un sitio web, se pueden utilizar distintos métodos como el DNI digital o certificados digitales de usuario,Certificado digital de usuario.Es un documento digital que contiene información acerca del usuario como el nombre o la dirección. Esa información está firmada por otra entidad, llamada entidad certificadora, que debe ser de confianza y garantiza que la información que contiene es cierta. pero el más extendido es solicitar al usuario cierta información que solo él conoce: la combinación de un nombre de usuario y una contraseña.
En la unidad anterior aprendiste a utilizar aplicaciones web para gestionar información almacenada en bases de datos. En la mayoría de los casos es importante implantar en este tipo de aplicaciones web, las que acceden a bases de datos y gestionan datos privados de usuarios, algún mecanismo de control de acceso que obligue al usuario a identificarse. Una vez identificado, se puede limitar el uso que puede hacer de la información.
Así, puede haber sitios web en los que los usuarios autentificados pueden utilizar sólo una parte de la información (como los bancos, que permiten a sus clientes acceder únicamente a la información relativa a sus cuentas). Otros sitios web necesitan separar en grupos a los usuarios autentificados, de tal forma que la información a la que accede un usuario depende del grupo en que éste se encuentre. Por ejemplo, una aplicación de gestión de una empresa puede tener un grupo de usuarios a los que permite visualizar la información, y otro grupo de usuarios que, además de visualizar la información, también la pueden modificar.
Debes distinguir la autentificación de los usuarios y el control de acceso, de la utilización de mecanismos para asegurar las comunicaciones entre el usuario del navegador y el servidor web. Aunque ambos aspectos suelen ir unidos, son independientes.
En los ejemplos de esta unidad, la información de autentificación (nombre y contraseña de los usuarios) se envía en texto plano desde el navegador hasta el servidor web. Esta práctica es altamente insegura y nunca debe usarse sin un protocolo como HTTPS que permita cifrar las comunicaciones con el servidor web. Sin embargo, la configuración de servidores web que permitan usar el protocolo HTTPS para cifrar la información que reciben y transmiten no forma parte de los contenidos de este módulo. Por este motivo, durante esta unidad utilizaremos únicamente el protocolo no seguro HTTP.
Creado con eXeLearning (Ventana nueva)
En esta unidad abordaremos fundamentalmente un mecanismo de autenticación de usuarios, aunque existen muchos mecanismos hoy día. El objetivo de este apartado es acercarte a ellos y darte una visión general de los mismos planteándonos diferentes preguntas sobre la misma.
Como se introdujo en el apartado anterior, en la web, la comunicación tiene dos partes que se envían datos entre sí usando el protocolo HTTP: el cliente y el servidor. Es primordial que el cliente pueda confiar en el servidor web para que no se produzcan situaciones de inseguridad, como:
Para resolver estos problemas de seguridad, la solución más ampliamente adoptada es implementar en el servidor el protocolo HTTPS. Como se comentó en el apartado anterior, es un aspecto relativo a configuración del servidor web, pero vamos a resumir los aspectos generales de su funcionamiento.
En el protocolo HTTPS los servidores web utilizan un certificado de clave asimétrica. Esto quiere decir que tienen dos claves: una privada que no comparten con nadie, y otra pública que comparten con el mundo. Las claves asimétricas tienen la cualidad curiosa: cuando se encripta una comunicación con una de las claves se debe usar la otra para desencriptarla. Usando este mecanismo, los servidores web hacen pública una de sus claves (la clave pública), lo que permite al cliente web (navegador) podrá usar dicha clave para encriptar y desencriptar la comunicación entre el mismo y el servidor web.
Sin embargo, surge una pregunta recurrente en este esquema: ¿quién asegura que una clave pública es de quien dice ser? Es decir, si yo genero un par de claves asimétricas y afirmo ser una empresa determinada, ¿cómo se garantiza que no estoy mintiendo? Aquí es donde entra en juego una tercera entidad llamada CA (Certification Autority en inglés o Autoridad de certificación en español). Esta entidad almacena parte de la información de los certificados de seguridad de los servidores web y certifica que son válidos, evitando que la comunicación se vea comprometida.
A la hora de establecer un vínculo de confianza entre el servidor web y el usuario final existen varios mecanismos, algunos sencillos y otros más sofisticados. Vamos a revisar los mecanismos principales:
Aparte de los mecanismos anteriores, existen situaciones donde se implementan mecanismos diferentes, como por ejemplo cuando se implementan servicios web o aplicaciones tipo SPA. En estos escenarios se utilizan autenticaciones basadas en tokens que pueden ser diferentes, como por ejemplo usando JWT (JSON Web Tokens), entre otras opciones.
Por último, no debemos olvidar abordar a la siguiente pregunta: tras la autenticación de un usuario, ¿qué puede hacer y qué puede no hacer en la aplicación web? Lo que un usuario final está autorizado a hacer o no en una aplicación web se denomina privilegios o permisos de usuario. La implementación de mecanismos para controlar los privilegios de cada usuario es muy importante en las aplicaciones web hoy día y normalmente se diferencian dos tipos de estrategias:
La diferencia entre ambos mecanismos es que en el segundo los permisos están mucho más granularizados.
Un servicio web es un conjunto de funcionalidades que se ofrecen a través de internet y del protocolo HTTP. Se basa en el uso de estándares y protocolos muy conocidos, como HTTP, SOAP, XML, JSON, etc., para permitir la comunicación entre diferentes sistemas (proveedor del servicio web) y aplicaciones (consumidores del servicio web). Los servicios web proporcionan una interfaz que permite a los clientes realizar solicitudes y recibir respuestas, como por ejemplo REST o SOAP, lo que les permite acceder y utilizar funcionalidades y datos remotos.
Una SPA (Single Page Application) es un tipo de aplicación web que se carga completamente en una sola página y ofrece una experiencia de usuario fluida y dinámica a través de frameworks Javascript. Este tipo de aplicaciones evita la recarga de páginas en cada petición HTTP y lo sustituye por la actualización dinámica del contenido de la página usando Javascript para crear una interfaz dinámica e interactiva. En este tipo de aplicaciones la comunicación entre el servidor web y el navegador se realiza desde Javascript a través de AJAX e incluso WebSockets.
En el siguiente enlace encontrarás más información sobre el funcionamiento de HTTPS:
Y en el siguiente enlace encontrarás un tutorial muy interesante sobre OAuth2:
Por último, puede interesarte consultar el siguiente enlace con información sobre OpenID:
Creado con eXeLearning (Ventana nueva)
Para abordar esta unidad adecuadamente tenemos que profundizar ligeramente en el protocolo HTTP. En este protocolo el navegador web puede realizar peticiones HTTP de diferentes tipos al servidor web. Hay muchos tipos de peticiones HTTP, pero las que manejamos ahora mismo son las peticiones GET y las peticiones POST. Al tipo de petición se le suele llamar método HTTP o verbo HTTP dependiendo del contexto.
En el método GET los datos se envían al servidor web en la mismo recurso, por ejemplo y el navegador podría solicitar la página web buscar.php de la página www.juntadeandalucia.es enviado un mensaje como este al servidor web:
GET /buscar.php?texto=ejemplo&idioma=es HTTP/1.1 Host: www.juntadeandalucia.es User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Accept-Language: es-ES,es;q=0.9,en;q=0.8
El script buscar.php no en ese dominio, es simplemente un ejemplo que puede servirte de orientación. Fíjate como en la ruta del recurso: /buscar.php?texto=ejemplo&idioma=es; se incluyen parámetros que luego en PHP encontraremos en la variable superglobal $_GET.
El uso del método POST es similar, cuando un navegador envía datos a un servidor web en una petición POST se enviaría algo como lo siguiente:
POST /buscar.php HTTP/1.1 Host: www.juntadeandalucia.es User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Accept-Language: es-ES,es;q=0.9,en;q=0.8 Content-Type: application/x-www-form-urlencoded Content-Length: 23 texto=ejemplo&idioma=es
Los mensajes anteriores contienen cabeceras de petición. Es decir, en la petición HTTP que realiza el navegador al servidor web se incluyen algunas cabeceras (como User-Agent o Accept-Language o Content-Length). Estas cabeceras permiten al navegador indicar datos adicionales que son o pueden ser importantes para que el servidor web genere una respuesta HTTP.
En respuesta a esas peticiones HTTP los servidores web generan una respuesta HTTP. Las respuestas HTTP suelen seguir el siguiente formato:
HTTP/1.1 200 OK
Date: Thu, 20 May 2023 15:30:00 GMT
Server: Apache/2.4.29 (Ubuntu)
Content-Length: 256
Content-Type: text/html; charset=UTF-8
<!DOCTYPE html>
<html>
<head>
<title>Resultados de búsqueda</title>
</head>
<body>
<h1>Resultados de búsqueda</h1>
<ul>
<li>Resultado 1: Ejemplo 1.</li>
<li>Resultado 2: Ejemplo 2.</li>
<li>Resultado 3: Ejemplo 3.</li>
</ul>
</body>
</html>
Analicemos el ejemplo anterior:
Server, Content-Length o Content-Type) que se usan para indicar al navegador datos que debe tener en cuenta.Desde PHP podemos utilizar la función header para enviar cabeceras específicas. Dos de los casos más habituales son la cabecera Location y la cabecera WWW-Authenticate.
Por ejemplo, la cabecera Location la podemos utilizar para reenviar al navegador a otra página tras realizar una serie de acciones, lo más habitual es que dicha cabecera vaya seguida de exit para finalizar la ejecución del script PHP:
header('Location: otrapagina.php');
exit();
Cuando el navegador recibe dicha cabecera detiene la petición actual y realiza otra nueva a la localización indicada.
La cabecera WWW-Authenticate se utiliza por otro lado para indicar al usuario que es necesaria una autenticación usando algún mecanismo implementado en el mismo protocolo HTTP. En el siguiente código puedes ver como se solicitaría al usuario que es necesario que se autentique:
header('WWW-Authenticate: Basic Realm="Contenido restringido"');
header('HTTP/1.0 401 Unauthorized');
exit();
La cabecera anterior va acompañada del código de respuesta 401, que significa que el usuario no está autorizado a acceder al recurso solicitado. Normalmente, tras realizar el envío de las cabeceras anteriores lo más habitual es terminar la ejecución del script.
Importante: para que nuestro código no genere error la función header debe usarse antes de generar ningún texto. Es decir, no se puede usar echo o print o similar, dado que puede provocar que se empiece a enviar el cuerpo del mensaje y una vez que se empieza a enviar el cuerpo del mensaje no es posible enviar cabeceras HTTP.
En la siguiente página de la documentación oficial de Mozilla encontrarás muy bien detallada y de forma muy clara las cabeceras que se pueden usar en cada contexto. Encontrarás cabeceras de consulta, que son las que el navegador envía al servidor web, y cabeceras de respuesta, que son las que el servidor web envía al navegador, entre otros tipos de cabeceras:
Creado con eXeLearning (Ventana nueva)
Cuando almacenamos datos de usuario tenemos que tener especial cuidado. Imagina que tienes una tabla como la siguiente:
-- Seleccionamos la base de datos USE dwes; -- Creamos la tabla CREATE TABLE usuarios ( usuario VARCHAR(20) NOT NULL PRIMARY KEY, contrasena VARCHAR(256) NOT NULL ) ENGINE = INNODB;
En la tabla anterior, en el campo contrasena almacenamos la contraseña del usuario. Pero, ¿cómo almacenamos su contraseña? Resulta que lo más habitual es no almacenar la contraseña de usuario, pero, ¿cómo es eso posible? Lo que se realiza normalmente es almacenar un resumen o hash de la misma o la contraseña encriptada. Es decir, en lugar de almacenar la contraseña encriptada tal cual, se almacenaría algo como:
INSERT INTO usuarios (usuario, contrasena) VALUES ('usuario',SHA2('password',256));
En el caso anterior se ha utilizado SHA2 de 256 bits, el cual proporciona bastante seguridad. En lugar de almacenar la contraseña, se ha almacenado un resumen de la misma. ¿Y esto por qué se hace? Básicamente se realiza así para evitar que un problema de seguridad en nuestra aplicación web revele contraseñas de usuarios. De esta forma, al no almacenar la contraseña en la base de datos, no hay peligro de que sea filtrada accidentalmente. Ahora, si queremos verificar si un usuario tiene una contraseña concreta, debemos comparar el resumen almacenado con el resumen calculado para la contraseña proporcionada:
$sql = "SELECT usuario FROM usuarios WHERE usuario = :usuario AND contrasena = SHA2(:contrasena, 256);";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':usuario', $usuario, PDO::PARAM_STR);
$stmt->bindParam(':contrasena', $contrasena, PDO::PARAM_STR);
if ($stmt->execute() && $datos = $stmt->fetch()) {
echo "El usuario {$datos['usuario']} se ha autenticado!";
} else {
echo "Autenticación incorrecta!";
}
En el ejemplo anterior, se verifica si la información de usuario almacenada en las variables $usuario y $contrasena encaja con algún usuario de la base de datos. Si encaja, el usuario se podría dar por autenticado, en caso contrario, la autenticación habría fallado. El proceso anterior podría hacerse con las funciones hash de la base de datos o con la función hash de PHP. Los algoritmos de hash más utilizados son:
sha256, sha384 o sha512, por ejemplo: $resumenMD5=hash('SHA256','TEXTO').En el siguiente ejemplo puedes ver como usaríamos el hash calculado desde PHP en una interacción con la base de datos:
$sql = "UPDATE usuarios SET contrasena = :nuevopassword WHERE usuario = :usuario AND contrasena = :passwordactual";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':usuario', $usuario, PDO::PARAM_STR);
$stmt->bindParam(':passwordactual', hash('sha256', $contrasenaActual), PDO::PARAM_STR);
$stmt->bindParam(':nuevopassword', hash('sha256', $nuevaContrasena), PDO::PARAM_STR);
if ($stmt->execute() && $stmt->rowCount() > 0) {
echo "La contraseña del usuario se ha actualizado.";
} else {
echo "El usuario no existe o la contraseña actual es incorrecta.";
}
En el código anterior confiamos en la función hash de PHP en lugar de confiar en las funciones de la base de datos, aunque al fin y al cabo el resultado es el mismo. En el ejemplo anterior, se ejemplifica como se podía llevar a cabo un cambio de contraseña, donde el usuario tiene que proporcionar su contraseña actual y la nueva contraseña.
Sobre este procedimiento hay varias consideraciones que tienes que tener en cuenta. El primero es que normalmente no se almacena solo el resumen de la contraseña, sino que se almacena el resumen de la contraseña añadiendo algún elemento que haga que el resumen sea aún más aleatorio. Por ejemplo, en vez de calcular hash('sha256', $contrasena) se realiza algo así hash('sha256', $contrasenaActual.$salt) donde .$salt añade ciertos caracteres al cálculo del resumen. Fíjate en la siguiente función:
/** Calcula el hash de los passwords */
function calcularPasswordHash($usuario,$password,$salt) {
return hash('sha256',md5($usuario.$password).$password.$salt);
}
//Uso:
echo calcularPasswordHash ('usuario','password','1234');
En el ejemplo anterior el cálculo de resumen usa una cadena fija ('1234') que podría configurarse a nivel global en la aplicación en algún archivo de configuración. Aparte de esa cadena fija, se añade otra información calculada (md5($usuario.$password)) que permite generar un resumen con una probabilidad mucho menor de colisiones.
Aparte de los mecanismos ya comentados, en PHP se proporcionan algunos mecanismos específicos para la gestión y verificación de contraseñas. Entre ellos cabe destacar las funciones password_hash y password_verify. Este par de funciones permite calcular un resumen de una contraseña de forma sencilla y utilizando una variedad de algoritmos:
$password = 'miContraseña'; $hashedPassword = password_hash($password, PASSWORD_DEFAULT); // $hashedPassword se guardaría en la base de datos
En el ejemplo anterior PASSWORD_DEFAULT indicaría que se usará el algoritmo por defecto (bcrypt a partir de PHP 5.5), aunque se puede elegir otro a nuestra conveniencia. Para verificar si una contraseña proporcionada por un usuario es correcta, usaríamos password_verify:
//Contraseña proporcionada por el usuario
$password = 'miContraseña';
//Hash almacenado en la base de datos y que hemos rescatado previamente
$storedHashedPassword = '...hashRescatadoDeLaBaseDeDatos...';
if (password_verify($password, $storedHashedPassword)) {
echo "La contraseña coincide con el hash, por lo que es válida.";
} else {
echo "La contraseña no coincide con el hash, por lo que no es válida.";
}
Una función hash es un algoritmo matemático que a partir de unos datos de entrada (un texto por ejemplo), produce una salida única, conocido como hash o resumen. El resumen generado siempre será el mismo para el mismo texto, pero será muy difícil encontrar dos textos que generen exactamente le mismo resumen. Las funciones hash se utilizan enormemente en criptografía, siendo los más comunes: MD5, SHA-1, SHA-256, entre otros.
En la siguiente página de la documentación oficial de PHP encontrarás información sobre los métodos para calcular resúmenes hash de contraseñas:
Creado con eXeLearning (Ventana nueva)
Como ya hemos comentado antes, uno de los mecanismos de autenticación de los que podemos hacer uso en nuestras aplicaciones web es HTTP Basic. Este mecanismo está definido en el estándar RFC 7617 e implica el uso de varias cabeceras HTTP.
En PHP puedes usar la función header para forzar al navegador a solicitar credenciales HTTP Basic a un usuario, también podríamos hacerlo para HTTP Digest, pero nos vamos a centrar en HTTP Basic por su sencillez. El siguiente código utiliza la cabecera WWW-Authenticate para indicar al navegador que el usuario debe proporcionar un usuario y contraseña:
<?php
if ($_SERVER['PHP_AUTH_USER']!='dwes' || $_SERVER['PHP_AUTH_PW']!='abc123.') {
header('WWW-Authenticate: Basic Realm="Contenido restringido"');
header('HTTP/1.0 401 Unauthorized');
echo "Usuario no reconocido!";
exit;
}
Cuando nuestro script PHP recibe información del usuario y la contraseña que se ha autenticado usando este mecanismo (HTTP Basic), almacenará dicha información en $_SERVER['PHP_AUTH_USER'] y en $_SERVER['PHP_AUTH_PW']. En nuestro script PHP seremos responsables de verificar si dicha información es válida o no.
En este escenario, cuando el navegador recibe la cabecera WWW-Authenticate: Basic Realm="Contenido restringido", mostrará al usuario una interfaz (generalmente una ventana emergente) para que introduzca su nombre de usuario y contraseña, por ejemplo:
En el código PHP anterior se verifica que el usuario y la contraseña sean dwes y abc123. , pero lo más normal es que se contrasten dichos datos con información almacenada en la base de datos.
Una característica importante del funcionamiento HTTP Basic es que una vez que el usuario final introduce su nombre de usuario y su contraseña en el navegador web, el navegador web enviará dicha información en cada petición posterior realizada al mismo servidor web. El navegador web por tanto almacenará el usuario y la contraseña proporcionados por el usuario final en memoria hasta que se cierre el navegador. Esto ocurrirá así salvo que el navegador reciba la cabecera HTTP/1.0 401 Unauthorized del servidor web, situación en la que descartará el nombre de usuario y contraseña suministrados por el usuario final.
Imagina que tienes creada la tabla de usuarios expuesta en apartados anteriores en tu base de datos. Recuerda que en dicha tabla se almacenaban nombres de usuarios y hash de contraseñas. ¿Sabrías realizar una aplicación que pudiera reclamar las credenciales a los usuarios utilizando HTTP Basic y que contrastara dichas credenciales con las almacenadas en la base de datos usando PDO?
Creado con eXeLearning (Ventana nueva)
Una vez resuelto el tema de la autentificación, el siguiente objetivo de Carlos es aprender a gestionar las cookies. Juan le ha explicado qué son y cómo funcionan, y seguramente las tengan que utilizar en la aplicación que están desarrollando.
Sabe que muchos de los sitios web que visita las utilizan, porque ha probado a desactivarlas en su navegador, y le han empezado a aparecer mensajes pidiéndole que las vuelva a activar. Se ha propuesto averiguar para qué las utilizan, y cómo las podrán gestionar desde PHP.
Una cookie es tradicionalmente un fichero de texto que un sitio web guarda en el entorno del usuario del navegador, aunque hoy día los navegadores utilizan técnicas más sofisticadas para almacenar las cookies. Sus usos más típicos son:
Las cookies están compuestas de la siguiente información:
formacionprofesional/ la ruta seríaEl servidor web enviará la información de la cookie usando la cabecera HTTP Set-Cookie y el navegador la almacenará durante un tiempo (que generalmente irá indicado en la cookie). A continuación tienes un ejemplo de lo que enviaría un servidor web al navegador:
Set-Cookie: cookieejemplo=cookievalor; Expires=Thu, 31 Dec 2023 23:59:59 GMT; Path=/formacionprofesional; Domain=educacionadistancia.juntadeandalucia.es; Secure; HttpOnly
Una vez se supera el tiempo de expiración (Expires), el navegador descartará la cookie. Mientras que no se supere el tiempo de expiración, el navegador enviará la cookie en cada petición HTTP que se realice al mismo dominio y ruta. Si no se especifica el tiempo de expiración (Expires), el navegador creará una cookie de sesión, la cual se almacena en el navegador mientras esté abierto, una vez que el usuario cierre el navegador las cookies de sesión se descartan.
En PHP, para almacenar una cookie en el navegador del usuario puedes utilizar la función header, componiendo tu mismo el contenido a enviar en la cabecera Set-Cookie. Sin embargo, existe un mecanismo más fácil, y es usar la función setcookie. El único parámetro obligatorio que tienes que usar es el nombre de la cookie, pero admite varios parámetros más opcionales. Para una referencia completa es mejor que le eches un vistazo a la documentación oficial:
Por ejemplo, si quieres almacenar en una cookie el nombre de usuario que se transmitió en las credenciales HTTP (es solo un ejemplo, no es en absoluto aconsejable almacenar información relativa a la seguridad en las cookies), puedes hacer:
setcookie("nombre_usuario", $_SERVER['PHP_AUTH_USER'], time()+3600);
Los dos primeros parámetros son el nombre de la cookie y su valor. El tercero es la fecha de caducidad de la misma expresado en número segundos (en el ejemplo se establece como expiración una hora desde el momento en que se genera la cookie). En caso de no figurar este parámetro, la cookie se eliminará cuando se cierre el navegador.
Como hemos comentado antes, las cookies se transmiten entre el navegador y el servidor web de la misma forma que las credenciales que acabas de ver; utilizando cabeceras del protocolo HTTP. Por ello, la función setcookie debe usarse antes de que el navegador muestre información alguna en pantalla.
El proceso de recuperación de la información que almacena una cookie es muy simple. Como hemos comentado antes, cuando el navegador recibe una cookie la almacena de forma temporal y la vuelve a enviar de forma automática en cada petición HTTP al mismo dominio y ruta. En nuestro script PHP podremos acceder a la información de las cookies que envía el navegador por medio del array superglobal $_COOKIE.
Siempre que utilices cookies en una aplicación web, debes tener en cuenta que en última instancia su disponibilidad está controlada por el cliente. Por ejemplo, algunos usuarios deshabilitan las cookies en el navegador porque piensan que la información que almacenan puede suponer un potencial problema de seguridad. O la información que almacenan puede llegar a perderse porque el usuario decide formatear el equipo o simplemente eliminarlas de su sistema.
Si una vez almacenada una cookie en el navegador quieres eliminarla antes de que expire, puedes utilizar la misma función setcookie pero indicando una fecha de caducidad anterior a la actual, por ejemplo:
//Indicamos que la cookie tiene contenido vacío y que expiró hace 15 horas
setcookie("nombre_usuario", "", time()-3600*15);
Creado con eXeLearning (Ventana nueva)
Las cookies pueden ser complicadas de gestionar, por lo que vamos a dedicar varios apartados a entender un poco mejor como se utilizan y gestionan las cookies.
Una cookie es información que envía nuestra aplicación web al cliente web (el navegador) y que será almacenada durante un tiempo por el cliente web. Cuando el cliente web realiza conexiones consecutivas al mismo sitio web vuelve a enviar las cookies almacenadas.
Para enviar una cookie al cliente web debes enviarlas antes de empezar a generar el cuerpo de la respuesta HTTP, es decir, antes de empezar a escribir el contenido de la página que el usuario va a ver en su navegador. Para enviar la cookie usamos el método setcookie.
| Correcto | Incorrecto |
|---|---|
<?php
setcookie('test',$test+1,time()+600);
?>
<!DOCTYPE html>
<html>
<head>
<title>Ejemplo</title>
</head>
<body>
Ejemplo
</body>
</html>
|
<!DOCTYPE html>
<html>
<head>
<title>Ejemplo</title>
</head>
<body>
Ejemplo
<?php
setcookie('test',$test+1,time()+600);
?>
</body>
</html>
|
Una vez que el navegador reciba la cookie este la almacenará en disco durante un tiempo. Esta cookie volverá a ser enviada por el navegador al servidor web en peticiones HTTP sucesivas. En PHP esa cookie recibida desde el navegador estará disponible en el array $_COOKIE:
<?php
if (isset($_COOKIE['test'])
{
echo $_COOKIE['test'];
}
Para comprender adecuadamente como funcionan las cookies y como usarlas vamos a revisar el procedimiento paso por paso en dos fases claramente diferenciadas.
En esta fase el cliente web no tiene ninguna cookie, bien porque sea la primera vez que accede al sitio o bien porque las cookies expiraron y se borraron de disco.
En este primer paso, el navegador no tiene una cookie todavía, por ello al iniciar la comunicación no envía ningún dato adicional:
En este segundo paso, la aplicación web prepara una respuesta que contendrá, una cookie aparte del contenido HTML:
Para facilitar esta tarea en PHP las cookies recibidas estarán almacenadas en el array superglobal $_COOKIE. En este paso la aplicación web deberá verificar si la cookie existe y preparar el envío de la misma:
<?php
if (!isset($_COOKIE['test']))
{
setcookie ('test','1',time()+600);
}
Después de preparar la respuesta HTTP esta se envía al cliente web. La respuesta HTTP incluirá las cookies y el HTML:
Cuando el cliente web recibe la cookie la almacena en disco durante un tiempo. Si el cliente web vuelve a iniciar una comunicación con el servidor web, volverá a enviarle la cookie.
Una vez que el cliente web ya tiene una o más cookies podemos aprovecharnos de esa información para diversos propósitos: identificar a cada cliente web, determinar las preferencias que el usuario establece sobre la configuración de la página, saber cual fue la última acción realizada en la aplicación web, etc. Todo depende del contenido de las cookies que desde el servidor web se envía al cliente web. En esta parte, el cliente web reenviará las cookies almacenadas al servidor web en la petición HTTP.
Este paso se produce cuando el navegador vuelve a realizar una petición HTTP al servidor web. Esto puede ocurrir en cualquier momento después de haber recibido la cookie por primera vez, dependerá de lo que el usuario final realice en su ordenador. En esa situación, si las cookies no han expirado, estas se reenvían al servidor web junto con la petición HTTP.
A priori, el navegador no modificará la información de las cookies, pero el servidor web es responsable de comprobar que la información no ha sido alterada. Las cookies pueden suponer un problema grave de seguridad si no son tratadas adecuadamente, un atacante malicioso podría aprovechar cualquier vulnerabilidad derivada de no comprobar adecuadamente la información de las mismas. Nuestra aplicación web recibe las cookies y prepara una respuesta adecuada:
La aplicación web en este caso puede optar por:
Para poder acometer las acciones anteriores ampliamos el código del paso 2 anterior. En este caso, actualizaremos el valor de la cookie hasta que el valor recibido de la misma sea 10, momento en el que procedemos a forzar la expiración, y en consecuencia eliminación, de la cookie en el navegador:
if (!isset($_COOKIE['test']) || !is_numeric($_COOKIE['test'] )) //No hay cookie o no es correcta
{
setcookie ('test','1',time()+600); //implantamos la cookie por primera vez
}
else { //Si hay cookie y es correcta
$test=intval($_COOKIE['test']);
if ($test<10)
{
setcookie ('test',$test+1,time()+600); //Actualizamos la cookie
}
else
{
setcookie ('test','',time()-20000); //Forzamos la expiración de la cookie
}
}
if (isset($test))
echo "El valor recibido de la cookie desde el cliente es $test.";
else
echo "No se ha recibido ninguna cookie del cliente.";
La respuesta HTTP al cliente web puede contener nuevas cookies o modificación de las anteriores.
Si nuestra aplicación web envía una nueva cookie, el navegador la almacenará junto al resto. También podemos enviar una cookie para modificar una existente o para forzar su expiración (como se explico en el paso 6). La siguiente imagen ilustra como la aplicación web envía una cookie para modificar otra con el mismo nombre ya almacenada en el navegador:
En muchas ocasiones, cuando estamos realizando una aplicación que usa cookies, necesitamos eliminar las cookies que tiene almacenadas, para ello cada navegador permite borrar las cookies, como puedes consultar en los siguientes enlaces:
Borrar cookies en Mozilla Firefox
Eliminar las cookies en Microsoft Edge
Gestionar las cookies en Chrome
Sin embargo, como desarrolladores y desarrolladoras, es normal que necesitemos un poco más de control. Para ello podemos hacer uso de las herramientas de desarrollo integradas en los navegadores, las cuales nos permitirán revisar las cookies almacenadas en el navegador y su contenido, entre otras gestiones (como borrar cookies específicas). Para arrancar las herramientas de ayuda al desarrollo de los navegadores basta con pulsar la tecla F12:
Introducción a las herramientas de apoyo al desarrollo de Mozilla Firefox
Chrome: cómo eliminar una cookie específica con herramientas de desarrollador
Creado con eXeLearning (Ventana nueva)
Cuando tenemos que enviar cookies con mucha información (siempre que no se supere el tamaño máximo permitido, que son aproximadamente 4096 bytes), podemos usar varios métodos para encapsular toda la información dentro de la cookie. Lo que realizamos en estos procesos es convertir la información compleja (array u objeto) a una representación de la misma en formato texto. Por ejemplo:
<?php
$datos=['a'=>1, 'b'=>1];
echo serialize($datos);
//Mostrará: a:2:{s:1:"a";i:1;s:1:"b";i:1;}
Veamos como podemos usar esta técnicas con las cookies:
Encapsulamos los datos a enviar en la cookie en un array que posteriormente serializamos con la función serialize:
<?php
$datos=['a'=>1,'b'=>1];
setcookie ('datos',serialize($datos),time()+600);
Cuando se reciben los datos de la cookie tendremos que realizar el proceso de deserialización, para ello usamos la función unserialize:
<?php
if (isset($_COOKIE['datos']))
{
$datos=unserialize($_COOKIE['datos']);
}
Si el dato no se puede deserializar, el método unserialize retornará false y generará error PHP tipo E_NOTICE, por lo que en estos casos es conveniente usar el operador @ para evitar que se generen errores y verificar si se ha podido llevar a cabo la deserialización:
<?php
if (isset($_COOKIE['datos']))
{
$datos=@unserialize($_COOKIE['datos']);
if ($datos===false)
{
echo 'No se ha podido deserializar la cookie "datos"';
//se proporciona un valor por defecto seguro
$datos=[];
}
}
En este caso usaremos el formato de JSON para serializar los datos. Encapsulamos los datos a enviar en la cookie en un array que posteriormente convertimos a formato JSON con la json_encode:
<?php
$datos=['a'=>1,'b'=>1];
setcookie('test',json_encode($datos),time()+600);
Cuando se reciben los datos de la cookie convertiremos los datos a un formato manipulable desde PHP, para ello usamos la función json_decode:
<?php
if (isset($_COOKIE['datos']))
{
$datos=json_decode($_COOKIE['test'], true);
}
Si json_decode no puede decodificar el documento JSON, retornará false, por lo que debemos comprobar siempre si $datos tiene un valor distinto de false:
<?php
if (isset($_COOKIE['datos']))
{
$datos=json_decode($_COOKIE['test'], true);
if ($datos===false)
{
echo 'No se ha podido decodificar el documento JSON de la cookie "datos"';
//Asignamos un valor por defecto seguro:
$datos=[];
}
}
Importante: JSON es una notación para representar un objeto. Al usar json_encode con un array como el anterior (['a'=>1,'b'=>1]), se generará un texto que contiene dichos datos pero que en realidad representan un objeto JSON ({"a":1,"b":1}).
Cuando usamos json_decode con los datos recibidos ({"a":1,"b":1}) no se puede determinar si los datos originales eran un array PHP o un objeto PHP. Al usar json_decode con datos como los anteriores se genera un objeto PHP:
<?php
$datos=json_decode ('{"a":1,"b":1}');
var_dump($datos); //Mostrará: object(stdClass)#1 (2) { ["a"]=> int(1) ["b"]=> int(1) }
echo $datos->a + $datos->b; //Mostrará 2
Para forzar a json_decode que encapsule los datos en un array PHP (y no en un objeto PHP) puedes poner el segundo argumento del método json_decode a true:
<?php
$datos=json_decode ('{"a":1,"b":1}',true);
var_dump($datos); //Mostrará: array(2) { ["a"]=> int(1) ["b"]=> int(1) }
echo $datos['a'] + $datos['b']; //Mostrará 2
La serialización es una forma de representar datos complejos (arrays, objetos, etc.) como si fueran una cadena de texto.
Crea un script PHP que gestione una cookie llamada numeros y que contendrá un número variable de números. El script debe realizar las siguientes acciones:
Ten en cuenta que la cookie numeros se utiliza para almacenar una serie de números generados aleatoriamente y mantener su estado entre diferentes solicitudes del navegador.
Es importante que tengas en cuenta que las cookies se cargan al conectarse o refrescar la página, por lo tanto el valor que se le dé a una cookie no se podrá recuperar hasta que se refresque la página.
Creado con eXeLearning (Ventana nueva)
Aunque el valor de una cookie se establece inicialmente por la aplicación web, el contenido de $_COOKIE es información que se recibe desde una fuente externa (el navegador) y requiere que le prestemos mayor atención que a la información contenida en $_GET o $_POST. En primer lugar, es necesario validar y verificar que dicha información es correcta, igual que hacemos con los datos recibidos en $_GET o $_POST, pero además debemos asegurar que dichas cookies no han sido modificadas de forma intencionada.
Para realizar esta verificación podemos usar diversas técnicas, entre las que podemos destacar:
Usar un algoritmo de hash requiere enviar dos cookies: una con los datos y otra con una cadena que permite verificar los datos; mientras que el uso de un algoritmo de encriptación enviaría solo un dato. La primera opción es muy rápida y la más usada, mientras que la segunda es más lenta y consume bastante CPU (algo que debemos evitar).
A continuación tienes un ejemplo de uso de un algoritmo de hashing, concretamente sha256, para verificar que la cookie no ha sido modificada.
<?php
if (!isset($_COOKIE['datos']) || !isset($_COOKIE['verificacion']))
{
$mensaje="No había cookies, por lo que se envían por primera vez";
$datos=['a'=>1,'b'=>2];
setcookie('datos',serialize($datos),time()+600);
setcookie('verificacion',hash('sha256',serialize($datos)),time()+600);
}
else
{
if (hash('sha256',$_COOKIE['datos'])===$_COOKIE['verificacion'])
{
$mensaje="cookie verificada";
$datos=unserialize($_COOKIE['datos']);
}
else
{
$mensaje="La verificación fallo, por lo que se borran las cookies";
$datos=[];
setcookie('datos','',time()-600);
setcookie('verificacion','',time()-600);
}
}
echo $mensaje;
Fíjate que:
Una técnica que puedes utilizar un poco más avanzada para calcular un resumen o hash de verificación es utilizar la función hash_hmac. Para entender el fundamento de esta función puedes revisar el siguiente artículo:
Artículo de la Wikipedia sobre el código de autenticación de mensajes en clave hash (HMAC)
Y en la siguiente página puedes consultar información sobre la función hash_hmac y como emplearla en PHP:
Creado con eXeLearning (Ventana nueva)
Carlos ha aprendido a utilizar las cookies, y cuáles son sus limitaciones. Está claro que les serán de utilidad, pero también que no cubren las necesidades de una aplicación web. Al viajar en las cabeceras HTTP, la información que pueden mantener está muy limitada.
Aunque sabe que aún le queda mucho por aprender, Juan le ha comentado que con los conocimientos que acaba de adquirir ya tiene una buena base para dar los primeros pasos programando en PHP. Dice que sólo le falta aprender cómo funcionan las sesiones para empezar a desarrollar aplicaciones web completas por su cuenta.
Animado por estos comentarios, Carlos decide dar un paso más. Veamos cómo funcionan las sesiones en PHP.
Como acabas de ver, una forma para guardar información particular de cada usuario es utilizar cookies. Sin embargo, existen diversos problemas asociados a las cookies, como el número de ellas que admite el navegador, o su tamaño máximo. Para solventar estos inconvenientes existen las sesiones. El término sesión hace referencia al conjunto de información relativa a un usuario concreto. Esta información puede ser tan simple como el nombre del propio usuario, o más compleja, como los artículos que ha depositado en la cesta de compra de una tienda online.
Cada usuario distinto de un sitio web tiene su propia información de sesión y la información almacenada en la sesión se guarda en el servidor web, a diferencia de las cookies, que se guardan íntegramente en el navegador. Para distinguir una sesión de otra se usan los identificadores de sesión (SID). Un SID es un atributo que se asigna a cada uno de los visitantes de un sitio web y lo identifica de forma unívoca en el servidor web. De esta forma, si el servidor web conoce el SID de un usuario, puede relacionarlo con toda la información que posee sobre él, que se mantiene en la sesión del usuario. Esa información se almacena en el servidor web, tal y como antes se ha comentado, generalmente en ficheros aunque también se pueden utilizar otros mecanismos de almacenamiento como bases de datos.
Como ya habrás supuesto, la cuestión ahora es: ¿Dónde se almacena ese SID, el identificador de la sesión, que es único para cada usuario? ¿Cómo llega dicho SID al servidor web? Pues la idea es, una vez generado el SID en el servidor web por primera vez, propagarlo de una visita a la siguiente de forma que siempre se sepa cual es el SID del usuario final. Para propagar el SID existen varias técnicas:
<A href="http://www.misitioweb.com/tienda/listado.php&PHPSESSID=34534fg4ffg34ty">Ver listado de de productos</A>
En el ejemplo anterior, el SID es el valor de PHPSESSID, que es el identificador por defecto usado en PHP.
Ninguna de las tres formas es perfecta. Ya sabes los problemas que tiene la utilización de cookies. Pese a ello, es el mejor método y el más utilizado son las cookies. Propagar el SID como parte de la URL conlleva desventajas notables, como la imposibilidad de mantener el SID entre distintas sesiones, o el hecho de que compartir la URL con otra persona implica compartir también el identificador de sesión (lo cual es un problema de seguridad grave).
La buena noticia es que el proceso de manejo de sesiones en PHP está automatizado en gran medida. Cuando un usuario visita un sitio web, no es necesario programar un procedimiento para ver si existe un SID previo y cargar los datos asociados con el mismo. Tampoco tienes que utilizar la función setcookie si quieres almacenar los SID en cookies, o ir pasando el SID entre las páginas web de tu sitio si te decides por propagarlo. Todo esto PHP lo hace automáticamente.
A la información que se almacena en la sesión de un usuario también se le conoce como cookies del lado del servidor (server side cookies). Debes tener en cuenta que aunque esta información no viaja entre el cliente y el servidor, sí lo hace el SID, bien como parte de la URL o en un encabezado HTTP si se guarda en una cookie. En ambos casos, esto plantea un posible problema de seguridad. El SID puede ser conseguido por otra persona, y a partir del mismo obtener la información de la sesión del usuario. La manera más segura de utilizar sesiones es almacenando los SID en cookies y utilizar HTTPS para encriptar la información que se transmite entre el servidor web y el cliente.
Creado con eXeLearning (Ventana nueva)
Por defecto, PHP incluye soporte de sesiones incorporado. Sin embargo, antes de utilizar sesiones en tu sitio web, debes configurar correctamente PHP utilizando las siguientes directivas en el fichero php.ini según corresponda:
| Directiva | Significado |
|---|---|
session.use_cookies |
Indica si se deben usar cookies (1) o propagación en la URL (0) para almacenar el SID. |
session.use_only_cookies |
Se debe activar (1) cuando utilizas cookies para almacenar los SID, y además no quieres que se reconozcan los SID que se puedan pasar como parte de la URL (este método se puede usar para usurpar el identificador de otro usuario). |
session.save_handler |
Se utiliza para indicar a PHP cómo debe almacenar los datos de la sesión del usuario. Existen cuatro opciones: en ficheros (files), en memoria (mm), en una base de datos SQLite (sqlite) o utilizando para ello funciones que debe definir el programador (user). El valor por defecto (files) funcionará sin problemas en la mayoría de los casos. |
session.name |
Determina el nombre de la cookie que se utilizará para guardar el SID. Su valor por defecto es PHPSESSID. |
session.auto_start |
Su valor por defecto es 0, y en este caso deberás usar la función session_start para gestionar el inicio de las sesiones. Si usas sesiones en el sitio web, puede ser buena idea cambiar su valor a 1 para que PHP active de forma automática el manejo de sesiones. |
session.cookie_lifetime |
Si utilizas la URL para propagar el SID, éste se perderá cuando cierres tu navegador. Sin embargo, si utilizas cookies, el SID se mantendrá mientras no se destruya la cookie. En su valor por defecto (0), las cookies se destruyen cuando se cierra el navegador. Si quieres que se mantenga el SID durante más tiempo, debes indicar en esta directiva ese tiempo en segundos. Este parámetro determina el tiempo de vida de la cookie en el navegador, pero no se puede utilizar para controlar el tiempo de inactividad del usuario y el cierre de sesión automático. La eliminación de la cookie en el navegador NO implica la eliminación de los datos asociados al SID correspondiente en el servidor. |
session.gc_maxlifetime |
Representa el tiempo en segundos después del cual el recolector de basura de sesiones podría eliminar el contenido de la sesión si no haya habido actividad en la sesión. Esto quiere decir que si no ha habido actividad en las sesión durante el periodo de tiempo configurado (por defecto, 1440 segundos, que equivalen a 24 minutos), el recolector de basura podría eliminar dicha información de sesión, pero no se garantiza la eliminación de la sesión tras ese periodo de inactividad del usuario. El control del tiempo de inactividad de usuario y el cierre de sesión tras un periodo de inactividad requiere la implementación de una lógica adicional de gestión de la inactividad. |
La función phpinfo, de la que ya hablamos con anterioridad, te ofrece información sobre la configuración actual de las directivas de sesión.
En la documentación de PHP tienes información sobre éstas y otras directivas que permiten configurar el manejo de sesiones.
Creado con eXeLearning (Ventana nueva)
El inicio de una sesión puede tener lugar de dos formas. Si has activado la directiva session.auto_start en la configuración de PHP, la sesión comenzará automáticamente en cuanto un usuario se conecte a tu sitio web. En caso de que ese usuario ya haya abierto una sesión con anterioridad, y esta no se haya eliminado, en lugar de abrir una nueva sesión se reanudará la anterior. Para ello se utilizará el SID anterior, que estará almacenado en una cookie (recuerda que si usas propagación del SID, no podrás restaurar sesiones anteriores; el SID figura en la URL y se pierde cuando cierras el navegador).
Si por el contrario, decides no utilizar el inicio automático de sesiones, deberás ejecutar la función session_start para indicar a PHP que inicie una nueva sesión o reanude la anterior. Aunque anteriormente esta función devolvía siempre true, a partir de la versión 5.3.0 de PHP su comportamiento es más coherente: devuelve false en caso de no poder iniciar o restaurar la sesión.
Como el inicio de sesión requiere utilizar cookies, y éstas se transmiten en los encabezados HTTP, debes tener en cuenta que para poder iniciar una sesión utilizando session_start, tendrás que hacer las llamadas a esta función antes de que la página web muestre información en el navegador.
Además, todas las páginas que necesiten utilizar la información almacenada en la sesión, deberán ejecutar la función session_start.
Mientras la sesión permanece abierta, puedes utilizar la variable superglobal $_SESSION para añadir información a la sesión del usuario, o para acceder a la información almacenada en la sesión. Por ejemplo, para contar el número de veces que el usuario visita la página, puedes hacer:
<?php
// Iniciamos la sesión o recuperamos la anterior sesión existente
session_start();
// Comprobamos si la variable ya existe
if (isset($_SESSION['visitas']))
$_SESSION['visitas']++;
else
$_SESSION['visitas'] = 0;
Si en lugar del número de visitas, quisieras almacenar el instante en que se produce cada una, la variable de sesión visitas deberá ser un array y por tanto tendrás que cambiar el código anterior por:
<?php
// Iniciamos la sesión o recuperamos la anterior sesión existente
session_start();
// En cada visita añadimos un valor al array "visitas"
if (!isset($_SESSION['visitas'])
$_SESSION['visitas'] = [time()];
else
$_SESSION['visitas'][] = time();
Cada vez que el usuario acceda a la página se actualizará el contenido de la sesión asociado a su identificador de sesión. La información de la sesión puede desaparecer de forma automática si no hay actividad en un tiempo determinado y el recolector de basura estima que debe eliminarse, si bien, este no es un mecanismo que pueda servir para cerrar la sesión en caso de inactividad del usuario, dado que no se puede garantizar que el recolector de basura vaya a eliminar las sesiones en un momento concreto. El recolector de basura es un proceso interno de PHP que opera dependiendo de la carga del servidor y de otros factores.
En múltiples ocasiones es necesario eliminar la información de sesión del usuario. Es común en escenarios tales como:
En esos casos, entre otros, es necesario eliminar la información de la sesión. En PHP tienes varias formas de eliminar la información almacenada en la sesión:
Por otro lado, es común almacenar información del momento en el que un usuario inicia sesión o cuando se guarda una información concreta en la sesión, para así detectar cuando es necesario eliminarla:
<?php
// Iniciamos la sesión o recuperamos la anterior sesión existente
session_start();
// Establecemos el momento en el que el usuario realizo por primera vez una acción
if (!isset($_SESSION['inicio']) {
$_SESSION['inicio']=time();<br />}
// Comprobamos que hace menos de 60 minutos que se conecto
if ($_SESSION['inicio']+3600>time())
{
//Acciones en caso de que no haya expirado la información
}
else
{
//Acciones en caso de que SI haya expirado la información
unset ($_SESSION['informacion']);
unset ($_SESSION['inicio']);
}
En el script anterior se utiliza $_SESSION['inicio'] para establecer un tiempo de vida para la información almacenada en la sesión. En caso de que se sobrepase dicho tiempo de vida, la información se descarta.
Vamos a implementar una autenticación basada en formulario donde se usen sesiones. ¿Recuerdas la tabla de usuarios mencionada en apartados anteriores?
CREATE TABLE usuarios ( usuario VARCHAR(20) NOT NULL PRIMARY KEY, contrasena VARCHAR(256) NOT NULL ) ENGINE = INNODB;
Crea scripts PHP (al menos dos) para permitir la autenticación de usuarios con las siguientes premisas:
¿Serás capaz de hacerlo? ¡¡Adelante!!
Creado con eXeLearning (Ventana nueva)
En este punto vas a ver paso a paso un ejemplo de utilización de sesiones para almacenar la información de un usuario autenticado. Se utilizará una base de datos para crear un prototipo de una tienda web dedicada a la venta de productos de informática:
Código SQL de la base de datos EjTienda
Nota: fíjate que en el SQL se crean dos usuarios, el usuario dwes con contraseña dwes y el usuario usuario con contraseña password, puedes usarlos para probar la aplicación.
Las páginas de que constará tu tienda online son las siguientes:
Aunque el aspecto de la aplicación no es importante para nuestro objetivo, utilizaremos una hoja de estilos (tienda.css), común a todas las páginas y una imagen, cesta.png, para ofrecer un interface más amigable.
Antes de comenzar ten en cuenta que la aplicación que vas a desarrollar no es completamente funcional. Además de no desarrollar la página con la información de pago, habrá algunas opciones que no tendrás en cuenta para simplificar el código. Por ejemplo:
Aunque reduzcamos en este ejemplo la funcionalidad de la tienda, te animamos a que una vez finalizado el mismo, añadas por tu cuenta todas aquellas opciones que quieras. Recuerda que la mejor forma de aprender programación es… ¡programando!
Aunque a lo largo de los siguientes apartados se van introduciendo los distintos ficheros del proyecto de ejemplo. ¿Serías capaz de juntarlo todo en un proyecto?
Creado con eXeLearning (Ventana nueva)
La primera página que vas a programar es la de autentificación del usuario (login.php). Para variar, utilizarás las capacidades de manejo de sesiones de PHP para almacenar la identificación de los usuarios. Además, utilizaremos la información de la tabla usuarios de la base de datos proporcionada, accediendo mediante PDO en lugar de MySQLi.
Vas a crear en la página un formulario con dos campos, uno de tipo text para el usuario, y otro de tipo password para la contraseña. Al pulsar el botón Enviar, el formulario se enviará a esta misma página, donde se compararán las credenciales proporcionadas por el usuario con las almacenadas en la base de datos. Si los datos son correctos, se iniciará una nueva sesión y se almacenará en ella el nombre del usuario que se acaba de conectar.
Vamos por pasos. El código HTML para crear el formulario, que irá dentro del cuerpo de la página (entre las etiquetas <body>) podría ser el siguiente:
<div id='login'>
<form action='login.php' method='post'>
<fieldset >
<legend>Login</legend>
<div><span class='error'><?php echo $error; ?></span></div>
<div class='campo'>
<label for='usuario' >Usuario:</label><br/>
<input type='text' name='usuario' id='usuario' maxlength="50" /><br/>
</div>
<div class='campo'>
<label for='password' >Contraseña:</label><br/>
<input type='password' name='password' id='password' maxlength="50" /><br/>
</div>
<div class='campo'>
<input type='submit' name='enviar' value='Enviar' />
</div>
</fieldset>
</form>
</div>
Fíjate que existe un espacio para poner los posibles mensajes de error que se produzcan, como la falta de algún dato necesario, o un error de credenciales erróneas.
El código PHP que debe figurar al comienzo de esta misma página (antes de que se muestre cualquier texto), se encargará de:
Podemos utilizar una función como la siguiente para conectarnos a la base de datos:
function conectar()
{
$db_host = DB_HOST; // hostname
$db_name = DB_NAME; // databasename
$db_user = DB_USER; // username
$user_pw = DB_PASSWD; // password
try {
$con = new PDO('mysql:host=' . $db_host . '; dbname=' . $db_name, $db_user, $user_pw);
$con->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$con->exec("set names utf8");
} catch (PDOException $e) { //Se capturan los mensajes de error
die("Error: " . $e->getMessage());
}
return $con;
}
Donde los datos para conectarnos a la base de datos podrían estar definidos en constantes:
define('DB_USER','dwes');
define('DB_PASSWD','%DWES4dwes%');
define('DB_NAME','ejtienda');
define('DB_HOST','localhost');
// Comprobamos si ya se ha enviado el formulario
if (isset($_POST['enviar'])) {
//Obtenemos los datos enviados por POST y los volcamos a variables
$usuario = filter_input(INPUT_POST, 'usuario', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$password = filter_input(INPUT_POST, 'password');
//Si el usuario o la contraseña esta vacío, mandamos un mensaje
if (!is_string($usuario) || !is_string($password)) {
$error = "Debes introducir un nombre de usuario y una contraseña";
} else {
//Conectamos a la base de datos para comenzar las comprobaciones del usuario
$con = conectar();
//Creamos la consulta parametrizada para comprobar el usuario y las credenciales, volcadas a la variable
$sql = "SELECT usuario FROM usuarios WHERE usuario=:login AND contrasena=:contrasena";
try {
//preparamos la consulta
$resultado = $con->prepare($sql);
//Parametros de la consulta
$resultado->bindValue(":login", $usuario);
$resultado->bindValue(":contrasena", hash('sha256', $password));
//Ejecutamos la consulta
if ($resultado->execute()) {
//Volcamos los resultados en un array
$fila = $resultado->fetch();
} else
$error = "Error al ejecutar la consulta.";
} catch (PDOException $e) {
$error = "Error en la base de datos al ejecutar la consulta.";
}
//Si el numero de filas es distinto de false, es que existe ese usuario
if (!isset($error)) {
if ($fila !== false) {
//Iniciamos la sesion
session_start();
//Creamos la variable de usuario con el nombre del usuario
$_SESSION['usuario'] = $usuario;
//Redireccionamos a la página que nos interesa
header("Location: productos.php");
} else {
// Si las credenciales no son válidas, se vuelven a pedir
$error = "Usuario o contraseña no válidos!";
}
}
Revisa el código completo de la página login.php y comprueba su funcionamiento antes de seguir con las demás.
Creado con eXeLearning (Ventana nueva)
Cuando un usuario proporciona unas credenciales de inicio de sesión correctas, se le redirige de forma automática a la página del listado de productos (productos.php) para que pueda empezar a hacer la compra. Esa es la página que vas a programar a continuación.
La página se divide en varias zonas, cada una definida por una etiqueta <div> en el código HTML:
Se crea un formulario por cada producto, con un botón Añadir que envía a esta misma página el código del producto seleccionado. Cuando se ejecuta el script, se comprueba si se ha enviado este formulario, y si fuera así se añade un elemento al array asociativo $_SESSION['cesta'] con los datos del nuevo producto.
if (isset($_POST['enviar'])) {
$codProd=filter_input(INPUT_POST,'producto',FILTER_SANITIZE_FULL_SPECIAL_CHARS);
if (is_string($codProd))
{
$prodSQL = <<<'SQL'
SELECT nombre_corto, PVP
FROM producto
WHERE cod=:codProd
SQL;
$stmt=$pdo->prepare($prodSQL);
$stmt->bindValue('codProd',$codProd);
if($stmt->execute())
{
if (($datos=$stmt->fetch())!==false)
{
$producto['nombre'] = $datos['nombre_corto'];
$producto['precio'] = $datos['PVP'];
$_SESSION['cesta'][$codProd] = $producto;
}
}
}
}
// Si la cesta está vacía, mostramos un mensaje
$cesta_vacia = true;
if (count($_SESSION['cesta'])==0) {
print "<p>Cesta vacía</p>";
}
// Si no está vacía, mostrar su contenido
else {
foreach ($_SESSION['cesta'] as $codigo => $producto)
print "<p>$codigo</p>";
$cesta_vacia = false;
}
// Comprobamos si se ha enviado el formulario de vaciar la cesta
if (isset($_POST['vaciar'])) {
unset($_SESSION['cesta']);
}
Además, tanto en esta página como en todas las demás, es necesario comprobar la variable de sesión $_SESSION['usuario'] para verificar que el usuario se ha autentificado correctamente. Para ello, se incluye el siguiente código al inicio de la página.
<?php
// Recuperamos la información de la sesión
session_start();
// Y comprobamos que el usuario se haya autentificado
if (!isset($_SESSION['usuario'])) {
die("Error - debe <a href='login.php'>identificarse</a>.<br />");
}
Si el usuario no se ha autentificado, se muestra un mensaje de error junto con un enlace a la página login.php.
Hay dos formas de construir un diseño de software. Una forma es hacerlo tan simple que es obvio que no hay deficiencias. Y la otra es hacerlo tan complicado que no hay deficiencias obvias.
C.A.R. Hoare
Creado con eXeLearning (Ventana nueva)
Si desde la página del listado de productos, el usuario pulsa sobre el botón Comprar, se le dirige a la página de la Cesta de la compra (cesta.php), en la que se le muestra un resumen de los productos que ha seleccionado junto al importe total de los mismos.
En la página figuran dos formularios que simplemente redirigen a otras páginas. El que contiene el botón Pagar, que redirige a la página pagar.php, que en nuestro caso lo único que debe hacer es eliminar la sesión del usuario. Y el que contiene el botón de desconexión, que es similar al que figuraba en la página productos.php, y dirige a la página logoff.php, que cierra la sesión del usuario.
Los datos que figuran en la página se obtienen todos de la información almacenada en la sesión del usuario. No es necesario establecer conexiones con la base de datos. El código que sirve para mostrar el listado de los productos seleccionados es el siguiente:
<?php
$total = 0;
foreach($_SESSION['cesta'] as $codigo => $producto) {
echo "<p><span class='codigo'>$codigo</span>";
echo "<span class='nombre'>${producto['nombre']}</span>";
echo "<span class='precio'>${producto['precio']}</span></p>";
$total += $producto['precio'];
}
?>
<hr />
<p><span class='pagar'>Precio total: <?php print $total; ?> €</span></p>
Tanto desde esta página como desde la página del listado de productos, se le ofrece al usuario la posibilidad de cerrar la sesión. Para ello se le dirige a la página logoff.php, que no muestra nada en pantalla. Su código es el siguiente:
<?php
// Recuperamos la información de la sesión
session_start();
// Y la eliminamos
session_unset();
header("Location: login.php");
?>
Tras eliminar la sesión, redirige al usuario a la página de autentificación donde puede iniciar una nueva sesión con el mismo o con otro usuario distinto.
Creado con eXeLearning (Ventana nueva)
Con lo que ha aprendido, Carlos se ha aventurado por su cuenta en la programación de una aplicación web sencilla. Ahora ya sabe cómo y dónde almacenar la información que genera cada página, para poderla utilizar más adelante. Ha cogido la página que había creado para catalogar su colección de comics, y ha decidido convertirla en una aplicación de verdad. Empieza a estructurar el código que tenía y decide programar algunas páginas más y crear un menú para moverse entre ellas.
Y entonces… comienzan a surgir los problemas: páginas que no muestran lo que se suponía que deberían mostrar, errores inexplicables cuando se intenta conectar a la base de datos, listados que algunas veces funcionan y otras no, etc.
Tras muchas horas intentando encontrar y solucionar los problemas que van apareciendo, busca en Internet y descubre que existen algunas utilidades que pueden ayudarle a arreglar los errores del código. No se trata de que no vaya a cometer fallos nunca más, sino de que cuando los cometa tenga una forma más cómoda y rápida de resolverlos.
Carlos está descubriendo por su cuenta cómo funciona la depuración de código en lenguaje PHP.
Seguramente en la aplicación de tienda online que acabas de programar te hayas encontrado con algunos problemas. Algún código que no funcionaba, problemas de conexión a la base de datos, o páginas que no mostraban lo que deberían.
Con lo que has visto hasta ahora ya puedes empezar a desarrollar algunas aplicaciones web sencillas en lenguaje PHP. Sin embargo, cuando empiezan a surgir problemas tienes que ingeniártelas para poder descubrir qué es lo que está fallando. Puedes poner trozos de código en las páginas que desarrollas para mostrar información interna de la aplicación, que muestren en pantalla la secuencia en que se ejecutan las líneas de código, o el contenido de las variables que usas. Para ello puedes utilizar echo, print, print_r o var_dump.
Por ejemplo, si quieres ver el contenido de la variable superglobal $_SERVER, puedes usar la función print_r para ver su contenido:
<?php print_r($_SERVER); ?>
O bien puedes usar la función var_dump:
<?php var_dump($_SERVER); ?>
Este método de depuración es muy tedioso y requiere hacer cambios constantes en el código de la aplicación. También existe la posibilidad de que una vez encontrado el fallo, te olvides de eliminar de tus páginas algunas de las líneas creadas a propósito para la depuración, con los consiguientes problemas.
Si ya has programado anteriormente en otros lenguajes, seguramente conoces algunas herramientas de depuración específicas que se integran con el entorno de desarrollo, permitiéndote por ejemplo detener la ejecución cuando se llega a cierta línea y ver el contenido de las variables en ese momento. Al usar estas herramientas minimizas o eliminas por completo la necesidad de introducir líneas específicas de depuración en tus programas y agilizas el procedimiento de búsqueda de errores.
De las herramientas disponibles que posibilitan la depuración en lenguaje PHP, en esta unidad aprenderás a configurar y utilizar la extensión Xdebug. Es una extensión de código libre, bajo licencia propia (The Xdebug License, basada en la licencia de PHP) que se integra perfectamente con el IDE que estás usando, NetBeans.
Si la depuración está activada, Xdebug controla la ejecución de los guiones en PHP. Puede pausar, reanudar y detener los programas en cualquier momento. Cuando el programa está pausado, Xdebug puede obtener información del estado de ejecución, incluyendo el valor de las variables (puede incluso cambiar su valor).
Xdebug es un servidor que recibe instrucciones de un cliente, que en nuestro caso será NetBeans. De esta forma, no es necesario que el entorno de desarrollo esté en la misma máquina que el servidor PHP con Xdebug.
En muchos escenarios es necesario ir recopilando información de los eventos más importantes producidos en nuestra aplicación web en archivos de log o de registro. Un archivo de log es un archivo donde se van recopilando los eventos importantes ocurridos en una aplicación web, y en ellos se puede ver la fecha y hora del evento, el tipo de evento (diferenciando eventos de error, advertencias, información, etc.), e información descriptiva del error, entre otra información que pueda ser interesante.
En una instalación típica de PHP sobre Apache, se generarán varios archivos de log, entre ellos el archivo errors.log. Este archivo, generalmente localizado en las carpetas /var/log/apache2/error.log en un servidor Linux o en C:\xampp\apache\logs\error.log en un servidor XAMPP, incluirá los errores que pueda haber en nuestro código PHP:
[Tue May 30 00:28:25.400297 2023] [php:notice] [pid 901] [client ::1:54054] PHP Parse error: Unclosed '{' on line 44 in /var/www/html/DWES03_EjemploTienda/productos.php on line 131, referer: http://localhost/DWES03_EjemploTienda/productos.php
Si deseamos registrar mensajes de error en dicho archivo, con motivos de depuración o por otro motivo, podemos usar la función error_log:
<?php
$v=[1,2,3];
error_log("ERROR_LOG --> El contenido de la variable $v es ".print_r($v,true));
El código anterior generaría un mensaje de log como el siguiente:
[Tue May 30 07:38:13.857684 2023] [php:notice] [pid 95] [client ::1:32972] ERROR_LOG --> El contenido de la variable 10 es 10
Por último es importante que sepas puedes desviar los mensajes de error generados en PHP a un archivo específico usando la directiva de configuración siguiente:
$archivoLog = __DIR__.'/log/milogdeerroresphp.log';
ini_set('error_log', $archivoLog);
El ejemplo anterior guardará los errores de log en un archivo indicado por nosotros, lo cual es algo muy útil en el momento de desarrollar una aplicación web, aunque no tanto en el momento de desplegarla y pasarla a producción (donde no debería existir código como el anterior). Si quieres tener un archivo de log un poco más sofisticado y más adaptado a lo que puede ser necesario en un entorno real y de producción, lo suyo es utilizar librería específicas como Monolog.
En la documentación de PHP puedes consultar información sobre la función var_dump.
Y también sobre la función print_r:
Creado con eXeLearning (Ventana nueva)
En este apartado vamos a instalar XDebug 3 en un sistema basado en Windows, como puede ser XAMPP 8.2.4. La configuración de cada entorno de desarrollo puede ser diferente, pero normalmente seguimos siempre una serie de pasos:
Vamos paso por paso:
Para este paso vamos a crear una pequeña página web en nuestro servidor web que muestre la información obtenida con phpinfo(). En dicha página deberás revisar la siguiente información:
En este paso nos tenemos que dirigir a la web de XDebug: xdebug.org. En la sección Install podrás descargar versiones de XDebug para diferentes entornos. En nuestro caso vamos a instalarlo en Windows, por lo que accederemos a la sección de descargas:
Descargas de XDebug última versión
Una vez aquí, debemos buscar la versión que se ajusta a nuestra instalación de PHP. En nuestro caso descargaríamos la versión 8.2 VS16 TS, que está en concordancia con lo indicado en la salida de phpinfo():
En el caso de XAMPP instalaríamos las librerías en la carpeta C:\xampp\php\ext (depende de donde hayas instalado XAMPP claro está). Lo suyo es que lo renombres a php_xdebug.dll.
Una vez que hemos copiado la DLL de xDebug en PHP es necesario configurar el archivo php.ini para indicarle que cargue la extensión. Para ello, edita el archivo php.ini:
También puedes encontrar el archivo php.ini en la carpeta C:\xampp\php\php.ini. El texto que hay que añadir es el siguiente:
[XDebug] zend_extension=xdebug ; Modos: develop --> incluye salidas mejoradas de erores y var_dump ; debug --> ejecución paso a paso ; profile --> analizar rendimiento y cuellos de botella (no activar) ; trace --> registrar cada llamada a función xdebug.mode=develop,debug,trace ; Iniciar la depuración al iniciar la petición HTTP xdebug.start_with_request=yes ; Key para conectar (hace la conexión al xdebug más segura) xdebug.idekey="netbeans-xdebug"
Tras el paso anterior, tienes que detener y volver a iniciar el servidor Apache para que se cargue el motor PHP con la nueva configuración.
A través de la ejecución de phpinfo() podemos comprobar que se ha cargado adecuadamente XDebug en nuestro Apache, exactamente aquí:
Adicionalmente, en esa misma información, si continúas leyendo, podrás ver una sección específica con la configuración de XDebug según el archivo php.ini:
Creado con eXeLearning (Ventana nueva)
Si estás utilizando Ubuntu Linux, la instalación se realizaría usando el gestor de paquetes de Ubuntu, llamado apt. Esto lo puedes realizar siempre que la instalación de Apache y MySQL (o Maria DB) la hayas también realizado usando el gestor de paquete apt. Si la instalación la has realizado usando un paquete XAMPP para Linux, entonces deberás instalar la extensión XDebug de forma manual (proceso similar al seguido para Windows pero con los binarios específicos para Linux).
Veamos como se haría si la instalación que hemos realizado ha sido usando apt, el gestor de paquetes de Linux.
En primer lugar entra en modo administrador usando un comando como el siguiente:
salva@MI-PC:~$ sudo su [sudo] password for salva
Después, localiza el paquete de XDebug para PHP:
root@MI-PC:/home/salva# apt search xdebug Sorting... Done Full Text Search... Done php-composer-xdebug-handler/jammy,now 2.0.4-1build1 all Restarts a process without Xdebug php-xdebug/jammy,now 3.1.2+2.9.8+2.8.1+2.5.5-4 amd64 Xdebug Module for PHP php-xdebug-all-dev/jammy 3.1.2+2.9.8+2.8.1+2.5.5-4 all Xdebug Module for PHP php8.1-xdebug/jammy,now 3.1.2+2.9.8+2.8.1+2.5.5-4 amd64 Xdebug Module for PHP
En el listado anterior se puede observar como existe el paquete php-xdebug. Ahora, procedemos a instalar dicho paquete:
root@SALVA-PC:/home/salva# apt install php-xdebug
Una vez instalado, llega el momento de configurarlo. Para ello Ubuntu Linux utiliza una estructura de configuración muy manejable. Para configurarlo, simplemente accede a la carpeta siguiente en modo administrador:
root@MI-PC:~# cd /etc/php/8.1/apache2/conf.d/
El directorio anterior es específico a la versión PHP 8.1, si tu versión de PHP es otra, en vez de aparecer 8.1, tendrás otra numeración. Dicho directorio tiene un montón de archivos de configuración de cada extensión de PHP, tienes que buscar el archivo de configuración específico de XDebug. Por ejemplo:
Puedes editar dicho archivo y modificar las opciones que estimes oportuno, aunque con la configuración por defecto debería funcionar. No obstante, es conveniente añadir la opción siguiente para que funcione de forma adecuada con NetBeans:
xdebug.idekey="netbeans-xdebug"
Es conveniente mantener actualizados los repositorios ejecutando sudo apt update antes de instalar paquetes.
Por último, recuerda reiniciar el servidor web siempre que modifiques el fichero php.ini para aplicar los cambios:
sudo service apache2 restart
Si necesitas instalar XDebug en sistemas Windows, puedes encontrar información al respecto en Internet. En la página web de la extensión XDebug tienes información en lenguaje inglés.
Creado con eXeLearning (Ventana nueva)
XDebug es por tanto un servidor al que nuestro IDE deberá conectarse para iniciar la depuración. En este caso, vamos a centrarnos en la depuración remota utilizando NetBeans como cliente.
En primer lugar, una vez que tenemos un proyecto PHP en NetBeans, lo que debemos realizar es revisar la configuración de NetBeans para usar el depurador:
Esta configuración se accede a través del menú "Herramientas" o "Tools", y después, haciendo clic en "Opciones" o "Options". Una vez ahí, si haces clic en la sección PHP y la pestaña "Depuración" o "Debugging", puedes ver las opciones de configuración de NetBeans, como cliente, para que se conecte al servidor XDebug para la depuración. Entre estas opciones las más importantes son:
Los datos anteriores deben coincidir con la configuración escrita en el archivo php.ini para XDebug.
Una vez verificada la configuración, la depuración es sencilla. Utilizando los menús, botones, o la combinación de teclas adecuada, puedes iniciar la depuración del proyecto actual (Ctrl+F5, comenzando por el archivo definido en la configuración de ejecución del mismo) o directamente la del archivo que tengas abierto en el editor (Ctrl+May+F5).
Una vez iniciada la depuración, se abre la aplicación en el navegador y se modifica la apariencia del editor. En las barras de herramientas y de menús, puedes controlar la ejecución con las opciones habituales (continuar, detener, paso a paso, etc.). En la parte inferior se muestran tres zonas nuevas con información sobre las variables que haya definidas en ese instante, la pila de llamadas y los puntos de interrupción.
En cualquier momento puedes añadir o eliminar un punto de interrupción en una línea de código simplemente pulsando con el ratón en la zona izquierda del editor, sobre los números de las líneas.
También puedes modificar el valor de las variables editando directamente sobre la zona en que se muestran.
Mientras estás depurando una aplicación, la ejecución se detendrá antes de ejecutar la primera línea de cada una de las páginas que se vayan abriendo. Éste es el comportamiento por defecto en NetBeans. En las opciones del entorno (Herramientas – Opciones – PHP) puedes modificarlo para que se detenga solo en los puntos de ruptura que hayas indicado.
En el siguiente tutorial oficial de Apache NetBeans puedes ver como configurar la depuración en NetBeans (es para la versión 19, pero es útil también para versiones anteriores)
Depuración en Apache NetBeans (en inglés)
Y en el siguiente tutorial puedes ver como configurar la depuración usando XDebug 3 en Visual Studio Code:
Depurar PHP en Visual Studio Code con XDebug y XAMPP (en inglés)
Creado con eXeLearning (Ventana nueva)
-- Creamos la base de datos
CREATE DATABASE `ejtienda` DEFAULT CHARACTER SET utf8 COLLATE utf8_spanish_ci;
USE `ejtienda`;
-- Creamos las tablas
CREATE TABLE `ejtienda`.`tienda` (
`cod` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`nombre` VARCHAR( 100 ) NOT NULL ,
`tlf` VARCHAR( 13 ) NULL
) ENGINE = INNODB;
CREATE TABLE `ejtienda`.`producto` (
`cod` VARCHAR( 12 ) NOT NULL ,
`nombre` VARCHAR( 200 ) NULL ,
`nombre_corto` VARCHAR( 50 ) NOT NULL ,
`descripcion` TEXT NULL ,
`PVP` DECIMAL( 10, 2 ) NOT NULL ,
`familia` VARCHAR( 6 ) NOT NULL ,
PRIMARY KEY ( `cod` ) ,
INDEX ( `familia` ) ,
UNIQUE ( `nombre_corto` )
) ENGINE = INNODB;
CREATE TABLE `ejtienda`.`familia` (
`cod` VARCHAR( 6 ) NOT NULL ,
`nombre` VARCHAR( 200 ) NOT NULL ,
PRIMARY KEY ( `cod` )
) ENGINE = INNODB;
CREATE TABLE `ejtienda`.`stock` (
`producto` VARCHAR( 12 ) NOT NULL ,
`tienda` INT NOT NULL ,
`unidades` INT NOT NULL ,
PRIMARY KEY ( `producto` , `tienda` )
) ENGINE = INNODB;
-- Creamos la tabla usuarios
CREATE TABLE `ejtienda`.`usuarios` (
`usuario` VARCHAR(20) NOT NULL PRIMARY KEY,
`contrasena` VARCHAR(256) NOT NULL
) ENGINE = INNODB;
-- Insertamos el usuario de la tienda dwes
INSERT INTO `ejtienda`.`usuarios` (usuario, contrasena) VALUES ('dwes', SHA2('dwes',256));
INSERT INTO `ejtienda`.`usuarios` (usuario, contrasena) VALUES ('usuario', SHA2('password',256));
-- Creamos las claves foráneas
ALTER TABLE `producto`
ADD CONSTRAINT `producto_ibfk_1`
FOREIGN KEY (`familia`) REFERENCES `familia` (`cod`)
ON UPDATE CASCADE;
ALTER TABLE `stock`
ADD CONSTRAINT `stock_ibfk_2`
FOREIGN KEY (`tienda`) REFERENCES `tienda` (`cod`)
ON UPDATE CASCADE,
ADD CONSTRAINT `stock_ibfk_1`
FOREIGN KEY (`producto`) REFERENCES `producto` (`cod`)
ON UPDATE CASCADE;
-- Creamos el usuario de la base de datos
CREATE USER 'admintienda'@'%' IDENTIFIED BY 'admintienda';
CREATE USER 'admintienda'@'localhost' IDENTIFIED BY 'admintienda';
-- Asignamos permisos al usuario
GRANT ALL ON `ejtienda`.*
TO `admintienda`;
-- Insertamos datos de ejemplo en la tienda
USE `ejtienda`;
INSERT INTO `tienda` (`cod`, `nombre`, `tlf`) VALUES
(1, 'CENTRAL', '600100100'),
(2, 'SUCURSAL1', '600100200'),
(3, 'SUCURSAL2', NULL);
INSERT INTO `familia` (`cod`, `nombre`) VALUES
('CAMARA', 'Cámaras digitales'),
('CONSOL', 'Consolas'),
('EBOOK', 'Libros electrónicos'),
('IMPRES', 'Impresoras'),
('MEMFLA', 'Memorias flash'),
('MP3', 'Reproductores MP3'),
('MULTIF', 'Equipos multifunción'),
('NETBOK', 'Netbooks'),
('ORDENA', 'Ordenadores'),
('PORTAT', 'Ordenadores portátiles'),
('ROUTER', 'Routers'),
('SAI', 'Sistemas de alimentación ininterrumpida'),
('SOFTWA', 'Software'),
('TV', 'Televisores'),
('VIDEOC', 'Videocámaras');
INSERT INTO `producto` (`cod`, `nombre`, `nombre_corto`, `descripcion`, `PVP`, `familia`) VALUES
('3DSNG', NULL, 'Nintendo 3DS negro', 'Consola portátil de Nintendo que permitirá disfrutar de efectos 3D sin necesidad de gafas especiales, e incluirá retrocompatibilidad con el software de DS y de DSi.', '270.00', 'CONSOL'),
('ACERAX3950', NULL, 'Acer AX3950 I5-650 4GB 1TB W7HP', 'Características:\r\n\r\nSistema Operativo : Windows® 7 Home Premium Original\r\n\r\nProcesador / Chipset\r\nNúmero de Ranuras PCI: 1\r\nFabricante de Procesador: Intel\r\nTipo de Procesador: Core i5\r\nModelo de Procesador: i5-650\r\nNúcleo de Procesador: Dual-core\r\nVelocidad de Procesador: 3,20 GHz\r\nCaché: 4 MB\r\nVelocidad de Bus: No aplicable\r\nVelocidad HyperTransport: No aplicable\r\nInterconexión QuickPathNo aplicable\r\nProcesamiento de 64 bits: Sí\r\nHyper-ThreadingSí\r\nFabricante de Chipset: Intel\r\nModelo de Chipset: H57 Express\r\n\r\nMemoria\r\nMemoria Estándar: 4 GB\r\nMemoria Máxima: 8 GB\r\nTecnología de la Memoria: DDR3 SDRAM\r\nEstándar de Memoria: DDR3-1333/PC3-10600\r\nNúmero de Ranuras de Memoria (Total): 4\r\nLector de tarjeta memoria: Sí\r\nSoporte de Tarjeta de Memoria: Tarjeta CompactFlash (CF)\r\nSoporte de Tarjeta de Memoria: MultiMediaCard (MMC)\r\nSoporte de Tarjeta de Memoria: Micro Drive\r\nSoporte de Tarjeta de Memoria: Memory Stick PRO\r\nSoporte de Tarjeta de Memoria: Memory Stick\r\nSoporte de Tarjeta de Memoria: CF+\r\nSoporte de Tarjeta de Memoria: Tarjeta Secure Digital (SD)\r\n\r\nStorage\r\nCapcidad Total del Disco Duro: 1 TB\r\nRPM de Disco Duro: 5400\r\nTipo de Unidad Óptica: Grabadora DVD\r\nCompatibilidad de Dispositivo Óptico: DVD-RAM/±R/±RW\r\nCompatibilidad de Medios de Doble Capa: Sí', '410.00', 'ORDENA'),
('ARCLPMP32GBN', NULL, 'Archos Clipper MP3 2GB negro', 'Características:\r\n\r\nAlmacenamiento Interno Disponible en 2 GB*\r\nCompatibilidad Windows o Mac y Linux (con soporte para almacenamiento masivo)\r\nInterfaz para ordenador USB 2.0 de alta velocidad\r\nBattería2 11 horas música\r\nReproducción Música3 MP3\r\nMedidas Dimensiones: 52mm x 27mm x 12mm, Peso: 14 Gr', '26.70', 'MP3'),
('BRAVIA2BX400', NULL, 'Sony Bravia 32IN FULLHD KDL-32BX400', 'Características:\r\n\r\nFull HD: Vea deportes películas y juegos con magníficos detalles en alta resolución gracias a la resolución 1920x1080.\r\n\r\nHDMI®: 4 entradas (3 en la parte posterior, 1 en el lateral)\r\n\r\nUSB Media Player: Disfrute de películas, fotos y música en el televisor.\r\n\r\nSintonizador de TV HD MPEG-4 AVC integrado: olvídese del codificador y acceda a servicios de TV que incluyen canales HD con el sintonizador DVB-T y DVB-C integrado con decodificador MPEG4 AVC (dependiendo del país y sólo con operadores compatibles)\r\n\r\nSensor de luz: ajusta automáticamente el brillo según el nivel de la iluminación ambiental para que pueda disfrutar de una calidad de imagen óptima sin consumo innecesario de energía.\r\n\r\nBRAVIA Sync: controle su sistema de ocio doméstico entero con un mismo mando a distancia universal que le permite reproducir contenidos o ajustar la configuración de los dispositivos compatibles con un solo botón.\r\n\r\nBRAVIA ENGINE 2: experimente colores y detalles de imagen increíblemente nítidos y definidos. \r\n\r\nLive Colour™: seleccione entre cuatro modos: desactivado, bajo, medio y alto, para ajustar el color y obtener imágenes vivas y una calidad óptima. \r\n\r\n24p True Cinema™: reproduzca una auténtica experiencia cinemática y disfrute de películas exactamente como el director las concibió a 24 fotogramas por segundo.', '356.90', 'TV'),
('EEEPC1005PXD', NULL, 'Asus EEEPC 1005PXD N455 1 250 BL', 'Características:\r\nProcesador: 1660 MHz, N455, Intel Atom, 0.5 MB. \r\nMemoria: 1024 MB, 2 GB, DDR3, SO-DIMM, 1 x 1024 MB. \r\nAccionamiento de disco: 2.5 ", 250 GB, 5400 RPM, \r\nSerial ATA, Serial ATA II, 250 GB. \r\nMedios de almacenaje: MMC, SD, SDHC. \r\nExhibición: 10.1 ", 1024 x 600 Pixeles, LCD TFT. \r\nCámara fotográfica: 0.3 MP. \r\nRed: 802.11 b/g/n, 10, 100 Mbit/s, \r\nFast Ethernet. \r\nAudio: HD. \r\nSistema operativo/software: Windows 7 Starter. \r\nColor: Blanco. \r\nContro de energía: 8 MB/s, Litio-Ion, 6 piezas, 2200 mAh, 48 W. \r\nPeso y dimensiones: 1270 g, 178 mm, 262 mm, 25.9 mm, 36.5 mm', '245.40', 'NETBOK'),
('HPMIN1103120', NULL, 'HP Mini 110-3120 10.1LED N455 1GB 250GB W7S negro', 'Características:\r\nSistema operativo instalado \r\nWindows® 7 Starter original 32 bits \r\n\r\nProcesador \r\nProcesador Intel® Atom™ N4551,66 GHz, Cache de nivel 2, 512 KB \r\n\r\nChipset NM10 Intel® + ICH8m \r\n\r\nMemoria \r\nDDR2 de 1 GB (1 x 1024 MB) \r\nMemoria máxima \r\nAdmite un máximo de 2 GB de memoria DDR2 \r\n\r\nRanuras de memoria \r\n1 ranura de memoria accesible de usuario \r\n\r\nUnidades internas \r\nDisco duro SATA de 250 GB (5400 rpm) \r\n\r\nGráficos \r\nTamaño de pantalla (diagonal) \r\nPantalla WSVGA LED HP Antirreflejos de 25,6 cm (10,1") en diagonal \r\n\r\nResolución de la pantalla \r\n1024 x 600 ', '270.00', 'NETBOK'),
('IXUS115HSAZ', NULL, 'Canon Ixus 115HS azul', 'Características:\r\nHS System (12,1 MP) \r\nZoom 4x, 28 mm. IS Óptico \r\nCuerpo metálico estilizado \r\nPantalla LCD PureColor II G de 7,6 cm (3,0") \r\nFull HD. IS Dinámico. HDMI \r\nModo Smart Auto (32 escenas) ', '196.70', 'CAMARA'),
('KSTDT101G2', NULL, 'Kingston DataTraveler 16GB DT101G2 USB2.0 negro', 'Características:\r\nCapacidades — 16GB\r\nDimensiones — 2.19" x 0.68" x 0.36" (55.65mm x 17.3mm x 9.05mm)\r\nTemperatura de Operación — 0° hasta 60° C / 32° hasta 140° F\r\nTemperatura de Almacenamiento — -20° hasta 85° C / -4° hasta 185° F\r\nSimple — Solo debe conectarlo a un puerto USB y está listo para ser utilizado\r\nPractico — Su diseño sin tapa giratorio, protege el conector USB; sin tapa que perder\r\nGarantizado — Cinco años de garantía', '19.20', 'MEMFLA'),
('KSTDTG332GBR', NULL, 'Kingston DataTraveler G3 32GB rojo', 'Características:\r\n\r\nTipo de producto Unidad flash USB\r\nCapacidad almacenamiento32GB\r\nAnchura 58.3 mm\r\nProfundidad 23.6 mm\r\nAltura 9.0 mm\r\nPeso 12 g\r\nColor incluido RED\r\nTipo de interfaz USB', '40.00', 'MEMFLA'),
('KSTMSDHC8GB', NULL, 'Kingston MicroSDHC 8GB', 'Kingston tarjeta de memoria flash 8 GB microSDHC\r\nÍndice de velocidad Class 4\r\nCapacidad almacenamiento 8 GB\r\nFactor de forma MicroSDHC\r\nAdaptador de memoria incluido Adaptador microSDHC a SD\r\nGarantía del fabricante Garantía limitada de por vida', '10.20', 'MEMFLA'),
('LEGRIAFS306', NULL, 'Canon Legria FS306 plata', 'Características:\r\n\r\nGrabación en tarjeta de memoria SD/SDHC \r\nLa cámara de vídeo digital de Canon más pequeña nunca vista \r\nInstantánea de Vídeo (Video Snapshot) \r\nZoom Avanzado de 41x \r\nGrabación Dual (Dual Shot) \r\nEstabilizador de la Imagen con Modo Dinámico \r\nPre grabación (Pre REC) \r\nSistema 16:9 de alta resolución realmente panorámico \r\nBatería inteligente y Carga Rápida \r\nCompatible con grabador de DVD DW-100 \r\nSISTEMA DE VÍDEO\r\nSoporte de grabación: Tarjeta de memoria extraíble (SD/SDHC)\r\nTiempo máximo de grabación: Variable, dependiendo del tamaño de la tarjeta de memoria.\r\nTarjeta SDHC de 32 GB: 20 horas 50 minutos', '175.00', 'VIDEOC'),
('LGM237WDP', NULL, 'LG TDT HD 23 M237WDP-PC FULL HD', 'Características:\r\n\r\nGeneral\r\nTamaño (pulgadas): 23\r\nPantalla LCD: Sí\r\nFormato: 16:9\r\nResolución: 1920 x 1080\r\nFull HD: Sí\r\nBrillo (cd/m2): 300\r\nRatio Contraste: 50.000:1\r\nTiempo Respuesta (ms): 5\r\nÁngulo Visión (°): 170\r\nNúmero Colores (Millones): 16.7\r\n\r\nTV\r\nTDT: TDT HD\r\nConexiones\r\nD-Sub: Sí\r\nDVI-D: Sí\r\nHDMI: Sí\r\nEuroconector: Sí\r\nSalida auriculares: Sí\r\nEntrada audio: Sí\r\nUSB Servicio: Sí\r\nRS-232C Servicio: Sí\r\nPCMCIA: Sí\r\nSalida óptico: Sí', '186.00', 'TV'),
('LJPROP1102W', NULL, 'HP Laserjet Pro Wifi P1102W', 'Impesora laserjet P1102W es facil de instalar y usar, ademas de que te ayudara a ahorrar energia y recursos. \r\nOlviadte de los cables y disfura de la libertad que te proporcina su tecnologia WIFI, imprime facilmente desdes cualquier de tu oficina. \r\n\r\nFormato máximo aceptado A4 A2 No\r\nA3 NoA4 Si\r\nA5 SiA6 Si\r\nB5 SiB6 Si\r\nSobres C5 (162 x 229 mm) SiSobres C6 (114 x 162 mm) Si', '99.90', 'IMPRES'),
('OPTIOLS1100', NULL, 'Pentax Optio LS1100', 'La LS1100 con funda de transporte y tarjeta de memoria de 2GB incluidas \r\nes la compacta digital que te llevarás a todas partes. \r\nEsta cámara diseñada por Pentax incorpora un sensor CCD de 14,1 megapíxeles y un objetivo gran angular de 28 mm.\r\n', '104.80', 'CAMARA'),
('PAPYRE62GB', NULL, 'Lector ebooks Papyre6 con SD2GB + 500 ebooks', 'Marca Papyre \r\nModelo Papyre 6.1 \r\nUso Lector de libros electrónicos \r\nTecnología e-ink (tinta electrónica, Vizplez) \r\nCPU Samsung Am9 200MHz \r\nMemoria - Interna: 512MB \r\n- Externa: SD/SDHC (hasta 32GB) \r\nFormatos PDF, RTF, TXT, DOC, HTML, MP3, CHM, ZIP, FB2, Formatos de imagen \r\nPantalla 6" (600x800px), blanco y negro, 4 niveles de grises ', '205.50', 'EBOOK'),
('PBELLI810323', NULL, 'Packard Bell I8103 23 I3-550 4G 640GB NVIDIAG210', 'Características:\r\n\r\nCPU CHIPSET\r\n\r\nProcesador : Ci3-550\r\nNorthBridge : Intel H57\r\n\r\nMEMORIA\r\nMemoria Rma : Ddr3 4096 MB\r\n\r\nDISPOSITIVOS DE ALMACENAMIENTO\r\nDisco Duro: 640Gb 7200 rpm\r\nÓptico : Slot Load siper multi Dvdrw\r\nLector de Tarjetas: 4 in 1 (XD, SD, HC, MS, MS PRO, MMC)\r\n\r\ndispositivos gráficos\r\nMonitor: 23 fHD\r\nTarjeta Gráfica: Nvidia G210M D3 512Mb\r\nMemoria Máxima: Hasta 1918Mb\r\n\r\nAUDIO\r\nAudio Out: 5.1 Audio Out\r\nAudio In: 1 jack\r\nHeasphone in: 1x jack\r\nAltavoces: Stereo\r\n\r\nACCESORIOS\r\nTeclado: Teclado y ratón inalámbrico\r\nMando a distancia: EMEA Win7 WMC\r\n\r\n\r\nCOMUNICACIONES\r\nWireless: 802.11 b/g/n mini card \r\nTarjeta de Red: 10/100/1000 Mbps\r\nBluetooth: Bluethoot\r\nWebcam: 1Mpixel Hd (1280x720)\r\nTv tuner: mCARD/SW/ DVB-T\r\n\r\nMONITOR\r\nTamaño: 23"\r\ncontraste: 1000:1\r\nTiempo de respuesta: 5MS\r\nResolución: 1920 X 1080\r\n\r\nPUERTOS E/S\r\nUsb 2.0 : 6\r\nMini Pci-e : 2\r\nEsata: 1\r\n\r\nSISTEMA OPERATIVO\r\nO.S: Microsoft Windows 7 Premium', '761.80', 'ORDENA'),
('PIXMAIP4850', NULL, 'Canon Pixma IP4850', 'Características:\r\n\r\nTipo: chorro de tinta cartuchos independientes\r\nConexión: Hi-Speed USB\r\nPuerto de impresión directa desde camaras\r\nResolución máxima: 9600x2400 ppp\r\nVelocidad impresión: 11 ipm (negro) / 9.3 ipm (color)\r\nTamaño máximo papel: A4\r\nBandeja entrada: 150 hojas\r\nDimensiones: 43.1 cm x 29.7 cm x 15.3 cm', '97.30', 'IMPRES'),
('PIXMAMP252', NULL, 'Canon Pixma MP252', 'Características:\r\n\r\nFunciones: Impresora, Escáner , Copiadora\r\nConexión: USB 2.0\r\nDimensiones:444 x 331 x 155 mm\r\nPeso: 5,8 Kg\r\n\r\nIMPRESORA\r\nResolución máxima: 4800 x 1200 ppp\r\nVelocidad de impresión:\r\nNegro/color: 7,0 ipm / 4,8 ipm\r\nTamaño máximo papel: A4\r\nCARTUCHOS\r\nNegro: PG-510 / PG-512\r\nColor: CL-511 / CL-513\r\n\r\nESCANER\r\nResolución máxima: 600 x 1200 ppp (digital: 19200 x 19200)\r\nProfundidad de color: 48/24 bits\r\nArea máxima de escaneado: A4\r\n\r\nCOPIA\r\nTiempo salida 1ª copia: aprox 39 seg.', '41.60', 'MULTIF'),
('PS3320GB', NULL, 'PS3 con disco duro de 320GB', 'Este Pack Incluye:\r\n- La consola Playstation 3 Slim Negra 320GB\r\n- El juego Killzone 3\r\n', '380.00', 'CONSOL'),
('PWSHTA3100PT', NULL, 'Canon Powershot A3100 plata', 'La cámara PowerShot A3100 IS, inteligente y compacta, presenta la calidad de imagen de Canon en un cuerpo\r\ncompacto y ligero para capturar fotografías sin esfuerzo; es tan fácil como apuntar y disparar.\r\nCaracterísticas:\r\n12,1 MP \r\nZoom óptico 4x con IS \r\nPantalla LCD de 6,7 cm (2,7") ', '101.40', 'CAMARA'),
('SMSGCLX3175', NULL, 'Samsung CLX3175', 'Características:\r\n\r\nFunción: Impresión color, copiadora, escáner\r\nImpresión \r\nVelocidad (Mono)Hasta 16 ppm en A4 (17 ppm en Carta)\r\nVelocidad (Color)Hasta 4 ppm en A4 (4 ppm en Carta)\r\nSalida de la Primer Página (Mono)Menos de 14 segundos (Desde el Modo Listo)\r\nResoluciónHasta 2400 x 600 dpi de salida efectiva\r\nSalida de la Primer Página (Color)Menos de 26 segundos (Dese el Modo Listo)\r\nDuplexManual\r\nEmulaciónSPL-C (Lenguaje de color de impresión SAMSUNG)\r\n\r\nCopiado \r\nSalida de la Primer Página (Mono)18 segundos\r\nMulticopiado1 ~ 99\r\nZoom25 ~ 400 %\r\nFunciones de CopiadoCopia ID, Clonar Copia, Copia N-UP, Copiar Poster\r\nResoluciónTexto, Texto / Foto, Modo Revista: hasta 600 x 600 ppp, Modo Foto: Hasta 1200 x 1200 ppp\r\nVelocidad (Mono)Hasta 17 ppm en Carta (16 ppm en A4)\r\nVelocidad (Color)Hasta 4 ppm en Carta (4 ppm en A4 )\r\nSalida de la Primer Página (Color)45 segundos\r\n\r\nEscaneado \r\n\r\nCompatibilidadNorma TWAIN, Norma WIA (Windows2003 / XP / Vista)\r\nMétodoEscáner plano color\r\nResolución (Óptica)1200 x 1200 dpi\r\nResolución (Mejorada)4800 x 4800 dpi\r\nEscaneado a Escanear a USB / Carpeta', '190.00', 'MULTIF'),
('SMSN150101LD', NULL, 'Samsung N150 10.1LED N450 1GB 250GB BAT6 BT W7 R', 'Características:\r\n\r\nSistema Operativo Genuine Windows® 7 Starter \r\n\r\nProcesador Intel® ATOM Processor N450 (1.66GHz, 667MHz, 512KB) \r\n\r\nChipset Intel® NM10\r\n\r\nMemoria del Sistema 1GB (DDR2 / 1GB x 1) Ranura de Memoria 1 x SODIMM \r\n\r\nPantalla LCD 10.1" WSVGA (1024 x 600), Non-Gloss, LED Back Light Gráficos \r\n\r\nProcesador Gráfico Intel® GMA 3150 DVMT \r\nMemoria Gráfica Shared Memory (Int. Grahpic) \r\n\r\nMultimedia \r\nSonido HD (High Definition) Audio \r\nCaracterísticas de Sonido SRS 3D Sound Effect \r\nAltavoces 3W Stereo Speakers (1.5W x 2) \r\nCámara Integrada Web Camera \r\n\r\nAlmacenamiento \r\nDisco duro 250GB SATA (5400 rpm S-ATA) \r\n\r\nConectividad\r\nWired Ethernet LAN (RJ45) 10/100 LAN \r\nWireless LAN 802.11 b/g/N\r\n\r\nBluetooth Bluetooth 3.0 High Speed \r\n\r\nI/O Port \r\nVGA \r\nHeadphone-out\r\nMic-in\r\nInternal Mic\r\nUSB (Chargable USB included) 3 x USB 2.0 \r\nMulti Card Slot 4-in-1 (SD, SDHC, SDXC, MMC)\r\nDC-in (Power Port)\r\n\r\nTipo de Teclado 84 keys \r\nTouch Pad, Touch Screen Touch Pad (Scroll Scope, Flat Type) \r\n\r\nSeguridad\r\nRecovery Samsung Recovery Solution \r\nVirus McAfee Virus Scan (trial version) \r\nSeguridad BIOS Boot Up Password / HDD Password \r\nBloqueo Kensington Lock Port \r\n\r\nBatería \r\nAdaptador 40 Watt Batería \r\n6 Cell Dimensiones ', '260.60', 'NETBOK'),
('SMSSMXC200PB', NULL, 'Samsung SMX-C200PB EDC ZOOM 10X', 'Características:\r\n\r\nSensor de Imagen Tipo 1 / 6” 800K pixel CCD\r\n\r\nLente Zoom Óptico 10 x optico\r\n\r\nCaracterísticas Grabación Vídeo Estabilizador de Imagen Hiper estabilizador de imagen digital\r\n\r\nInterfaz Tarjeta de Memoria Ranura de Tarjeta SDHC / SD', '127.20', 'VIDEOC'),
('STYLUSSX515W', NULL, 'Epson Stylus SX515W', 'Características:\r\n\r\nResolución máxima5760 x 1440 DPI\r\nVelocidad de la impresión\r\nVelocidad de impresión (negro, calidad normal, A4)36 ppm\r\nVelocidad de impresión (color, calidad normal, A4)36 ppm\r\n\r\nTecnología de la impresión\r\nTecnología de impresión inyección de tinta\r\nNúmero de cartuchos de impresión4 piezas\r\nCabeza de impresoraMicro Piezo\r\n\r\nExploración\r\nResolución máxima de escaneado2400 x 2400 DPI\r\nEscaner color: si\r\nTipo de digitalización Escáner plano\r\nEscanaer integrado: si\r\nTecnología de exploración CIS\r\nWLAN, conexión: si', '77.50', 'MULTIF'),
('TSSD16GBC10J', NULL, 'Toshiba SD16GB Class10 Jewel Case', 'Características:\r\n\r\nDensidad: 16 GB\r\nPINs de conexión: 9 pins\r\nInterfaz: Tarjeta de memoria SD standard compatible\r\nVelocidad de Escritura: 20 MBytes/s* \r\nVelocidad de Lectura: 20 MBytes/s*\r\nDimensiones: 32.0 mm (L) × 24.0 mm (W) × 2.1 mm (H)\r\nPeso: 2g\r\nTemperatura: -25°C a +85°C (Recomendada)\r\nHumedad: 30% to 80% RH (sin condensación)', '32.60', 'MEMFLA'),
('ZENMP48GB300', NULL, 'Creative Zen MP4 8GB Style 300', 'Características:\r\n\r\n8 GB de capacidad\r\nAutonomía: 32 horas con archivos MP3 a 128 kbps\r\nPantalla TFT de 1,8 pulgadas y 64.000 colores\r\nFormatos de audio compatibles: MP3, WMA (DRM9), formato Audible 4\r\nFormatos de foto compatibles: JPEG (BMP, TIFF, GIF y PNG\r\nFormatos de vídeo compatibles: AVI transcodificado (Motion JPEG)\r\nEcualizador de 5 bandas con 8 preajustes\r\nMicrófono integrado para grabar voz\r\nAltavoz y radio FM incorporada', '58.90', 'MP3');
INSERT INTO `stock` (`producto`, `tienda`, `unidades`) VALUES
('3DSNG', 1, 2),
('3DSNG', 2, 1),
('ACERAX3950', 1, 1),
('ARCLPMP32GBN', 2, 1),
('ARCLPMP32GBN', 3, 2),
('BRAVIA2BX400', 3, 1),
('EEEPC1005PXD', 1, 2),
('EEEPC1005PXD', 2, 1),
('HPMIN1103120', 2, 1),
('HPMIN1103120', 3, 2),
('IXUS115HSAZ', 2, 2),
('KSTDT101G2', 3, 1),
('KSTDTG332GBR', 2, 2),
('KSTMSDHC8GB', 1, 1),
('KSTMSDHC8GB', 2, 2),
('KSTMSDHC8GB', 3, 2),
('LEGRIAFS306', 2, 1),
('LGM237WDP', 1, 1),
('LJPROP1102W', 2, 2),
('OPTIOLS1100', 1, 3),
('OPTIOLS1100', 2, 1),
('PAPYRE62GB', 1, 2),
('PAPYRE62GB', 3, 1),
('PBELLI810323', 2, 1),
('PIXMAIP4850', 2, 1),
('PIXMAIP4850', 3, 2),
('PIXMAMP252', 2, 1),
('PS3320GB', 1, 1),
('PWSHTA3100PT', 2, 2),
('PWSHTA3100PT', 3, 2),
('SMSGCLX3175', 2, 1),
('SMSN150101LD', 3, 1),
('SMSSMXC200PB', 2, 1),
('STYLUSSX515W', 1, 1),
('TSSD16GBC10J', 3, 2),
('ZENMP48GB300', 1, 3),
('ZENMP48GB300', 2, 2),
('ZENMP48GB300', 3, 2);
Creado con eXeLearning (Ventana nueva)
Materiales desarrollados inicialmente por el Ministerio de Educación, Cultura y Deporte y actualizados por el profesorado de la Junta de Andalucía bajo licencia Creative Commons BY-NC-SA.

Antes de cualquier uso leer detenidamente el siguente Aviso legal
| Versión: 02.01.02 | Fecha de actualización: 04/01/25 | |
|---|---|---|
| Actualización de materiales y correcciones menores. | ||
| Versión: 02.01.00 | Fecha de actualización: 26/11/23 | Autoría: Salvador Romero Villegas |
|---|---|---|
Ubicación: Apartado 4 | ||
| Versión: 02.00.00 | Fecha de actualización: 31/05/23 | Autoría: Salvador Romero Villegas |
|---|---|---|
Ubicación: En todos los epígrafes Ubicación: Mapa conceptual - estructura del mapa conceptual y nombre de unidad. Ubicación: Orientaciones - estructura del contenido, temporalización y nombre de unidad. | ||
| Versión: 01.01.00 | Fecha de actualización: 17/11/14 | Autoría: Manuel Alberto Domínguez Vega |
|---|---|---|
| añadidas las credenciales a imagenes formato correcto de las multimedias añadidas las definiciones arreglados los enlaces rotos (S) | ||
| Versión: 01.00.00 | Fecha de actualización: 17/11/14 | |
|---|---|---|
| Versión inicial de los materiales. | ||