- categorias.html: lista con conteo de movimientos, drawer para agregar/editar - api/categorias.php: GET/POST/PUT/DELETE (DELETE bloqueado si tiene movimientos) - categorias.js: grid de 30 íconos Material Symbols + paleta de 12 colores, preview en vivo - Sidebar actualizado en todas las páginas con link a Categorías - Sesiones PHP guardadas en pagos/db/sessions/ (bind mount) → sobreviven reinicios Docker - Fix selector de categorías en add.html: sesiones persistentes resuelven el Cargando… Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
57 lines
2.3 KiB
PHP
57 lines
2.3 KiB
PHP
<?php
|
|
require_once 'auth.php';
|
|
header('Content-Type: application/json');
|
|
require_once 'db.php';
|
|
|
|
$pdo = getDB();
|
|
$method = $_SERVER['REQUEST_METHOD'];
|
|
|
|
if ($method === 'GET') {
|
|
$stmt = $pdo->query("
|
|
SELECT c.*, COUNT(m.id) as total_movimientos
|
|
FROM categorias c
|
|
LEFT JOIN movimientos m ON m.id_categoria = c.id
|
|
GROUP BY c.id ORDER BY c.nombre
|
|
");
|
|
echo json_encode(['status' => 'success', 'categorias' => $stmt->fetchAll(PDO::FETCH_ASSOC)]);
|
|
|
|
} elseif ($method === 'POST') {
|
|
$d = json_decode(file_get_contents('php://input'), true);
|
|
if (empty($d['nombre']) || empty($d['icono'])) {
|
|
http_response_code(400);
|
|
die(json_encode(['status' => 'error', 'message' => 'Nombre e ícono son obligatorios']));
|
|
}
|
|
$stmt = $pdo->prepare("INSERT INTO categorias (nombre, icono, color_hex) VALUES (?, ?, ?)");
|
|
$stmt->execute([trim($d['nombre']), trim($d['icono']), $d['color_hex'] ?? '#005050']);
|
|
echo json_encode(['status' => 'success', 'id' => $pdo->lastInsertId()]);
|
|
|
|
} elseif ($method === 'PUT') {
|
|
$d = json_decode(file_get_contents('php://input'), true);
|
|
if (empty($d['id']) || empty($d['nombre']) || empty($d['icono'])) {
|
|
http_response_code(400);
|
|
die(json_encode(['status' => 'error', 'message' => 'id, nombre e ícono son obligatorios']));
|
|
}
|
|
$stmt = $pdo->prepare("UPDATE categorias SET nombre=?, icono=?, color_hex=? WHERE id=?");
|
|
$stmt->execute([trim($d['nombre']), trim($d['icono']), $d['color_hex'] ?? '#005050', (int)$d['id']]);
|
|
echo json_encode(['status' => 'success']);
|
|
|
|
} elseif ($method === 'DELETE') {
|
|
$d = json_decode(file_get_contents('php://input'), true);
|
|
if (empty($d['id'])) {
|
|
http_response_code(400);
|
|
die(json_encode(['status' => 'error', 'message' => 'Falta el ID']));
|
|
}
|
|
$count = $pdo->prepare("SELECT COUNT(*) FROM movimientos WHERE id_categoria = ?");
|
|
$count->execute([(int)$d['id']]);
|
|
if ($count->fetchColumn() > 0) {
|
|
http_response_code(409);
|
|
die(json_encode(['status' => 'error', 'message' => 'No se puede eliminar: tiene movimientos asociados']));
|
|
}
|
|
$pdo->prepare("DELETE FROM categorias WHERE id = ?")->execute([(int)$d['id']]);
|
|
echo json_encode(['status' => 'success']);
|
|
|
|
} else {
|
|
http_response_code(405);
|
|
echo json_encode(['status' => 'error', 'message' => 'Método no permitido']);
|
|
}
|