Hola fedegaray
Se nota ese muuuuucho trabajo y los avances como programador
Haces buen uso de super con lo cual reutilizas el código y te adaptas a los principios de la programación orientada a objetos con herencia
En la clase EnvioDeProductos trabajas con el tipo Producto y gracias al polimorfismo eso permite que la colección que es el envío almacene productos de los distintos tipos existentes (por ejemplo congelado, fresco, etc.)
Para sacarle partido al polimorfismo se recomienda declarar los tipos estáticos del tipo más amplio y luego instanciar los objetos en las clase concretas, por ejemplo
ProductoFresco proFres1 = new ProductoFresco ("25/03/2017", 5542, "14/01/2013", "México");
Se escribiría
Producto proFres1 = new ProductoFresco ("25/03/2017", 5542, "14/01/2013", "México");
De este modo a la izquierda está el tipo de la superclase y a la derecha el tipo específico.
En la clase Envio hay algo que ya se ha comentado en otros hilos, quizás no lo hayas leído:
public void dameDatos () {
int i = 0;
for (Producto tmp : listaDeEnvio) {
i++;
System.out.println ("PRODUCTO " + i);
tmp.dameDatos();
System.out.println ("");
}
Un contador como i no se suele usar cuando se usa un for extendido.
Para esto tenemos el bucle for tradicional que ya nos proporciona el contador, sería:
for (int i=0; i<listaDeEnvio.size(); i++){
System.out.println ("PRODUCTO " + i);
dameDatos().get(i);
System.out.println ("");
}
El for extendido se suele usar cuando no necesitamos índice o desconocemos el número de elementos que hay que recorrer.
Enhorabuena por el trabajo!
Saludos