import React, { useState, useEffect, useRef } from 'react';
import {
LayoutDashboard, FileText, Users, Settings, LogOut,
Plus, Search, Filter, AlertCircle, CheckCircle2,
Clock, ChevronRight, PenTool, Download, UserCircle, Menu, X, Bell, Activity
} from 'lucide-react';
// ==========================================
// 1. MOCK DATA & SCHEMAS (Simulando MySQL DB)
// ==========================================
const MOCK_USERS = [
{ id: 1, name: 'Hna. Helen Gomez', role: 'TUTOR', email: 'helen@mundoabc.edu', avatar: 'HG' },
{ id: 2, name: 'Admin Principal', role: 'ADMIN', email: 'admin@mundoabc.edu', avatar: 'AD' },
{ id: 3, name: 'Carlos Cardales', role: 'PADRE', email: 'padre@correo.com', avatar: 'CC' },
{ id: 4, name: 'Yeiber Cardales', role: 'ESTUDIANTE', email: 'yeiber@mundoabc.edu', avatar: 'YC', grade: '6º', tutorId: 1 },
{ id: 5, name: 'Director Académico', role: 'DIRECTOR', email: 'director@mundoabc.edu', avatar: 'DA' },
];
const INFRACTION_TYPES = [
'Muestra conducta agresiva',
'Falta de respeto a compañeros o tutores',
'Traer objetos no permitidos al aula',
'No cumplió las normas de convivencia establecidas',
'Uso indebido de dispositivos electrónicos durante actividades académicas',
'No cumple con las normas en el aula'
];
let initialIncidents = [
{
id: 'INC-2026-001',
studentId: 4,
tutorId: 1,
date: '2026-05-13',
infractions: ['No cumple con las normas en el aula', 'Uso indebido de dispositivos electrónicos durante actividades académicas'],
description: 'El estudiante ha presentado problemas de conducta dentro del aula.',
status: 'PENDIENTE_FIRMAS', // PENDIENTE_FIRMAS, CERRADO, EN_REVISION
agreements: 'El estudiante se compromete a prestar atención. Los padres supervisarán su comportamiento.',
signatures: {
tutor: { signed: true, date: '2026-05-13', data: 'mock-signature-data' },
padre: { signed: false, date: null, data: null },
estudiante: { signed: false, date: null, data: null },
director: { signed: false, date: null, data: null }
}
}
];
// ==========================================
// 2. COMPONENTES UI REUTILIZABLES
// ==========================================
const Badge = ({ children, variant = 'gray' }) => {
const variants = {
gray: 'bg-gray-100 text-gray-800',
blue: 'bg-blue-100 text-blue-800',
yellow: 'bg-yellow-100 text-yellow-800',
red: 'bg-red-100 text-red-800',
green: 'bg-green-100 text-green-800',
};
return (
{children}
);
};
const Button = ({ children, onClick, variant = 'primary', className = '', icon: Icon, type="button" }) => {
const baseStyle = "inline-flex items-center justify-center px-4 py-2 border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors";
const variants = {
primary: "border-transparent text-white bg-blue-600 hover:bg-blue-700 focus:ring-blue-500",
secondary: "border-gray-300 text-gray-700 bg-white hover:bg-gray-50 focus:ring-blue-500",
danger: "border-transparent text-white bg-red-600 hover:bg-red-700 focus:ring-red-500",
};
return (
);
};
// ==========================================
// 3. COMPONENTE FIRMA DIGITAL (Canvas)
// ==========================================
const SignaturePad = ({ onSave, onCancel, roleToSign }) => {
const canvasRef = useRef(null);
const [isDrawing, setIsDrawing] = useState(false);
useEffect(() => {
const canvas = canvasRef.current;
if (canvas) {
const ctx = canvas.getContext('2d');
ctx.lineWidth = 2;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = '#0f172a'; // Slate 900
// Fix resolution for high DPI displays
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * 2;
canvas.height = rect.height * 2;
ctx.scale(2, 2);
}
}, []);
const getCoordinates = (e) => {
const canvas = canvasRef.current;
const rect = canvas.getBoundingClientRect();
if (e.touches && e.touches.length > 0) {
return {
x: e.touches[0].clientX - rect.left,
y: e.touches[0].clientY - rect.top
};
}
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
};
const startDrawing = (e) => {
e.preventDefault();
const { x, y } = getCoordinates(e);
const ctx = canvasRef.current.getContext('2d');
ctx.beginPath();
ctx.moveTo(x, y);
setIsDrawing(true);
};
const draw = (e) => {
e.preventDefault();
if (!isDrawing) return;
const { x, y } = getCoordinates(e);
const ctx = canvasRef.current.getContext('2d');
ctx.lineTo(x, y);
ctx.stroke();
};
const stopDrawing = () => {
setIsDrawing(false);
};
const handleClear = () => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
};
const handleSave = () => {
const dataUrl = canvasRef.current.toDataURL('image/png');
// Basic check to see if canvas is empty (simplified)
onSave(dataUrl, roleToSign);
};
return (
Firma Digital - {roleToSign}
Por favor, dibuje su firma en el recuadro inferior usando su mouse o pantalla táctil.
);
};
// ==========================================
// 4. VISTAS PRINCIPALES
// ==========================================
const DashboardView = ({ incidents, role }) => {
const pending = incidents.filter(i => i.status === 'PENDIENTE_FIRMAS').length;
const closed = incidents.filter(i => i.status === 'CERRADO').length;
return (
Panel de Control
{/* KPI Cards */}
Total Incidencias
{incidents.length}
Pendientes de Firma
{pending}
{/* Gráfico de Estadísticas (NUEVO) */}
Estadísticas de Incidencias por Mes
{/* Barras simuladas */}
{[4, 7, 3, 8, 2, 5, 9, 4, 6, 10, 5, 8].map((val, idx) => (
))}
{/* Actividad Reciente */}
Incidencias Recientes
{incidents.slice(0, 5).map(inc => {
const student = MOCK_USERS.find(u => u.id === inc.studentId);
return (
{inc.status === 'CERRADO' ?
:
}
{inc.id} - {student?.name}
{inc.description}
{inc.date}
{inc.status.replace('_', ' ')}
);
})}
);
};
const CreateIncidentView = ({ onSubmit, onCancel }) => {
const [formData, setFormData] = useState({
studentId: '',
date: new Date().toISOString().split('T')[0],
infractions: [],
description: '',
agreements: ''
});
const students = MOCK_USERS.filter(u => u.role === 'ESTUDIANTE');
const toggleInfraction = (infraction) => {
setFormData(prev => ({
...prev,
infractions: prev.infractions.includes(infraction)
? prev.infractions.filter(i => i !== infraction)
: [...prev.infractions, infraction]
}));
};
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(formData);
};
return (
Registrar Nueva Incidencia
);
};
const IncidentDetailView = ({ incident, onBack, onSign, currentUser }) => {
const student = MOCK_USERS.find(u => u.id === incident.studentId);
const tutor = MOCK_USERS.find(u => u.id === incident.tutorId);
const getSignatureBox = (roleLabel, roleKey, signData) => {
const canSign = currentUser.role === roleKey.toUpperCase() ||
(currentUser.role === 'ADMIN' && !signData.signed);
return (
{roleLabel}
{signData.signed ? (
{signData.data && signData.data.startsWith('data:image') ? (

) : (
{signData.data || 'Firmado'}
)}
Firmado el: {signData.date}
Verificado
) : (
Pendiente de firma
{canSign && (
)}
)}
);
};
const handlePrint = () => {
window.print();
};
return (
{/* Documento Estilo Print */}
{/* Header Institucional */}
M
INCIDENCIA ESCOLAR
Institución Educativa Mundo ABC
Folio
{incident.id}
Fecha: {incident.date}
{/* Info Básica */}
Del Estudiante
{student?.name}
Grado: {student?.grade}
Tutor(a) Encargado
{tutor?.name}
Docente Titular
{/* Situación */}
Situación Reportada
{INFRACTION_TYPES.map((inf, idx) => {
const isChecked = incident.infractions.includes(inf);
return (
-
{isChecked && }
{inf}
)
})}
{/* Descripción */}
{/* Acuerdos */}
Acuerdos y Compromisos
{incident.agreements}
{/* Timeline de Seguimiento */}
Línea de Tiempo del Caso
Incidencia Registrada
{incident.date} - Reportado por {tutor?.name}
{incident.signatures.padre.signed && (
Acuerdo Aceptado (Acudiente)
{incident.signatures.padre.date} - Firma digital registrada en el sistema
)}
{incident.status === 'CERRADO' && (
Caso Cerrado y Archivado
Todas las firmas de conformidad han sido recolectadas
)}
{/* Firmas */}
Firmas de Conformidad
{getSignatureBox('Docente / Tutor', 'tutor', incident.signatures.tutor)}
{getSignatureBox('Padre o Tutor Legal', 'padre', incident.signatures.padre)}
{getSignatureBox('Alumno(a)', 'estudiante', incident.signatures.estudiante)}
{getSignatureBox('Directivo', 'director', incident.signatures.director)}
A.B.C.D.E.F.G.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.X.Y.Z
);
};
// ==========================================
// 5. APLICACIÓN PRINCIPAL (Root Component)
// ==========================================
export default function App() {
const [user, setUser] = useState(null); // null = no logueado
const [currentView, setCurrentView] = useState('dashboard'); // dashboard, list, create, detail
const [incidents, setIncidents] = useState(initialIncidents);
const [selectedIncidentId, setSelectedIncidentId] = useState(null);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [signingRole, setSigningRole] = useState(null);
const [showNotifications, setShowNotifications] = useState(false);
const notifications = [
{ id: 1, text: "Nueva incidencia registrada (Yeiber C.)", time: "Hace 5 min", unread: true },
{ id: 2, text: "Firma de padre pendiente en INC-2026-001", time: "Hace 2 horas", unread: true },
];
// --- Handlers ---
const handleLogin = (mockUser) => {
setUser(mockUser);
setCurrentView('dashboard');
};
const handleLogout = () => {
setUser(null);
};
const handleCreateIncident = (formData) => {
const newIncident = {
id: `INC-2026-${String(incidents.length + 1).padStart(3, '0')}`,
...formData,
status: 'PENDIENTE_FIRMAS',
tutorId: user.id, // El creador
signatures: {
tutor: { signed: true, date: formData.date, data: 'Firma Digital Autenticada' }, // auto-firma del creador
padre: { signed: false, date: null, data: null },
estudiante: { signed: false, date: null, data: null },
director: { signed: false, date: null, data: null }
}
};
setIncidents([newIncident, ...incidents]);
setCurrentView('list');
};
const handleSignRequest = (role) => {
setSigningRole(role);
};
const handleSaveSignature = (dataUrl, roleKey) => {
setIncidents(prevIncidents => prevIncidents.map(inc => {
if (inc.id === selectedIncidentId) {
const updatedSignatures = {
...inc.signatures,
[roleKey]: {
signed: true,
date: new Date().toISOString().split('T')[0],
data: dataUrl
}
};
// Verificar si todas están firmadas para cambiar status
const allSigned = Object.values(updatedSignatures).every(s => s.signed);
return {
...inc,
signatures: updatedSignatures,
status: allSigned ? 'CERRADO' : 'PENDIENTE_FIRMAS'
};
}
return inc;
}));
setSigningRole(null);
};
// --- Renderización Condicional (Login Mock) ---
if (!user) {
return (
M
Mundo ABC
Sistema de Gestión de Incidencias
Seleccione un perfil para ingresar
{MOCK_USERS.map((mockUser) => (
))}
);
}
// --- Layout Principal ---
const SidebarContent = () => (
);
return (
{/* Mobile Sidebar Overlay */}
{isMenuOpen && (
setIsMenuOpen(false)} />
)}
{/* Sidebar Desktop & Mobile */}
{/* Main Content */}
{/* Header Superior (Notificaciones y Menu) */}
Portal Institucional
{showNotifications && (
Notificaciones Recientes
Marcar leídas
{notifications.map(n => (
))}
)}
{/* Content Area */}
{currentView === 'dashboard' &&
}
{currentView === 'list' && (
Registro de Incidencias
{(user.role === 'ADMIN' || user.role === 'TUTOR') && (
)}
| Código |
Fecha |
Estudiante |
Estado |
Acciones |
{incidents.map((inc) => {
const student = MOCK_USERS.find(u => u.id === inc.studentId);
return (
| {inc.id} |
{inc.date} |
{student?.name}
Grado: {student?.grade}
|
{inc.status.replace('_', ' ')}
|
|
);
})}
)}
{currentView === 'create' && (
setCurrentView('list')}
/>
)}
{currentView === 'detail' && (
i.id === selectedIncidentId)}
currentUser={user}
onBack={() => {
setSelectedIncidentId(null);
setCurrentView('list');
}}
onSign={handleSignRequest}
/>
)}
{/* Modal de Firma */}
{signingRole && (
setSigningRole(null)}
/>
)}
);
}