17. Creating a method to save generated files into database

 

17. Creación de un método para guardar archivos generados en la base de datos

¡Hola y bienvenido de nuevo!

En esta lección vamos a crear un método crucial para persistir nuestros datos. Hasta ahora convertimos audio a texto, pero esos resultados se pierden al refrescar la página. Ahora vamos a guardarlos en la base de datos para poder consultarlos después, mostrarlos en una lista y permitir traducciones.


🎯 ¿Qué vamos a crear?

Vamos a crear el método save() que:

  1. Recibe el archivo, contenido y tipo

  2. Prepara una consulta SQL segura

  3. Inserta los datos en la tabla files

  4. Retorna el ID generado para redireccionar al usuario


🔍 Estructura de la base de datos

Tabla files en MySQL:

sql

CREATE TABLE files (

    id INT AUTO_INCREMENT PRIMARY KEY,

    fileUrl VARCHAR(255) NOT NULL,      -- Ruta del archivo (ej: files/abc123.mp3)

    content TEXT,                        -- Texto transcrito

    type ENUM('audio', 'video') NOT NULL, -- Tipo de archivo

    translated TEXT NULL,                -- Para traducciones futuras

    language VARCHAR(50) NULL,           -- Idioma de traducción

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

);

Visualización de la tabla:

text

┌─────┬──────────────────────┬────────────────────────────┬────────┬────────────┬──────────┬─────────────────────┐

│ id  │ fileUrl              │ content                    │ type   │ translated │ language │ created_at          │

├─────┼──────────────────────┼────────────────────────────┼────────┼────────────┼──────────┼─────────────────────┤

│ 1   │ files/a1b2c3.mp3     │ Hola, esto es una prueba   │ audio  │ NULL       │ NULL     │ 2024-01-15 10:30:00 │

│ 2   │ files/x9y8z7.mp4     │ Buenos días a todos        │ video  │ NULL       │ NULL     │ 2024-01-15 11:15:00 │

└─────┴──────────────────────┴────────────────────────────┴────────┴────────────┴──────────┴─────────────────────┘


🛠️ Vamos a crear el método save()

php

/**

 * Guarda el archivo procesado en la base de datos

 * 

 * @param string $file    Ruta del archivo (ej: 'files/abc123.mp3')

 * @param string $content Texto transcrito del archivo

 * @param string $type    Tipo de archivo ('audio' o 'video')

 * @return int ID del registro insertado

 */

public function save($file, $content, $type)

