up
This commit is contained in:
@@ -0,0 +1,254 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- Migración 003 – Módulo Turnero (Oleada 1)
|
||||||
|
-- Sistema de Turnero Inteligente con consentimientos informados
|
||||||
|
-- FASE 3.1 del plan ERP multi-módulo.
|
||||||
|
-- Segura para re-ejecutar (IF NOT EXISTS / INSERT IGNORE).
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- ── 1. Catálogo maestro de tipos de examen ──────────────────
|
||||||
|
-- Compartido con Oleada 2 (registro_exams).
|
||||||
|
-- En Migración 005 se agregan precio_base, requiere_ayunas, instrucciones.
|
||||||
|
CREATE TABLE IF NOT EXISTS exam_tipos (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
codigo VARCHAR(20) NOT NULL COMMENT 'Ej: HEM, GLU, URIN',
|
||||||
|
nombre VARCHAR(150) NOT NULL COMMENT 'Ej: Hemograma completo',
|
||||||
|
categoria VARCHAR(80) DEFAULT NULL COMMENT 'Ej: Hematología, Química',
|
||||||
|
activo TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
creado_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uq_codigo (codigo),
|
||||||
|
INDEX idx_activo (activo),
|
||||||
|
INDEX idx_categoria (categoria)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
|
COMMENT='Catálogo maestro de tipos de examen del laboratorio';
|
||||||
|
|
||||||
|
-- ── 2. Relación M:N examen ↔ consentimiento (formulario) ────
|
||||||
|
-- Si un examen no tiene registro aquí, no se exige consentimiento.
|
||||||
|
-- Varios exámenes del mismo turno que apunten al mismo formulario_id
|
||||||
|
-- generan UN SOLO consentimiento (deduplicado en la lógica PHP).
|
||||||
|
CREATE TABLE IF NOT EXISTS exam_tipo_consentimientos (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
exam_tipo_id INT UNSIGNED NOT NULL,
|
||||||
|
formulario_id INT NOT NULL COMMENT 'FK lab_formularios.id (tipo=consentimiento)',
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uq_examen_form (exam_tipo_id, formulario_id),
|
||||||
|
INDEX idx_formulario (formulario_id),
|
||||||
|
CONSTRAINT fk_etc_exam FOREIGN KEY (exam_tipo_id) REFERENCES exam_tipos (id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
|
COMMENT='M:N entre tipos de examen y formularios de consentimiento';
|
||||||
|
|
||||||
|
-- ── 3. Lugares / estaciones de servicio configurables ───────
|
||||||
|
-- Recepción NO es un lugar; es el paso implícito del flujo.
|
||||||
|
-- Cada lugar tiene su propia cola y su propia pantalla TV.
|
||||||
|
CREATE TABLE IF NOT EXISTS turnero_lugares (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
nombre VARCHAR(100) NOT NULL COMMENT 'Ej: Toma de Muestras 1, Rayos X',
|
||||||
|
descripcion TEXT DEFAULT NULL,
|
||||||
|
activo TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
sort_order INT NOT NULL DEFAULT 99 COMMENT 'Orden en pantalla TV general',
|
||||||
|
creado_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
INDEX idx_activo (activo),
|
||||||
|
INDEX idx_sort (sort_order)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
|
COMMENT='Estaciones de servicio configurables del turnero';
|
||||||
|
|
||||||
|
-- Lugares de ejemplo (ajustar según el laboratorio real)
|
||||||
|
INSERT IGNORE INTO turnero_lugares (id, nombre, descripcion, activo, sort_order) VALUES
|
||||||
|
(1, 'Toma de Muestras 1', 'Primera estación de toma de muestras', 1, 1),
|
||||||
|
(2, 'Toma de Muestras 2', 'Segunda estación de toma de muestras', 1, 2),
|
||||||
|
(3, 'Rayos X', 'Área de radiología', 1, 3);
|
||||||
|
|
||||||
|
-- ── 4. Catálogo de prioridades (códigos A-F) ─────────────────
|
||||||
|
-- Editable desde el panel admin. orden_peso menor = mayor prioridad.
|
||||||
|
CREATE TABLE IF NOT EXISTS turnero_prioridades (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
codigo CHAR(1) NOT NULL COMMENT 'A, B, C, D, E, F',
|
||||||
|
nombre VARCHAR(100) NOT NULL,
|
||||||
|
orden_peso INT NOT NULL COMMENT 'Menor número = mayor prioridad',
|
||||||
|
color VARCHAR(7) NOT NULL DEFAULT '#6b7280' COMMENT 'Hex color para UI',
|
||||||
|
activo TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uq_codigo (codigo),
|
||||||
|
INDEX idx_orden (orden_peso)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
|
COMMENT='Catálogo configurable de prioridades de atención';
|
||||||
|
|
||||||
|
-- Prioridades por defecto A-F
|
||||||
|
INSERT IGNORE INTO turnero_prioridades (codigo, nombre, orden_peso, color, activo) VALUES
|
||||||
|
('A', 'Niños', 1, '#ef4444', 1),
|
||||||
|
('B', 'Embarazadas', 2, '#f97316', 1),
|
||||||
|
('C', 'Adulto mayor', 3, '#eab308', 1),
|
||||||
|
('D', 'Discapacidad', 4, '#8b5cf6', 1),
|
||||||
|
('E', 'Paciente general', 5, '#3b82f6', 1),
|
||||||
|
('F', 'Muestra pendiente', 6, '#6b7280', 1);
|
||||||
|
|
||||||
|
-- ── 5. Sesiones diarias de atención ─────────────────────────
|
||||||
|
CREATE TABLE IF NOT EXISTS turnero_sesiones (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
fecha DATE NOT NULL,
|
||||||
|
abierto_por INT DEFAULT NULL COMMENT 'FK admin_users.id',
|
||||||
|
cerrado_por INT DEFAULT NULL COMMENT 'FK admin_users.id',
|
||||||
|
inicio_at DATETIME DEFAULT NULL,
|
||||||
|
fin_at DATETIME DEFAULT NULL,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uq_fecha (fecha),
|
||||||
|
INDEX idx_fecha (fecha)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
|
COMMENT='Sesión de atención del día (una por fecha)';
|
||||||
|
|
||||||
|
-- ── 6. Turnos ────────────────────────────────────────────────
|
||||||
|
-- Motor principal del módulo. Un turno = un paciente en una visita.
|
||||||
|
CREATE TABLE IF NOT EXISTS turnero_turnos (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
sesion_id INT UNSIGNED NOT NULL,
|
||||||
|
numero INT NOT NULL COMMENT 'Correlativo dentro de la sesión',
|
||||||
|
codigo VARCHAR(10) NOT NULL COMMENT 'Ej: A001, E042',
|
||||||
|
prioridad_id INT UNSIGNED NOT NULL,
|
||||||
|
paciente_nombre VARCHAR(150) DEFAULT NULL COMMENT 'Capturado en kiosko (opcional)',
|
||||||
|
paciente_cel VARCHAR(20) DEFAULT NULL,
|
||||||
|
estado ENUM(
|
||||||
|
'espera',
|
||||||
|
'en_recepcion',
|
||||||
|
'en_espera_lugar',
|
||||||
|
'en_servicio',
|
||||||
|
'finalizado',
|
||||||
|
'ausente',
|
||||||
|
'cancelado'
|
||||||
|
) NOT NULL DEFAULT 'espera',
|
||||||
|
lugar_destino_id INT UNSIGNED DEFAULT NULL COMMENT 'Asignado por recepción',
|
||||||
|
-- Timestamps por etapa
|
||||||
|
creado_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
llamado_recepcion_at DATETIME DEFAULT NULL,
|
||||||
|
inicio_recepcion_at DATETIME DEFAULT NULL,
|
||||||
|
fin_recepcion_at DATETIME DEFAULT NULL,
|
||||||
|
llamado_lugar_at DATETIME DEFAULT NULL,
|
||||||
|
inicio_lugar_at DATETIME DEFAULT NULL,
|
||||||
|
fin_lugar_at DATETIME DEFAULT NULL,
|
||||||
|
-- Responsable de atención
|
||||||
|
atendido_recepcion_por INT DEFAULT NULL COMMENT 'FK admin_users.id',
|
||||||
|
atendido_lugar_por INT DEFAULT NULL COMMENT 'FK admin_users.id',
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uq_sesion_numero (sesion_id, numero),
|
||||||
|
INDEX idx_sesion_estado (sesion_id, estado),
|
||||||
|
INDEX idx_lugar_estado (lugar_destino_id, estado),
|
||||||
|
INDEX idx_prioridad (prioridad_id),
|
||||||
|
INDEX idx_creado (creado_at),
|
||||||
|
CONSTRAINT fk_tt_sesion FOREIGN KEY (sesion_id) REFERENCES turnero_sesiones (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_tt_prioridad FOREIGN KEY (prioridad_id) REFERENCES turnero_prioridades (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_tt_lugar FOREIGN KEY (lugar_destino_id) REFERENCES turnero_lugares (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
|
COMMENT='Turnos del sistema de atención';
|
||||||
|
|
||||||
|
-- ── 7. Solicitud interna (crea recepción) ────────────────────
|
||||||
|
-- Registra paciente, exámenes, pago y lugar destino.
|
||||||
|
-- Una solicitud por turno (UNIQUE turno_id).
|
||||||
|
CREATE TABLE IF NOT EXISTS turnero_solicitudes (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
turno_id INT UNSIGNED NOT NULL,
|
||||||
|
paciente_id INT NOT NULL COMMENT 'FK lab_pacientes.id',
|
||||||
|
lugar_id INT UNSIGNED NOT NULL COMMENT 'FK turnero_lugares.id',
|
||||||
|
total_cobrado DECIMAL(10,2) DEFAULT NULL,
|
||||||
|
metodo_pago ENUM(
|
||||||
|
'efectivo',
|
||||||
|
'transferencia',
|
||||||
|
'tarjeta',
|
||||||
|
'eps',
|
||||||
|
'cortesia'
|
||||||
|
) DEFAULT NULL,
|
||||||
|
observaciones TEXT DEFAULT NULL,
|
||||||
|
creado_por INT NOT NULL COMMENT 'FK admin_users.id',
|
||||||
|
creado_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uq_turno (turno_id),
|
||||||
|
INDEX idx_paciente (paciente_id),
|
||||||
|
INDEX idx_lugar (lugar_id),
|
||||||
|
INDEX idx_creado (creado_at),
|
||||||
|
CONSTRAINT fk_ts_turno FOREIGN KEY (turno_id) REFERENCES turnero_turnos (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_ts_lugar FOREIGN KEY (lugar_id) REFERENCES turnero_lugares (id) ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
|
COMMENT='Solicitud interna de agendamiento creada en recepción';
|
||||||
|
|
||||||
|
-- ── 8. Exámenes de la solicitud ──────────────────────────────
|
||||||
|
CREATE TABLE IF NOT EXISTS turnero_examen_items (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
solicitud_id INT UNSIGNED NOT NULL,
|
||||||
|
exam_tipo_id INT UNSIGNED NOT NULL,
|
||||||
|
creado_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
INDEX idx_solicitud (solicitud_id),
|
||||||
|
INDEX idx_exam_tipo (exam_tipo_id),
|
||||||
|
CONSTRAINT fk_tei_solicitud FOREIGN KEY (solicitud_id) REFERENCES turnero_solicitudes (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT fk_tei_exam FOREIGN KEY (exam_tipo_id) REFERENCES exam_tipos (id) ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
|
COMMENT='Exámenes individuales de cada solicitud de turno';
|
||||||
|
|
||||||
|
-- ── 9. Consentimientos por turno (deduplicados) ──────────────
|
||||||
|
-- Un registro por formulario distinto requerido en el turno.
|
||||||
|
-- token = enlace único de firma (UUID).
|
||||||
|
CREATE TABLE IF NOT EXISTS turnero_consentimientos (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
turno_id INT UNSIGNED NOT NULL,
|
||||||
|
formulario_id INT NOT NULL COMMENT 'FK lab_formularios.id',
|
||||||
|
token VARCHAR(64) NOT NULL COMMENT 'UUID único para enlace de firma',
|
||||||
|
estado ENUM(
|
||||||
|
'pendiente',
|
||||||
|
'enviado',
|
||||||
|
'visto',
|
||||||
|
'firmado',
|
||||||
|
'rechazado'
|
||||||
|
) NOT NULL DEFAULT 'pendiente',
|
||||||
|
enviado_at DATETIME DEFAULT NULL,
|
||||||
|
firmado_at DATETIME DEFAULT NULL,
|
||||||
|
ip_firma VARCHAR(45) DEFAULT NULL COMMENT 'IPv4 o IPv6 del firmante',
|
||||||
|
ua_firma VARCHAR(500) DEFAULT NULL COMMENT 'User-Agent del dispositivo',
|
||||||
|
version_formulario INT DEFAULT NULL COMMENT 'Snapshot de versión al firmar',
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY uq_token (token),
|
||||||
|
UNIQUE KEY uq_turno_form (turno_id, formulario_id),
|
||||||
|
INDEX idx_turno (turno_id),
|
||||||
|
INDEX idx_estado (estado),
|
||||||
|
CONSTRAINT fk_tc_turno FOREIGN KEY (turno_id) REFERENCES turnero_turnos (id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
|
COMMENT='Consentimientos informados vinculados a un turno (uno por formulario distinto)';
|
||||||
|
|
||||||
|
-- ── 10. Columna tipo en lab_formularios ──────────────────────
|
||||||
|
-- Permite marcar un formulario como consentimiento informado.
|
||||||
|
ALTER TABLE lab_formularios
|
||||||
|
ADD COLUMN IF NOT EXISTS tipo
|
||||||
|
ENUM('formulario', 'consentimiento') NOT NULL DEFAULT 'formulario'
|
||||||
|
COMMENT 'Tipo de formulario: genérico o consentimiento informado';
|
||||||
|
|
||||||
|
-- Índice para filtrar rápido en las consultas del turnero
|
||||||
|
ALTER TABLE lab_formularios
|
||||||
|
ADD INDEX IF NOT EXISTS idx_tipo (tipo);
|
||||||
|
|
||||||
|
-- ── 11. Asignar módulo turnero a roles ───────────────────────
|
||||||
|
-- Activar en system_modules si estaba inactivo
|
||||||
|
UPDATE system_modules
|
||||||
|
SET is_active = 1
|
||||||
|
WHERE slug = 'turnero';
|
||||||
|
|
||||||
|
-- Permisos del recepcionista sobre el módulo turnero
|
||||||
|
INSERT IGNORE INTO role_modules (role_id, module_slug, can_view, can_create, can_edit, can_delete, can_export)
|
||||||
|
SELECT r.id, 'turnero', 1, 1, 1, 0, 0
|
||||||
|
FROM roles r WHERE r.slug = 'recepcionista';
|
||||||
|
|
||||||
|
-- Permisos del bacteriólogo sobre el módulo turnero (solo lectura + edición de servicio)
|
||||||
|
INSERT IGNORE INTO role_modules (role_id, module_slug, can_view, can_create, can_edit, can_delete, can_export)
|
||||||
|
SELECT r.id, 'turnero', 1, 0, 1, 0, 0
|
||||||
|
FROM roles r WHERE r.slug = 'bacteriologo';
|
||||||
|
|
||||||
|
-- ── 12. Datos de ejemplo de tipos de examen ──────────────────
|
||||||
|
-- Solo si la tabla está vacía (evita duplicar en re-ejecución)
|
||||||
|
INSERT IGNORE INTO exam_tipos (codigo, nombre, categoria) VALUES
|
||||||
|
('HEM', 'Hemograma completo', 'Hematología'),
|
||||||
|
('GLU', 'Glucosa en ayunas', 'Química'),
|
||||||
|
('URIN', 'Uroanálisis completo', 'Urología'),
|
||||||
|
('UROCUL','Urocultivo', 'Microbiología'),
|
||||||
|
('COLEST','Perfil lipídico (colesterol)', 'Química'),
|
||||||
|
('TSH', 'TSH (Función tiroidea)', 'Endocrinología'),
|
||||||
|
('HIV', 'Prueba VIH', 'Serología'),
|
||||||
|
('HEPB', 'Hepatitis B (HBsAg)', 'Serología'),
|
||||||
|
('RXP', 'Rayos X de pecho', 'Radiología'),
|
||||||
|
('ECG', 'Electrocardiograma', 'Cardiología');
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Descriptor del módulo Turnero — Oleada 1
|
||||||
|
* Sistema de Turnero Inteligente con gestión de consentimientos informados.
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'slug' => 'turnero',
|
||||||
|
'name' => 'Turnero',
|
||||||
|
'icon' => 'fas fa-ticket-alt',
|
||||||
|
'category' => 'turnero',
|
||||||
|
'route' => '/erp.php?m=turnero&v=dashboard',
|
||||||
|
'is_active' => true,
|
||||||
|
'sort_order' => 40,
|
||||||
|
'oleada' => 1,
|
||||||
|
'description' => 'Sistema de turnos presenciales en recepción con prioridades, consentimientos y pantallas TV',
|
||||||
|
'links' => [
|
||||||
|
['name' => 'Dashboard', 'icon' => 'fas fa-tachometer-alt', 'route' => '/erp.php?m=turnero&v=dashboard'],
|
||||||
|
['name' => 'Recepción', 'icon' => 'fas fa-concierge-bell', 'route' => '/erp.php?m=turnero&v=recepcion'],
|
||||||
|
['name' => 'Configuración', 'icon' => 'fas fa-sliders-h', 'route' => '/erp.php?m=turnero&v=configuracion'],
|
||||||
|
['name' => 'Kiosko', 'icon' => 'fas fa-desktop', 'route' => '/erp.php?m=turnero&v=kiosko'],
|
||||||
|
['name' => 'Pantalla TV', 'icon' => 'fas fa-tv', 'route' => '/erp.php?m=turnero&v=display'],
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Runner — Migración 003: Módulo Turnero (Oleada 1)
|
||||||
|
*
|
||||||
|
* Uso desde terminal:
|
||||||
|
* php run_003_turnero.php
|
||||||
|
*
|
||||||
|
* O desde el navegador (solo en desarrollo, no exponer en producción):
|
||||||
|
* https://tu-dominio/run_003_turnero.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Ejecutar solo desde CLI o con token de protección básica en web
|
||||||
|
if (php_sapi_name() !== 'cli') {
|
||||||
|
// En web: exigir token para evitar ejecuciones accidentales
|
||||||
|
$token = $_GET['token'] ?? '';
|
||||||
|
if (empty($token) || $token !== (defined('MIGRATION_TOKEN') ? MIGRATION_TOKEN : '')) {
|
||||||
|
http_response_code(403);
|
||||||
|
die("Acceso denegado. Usa CLI: php run_003_turnero.php\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/config/config.php';
|
||||||
|
|
||||||
|
$sqlFile = __DIR__ . '/migrations/003_turnero.sql';
|
||||||
|
|
||||||
|
if (!file_exists($sqlFile)) {
|
||||||
|
echo "❌ Archivo no encontrado: $sqlFile\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$pdo = $db->getConnection();
|
||||||
|
|
||||||
|
$sql = file_get_contents($sqlFile);
|
||||||
|
$statements = [];
|
||||||
|
|
||||||
|
// Separar sentencias por ; respetando comentarios de línea y bloques
|
||||||
|
$lines = explode("\n", $sql);
|
||||||
|
$current = '';
|
||||||
|
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$trimmed = trim($line);
|
||||||
|
|
||||||
|
// Saltar líneas de comentario
|
||||||
|
if (str_starts_with($trimmed, '--') || str_starts_with($trimmed, '#')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$current .= ' ' . $trimmed;
|
||||||
|
|
||||||
|
if (str_ends_with($trimmed, ';')) {
|
||||||
|
$stmt = trim($current);
|
||||||
|
if (!empty($stmt) && $stmt !== ';') {
|
||||||
|
$statements[] = $stmt;
|
||||||
|
}
|
||||||
|
$current = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n🚀 Ejecutando Migración 003 — Turnero\n";
|
||||||
|
echo str_repeat('─', 60) . "\n\n";
|
||||||
|
|
||||||
|
$ok = 0;
|
||||||
|
$err = 0;
|
||||||
|
|
||||||
|
foreach ($statements as $stmt) {
|
||||||
|
$preview = substr(preg_replace('/\s+/', ' ', $stmt), 0, 90);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->exec($stmt);
|
||||||
|
echo " ✓ " . $preview . "\n";
|
||||||
|
$ok++;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$msg = $e->getMessage();
|
||||||
|
|
||||||
|
// Errores ignorables: duplicado, ya existe, columna ya existe
|
||||||
|
$ignorable = (
|
||||||
|
str_contains($msg, 'Duplicate entry') ||
|
||||||
|
str_contains($msg, 'already exists') ||
|
||||||
|
str_contains($msg, "Duplicate key name")||
|
||||||
|
str_contains($msg, 'errno: 1050') || // table exists
|
||||||
|
str_contains($msg, 'errno: 1060') || // column exists
|
||||||
|
str_contains($msg, 'errno: 1061') // key exists
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($ignorable) {
|
||||||
|
echo " ℹ️ (ya existía) " . $preview . "\n";
|
||||||
|
$ok++;
|
||||||
|
} else {
|
||||||
|
echo " ✗ ERROR: " . $msg . "\n";
|
||||||
|
echo " SQL: " . $preview . "\n\n";
|
||||||
|
$err++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n" . str_repeat('─', 60) . "\n";
|
||||||
|
echo "✅ Completado: $ok OK";
|
||||||
|
if ($err) {
|
||||||
|
echo " | ⚠️ $err errores";
|
||||||
|
}
|
||||||
|
echo "\n\n";
|
||||||
|
|
||||||
|
// ── Verificación post-migración ──────────────────────────
|
||||||
|
echo "📋 Verificación de tablas creadas:\n";
|
||||||
|
|
||||||
|
$tablas = [
|
||||||
|
'exam_tipos', 'exam_tipo_consentimientos',
|
||||||
|
'turnero_lugares', 'turnero_prioridades',
|
||||||
|
'turnero_sesiones', 'turnero_turnos',
|
||||||
|
'turnero_solicitudes', 'turnero_examen_items',
|
||||||
|
'turnero_consentimientos',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($tablas as $tabla) {
|
||||||
|
try {
|
||||||
|
$count = $pdo->query("SELECT COUNT(*) FROM `$tabla`")->fetchColumn();
|
||||||
|
echo " ✓ $tabla ($count filas)\n";
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo " ✗ $tabla — NO ENCONTRADA\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar columna tipo en lab_formularios
|
||||||
|
try {
|
||||||
|
$cols = $pdo->query("SHOW COLUMNS FROM lab_formularios LIKE 'tipo'")->fetchAll();
|
||||||
|
if (!empty($cols)) {
|
||||||
|
echo " ✓ lab_formularios.tipo (ENUM)\n";
|
||||||
|
} else {
|
||||||
|
echo " ✗ lab_formularios.tipo — NO SE AGREGÓ\n";
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo " ✗ lab_formularios — " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar prioridades
|
||||||
|
echo "\n🎫 Prioridades insertadas:\n";
|
||||||
|
$prios = $pdo->query("SELECT codigo, nombre, color FROM turnero_prioridades ORDER BY orden_peso")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach ($prios as $p) {
|
||||||
|
echo " [{$p['codigo']}] {$p['nombre']} {$p['color']}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar lugares
|
||||||
|
echo "\n📍 Lugares creados:\n";
|
||||||
|
$lugares = $pdo->query("SELECT id, nombre FROM turnero_lugares ORDER BY sort_order")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach ($lugares as $l) {
|
||||||
|
echo " [{$l['id']}] {$l['nombre']}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar exámenes de ejemplo
|
||||||
|
echo "\n🧪 Tipos de examen de ejemplo:\n";
|
||||||
|
$exams = $pdo->query("SELECT codigo, nombre, categoria FROM exam_tipos ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach ($exams as $e) {
|
||||||
|
echo " [{$e['codigo']}] {$e['nombre']} — {$e['categoria']}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n✅ Migración 003 lista. Continúa con FASE 3.2 (Motor de cola + API).\n\n";
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "❌ Error fatal: " . $e->getMessage() . "\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user