<?php

/*

				-  J B D D  -

		Pequeño Manejador de Base de Datos

			- Javier 10 19 -
				- versión 0.4 -



*/











	// Tabla de datos genérica -------------------------------------------------------
	// No representa una "tabla" de base de datos, sino una tabla (arreglo) de valores para realizar consultas

	class BDDTabla {

		// indica la tarea que realizará con el dato
		const BDD_VALOR     = 'valor';
		const BDD_CONDICION = 'condicion';
		const BDD_SELECCION = 'seleccion';
		const BDD_RELACION  = 'relacion';
		const BDD_TIPO      = 'tipo';
		const BDD_LIMITE    = 'limite';
		const BDD_ORDEN     = 'orden';
		const BDD_GRUPO     = 'grupo';


		// indica el tipo de columna
		const BDD_TIPO_entero      = 'entero';
		const BDD_TIPO_caracteres  = 'caracter';
		const BDD_TIPO_caracter250 = 'caracter250';
		const BDD_TIPO_caracter50  = 'caracter50';
		const BDD_TIPO_caracter100 = 'caracter100';
		const BDD_TIPO_caracter10  = 'caracter10';
		const BDD_TIPO_texto       = 'texto';
		const BDD_TIPO_fecha       = 'fecha';
		const BDD_TIPO_id          = 'id';








		// información
		private $datos =array();

		// nombre de la tabla
		private $nombre = 'tabla';


		// asocia una base de datos (opcional)
		// si se asocia una base de datos se podrá ejecutar comandos desde el objeto tabla
		private $base = null;


		public function __construct($nombre, $base = null) {
			$this->nombre = $nombre;
			$this->base   = $base;
			$this->nuevo();
		}


		// crea la matriz de arreglos que contiene información (tabla)
		public function nuevo () {
			$this->datos            = array (
				self::BDD_VALOR     =>array (),
				self::BDD_CONDICION =>array (),
				self::BDD_RELACION  =>array (),
				self::BDD_SELECCION =>array (),
				self::BDD_ORDEN     =>array (),
				self::BDD_GRUPO     =>array (),
				self::BDD_TIPO 		=>array (),
				self::BDD_LIMITE    =>''
				);
			$this->seleccion('*');
			return $this;
		}




		public function nombre ($nombre = null) {
			if ($nombre == null)
				return $this->nombre;
			$this->nombre = $nombre;
				return $this;
		}

		public function base   () { return $this->base;   }








		// --------------------------------------------------------
		// ingreso de datos
		// --------------------------------------------------------

		// agrega pares "columna = dato" para insertar o actualizar
		public function valor ($columna, $valor) {
			$this->asociar (self::BDD_VALOR, $columna, $valor);
			return $this;
		}

		// agrega pares "columna = dato" para comparar | también agrega columna comparacion dato
		// condicion ("columna", "valor"); ===== condicion ("columna", "=", "valor");
		// condicion ("columna", ">", "valor");
		public function condicion ($columna, $comparacion ,$valor=null) {
			if ($valor == null) {
				$valor = $comparacion;
				$comparacion = '=';
			}
			$info = array (	'COMPARACION' => $comparacion,
					               'VALOR'=> $valor);

			$this->asociar (self::BDD_CONDICION, $columna, $info);
			return $this;
		}


		// agrega columnas de selección
		// seleccion ("usuario", "clave", "estado", ...);
		public function columna (...$columnas) {
			// quitar este comando
			return $this->seleccion(...$columnas);
		}
		public function seleccion (...$columnas) {
			// si hay un * - se borra, para eviar "select *, usuario" y que quede "select usuario"
			if (isset ($this->datos[self::BDD_SELECCION]['*']))
				$this->datos[self::BDD_SELECCION] = array();

			// si es un * - se borra, para eviar "select *, usuario" y que quede "select usuario"
			if ($this->datos[self::BDD_SELECCION]==array('*'))
				$this->datos[self::BDD_SELECCION] = array();

			// si no hay datos de columnas, agrega *
			if ($columnas == array())
				$columnas = array('*');

			foreach ($columnas as $columna)
				$this->asociar (self::BDD_SELECCION, $columna);

			return $this;
		}


		// agrega una tabla relacionada y sus columnas en común
		// relacion ($comentarios, "usuario", "id");
		public function relacion ($otraTabla, $otraColumna, $miColumna=null) {
			if ($miColumna == null)
				$miColumna = $otraColumna;
			$info = array (
				'TABLA' => $otraTabla,
				'COLUMNAORIGEN'=> $miColumna,
				'COLUMNADESTINO'=>$otraColumna);
			$this->asociar(self::BDD_RELACION, $miColumna, $info);
			return $this;
		}



		// agrega pares "columna = tipo" para crear columnas
		// tipo_columna ("nombre", "caracteres");
		public function tipo_columna ($columna, $tipo = self::BDD_TIPO_caracteres) {
			$this->asociar (self::BDD_TIPO, $columna, $tipo);
			return $this;
		}


		// ingresa datos desde un arreglo asociativo
		// $tabla->arreglo ($_POST, 'usuario', 'clave', 'fecha'...);
		public function arreglo ($arreglo, ...$indices) {
			if (is_array($arreglo))
				foreach ($indices as $indice)
					if (isset ($arreglo[$indice]))
						$this-> valor ( $indice, $arreglo [$indice]);
			return $this;
		}


		public function limite  ($limite) {
			$this->datos[self::BDD_LIMITE] = $limite;
			return $this;
		}

		public function orden ($columna, $orden= 'asc') {
			$this->asociar (self::BDD_ORDEN, $columna, $orden);
			return $this;
		}

		public function grupo (...$columnas) {
			foreach ($columnas as $columna)
				$this->asociar (self::BDD_GRUPO, $columna);
			return $this;
		}




		// FUNCIÓN COMÚN - DE USO INTERNO, ASOCIA DATOS Y ALMACENA EN EL ARREGLO DE DATOS
		private function asociar ($tipo, $columna, $valor='') {
			$this->datos[$tipo] = array_merge($this->datos[$tipo] ,  array($columna => $valor));
		}





		// --------------------------------------------------------
		// obtención de datos
		// --------------------------------------------------------

		public function columnas      () { return $this->datos[self::BDD_SELECCION]; }
		public function valores       () { return $this->datos[self::BDD_VALOR    ]; }
		public function condiciones   () { return $this->datos[self::BDD_CONDICION]; }
		public function relaciones    () { return $this->datos[self::BDD_RELACION ]; }
		public function tipo_columnas () { return $this->datos[self::BDD_TIPO     ]; }
		public function limitacion    () { return $this->datos[self::BDD_LIMITE   ]; }
		public function agrupacion    () { return $this->datos[self::BDD_GRUPO    ]; }
		public function ordenamiento  () { return $this->datos[self::BDD_ORDEN    ]; }






		// devuelve un arreglo con la lista de columnas (quitando los valores)
		// columnasDe ("seleccion");
		// columnasDe (BDD_SELECCION);
		// columnasDe (array('columna' => 'dato'));
		public function columnasDe ($elemento) {
			if (is_array($elemento))
				return array_keys ($elemento);				// elemento es un arreglo de datos
			return array_keys ($this->datos[$elemento]);	// elemento es una cadena que especifica un tipo de datos
		}

		// devuelve un arreglo con la lista de valores (Quitando el nombre de la columna)
		public function valoresDe ($elemento) {
			if (is_array($elemento))
				return array_values ($elemento);			// elemento es un arreglo de datos
			return array_values ($this->datos[$elemento]);	// elemento es una cadena que especifica un tipo de datos
		}

		public function datos ($tipo) {
			return $this->datos[$tipo];
		}


		// obtiene el valor de una columna especifica
		public function valorDe ($columna) {
			if (array_key_exists($columna, $this->valores()))
				return $this->valores()[$columna];
			return '';
		}




		// --------------------------------------------------------
		// comandos
		// --------------------------------------------------------


		// invierte comandos
		// en vez de $bd->insertar(tabla);
		// se usa    $tabla->insertar()


		// selección rápida
		public function seleccionar (...$columnas) {
			// si $columnas contiene datos... los procesa
			if ($columnas != array())
				$this->seleccion(...$columnas);
			$this->base->seleccionar ($this);
			return $this;
		}

		public function insertar   () { $this->base->insertar   ($this); return $this; }
		public function actualizar () { $this->base->actualizar ($this); return $this; }
		public function eliminar   () { $this->base->eliminar   ($this); return $this; }
		public function crearTabla () { $this->base->crearTabla ($this); return $this; }
		public function tuplas     () { $this->base->tuplas     ($this); return $this; }
		public function total      () { $this->base->total      ($this); return $this; }
		public function afectados  () { $this->base->afectados  ($this); return $this; }
		public function afectadas  () { $this->base->afectadas  ($this); return $this; }
		public function infoTabla  () { $this->base->infoTabla  ($this); return $this; }
		public function imprimir   ($estilo= null) { $this->base->imprimir  ($estilo); return $this; }

		public function agregarColumna      () { $this->base->agregarColumna      ($this); return $this; }
		public function siguienteTupla      () { $this->base->siguienteTupla      ($this); return $this; }
		public function crearTablaYColumnas () { $this->base->crearTablaYColumnas ($this); return $this; }
		public function existe (...$columnas)  { return $this->base->existe              ($this, ...$columnas); }
		public function insertarSiNoExiste  (...$columnas) { $this->base->insertarSiNoExiste  ($this, ...$columnas); return $this; }
		public function actualizarOInsertar () { $this->base->actualizarOInsertar ($this); return $this; }





	}























		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------











	// clase intermedia de base de datos
	// esta clase manejará cualquier base de datos desconocida
	// aquí no debe ir código de consulta

	class BDD {

		private $direccion = 'localhost';
		private $usuario   = 'root';
		private $clave     = '';
		private $base      = 'bdd';
		private $conexion  = null;			 // clase que maneja una base de datos conocida o especifica



		public function __construct ($base, $usuario = 'root', $clave = '', $direccion = 'localhost') {

			$this->base      = $base;
			$this->usuario   = $usuario;
			$this->clave     = $clave;
			$this->direccion = $direccion;

			$this->conexion = new BDDAdaptador();
		}





		public function conectar () {
			$this->conexion->conectar ($this->base, $this->usuario, $this->clave, $this->direccion);
		}


		// para verificar si se conectó correctamente
		public function desconectado () {
			return $this->conexion->desconectado();
		}

		public function crear () {
			$this->conexion->crearBDD ($this->base, $this->usuario, $this->clave, $this->direccion);
		}


		public function cerrar         () {        $this->desconectar          (); }
		public function desconectar    () {        $this->conexion->desconectar(); }
		public function liberar        () {        $this->conexion->liberar    (); }
		public function utf8           () {        $this->conexion->utf8       (); }
		public function total          () { return $this->conexion->total      (); }
		public function afectadas      () { return $this->afectados            (); }
		public function afectados      () { return $this->conexion->afectados  (); }
		public function tuplas         () { return $this->conexion->tuplas     (); }
		public function error          () { return $this->conexion->error      (); }
		public function siguienteTupla () { return $this->conexion->siguienteTupla(); }
		public function seleccionar (   $tabla ) { return $this->conexion->seleccionar ($tabla); }
		public function actualizar  (...$tablas) { foreach ($tablas as $tabla) $this->conexion->actualizar ($tabla); return $this; }
		public function eliminar    (...$tablas) { foreach ($tablas as $tabla) $this->conexion->eliminar   ($tabla); return $this; }
		public function insertar    (...$tablas) { foreach ($tablas as $tabla) $this->conexion->insertar   ($tabla); return $this; }



		// compara si existen los datos antes de insertar
		// si no se especifica columnas a comparar, compara con todas las columnas
		public function insertarSiNoExiste ($tabla, ...$columnas) {
			$this->conexion->inicio();
			if ($this->existe($tabla, ...$columnas) == false)
				$this->insertar($tabla);
			return $this->conexion->fin();
		}


		// compara si existen los datos antes de actualizar
		// si existe actualiza, si no existe inserta
		// si no se especifica columnas a comparar, compara con todas las columnas
		public function actualizarOInsertar ($tabla) {



			$this->conexion->inicio();

			// si existe la fila, actualiza
			$this->seleccionar($tabla);
			if ($this->total() > 0) {
				$this->actualizar($tabla);
				return $this->conexion->fin();
			}

			// en caso de que no exista, la creará



			// usa una tabla temporal para convertir condiciones en valores para agregar
			$tablaTemporal = new BDDTabla($tabla->nombre());

			foreach ($tabla->valores() as $columna => $valor)
				$tablaTemporal->valor($columna, $valor);


			foreach ($tabla->condiciones() as $columna => $valor)
				$tablaTemporal->valor($columna, $valor['VALOR']);

			$this->insertar($tablaTemporal);

			return $this->conexion->fin();

		}













		// verifica si una tabla con valores existe
		// compara por valores, no por condiciones
		// si no se especifica columnas a comparar, compara con todas las columnas
		public function existe ($tabla, ...$columnas) {
			// crea una tabla temporal para hacer la consulta sobre columnas especificas
			$tablaTemporal = new BDDTabla($tabla->nombre());

			// verifica si hay columnas especificas
			if ($columnas == array()) // si columnas es un arreglo vacío
				foreach ($tabla->valores() as $columna => $valor)
					$tablaTemporal->condicion($columna, $valor);
			else
				foreach ($columnas as $columna)
					$tablaTemporal->condicion($columna, $tabla->valorDe($columna));


			$this->seleccionar($tablaTemporal);


			return $this->total() > 0;
		}




		// TABLAS
		public function crearTabla     (...$tablas) { foreach ($tablas as $tabla) $this->conexion->crearTabla     ($tabla); return $this; }
		public function agregarColumna (...$tablas) { foreach ($tablas as $tabla) $this->conexion->agregarColumna ($tabla); return $this; }
		public function infoTabla      ($tabla) { $this->conexion->informacionTabla($tabla); return $this; }

		public function crearTablaYColumnas (...$tablas) {
			foreach ($tablas as $tabla) {
				$this->crearTabla     ($tabla);
				$this->agregarColumna ($tabla);
			}
			return $this;
		}




		// --------------------------------------------------------
		// --------------------------------------------------------


		// ejecuta la consulta y devuelve tuplas
		public function ejecutar ($consulta) {
			$this->conexion->ejecutar($consulta);
			return $this->tuplas();
		}



		public function visor ($modo = true) {
			$this->conexion->visor = $modo;
		}

		public function imprimir ($estilo = null) {
			$this-> conexion -> imprimir ($estilo);
		}

	}



























	// --------------------------------------------------------
	// --------------------------------------------------------
	// --------------------------------------------------------
	// --------------------------------------------------------
	// --------------------------------------------------------
	// --------------------------------------------------------
	// --------------------------------------------------------
	// --------------------------------------------------------
	// --------------------------------------------------------
	// --------------------------------------------------------











	// clase que genera código de consultas de base de datos
	// en este caso es MySQL pero podría ser cualquier otra

	class BDDAdaptador {

		const BDD_TIPO      	   = 'tipo_columna';

		private $conexion;
		private $tuplas;
		public  $visor = false;
		public  $error = false;
		public  $errorMensaje = '';



		public function conectar ($base, $usuario = 'root', $clave = '', $direccion = 'localhost') {
			$this->error = false;
			$this->base = $base;

			//$this->conexion = mysqli_connect($direccion, $usuario, $clave, $base) or $this->generarError();
			$this->conexion = mysqli_connect($direccion, $usuario, $clave) or $this->generarError();

			if ($this->error == false)
				mysqli_select_db ($this->conexion, $base)  or $this->generarError();
		}


		public function crearBDD ($base, $usuario = 'root', $clave = '', $direccion = 'localhost') {
			$this->conexion = mysqli_connect($direccion, $usuario, $clave) or $this->generarError();
			$this->ejecutar ($this->consultaCrearBDD ($base));
			return mysqli_select_db ($this->conexion, $base);
		}

		public function utf8 () {
			if ($this->conexion == null)
				return;
			mysqli_set_charset ($this->conexion, 'utf8');
		}

		public function desconectar () {
			if ($this->conexion == null);
				return;
			mysqli_close($this->conexion) or $this->generarError();
		}

		// para verificar si se conectó correctamente
		public function desconectado () {
			if ($this->conexion == null)
				return true;
			if (mysqli_connect_errno()>0)
				return true;


			return $this->error();
		}



		public function siguienteTupla () {
			if ($this->existeTupla()== false)
				return null;
			return mysqli_fetch_array($this->tuplas);
		}

		public function total () {
			if ($this->existeTupla()== false)
				return 0;
			return mysqli_num_rows($this->tuplas);
		}

		public function liberar () {
			if ($this->existeTupla()== false)
				return;
			mysqli_free_result($this->tuplas);
		}

		private function existeTupla () {
			if ($this->tuplas == null)
				return false;
			if (is_bool($this->tuplas))
				return false;
			return true;
		}

		public function tuplas    () { return $this->tuplas;      }
		public function afectadas () { return $this->afectados(); }
		public function afectados () { return mysqli_affected_rows     ($this->conexion) or $this->generarError(); }
		public function inicio    () { return mysqli_begin_transaction ($this->conexion) or $this->generarError(); }
		public function fin       () { return mysqli_commit            ($this->conexion) or $this->generarError(); }







		//--------------------------------------------------
		// ejecuta consultas
		//--------------------------------------------------

		public function seleccionar      ($tabla) { return $this->ejecutar ($this->consultaSeleccionar      ($tabla)); }
		public function insertar         ($tabla) { return $this->ejecutar ($this->consultaInsertar         ($tabla)); }
		public function actualizar       ($tabla) { return $this->ejecutar ($this->consultaActualizar       ($tabla)); }
		public function eliminar         ($tabla) { return $this->ejecutar ($this->consultaEliminar         ($tabla)); }
		public function crearTabla       ($tabla) { return $this->ejecutar ($this->consultaCrearTabla       ($tabla)); }
		public function agregarColumna   ($tabla) { return $this->ejecutar ($this->consultaAgregarColumna   ($tabla)); }
		public function informacionTabla ($tabla) { return $this->ejecutar ($this->consultaInformacionTabla ($tabla)); }





		// ejecuta la consulta en MySQL y devuelve tuplas ----------
		public function ejecutar ($consulta) {
			if ($this->conexion == null)
				return null;
			if ($this->visor)
				echo "<br/>ejecutando consulta: <pre>$consulta </pre>\n\r";
			$this->liberar();
			$this->tuplas = mysqli_query($this->conexion, $consulta) or $this->generarError();

			return $this->tuplas;
		}



		private function generarError () {
			$this->error = true;
			$this->errorMensaje = mysqli_error($this->conexion);
			if ($this->visor)
				echo "<br/><span class = 'error'>¡ERROR! -> " . $this->errorMensaje . '</span>';
		}




		public function error () {
			if ($this->conexion == null)
				return true;
			if (mysqli_error($this->conexion) != '')
				return true;
			return $this->error;
		}


		public function mensajeError () {
			if ($this->conexion == null)
				return 'Sin conexión a la Base de datos';
			return mysqli_error($this->conexion);
		}









		// --------------------------------------------------------
		// Generadores de consultas | MySQL
		// --------------------------------------------------------


		// código para seleccionar
		private function consultaSeleccionar ($tabla) {

			$condiciones  = $this->condiciones  ($tabla);
			$relaciones   = $this->relaciones   ($tabla);
			$proyecciones = $this->proyecciones ($tabla);
			$ordenamiento = $this->ordenamiento ($tabla);
			$agrupacion   = $this->agrupacion   ($tabla);



			$limite = '';
			if ($tabla->limitacion() != null)
				$limite = 'LIMIT '. $tabla->limitacion();

			return
			  'SELECT ' . $proyecciones
			.  ' FROM ' . $tabla->nombre()
			.       ' ' . $relaciones
			.       ' ' . $condiciones
			.       ' ' . $agrupacion
			.       ' ' . $ordenamiento
			.       ' ' . $limite;
		}


		// código para insertar
		private function consultaInsertar ($tabla) {

			return 'INSERT INTO '
			. $tabla->nombre()
			. ' ('
			. $this->cadena($tabla->columnasDe(BDDTabla::BDD_VALOR))
			. ') '
			. 'VALUES ('
			. $this->cadena($this->comillas($tabla->valoresDe(BDDTabla::BDD_VALOR)))
			. ') ';
		}



		// código para actualizar
		private function consultaActualizar ($tabla) {
			$condiciones = $this->pares ($tabla->condiciones());
			$cambios = $this->pares ($tabla->valores(), 'SET');

			return 'UPDATE '
			. $tabla->nombre()
			. ' ' . $cambios
			. ' ' . $condiciones ;

		}


		// código para eliminar
		private function consultaEliminar ($tabla) {
			$condiciones = $this->pares ($tabla->condiciones());

			return 'DELETE FROM '
			. $tabla->nombre()
			. ' ' . $condiciones;

		}






		 // TABLAS -------------------------------------------------

		// código para crear tablas
		private function consultaCrearTabla ($tabla) {
			$columnas = $this->cadena($this->tipo_columnas($tabla), '', '', ',');
			return 'CREATE TABLE IF NOT EXISTS '. $tabla->nombre() . ' (' . ' ' . $columnas . ') ';
		}




		// código para agregar columnas
		private function consultaAgregarColumna ($tabla) {
			$columnas = $this->cadena($this->tipo_columnas($tabla), '', '', ',');
			$columnas = str_replace (',', ', ADD COLUMN IF NOT EXISTS ', $columnas);
			return 'ALTER TABLE ' . $tabla->nombre () . ' ADD COLUMN IF NOT EXISTS ' . $columnas;
		}


		// código para eliminar tablas
		private function consultaEliminarTabla ($tabla) {
			return 'DROP TABLE IF EXISTS '. $tabla->nombre();
		}

		// código para emitir información de tablas
		private function consultaInformacionTabla ($tabla) {
			return 'SHOW COLUMNS FROM ' . $tabla->nombre();
		}


		// código para crear base de datos
		private function consultaCrearBDD ($base) {
			return 'CREATE DATABASE IF NOT EXISTS ' . $base . ' DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci';
		}






		// --------------------------------------------------------
		// Herramientas para generadores de consultas
		// --------------------------------------------------------
		// Aquí es donde se pone FEO
		// --------------------------------------------------------


		// genera la cadena de condiciones
		private function pares ($arregloDatos, $instruccon= 'WHERE', $comilla = "'", $nexo = 'AND') {

			$condicion = '';

			foreach ($arregloDatos as $columna => $infoCondicion) {
				// verificar si el dato es un arreglo, o un valor
				if (is_array($infoCondicion)) {
					$valor=$infoCondicion['VALOR'];
					$comparacion = $infoCondicion['COMPARACION'];
				} else {
					$valor = $infoCondicion;
					$comparacion = '=';
				}

				if ($condicion != '')
					$condicion = $condicion . ' ' . $nexo . ' ';
				$condicion =  $condicion . $columna . ' ' . $comparacion . ' ' .$comilla . $valor . $comilla ;
			}

			if ($condicion != '')
				$condicion = $instruccon . ' ' . $condicion;
			return $condicion;
		}





	// genera la cadena de relaciones
		private function relaciones ($tabla) {

			$infoRelacion = $tabla->relaciones();
			$igualdad = '';
			foreach ($infoRelacion as $columna => $asociacion) {
				$tablaRelacionada   = $asociacion['TABLA'];
				$nombreTabla        = $tablaRelacionada->nombre();
				$columnaRelacionada = $asociacion['COLUMNADESTINO'];

				$igualdad = 'INNER JOIN '
					. $nombreTabla
					. ' ON ' . $tabla->nombre()
					. '.'    . $columna
					. ' = '  . $nombreTabla
					. '.'    . $columnaRelacionada;
			}
			return $igualdad;
		}





		// genera un arreglo con columnas de selección
		private function proyecciones ($tabla) {
			$respuesta = array();
			foreach ($this->colaterales ($tabla, BDDTabla::BDD_SELECCION) as $columna => $valor)
				$respuesta =  array_merge($respuesta,  array ($columna));
			return $this->cadena ($respuesta);
		}





		// usado para proyecciones
		// obtiene las condiciones columna = valor
		// también obtiene las condiciones nombreTabla.columna = valor

		private function condiciones ($tabla) {
			return $this->pares ($this->colaterales ($tabla, BDDTabla::BDD_CONDICION));
		}



		// arreglo de columnas para agrupar
		private function agrupacion ($tabla) {
			$respuesta = array();
			foreach ($this->colaterales ($tabla, BDDTabla::BDD_GRUPO) as $columna => $valor)
				$respuesta =  array_merge($respuesta,  array ($columna));

			if ($respuesta == array())
				return '';
			return 'GROUP BY ' . $this->cadena ($respuesta);
		}

		// arreglo con columnas para ordenar
		private function ordenamiento ($tabla) {
			$respuesta = array();
			foreach ($this->colaterales ($tabla, BDDTabla::BDD_ORDEN) as $columna => $valor)
				$respuesta =  array_merge($respuesta,  array ($columna .  ' ' . $valor));

			if ($respuesta == array())
				return '';
			return 'ORDER BY ' . $this->cadena ($respuesta);
		}






		// devuelve un arreglo con información de un mismo tipo de tablas relacionadas
		private function colaterales ($tabla, $tipo = 'BDDTabla::BDD_SELECCION') {
			if ($tabla->relaciones() == array()) // si no hay relaciones (= arreglo vacío)
				return $tabla->datos($tipo);     // devuelve la información sin el nombre de tabla

			$respuesta = array ();
			$respuesta = array_merge ($this->agregarNombreDeTabla ($tabla->nombre(), $tabla->datos($tipo)));
			foreach ($this->tablasRelacionadas($tabla) as $tablaRelacionada)
				$respuesta = array_merge ($respuesta, $this->agregarNombreDeTabla ($tablaRelacionada->nombre(), $tablaRelacionada->datos($tipo)));
			return $respuesta;
		}




		// genera un arreglo con tipos de columnas
		private function tipo_columnas ($tabla) {
			$respuesta = array();
			foreach ($tabla->tipo_columnas() as $columna => $tipo)
				//$respuesta = array_merge ($respuesta , array ($columna => $this->tipo ($tipo)));
				$respuesta = array_merge ($respuesta , array ($columna .  ' ' . $this->tipo ($tipo)));

			return $respuesta;
		}






	private function tipo ($tipo) {

		switch ($tipo) {
			case 'entero'      : return 'int(11)';
			case 'caracter'    : return 'varchar(250)';
			case 'caracter250' : return 'varchar(250)';
			case 'caracter50'  : return 'varchar(50)';
			case 'caracter100' : return 'varchar(100)';
			case 'caracter10'  : return 'varchar(10)';
			case 'texto'       : return 'text';
			case 'fecha'       : return 'datetime';
			case 'id'	       : return 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'	;
		}

		return 'varchar(250)';
	}



		// obtiene las tablas relacionadas
		private function tablasRelacionadas ($tabla) {
			$tablas = array();
			foreach ($tabla->relaciones() as $columna => $asociacion)
				$tablas = array_merge ($tablas, array($asociacion['TABLA']));
			return $tablas;
		}


		// usado para unión de tablas
		// devuelve nombreTabla.columna, valor
		private function agregarNombreDeTabla ($nombre, $columnas) {
			$respuesta = array();
			foreach ($columnas as $columna=> $valor)
				$respuesta = array_merge ($respuesta, array($nombre.'.'.$columna => $valor));
			return $respuesta;
		}


		// devuelve "tabla.columna" - usado para consultas con tablas relacionadas
		// devuelve solo el nombre completo de la tabla
		private function columnaCompleta ($tabla, $tipo = BDDTabla::BDD_SELECCION) {

			$respuesta = array();
			foreach ($tabla->columnasDe($tipo) as $columna)
					$respuesta = array_merge($respuesta , array ($tabla->nombre() . '.' . $columna));
			return $respuesta;
		}




		// convierte un arreglo en una cadena de caracteres, separando elementos con una coma
		private function cadena ($arreglo) {
			return implode(', ' , $arreglo);
		}


		// devuelve un arreglo, agrega comillas a los valores
		private function comillas ($arreglo) {
			return array_map (function ($info) { return "'" . $info . "'"; }, $arreglo);
		}










	public function imprimir ($estilo = null) {
			if ($this->existeTupla() == false)
				return;
			if ($estilo == null)
				$estilo = 'border=1';

			$tuplas = $this->tuplas;

			$columnas = array ();
			while ($x = mysqli_fetch_field($tuplas))
				$columnas = array_merge ($columnas, array ($x->name));

			echo "<table $estilo>";
				echo '<tr>';
					foreach ($columnas as $columna)
						echo '<th>' . $columna .  '</th>';
				echo '</tr>';

						while ($x = mysqli_fetch_array($tuplas))  {
							echo '<tr>';
							foreach ($columnas as $columna)
								echo '<td>' . $x[$columna] .  '</td>';
							echo '</tr>';
						}
				echo '</tr>';
			echo '</table>';
		}





	}









	/*
	// para realizar consultas con columnas personalizadas
	// facilita creación de etiquetas

	class BDDEnlace {
		private $parametros = array ();
		private $contenidos = array ();
		private $hayParametros = false;



		public function parametro ($parametro, $valor=null) {

			if ($valor == null)
				$valor = $parametro;

			if ($this->hayParametros)
				$parametro = '&'.$parametro;
			else
				$parametro = '?'.$parametro;
			$this->hayParametros = true;


			$parametro = "'".$parametro."='";
			$this->parametros[] = $parametro;
			$this->parametros[] = $valor;
		}

		public function parametroFijo ($parametro) {
			if ($this->parametros)
				$parametro = '&'.$parametro;
			else
				$parametro = '?'.$parametro;
			$this->hayParametros = true;
			$parametro = "'".$parametro."'";
			$this->parametros[] = $parametro;

		}

		public function contenidoVisible ($contenido) {
			$contenido = "'".$contenido."'";
			$this->contenidos[] = $contenido;
		}

		public function columnaVisible ($contenido) {
			$this->contenidos[] = $contenido;
		}

		private function obtenerParametros () {
			return implode (', ', $this->parametros);
		}

		private function obtenerContenidos () {
			return implode (', ', $this->contenidos);
		}


		public function obtenerEnlace ($url, $contenido = '', ...$parametros) {
			 $parametro = "href=". "\'" . $url. '\', ' . $this->obtenerParametros() . ", '" . "\'";
			 if ($contenido == '')
				 if ($this->obtenerContenidos() != '')
					$contenido = " ', " . $this->obtenerContenidos(). " , '";
			if ($contenido == '')
				$contenido = 'Clic Aquí';
			 $enlace =  $this->obtenerEtiqueta ('a', $contenido, $parametro,...$parametros);
			 return "concat ('$enlace')";
		}





		public function obtenerEtiqueta ($nombre = 'a', $contenido = '', ... $parametros) {

			$etiqueta
			= '<'
			. $nombre
			. ' '
			.  implode (' ', $parametros)
			;

			if ($contenido == '')
				$etiqueta = $etiqueta . '/>';
			else
				$etiqueta = $etiqueta . ' >' . $contenido .'</'.$nombre.'>';
			return $etiqueta;
		}

	}


	// se usa para seleccionar columnas y crear enlaces en la tabla
	function bddenlace ($url = 'pagina.php', $parametro = 'eliminar', $columna = 'id', $contenido='Borrar', $encabezado= null, $clase = 'tablaenlace') {
		if ($encabezado == null)
			$encabezado = $contenido;
		return "concat ('<a href=\'$url?$parametro=',$columna,'\'  class = \'$clase\' >$contenido</a>') as '$encabezado'";
	}


	*/





		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------
		// --------------------------------------------------------



		class BDDEtiqueta {
		private $datos = array ();




		public function literal (...$parametros) {
			foreach ($parametros as $parametro)  $this->datos[]= array('literal'=>$parametro);
		}

		public function columna (...$parametros) {
			foreach ($parametros as $parametro)  $this->datos[]= array('columna'=>$parametro);
		}

		public function atributo ($nombre) {
			$this->datos[] = array('atributo'=>$nombre);
		}

		public function finAtributo () {
			$this->datos[] =  array('atributo'=>'');
		}

		public function contenido ($contenido= null) {
			$this->datos[] = array('contenido'=>true);
			if ($contenido != null)
				$this->literal($contenido);
		}

		public function finContenido () {
			$this->datos[] =  array('contenido'=>false);
		}

		public function parametro ($parametro, $columna = null) {
			if ($columna == null)
				$columna = $parametro;
			$this->literal($parametro);
			$this->columna($columna);
		}



		public function obtener ($nombre) {

			$atributo = array();
			$contenido =  array();

			$valoresContenido = false;



			foreach ($this->datos as $dato)
			foreach ($dato as $info => $valor)
				switch ($info) {
					case 'atributo':
						if ($valor == '') {
							$atributo[] = "'\''";
						} else {
							$atributo[] = "' $valor=\''";

						}
					break;

					case 'literal':
						if ($valoresContenido)
							$contenido[] = "'$valor'";
						else
							$atributo[] = "'$valor'";
					break;

					case 'columna':
						if ($valoresContenido)
							$contenido[] = $valor;
						else
							$atributo[] = $valor;
					break;

					case 'contenido':
						$valoresContenido = $valor;
					break;
				}




			if ($contenido == array())
				$etiqueta = "'<$nombre ', " . implode (', ', $atributo) . " , '/>'";
			else
				$etiqueta = "'<$nombre ', " . implode (', ', $atributo) . " , '>' , ". implode (', ', $contenido) ." , '</$nombre>' ";

			return $etiqueta;
		}





	}




?>
