Disparadores - Trigger 1 DBD 2006-2007

Anuncio
Disparadores - Trigger
1
Disparadores (triggers - T)
• Definición
Disparadores (triggers)
Originales de Sybase SQL Server.
• Elementos de un T
– No forman parte de SQL2 (SQL92)
– Pero sí de SQL3
• Tipo de T según cuando se dispare
Procedimientos que ejecuta de manera implícita el
• Estado de una T
SGBD como respuesta a un evento
• Acoplamiento o Consideración de T
• Combinación de T
– Base fundamental de las Bases de Datos Activas
Por ejemplo:
• Algoritmos y modelos de Ejecución
– Cuando hay menos de X productos en stock, hacer un nuevo pedido
• Uso de las T
– Si el producto caduca hoy avisar almacén para que lo retiren
– Cuando sean las 17:00h hacer copia de seguridad de la BD
Elementos de un disparador
Disparadores son ECA: Evento + Condición + Acción
Evento:
– Suceso que causa que se dispare un trigger
• INSERT, UPDATE, DELETE
• Oracle (además: CREATE, ALTER, DROP, LOGON, LOGOFF,
STARTUP, SHUTDOWN, SERVERERROR)
Condición:
– Expresión booleana que debe ser TRUE para que se dispare el
trigger realmente. La acción no se ejecuta si la expresión se
evalúa a FALSE o Unknown.
Acción:
– Procedimiento que se debe ejecutar cuando se dispare el trigger.
• Oracle: PL/SQL, Java, Llamada a procedimiento en C
• SQL Server: Transact-SQL
• No admiten instrucciones de control de transacciones
Sintaxis: Disparadores en SQL3
CREATE TRIGGER <identificador>
{AFTER | BEFORE | INSTEAD OF}
{INSERT | DELETE | UPDATE [OF <atributo> ?} ON <tabla>
[FOR EACH ROW | STATEMENT?
[REFERENCING OLD AS <nombre> NEW AS <nombre>?
[REFERENCING OLD_TABLE AS <nom> NEW_TABLE AS <nom> ?
[WHEN <condición> ?
<bloque de acciones sobre la base de datos> o
<llamada a un procedimiento almacenado> o
<llamada a una función externa>
DROP TRIGGER <identificador>
REPLACE TRIGGER <identificador> <lo mismo que create>
Disparadores. Primer ejemplo
Disparadores. Primer ejemplo
Evento:
CREATE TRIGGER: Nombre del T o regla activa. Ej CREATE TRIGGER NuevoPedido
– Se modifican las existencias de la tabla productos (a menos)
Condición:
AFTER: Regla que se activará despues de que tenga lugar el evento (E)
ON: Tabla o relación asociada al E. Por ej, ON Producto
– Hay menos unidades que el mínimo (x)
Acción:
UPDATE OF: Atributos actualizables para los E Upadate. Por ej, UPDATE OF unidades
WHEN: Condición (C) a verificar después de activarse el T. Por ej, WHEN New.unidades < X
– Hacer un nuevo pedido de aquellos productos que tienen menos
unidades que X.
FOR EACH ROW: Se activara una vez para cada una tuplaque afecte al T.
BEGIN ... END: La Acción (A) a emprender. Por ej, BEGIN INSERT INTO ... END
CREATE TRIGGER NuevoPedido
AFTER UPDATE OF unidades ON Producto E
WHEN NEW.unidades < X
C
FOR EACH ROW
BEGIN
INSERT INTO PEDIDO
CrearNuevoPedido A
VALUES (:OLD.código, X, today ())
...
END
DBD 2006-2007
NEW y OLD: Permite referenciar a la tupla insertada y eliminada por el T. Por ej, Old.código
EJ.
CREATE TRIGGER NuevoPedido
AFTER UPDATE OF unidades ON Producto
WHEN New.unidades<X
FOR EACH ROW
BEGIN INSERT INTO PEDIDO VALUES ( Old.código, X, today()) ... END
Disparadores - Trigger
Disparadores: Tipos
CREATE TRIGGER <nombre del T.>
{AFTER | BEFORE | INSTEAD OF}
{INSERT | DELETE | UPDATE [OF <atributo>?} ON <tabla>
2
Comparación BEF-AFT-INS
Antes
Create trigger T
T
INSERT...
BEFORE insert ON Cuenta
Según cuando se ejecutan la acción:
– BEFORE trigger
• Se ejecuta la acción antes de que se realice la operación que desencadena
el evento
– Antes de insertar, borrar, modificar...
– AFTER trigger
• Se ejecuta después de que se realice la operación que desencaden a el
evento
– Después de insertar, borrar, modificar...
– INSTEAD OF trigger
EnviarMensaje()
Despues
INSERT...
Create trigger T
EnviarMensaje()
En lugar de
INSERT...
Create trigger T
INSTEAD OF insert ON Cuenta
T
EnviarMensaje()
INSERT INTO Cuenta
VALUES (...)
• Se ejecuta en lugar de la operación que desencadena el evento
– En lugar de insertar, borrar, modificar
Disparadores inmediatos y vistas
BEFORE y AFTER
– Sólo para tablas de base
– NO para vistas
• si se inserta en una vista y la tabla de base subyacente tiene
un trigger BEFORE o AFTER, entonces sí se dispara.
INSTEAD OF
– Para vistas
• Es una manera transparente de modificar vistas no
modificables.
T
AFTER insert ON Cuenta
Disparadores: Tipos
CREATE TRIGGER <identificador>
{AFTER | BEFORE | INSTEAD OF}
{INSERT | DELETE | UPDATE [OF <atributo>?} ON <tabla>
[FOR EACH ROW | STATEMENT ?
Según el nivel
• EACH ROW (A nivel de fila)
– Se dispara el trigger una vez por cada una de las tuplas afectadas
por la sentencia que desencadena el evento
• Insertar 5 tuplas: ?se dispara 5 veces
• Modificar 0 tuplas: ?no se dispara
• Se pueden usar los valores NEW y OLD para referenciar a los
cambios a realizar a la BD.
• Si se sabe cuál es la operación asociada a una inserción en
una tupla, se puede indicar en un trigger la operación
asociada.
Disparadores: Tipos
CREATE TRIGGER <identificador>
{AFTER | BEFORE | INSTEAD OF}
{INSERT | DELETE | UPDATE [OF <atributo> ?} ON <tabla>
[FOR EACH ROW | STATEMENT ?
Según el nivel
•
EACH ROW (A nivel de fila)
•
STATEMENT
Se dispara el trigger una vez por cada sentencia que desencadena el
evento (sin tener en cuenta las tuplas afectadas ni la transacción en
que se encuentren)
• Insertar 5 tuplas: ?se dispara 1 vez el trigger.
• Modificar 0 tuplas: ?se dispara 1 vez el trigger.
• Se pueden usar las tablas NEW_TABLE y OLD_TABLE para referenciar a los
cambios que se realizan a la BD (SQL Server: INSERTED, DELETED)
DBD 2006-2007
• STATEMENT (A nivel de Sentencia)
Comparación ROW-STAT
Create trigger T
BEFORE update ON Cuenta
FOR EACH ROW
Mensaje()
T
Update
T
Update
Update modifica 2 tuplas
Create trigger T
BEFORE update ON Cuenta
STATEMENT
Mensaje()
T
Update
Update
Disparadores - Trigger
3
Combinaciones de triggers
Renombrar a OLD y NEW
BEFORE statement trigger
– Antes de ejecutarse la operación que desencadena el evento se
ejecuta la acción del trigger.
BEFORE row trigger
– Antes de modificar cada una de las filas afectadas por la operación
que desencadena el evento y antes de comprobar las reglas de
integridad se ejecuta la acción del trigger (éstos no bloquean la fila).
AFTER row trigger
– Después de modificar cada una de las filas afectadas por la
operación que desencadena el evento y las reglas de integridad
asociadas, se ejecuta la acción del trigger para la fila actual (éstos
bloquean la fila)
AFTER statement trigger
– Se ejecuta la acción del trigger después de ejecutar la operación que
desencadena el evento y después de aplicar las restricciones
diferidas.
CREATE TRIGGER <identificador>
{AFTER | BEFORE | INSTEAD OF}
{INSERT | DELETE | UPDATE [OF <atributo>?} ON <tabla>
[FOR EACH ROW | STATEMENT ?
[REFERENCING OLD AS <nombre> NEW AS <nombre>?
[REFERENCING OLD_TABLE AS <nom> NEW_TABLE AS <nom> ?
FOR EACH ROW
REFERENCING OLD AS <nombre> NEW AS <nombre>
- Es posible renombrar las tuplas OLD y NEW
STATEMENT
REFERENCING OLD_TABLE AS <nom> NEW_TABLE AS <nom>
- Es posible renombrar las talbas OLD y NEW
Estado de triggers
Sintaxis: Disparadores en SQL3
Una regla o trigger, por medio de su nombre, se puede:
CREATE TRIGGER <identificador>
{AFTER | BEFORE | INSTEAD OF}
{INSERT | DELETE | UPDATE [OF <atributo>?} ON <tabla>
[FOR EACH ROW | STATEMENT ?
[REFERENCING OLD AS <nombre> NEW AS <nombre>?
[REFERENCING OLD_TABLE AS <nom > NEW_TABLE AS <nom > ?
[WHEN <condici ón> ?
– Activar / ENABLE
• Se puede disparar
• Por defecto (Consultar SGBD)
– Desactivar / DISABLE
• No se puede disparar
La condiciones pueden ser:
• Bloque de acciones sobre la base de datos
• Llamada a un procedimiento almacenado
• Llamada a una función externa
Consideración o Acoplamiento
– Eliminar / DROP
Nota: Se podrían agrupara para facilitar las acciones anteriores .
Sintaxis:
ALTER TRIGGER <identificador> {ENABLE | DISABLE}
DROP TRIGGER <identificador>
Consideración o Acoplamiento
Acoplamiento Inmediato (INMEDIATE)
Cuando se deber realizar la acción activada.
La condición se evalúa dentro de la misma transacción que la del
• Antes del evento / BEFORE
evento que la desencadena y se hace inmediatamente
• Despues del evento / AFTER
• Simultaneamente al evento / INSTEAD OF
• BEFORE: Se evalúa la condición antes de la ejecución de la operación
• AFTER: Se evalúa la condición después
• INSTEAD OF: Se evalúa la condición en lugar de realizar la operación
Acoplamiento diferido (DEFERRED)
La acción se realiza en la misma transacción o en una nueva
• Consideración Inmediata / INMEDIATE
• Consideración Diferida / DEFERRED
• Consideración Separada / DETACHED
DBD 2006-2007
– La condición se evalúa al final de la transacción que contenía l a
operación desencadenadora. En este caso puede haber muchos
triggers esperando a evaluar su condición.
Acoplamiento separado (DETACHED)
– La condición se evalúa dentro de una transacción separada nacida de
la transacción desencadenadora.
Disparadores - Trigger
4
Acoplamiento inmediato
Acoplamiento diferido
comienzo de transacción
Uso: restricciones de
integridad complejas
diferidas
comienzo de transacción
ejecución
acción
¿condición?
¿condición?
final de transacción
Uso: restricciones de integridad
complejas que requieren una
comprobación inmediata
Acoplamiento Inmediato (INMEDIATE)
La condición se evalúa dentro de la misma transacción que
la del evento que la desencadena y se hace inmediatamente
final de transacción
Acoplamiento diferido (DEFERRED)
La condición se evalúa al final de la transacción que contenía l a operación
desencadenadora. En este caso puede haber muchos triggers esperando
a evaluar su condición.
Acoplamiento separado
Ejemplo
Uso: restricciones de
integridad complejas
diferidas
comienzo de transacción
subtransacción
¿condición?
ejecución
acción
ejecución
acción
fin subtransacción
final de transacción
CREATE TRIGGER Ejemplo
BEFORE DELETE OR INSERT OR UPDATE OF salario, primas
ON TablaEmpleado
FOR EACH ROW
WHEN salario > 10000
BEGIN
...
IF DELETING THEN ... END IF;
IF INSERTING THEN ... END IF;
IF UPDATING(salario) THEN ... END IF;
…
END
Acoplamiento separado (DETACHED)
La condición se evalúa dentro de una transacción separada nacida
de la transacción desencadenadora.
Ejemplo: BEFORE statement
Evitar que los fines de semana los empleados
cambien los datos de otros empleados o de
ellos mismos
Ejemplo: BEFORE statement
CREATE TRIGGER Ejemplo
BEFORE DELETE OR INSERT OR
UPDATE OF suSalario
Eventos:
ON TablaEmpleado
– Insertar, borrar o modificar Tabla Empleado
• DELETE OR INSERT OR UPDATE ON TablaEmpleado
Condición:
– Es fin de semana
DAYOFWEEK(TODAY())=‘Sat’ OR
DAYOFWEEK(today())=‘Sun’
Acción:
– Enviar mensaje de error
DBD 2006-2007
WHEN
DAYOFWEEK(today())=‘Sat’ OR
DAYOFWEEK(today())=‘Sun’
Procedimiento_ChivatoAlJefe()
Disparadores - Trigger
5
Ejemplo: BEFORE row
Ejemplo: AFTER row
CREATE TRIGGER nuevoPedido
BEFORE INSERT ON Pedido
FOR EACH ROW
PEDIDO (nomCli, códigoProd, fecha, cantidad, importe)
WHEN True
PRODUCTO (códigoProd, fabricante, existencias)
BEGIN
REPVENTAS (nomCli, ventas)
UPDATE RepVentas
SET Ventas = Ventas + :NEW.importe
WHERE Repventas.nomCli = :NEW.nomCli;
Al Introducir un nuevo depósito con saldo
negativo se deja el depósito con saldo 0 y se
crea un préstamo nuevo con capital=saldo
deudor
insert
UPDATE Producto
SET Existencias = Existencias - :NEW.cantidad
WHERE Producto.códigoProd = :NEW.códigoProd
END
Depósito
Ejemplo: AFTER row
update
Depósito
insert
Préstamo
¿Saldo<0?
Ejemplo: AFTER row
CREATE TRIGGER saldoDeudor
AFTER INSERT ON Depósito
FOR EACH ROW
WHEN New.saldo<0 DEPÓSITO (numCuenta, nomSucursal, nomCliente, saldo)
BEGIN
PRÉSTAMO (numCuenta, nomSucursal, nomCliente, saldo)
UPDATE Depósito
SET saldo = 0
WHERE numCuenta = :NEW.numCuenta;
Evento
– Introducir un nuevo depósito
– FOR INSERT ON Depósito
Condición
– New.saldo<0
Acción
– Dejar saldo a 0 (UPDATE...
INSERT INTO Préstamo
– Crear un nuevo préstamo (INSERT...
VALUES(:NEW.nomSucursal, :NEW.numCuenta,
:NEW.nomCliente, :NEW.saldo)
END
Ejemplo: AFTER row
Ejemplo: AFTER row
Al modificar el saldo de un depósito y dejarlo con saldo
negativo se deja el depósito con saldo 0 y se crea un
préstamo nuevo con capital = saldo deudor o se añade
el saldo deudor al préstamos existente
Evento
– Modificar el saldo de un depósito
– FOR UPDATE OF saldo ON Depósito
Condición
update
update
– New.saldo<0
Depósito
Acción
Depósito
¿Saldo<0?
– Dejar saldo a 0 (UPDATE...
insert
Préstamo
– Si existe préstamo, UPDATE Préstamo
– Si no, Crear un nuevo préstamo (INSERT...
DBD 2006-2007
Disparadores - Trigger
6
Ejemplo: datos derivados
Ejemplo: AFTER row
CREATE TRIGGER saldoDeudor
AFTER UPDATE OF saldo ON Depósito
FOR EACH ROW
WHEN New.saldo<0
BEGIN
UPDATE Depósito SET saldo=0 WHERE numCuenta = :New.numCuenta;
IF EXISTS
(SELECT * FROM Préstamo WHERE numCuenta = :New.numCuenta)
THEN
UPDATE Préstamo SET saldo = saldo + :New.saldo
WHERE numCuenta = :New.numCuenta;
ELSE
INSERT INTO Préstamo
VALUES(: New.numCuenta, :New.nomSucursal,
:New.nomCliente, :New.saldo)
END IF
Regla de negocio:
– El presupuesto en personal de los departamentos es la suma de
los sueldos de todos sus empleados
– Los empleados pueden aparecer temporalmente sin adscripción
BD
– Empleado (dni, nombre, sueldo, numDpto, dniDeSuJefe)
– Departamento (núm, nombreD, presupuesto, dniDelDirector)
Eventos importantes:
– Contratar empleados (insertar empleados)
– Cambiar el salario de uno o más empleados (modificar)
– Reasignar un empleado de un depto a otro (modificar)
– Despedir empleados (borrar empleados)
END
Ejemplo: datos derivados
Evento:
Ejemplo: datos derivados
CREATE TRIGGER añadirEmpleados
– Contratar nuevos empleados
– INSERT ON Empleado
Condición
– El empleado nuevo está adscrito a un departamento
– New.numDepto IS NOT NULL
BEFORE INSERT ON Empleado
FOR EACH ROW
WHEN New.numDpto IS NOT NULL
UPDATE Departamento
SET presupuesto = presupuesto + :New.sueldo
Acción
– Actualizar presupuesto personal de ese
departamento
Ejemplo: datos derivados
Evento:
– Cambiar el salario de uno o varios empleados
– UPDATE OF sueldo ON Empleado
Condición
– El empleado nuevo está adscrito a un departamento
Acción
– Actualizar presupuesto personal de ese
departamento
DBD 2006-2007
WHERE num = :New.numDpto
Ejemplo: datos derivados
CREATE TRIGGER cambiarSueldoEmpleados
AFTER UPDATE OF sueldo ON Empleado
FOR EACH ROW
WHEN New.numDpto IS NOT NULL
UPDATE Departamento
SET presupuesto = presupuesto + :New.sueldo :Old.sueldo
WHERE num = :New.numDpto
Disparadores - Trigger
7
Ejemplo: datos derivados
Evento:
Ejemplo: datos derivados
– Cambiar la adscripción de uno o más empleados de
un depto a otro
CREATE TRIGGER reasignarEmpleados
AFTER UPDATE OF numDpto ON Empleado
FOR EACH ROW
– UPDATE OF numDpto ON Empleado
BEGIN
UPDATE Departamento
SET presupuesto = presupuesto + :New.sueldo
Condición
– No hay
WHERE num = :New.numDpto
UPDATE Departamento
Acción
– Actualizar presupuesto personal del departamento
origen
– Actualizar presupuesto personal del departamento
destino
SET presupuesto = presupuesto - :New.sueldo
WHERE num = :Old.numDpto
END
Ejemplo: datos derivados
Evento:
Ejemplo: datos derivados
CREATE TRIGGER añadirEmpleados
– Despedir empleados
AFTER DELETE ON Empleado
– DELETE ON Empleado
FOR EACH ROW
Condición
WHEN Old.numDpto IS NOT NULL
– El empleado estaba adscrito a un
departamento
Acción
UPDATE Departamento
SET presupuesto = presupuesto - :Old.sueldo
WHERE num = :Old.numDpto
– Actualizar presupuesto personal de ese
departamento
Ejemplo: INSTEAD OF
CREATE VIEW EmpleadosConDepto AS
SELECT Empleado.nombre, Departamento.nombreD
FROM Empleado INNER JOIN Departamento
ON numdpto=num
Ejemplo: INSTEAD OF
Con esta vista sólo se puede reasignar empleados a
otros departamentos que ya existen. Cualquier otra
operación se debe ignorar (ignorar para simplificar, error
sería más conveniente).
Evento
– UPDATE OF nombreD ON EmpleadosConDepto
Esta vista, ¿Es modificable?
¡NO! (join, sin claves, etc.)
Condición
– El nombre del nuevo departamento existe ya
– EXISTS (SELECT * FROM Departamento
WHERE Departamento.nombreD=New.nombreD)
El ABD puede considerar cómo se deben interpretar las
inserciones en la vista y aplicarlo mediante un trigger
INSTEAD OF
DBD 2006-2007
Acción
– Localizar el código del departamento nuevo
– Asignar el código de ese departamento al empleado
Disparadores - Trigger
Ejemplo: INSTEAD OF
CREATE TRIGGER actualizarVista
INSTEAD OF UPDATE OF nombreD ON EmpleadosConDepto
WHEN EXISTS (SELECT * FROM Departamento
WHERE nombreD=New.nombreD)
FOR EACH ROW
DECLARE elCódigoDpto NUMBER;
BEGIN
SELECT num INTO : elCódigoDpto FROM Departamento
WHERE nombreD = :New.nombreD
UPDATE Empleado SET numDpto = :elCódigoDpto
WHERE nombre = :New.nombre
END
8
Algoritmo de ejecución
Dada una operación P a realizar sobre la base de datos
• Ejecutar los BEFORE statement triggers de las tablas
afectadas por P
• Para cada fila afectada por la operación
– Ejecutar BEFORE row triggers
– Bloquear, efectuar operación sobre la fila y comprobar posibles
restricciones de integridad (no se libera el bloqueo hasta que se
acaba la transacción)
– Ejecutar AFTER row triggers
• Comprobar restricciones de integridad intertabla
• Ejecutar los AFTER statement triggers de las tablas
afectadas por P
Modelo de ejecución
Modelo de ejecución
• Dimensiones
– ¿Cuántas instancias de regla se disparan?
• todas
– ¿Qué ocurre si se produce un evento
durante la ejecución de una regla?
• Proceso recursivo
• Procesos anidados
– ¿Cuándo se evalúa la condición (acción)
respecto al evento (condición)? (modo de
acoplamiento)
• inmediato, diferido, separado
Usos de los disparadores
• Conservar integridad de datos más allá de la int. Ref.
– Prevenir transacciones inválidas
– Otras restricciones de integridad
• Restricciones de integridad referencial entre tablas de una BD
distribuida
• Otras RI que no se pueden representar con los métodos normales
– NOT NULL, UNIQUE, PRIMARY KEY, FOREIGN KEY, CHECK
– DELETE CASCADE, UPDATE CASCADE, DELETE SET NULL...
• Asegurar el cumplimiento de reglas de negocio
complejas
• Mantener datos derivados
–
–
–
–
–
En otras tablas actualizados (totales...)
Generación automática de columnas derivadas
Mantener réplicas síncronas de tablas
Mantenimiento de vistas materializadas (vistas almacenadas)
Modificar datos de las tablas de base
DBD 2006-2007
• Oracle y SQL Server
– ¿Cuántas instancias de reglas se disparan?
• todas (orden indeterminado)
– ¿Qué ocurre si se produce un evento durante la ejecución
de una regla?
• proceso recursivo (máximo 32)
– ¿Cuándo se evalúa la condición respecto al evento?
• inmediato
– ¿Qué ocurre si se produce un error durante la ejecución de
una regla?
• todo el proceso es invalidado
Usos de los disparadores
• Auditar ciertos eventos aparte de los que ofrece
el SGBD
– Crear un “log” con eventos que nos interesen
– Recoger estadísticas de actualización a las tablas
• Llamar a sistemas externos al SGBD
– Otras aplicaciones asociadas al SGBD
– Informar a otras aplicaciones sobre eventos sobre la
BD
• Notificar situaciones de los datos a usuarios
– Enviar e-mail, mensaje al móvil, busca.
Disparadores - Trigger
9
Inconvenientes
Ventajas
• SGBD centraliza y aplica las reglas de negocio
– Se reducen los programas de aplicación
• BD más compleja
– Más complejidad en el diseño, implementación y administración
de las BD
• Hay que incluir la lógica de los disparadores
– Funcionalidad oculta
• Una operación sencilla puede tener efectos laterales
– El usuario no tiene control sobre todo lo que ocurre en la BD
– Pueden existir dos triggers que se contradigan
• Sobrecarga del sistema
– El sistema tiene que evaluar las condiciones de los triggers para
cada operación con la BD
• Menos eficiencia, cuantos más triggers, peor
• No se garantiza la terminación
– SQL Server + Oracle (máximo 32 anidamientos)
Disparadores en otros sistemas
SQL3 Oracle MSSQL Informix Ingres Rdb Interbase
opción before
Sí
Sí
Sí
Sí
sí
Sí
Sí
opción after
Sí
Sí
Sí
Sí
Sí
Sí
Sí
opción instead_of
Sí
No
Sí
No
No
No
No
condición
Sí
Sí
Sí
Sí
Sí
Sí
No
row triggers
Sí
Sí
No
Sí
Sí
Sí
Sí
ref. old/new
Sí
Sí
No
Sí
Sí
Sí
Sí
statement triggers
Sí
Sí
Sí
Sí
No
No
No
ref. old/new table
Sí
No
Sí
No
No
No
No
prioridades
Sí
No
No
No
No
No
Sí
disparo en cascada
Sí
Sí
Sí
Sí
Sí
Sí
Sí
profundidad cascada infinito
32
32
61
20
infinito
?
DBD 2006-2007
Descargar