{

    // 1. Preparamos la consulta SQL con placeholders

    $stmt = $this->DB->prepare("

        INSERT INTO `files` 

        (`fileUrl`, `content`, `type`) 

        VALUES (:fileUrl, :content, :type)

    ");

    

    // 2. Vinculamos los parámetros (evita SQL injection)

    $stmt->bindParam(":fileUrl", $file, PDO::PARAM_STR);

    $stmt->bindParam(":content", $content, PDO::PARAM_STR);

    $stmt->bindParam(":type", $type, PDO::PARAM_STR);

    

    // 3. Ejecutamos la consulta

    $stmt->execute();

    

    // 4. Retornamos el ID generado

    return $this->DB->lastInsertId();

}


📝 Explicación detallada del método

1. ¿Por qué usar PDO?

PDO (PHP Data Objects) es una interfaz para acceder a bases de datos en PHP. Es más seguro que funciones antiguas como mysql_query().

Ventajas:

  • ✅ Prevención de SQL Injection

  • ✅ Portabilidad entre diferentes bases de datos

  • ✅ Mejor manejo de errores

  • ✅ Soporte para transacciones

2. Placeholders y bindParam

php

// MAL (vulnerable a SQL injection):

$query = "INSERT INTO files VALUES ('$file', '$content', '$type')";


// BIEN (seguro con placeholders):

$query = "INSERT INTO files VALUES (:fileUrl, :content, :type)";

$stmt->bindParam(":fileUrl", $file, PDO::PARAM_STR);

Analogía:

Placeholders son como formularios con espacios en blanco.
bindParam es como escribir con tinta indeleble en esos espacios.
Así, aunque alguien malintencionado ponga código dañino, solo se guardará como texto normal.

3. Tipos de datos PDO:

  • PDO::PARAM_STR → Para strings (texto)

  • PDO::PARAM_INT → Para números enteros

  • PDO::PARAM_BOOL → Para booleanos

  • PDO::PARAM_NULL → Para valores NULL

4. lastInsertId()

  • Retorna el último ID autoincremental generado

  • Útil para redireccionar al registro recién creado

  • Solo funciona con tablas que tienen columna autoincremental


🔧 Implementación en index.php

php

// En index.php, después de procesar el archivo:

if($_SERVER['REQUEST_METHOD'] === "POST"){

    if(isset($_FILES['file'])){

        if(!empty($_FILES['file']['name'][0])){

            

            // 1. Subir archivo físicamente

            $file = $whisperObj->upload($_FILES['file']);

            

            if($file){

                // 2. Configurar para ASR

                $whisperObj->dataType = 'ASR';

                $whisperObj->file = $file;

                

                // 3. Convertir audio a texto

                $text = $whisperObj->convert();

                

                // 4. Determinar tipo de archivo

                $fileType = $_FILES['file']['type'];

                $type = '';

                

                if(strpos($fileType, 'audio/') === 0){

                    $type = 'audio';

                } else if(strpos($fileType, 'video/') === 0){

                    $type = 'video';

                }

                

                // 5. GUARDAR EN BASE DE DATOS (¡NUEVO!)

                $fileID = $whisperObj->save($file, $text['text'], $type);

                

                // 6. Redireccionar a la vista del archivo

                header("location: view.php?file={$fileID}");

            }

        }

    }

}


🔍 Explicando strpos() para determinar el tipo

php

// strpos() busca una cadena dentro de otra

// Retorna la posición (0, 1, 2...) o FALSE si no encuentra


$fileType = "audio/mp3";


// Busca "audio/" al inicio del string

$posicion = strpos($fileType, 'audio/'); // Retorna 0


// Si es 0, significa que "audio/" está al inicio

if($posicion === 0){

    $type = 'audio';

}


// Tipos comunes de MIME:

// audio/mpeg, audio/wav, audio/mp3 → "audio"

// video/mp4, video/mpeg, video/quicktime → "video"

Ejemplos:

  • "audio/mp3"strpos() retorna 0 → es audio

  • "video/mp4"strpos() retorna 0 → es video

  • "application/pdf"strpos() retorna FALSE → no es ni audio ni video


🎨 Diagrama del flujo completo

text

┌─────────────────────────────────────────────────────────────┐

│            FLUJO COMPLETO: SUBIR → PROCESAR → GUARDAR       │

├─────────────────────────────────────────────────────────────┤

│ 1. USUARIO sube archivo.mp3                                 │

│    ↓                                                        │

│ 2. upload() guarda en: files/a1b2c3.mp3                     │

│    ↓                                                        │

│ 3. Configurar:                                              │

│    • dataType = 'ASR'                                       │

│    • file = 'files/a1b2c3.mp3'                              │

│    ↓                                                        │

│ 4. convert() envía a OpenAI                                 │

│    ↓                                                        │

│ 5. OpenAI responde: {"text": "Hola mundo"}                  │

│    ↓                                                        │

│ 6. Determinar tipo: strpos("audio/mp3", "audio/") → 0       │

│    → type = 'audio'                                         │

│    ↓                                                        │

│ 7. save() inserta en BD:                                    │

│    ┌─────────────────────────────────────────────────────┐  │

│    │ TABLA files                                         │  │

│    │ id: 25 (auto)                                       │  │

│    │ fileUrl: 'files/a1b2c3.mp3'                         │  │

│    │ content: 'Hola mundo'                               │  │

│    │ type: 'audio'                                       │  │

│    └─────────────────────────────────────────────────────┘  │

│    ↓                                                        │

│ 8. Retorna ID: 25                                          │

│    ↓                                                        │

│ 9. Redirecciona a: view.php?file=25                        │

└─────────────────────────────────────────────────────────────┘


💡 La importancia de guardar en base de datos

¿Por qué no solo guardar el texto en un archivo?

Base de Datos

Archivo de Texto

✅ Búsqueda rápida

❌ Búsqueda lenta

✅ Relaciones entre datos

❌ Datos aislados

✅ Seguridad integrada

❌ Vulnerable

✅ Escalabilidad

❌ Difícil de mantener

✅ Consultas complejas

❌ Solo lectura lineal

Casos de uso futuros:

  1. Historial → Ver todos los archivos procesados

  2. Búsqueda → Encontrar archivos por contenido

  3. Estadísticas → Cuántos audios vs videos

  4. Traducciones → Guardar múltiples versiones

  5. Compartir → Enlaces específicos a cada archivo


🔧 Ejemplo práctico paso a paso

Paso 1: Subir archivo "entrevista.mp3"

php

// Archivo subido:

$_FILES['file'] = [

    'name' => 'entrevista.mp3',

    'type' => 'audio/mpeg',

    'tmp_name' => '/tmp/phpABC123',

    'size' => 1048576  // 1MB

];


// Después de upload():

$file = 'files/d4e5f678.mp3'; // Nombre encriptado

Paso 2: Procesar con OpenAI

php

$text = $whisperObj->convert();

// $text = ['text' => 'Buenos días, hoy hablaremos sobre IA.']

Paso 3: Determinar tipo

php

$fileType = 'audio/mpeg';

if(strpos($fileType, 'audio/') === 0){

    $type = 'audio';  // ¡Sí es audio!

}

Paso 4: Guardar en BD

sql

-- Consulta ejecutada:

INSERT INTO files (fileUrl, content, type) 

VALUES ('files/d4e5f678.mp3', 'Buenos días, hoy hablaremos sobre IA.', 'audio')


-- Resultado:

-- id: 42 (generado automáticamente)

Paso 5: Redireccionar

php

header("location: view.php?file=42");

// Ahora el usuario ve su archivo procesado


⚠️ Consideraciones de seguridad importantes

1. SQL Injection

php

// NUNCA HACER ESTO (¡PELIGROSO!):

$sql = "INSERT INTO files VALUES ('{$_POST['file']}')";

// Un atacante podría poner: audio.mp3'); DROP TABLE files; --


// SIEMPRE HACER ESTO (SEGURO):

$stmt = $pdo->prepare("INSERT INTO files VALUES (:file)");

$stmt->bindParam(':file', $_POST['file'], PDO::PARAM_STR);

2. Validación de tipos MIME

php

// Verificar realmente el tipo de archivo

$finfo = finfo_open(FILEINFO_MIME_TYPE);

$mime = finfo_file($finfo, $_FILES['file']['tmp_name']);

finfo_close($finfo);


// Comparar con lista permitida

$allowed = ['audio/mpeg', 'audio/wav', 'video/mp4'];

if(!in_array($mime, $allowed)){

    die("Tipo de archivo no permitido");

}


📊 Esquema de relaciones futuras

text

┌─────────────────────────────────────────────────────┐

│                 BASE DE DATOS COMPLETA               │

├─────────────────────────────────────────────────────┤

│                     TABLA files                      │

│  ┌─────────┬──────────────┬──────────────────────┐  │

│  │ id: PK  │ fileUrl      │ content              │  │

│  ├─────────┼──────────────┼──────────────────────┤  │

│  │ 1       │ files/a.mp3  │ "Texto original"     │  │

│  │ 2       │ files/b.mp4  │ "Otro texto"         │  │

│  └─────────┴──────────────┴──────────────────────┘  │

│           │                                          │

│           └─────┬────────────────────────────────────┘

│                 │

│       ┌─────────▼────────────────────────────────────┐

│       │           TABLA translations                  │

│       │  ┌─────┬─────────┬─────────────┬──────────┐  │

│       │  │ id  │ file_id │ language    │ content  │  │

│       │  ├─────┼─────────┼─────────────┼──────────┤  │

│       │  │ 1   │ 1       │ English     │ "Text"   │  │

│       │  │ 2   │ 1       │ French      │ "Texte"  │  │

│       │  └─────┴─────────┴─────────────┴──────────┘  │

│       └──────────────────────────────────────────────┘

│  PK = Primary Key (Clave primaria)

│  FK = Foreign Key (Clave foránea - futuro)

└─────────────────────────────────────────────────────┘


🎯 Puntos Clave para Recordar

  1. PDO::prepare() → Prepara consultas SQL de forma segura

  2. Placeholders (:nombre) → Marcadores para valores variables

  3. bindParam() → Vincula valores a placeholders (evita SQL injection)

  4. strpos() → Encuentra subcadenas para determinar tipos MIME

  5. lastInsertId() → Obtiene el ID del último registro insertado

  6. header("location: ...") → Redirecciona al usuario


📋 Cuestionario de Repaso

Pregunta 1:

¿Cuál es la principal ventaja de usar placeholders (:fileUrl) en consultas SQL?
a) Hace que el código se ejecute más rápido
b) Previene ataques de SQL injection
c) Permite usar menos código
d) Hace que las consultas sean más legibles

