multiples documentos

This commit is contained in:
Lizandro Guarnizo
2026-04-08 09:58:08 -05:00
parent db254ca02f
commit 882ce56e96
4 changed files with 216 additions and 70 deletions
+19
View File
@@ -117,9 +117,28 @@ function _extraerCampos(array $datos, string $tipo): array {
$cuerpo = isset($datos['cuerpo'])
? strip_tags((string)$datos['cuerpo'], ALLOWED_HTML_TAGS)
: null;
// Validar y limpiar archivos (array de {nombre,path,mime,size})
$archivos = null;
if (!empty($datos['archivos']) && is_array($datos['archivos'])) {
$limpios = [];
foreach ($datos['archivos'] as $a) {
$path = preg_replace('/[^a-zA-Z0-9._\-]/', '', (string)($a['path'] ?? ''));
if (!$path) continue;
$limpios[] = [
'nombre' => mb_substr(strip_tags((string)($a['nombre'] ?? $path)), 0, 100),
'path' => $path,
'mime' => preg_replace('/[^a-zA-Z0-9\/\-+.]/', '', (string)($a['mime'] ?? '')),
'size' => (int)($a['size'] ?? 0),
];
}
if ($limpios) $archivos = json_encode($limpios, JSON_UNESCAPED_UNICODE);
}
return array_filter([
'titulo' => isset($datos['titulo']) ? mb_substr(trim($datos['titulo']), 0, 200) : null,
'cuerpo' => $cuerpo ?: null,
'imagen_path' => isset($datos['imagen_path']) ? preg_replace('/[^a-zA-Z0-9._\-]/', '', (string)$datos['imagen_path']) : null,
'archivos' => $archivos,
], fn($v) => $v !== null);
}
+88 -35
View File
@@ -1,10 +1,12 @@
<?php
/**
* POST /api/lab/upload_nota_imagen.php
* Sube una imagen adjunta a una nota libre de domicilio.
* Sube uno o varios archivos adjuntos a una nota de domicilio.
* Acepta: imágenes (jpg, png, webp, gif), PDF, Word, Excel, texto.
* Límite: 10 MB por archivo.
*
* POST multipart/form-data con campo 'file'
* Devuelve: { success, imagen_path, file_size }
* POST multipart/form-data con campo 'file' (uno) o 'files[]' (varios)
* Devuelve: { success, archivos: [{nombre, path, mime, size}] }
*/
require_once __DIR__ . '/../../config/config.php';
require_once __DIR__ . '/../../classes/Database.php';
@@ -20,49 +22,100 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
exit;
}
if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
$code = $_FILES['file']['error'] ?? -1;
echo json_encode(['success' => false, 'error' => "No se recibió imagen (código $code)"]);
exit;
$allowedMimes = [
// Imágenes
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/webp' => 'webp',
'image/gif' => 'gif',
// Documentos
'application/pdf' => 'pdf',
'application/msword' => 'doc',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
'application/vnd.ms-excel' => 'xls',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
'text/plain' => 'txt',
'text/csv' => 'csv',
];
// Normalizar: acepta 'file' (uno) o 'files[]' (varios)
$incoming = [];
if (!empty($_FILES['files']['name'][0])) {
// múltiples
for ($i = 0; $i < count($_FILES['files']['name']); $i++) {
$incoming[] = [
'name' => $_FILES['files']['name'][$i],
'tmp_name' => $_FILES['files']['tmp_name'][$i],
'error' => $_FILES['files']['error'][$i],
'size' => $_FILES['files']['size'][$i],
];
}
} elseif (!empty($_FILES['file']['tmp_name'])) {
$incoming[] = [
'name' => $_FILES['file']['name'],
'tmp_name' => $_FILES['file']['tmp_name'],
'error' => $_FILES['file']['error'],
'size' => $_FILES['file']['size'],
];
}
$file = $_FILES['file'];
// Máximo 5 MB para imágenes de notas
if ($file['size'] > 5 * 1024 * 1024) {
echo json_encode(['success' => false, 'error' => 'La imagen no puede superar 5 MB']);
if (empty($incoming)) {
echo json_encode(['success' => false, 'error' => 'No se recibieron archivos']);
exit;
}
// Validar MIME real (solo imágenes)
$allowedMimes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mime, $allowedMimes, true)) {
echo json_encode(['success' => false, 'error' => "Tipo de archivo no permitido ($mime). Solo imágenes."]);
exit;
}
// Extensión segura
$mimeToExt = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', 'image/gif' => 'gif'];
$ext = $mimeToExt[$mime] ?? 'jpg';
$newName = 'nota_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
$uploadDir = __DIR__ . '/../../uploads/media';
if (!is_dir($uploadDir)) {
@mkdir($uploadDir, 0755, true);
}
$destPath = realpath($uploadDir) . '/' . $newName;
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
echo json_encode(['success' => false, 'error' => 'Error al guardar el archivo en el servidor']);
exit;
$resultado = [];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
foreach ($incoming as $file) {
if ($file['error'] !== UPLOAD_ERR_OK) {
echo json_encode(['success' => false, 'error' => "Error al recibir archivo: código {$file['error']}"]);
finfo_close($finfo);
exit;
}
if ($file['size'] > 10 * 1024 * 1024) {
echo json_encode(['success' => false, 'error' => "El archivo '{$file['name']}' supera 10 MB"]);
finfo_close($finfo);
exit;
}
$mime = finfo_file($finfo, $file['tmp_name']);
if (!isset($allowedMimes[$mime])) {
echo json_encode(['success' => false, 'error' => "Tipo no permitido ($mime). Imágenes, PDF o documentos Office."]);
finfo_close($finfo);
exit;
}
$ext = $allowedMimes[$mime];
$newName = 'nota_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
$dest = realpath($uploadDir) . '/' . $newName;
if (!move_uploaded_file($file['tmp_name'], $dest)) {
echo json_encode(['success' => false, 'error' => 'Error al guardar el archivo en el servidor']);
finfo_close($finfo);
exit;
}
$nombreOriginal = mb_substr(preg_replace('/[^\w.\-áéíóúÁÉÍÓÚñÑ ]/u', '', $file['name']), 0, 100);
$resultado[] = [
'nombre' => $nombreOriginal ?: $newName,
'path' => $newName,
'mime' => $mime,
'size' => $file['size'],
];
}
finfo_close($finfo);
echo json_encode([
'success' => true,
'imagen_path' => $newName,
'file_size' => $file['size'],
'success' => true,
'archivos' => $resultado,
// retrocompatibilidad: si solo fue uno, devolver también imagen_path
'imagen_path'=> count($resultado) === 1 ? $resultado[0]['path'] : null,
]);