feat: 6 nuevas funcionalidades - notas obligatorias turno, reporte enfermero, plantilla WhatsApp, adjuntar orden modal, roles solo lectura, fix valores copago

This commit is contained in:
Lizandro Guarnizo
2026-04-02 09:41:49 -05:00
parent 052a263659
commit d8ceb3f822
14 changed files with 2794 additions and 49 deletions
+36
View File
@@ -0,0 +1,36 @@
<?php
/**
* API — Bloquear / Desbloquear usuario de WhatsApp
* POST { user_id: int, blocked: bool }
* Cambia users.status entre 'active' y 'blocked'.
* El BotService omite cualquier mensaje entrante de usuarios bloqueados.
*/
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
requireAuthentication();
try {
$input = json_decode(file_get_contents('php://input'), true) ?: [];
$userId = isset($input['user_id']) ? intval($input['user_id']) : 0;
if (!isset($input['blocked'])) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'user_id and blocked are required']);
exit;
}
$blocked = (bool)$input['blocked'];
if ($userId <= 0) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Invalid user_id']);
exit;
}
$db = Database::getInstance();
$status = $blocked ? 'blocked' : 'active';
$db->update('users', ['status' => $status], 'id = :id', ['id' => $userId]);
echo json_encode(['success' => true, 'blocked' => $blocked, 'status' => $status]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
+3 -1
View File
@@ -26,6 +26,7 @@ try {
u.id as user_id,
u.phone_number,
COALESCE(u.name, u.phone_number) as name,
u.status as user_status,
c.content as last_message,
c.direction as last_direction,
c.message_type as last_message_type,
@@ -43,7 +44,7 @@ try {
FROM conversations
GROUP BY user_id
)
GROUP BY u.id, u.phone_number, u.name, c.content, c.direction, c.message_type, c.created_at, c.status, u.advisor_requested, u.terms_pending, u.terms_accepted_at
GROUP BY u.id, u.phone_number, u.name, u.status, c.content, c.direction, c.message_type, c.created_at, c.status, u.advisor_requested, u.terms_pending, u.terms_accepted_at
ORDER BY c.created_at DESC
LIMIT 50"
);
@@ -59,6 +60,7 @@ try {
'user_id' => intval($conv['user_id']),
'phone_number' => $conv['phone_number'],
'name' => $conv['name'],
'user_status' => $conv['user_status'] ?? 'active',
'last_message' => $conv['last_message'] ?? '',
'last_direction' => $conv['last_direction'] ?? 'incoming',
'last_message_type' => $conv['last_message_type'] ?? 'text',
+84
View File
@@ -0,0 +1,84 @@
<?php
/**
* API: Reporte de domicilios agrupado por enfermero, año y mes.
*
* GET params (todos opcionales):
* anio Año a filtrar (ej. 2026). Si se omite, devuelve todos.
* mes Mes 1-12. Sólo funciona junto con anio.
* enfermera_id ID de la enfermera. Si se omite, devuelve todas.
*/
require_once __DIR__ . '/../../config/config.php';
require_once __DIR__ . '/../../classes/Database.php';
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
requireAuthentication();
$db = Database::getInstance();
$anio = isset($_GET['anio']) && ctype_digit($_GET['anio']) ? (int)$_GET['anio'] : null;
$mes = isset($_GET['mes']) && ctype_digit($_GET['mes']) ? (int)$_GET['mes'] : null;
$enfermeraId = isset($_GET['enfermera_id'])&& ctype_digit($_GET['enfermera_id']) ? (int)$_GET['enfermera_id']: null;
$wheres = [];
$params = [];
if ($anio !== null) {
$wheres[] = 'YEAR(d.fecha_programada) = ?';
$params[] = $anio;
if ($mes !== null) {
$wheres[] = 'MONTH(d.fecha_programada) = ?';
$params[] = $mes;
}
}
if ($enfermeraId !== null) {
$wheres[] = 'e.id = ?';
$params[] = $enfermeraId;
}
$where = $wheres ? 'WHERE ' . implode(' AND ', $wheres) : '';
$filas = $db->fetchAll(
"SELECT
e.id AS enfermera_id,
e.nombre_completo AS enfermera,
YEAR(d.fecha_programada) AS anio,
MONTH(d.fecha_programada) AS mes,
COUNT(*) AS total,
SUM(d.estado = 'completado') AS completados,
SUM(d.estado = 'cancelado') AS cancelados,
SUM(d.estado NOT IN ('completado','cancelado')) AS en_proceso
FROM lab_domicilios d
JOIN lab_asignaciones a ON a.domicilio_id = d.id
AND a.estado NOT IN ('liberada')
JOIN lab_enfermeras e ON e.id = a.enfermera_id
$where
GROUP BY e.id, YEAR(d.fecha_programada), MONTH(d.fecha_programada)
ORDER BY e.nombre_completo, YEAR(d.fecha_programada) DESC, MONTH(d.fecha_programada) DESC",
$params
);
$meses_es = [
1=>'Ene',2=>'Feb',3=>'Mar',4=>'Abr',5=>'May',6=>'Jun',
7=>'Jul',8=>'Ago',9=>'Sep',10=>'Oct',11=>'Nov',12=>'Dic',
];
foreach ($filas as &$f) {
$t = (int)$f['total'];
$f['tasa_pct'] = $t ? round((int)$f['completados'] / $t * 100) : 0;
$f['mes_nombre'] = $meses_es[(int)$f['mes']] ?? $f['mes'];
}
unset($f);
// Lista de enfermeras para el selector del filtro
$enfermeras = $db->fetchAll(
"SELECT id, nombre_completo FROM lab_enfermeras WHERE activa = 1 ORDER BY nombre_completo"
);
echo json_encode([
'success' => true,
'filas' => $filas,
'enfermeras' => $enfermeras,
], JSON_UNESCAPED_UNICODE);