Pregunta 2:

¿Qué retorna strpos("audio/mp3", "audio/")?
a) TRUE
b) FALSE
c) 0 (porque "audio/" está al inicio)
d) 6

Pregunta 3:

¿Qué hace el método lastInsertId()?
a) Cuenta cuántos registros hay en la tabla
b) Retorna el último ID autoincremental generado
c) Obtiene el ID del usuario actual
d) Busca el registro más antiguo

Pregunta 4:

¿Por qué necesitamos determinar si un archivo es "audio" o "video"?
a) Para cobrar diferente por cada tipo
b) Para mostrar iconos diferentes en la interfaz
c) Para guardar correctamente en la columna type de la BD
d) Para usar diferentes APIs de OpenAI

Pregunta 5:

¿Qué significa PDO::PARAM_STR en bindParam()?
a) Que el valor es un número
b) Que el valor es una cadena de texto
c) Que el valor es una fecha
d) Que el valor es opcional


📝 Respuestas del Cuestionario

  1. b) Los placeholders previenen SQL injection porque separan los datos del código SQL. Los valores se tratan como datos, no como parte del código ejecutable.

  2. c) strpos() retorna la posición donde encuentra la subcadena. Como "audio/" está al inicio de "audio/mp3", retorna 0 (las posiciones empiezan en 0).

  3. b) lastInsertId() retorna el valor del último ID autoincremental generado por una consulta INSERT, lo que nos permite redireccionar al registro recién creado.

  4. c) Necesitamos determinar el tipo para guardarlo correctamente en la base de datos y poder filtrar, organizar y mostrar los archivos apropiadamente.

  5. b) PDO::PARAM_STR indica que el parámetro es de tipo string (cadena de texto). Esto ayuda a PDO a tratar el valor apropiadamente y añade una capa extra de seguridad.


🚀 Lo que viene después

¡Excelente! Ahora tenemos:
✅ Subida de archivos
✅ Procesamiento con IA
✅ Guardado en base de datos
✅ Redirección automática

En la próxima lección vamos a:

  1. Crear view.php para mostrar archivos individuales

  2. Mostrar el reproductor de audio/video

  3. Exponer el texto transcrito

  4. Preparar la interfaz para traducciones

✨ ¡Gran trabajo! Tu aplicación ahora guarda historial completo de todos los procesamientos. Los usuarios pueden volver a ver sus archivos en cualquier momento.




Comentarios

Entradas más populares de este blog

1-7. Transforma tu audio a texto

10. Haz que tu asistente hable

8. NUEVO - Solución si tu micrófono no está captando tu audio