# 📖 Guía Completa para Crear Nuevos Módulos

## 🎯 Introducción

Esta guía te ayudará a crear nuevos módulos completos en el sistema POS Abarrotes siguiendo las mismas convenciones y patrones establecidos. Cada módulo incluye frontend React, backend PHP y integración con el sistema de roles y permisos.

---

## 🗂️ Estructura de un Módulo Completo

Un módulo típico incluye:

### **Frontend (React)**
```
src/pages/{modulo}/
├── {Modulo}Page.js         # Página principal con tabla
├── {Modulo}Form.js         # Formulario de creación/edición
├── {Modulo}Details.js      # Vista detallada (opcional)
├── {Modulo}Page.css        # Estilos del módulo
└── components/             # Componentes auxiliares
```

### **Backend (PHP)**
```
api_pos/reportes/funciones_php/
└── {Modulo}.php           # Endpoints del módulo
```

### **Base de Datos**
```sql
-- Tabla principal
CREATE TABLE {modulo}s (...)

-- Permiso en sistema
INSERT INTO permisos (nombre, descripcion, modulo, accion)
VALUES ('{modulo}_ver', 'Acceso a {Modulo}', '{modulo}', 'ver');
```

---

## 🔧 Paso a Paso para Crear un Nuevo Módulo

### **PASO 1: Planificación**

Antes de comenzar, define:

1. **Nombre del módulo** (ej: `proveedores`)
2. **Campos de la tabla** principal
3. **Funcionalidades** requeridas (CRUD básico)
4. **Relaciones** con otras tablas
5. **Permisos** necesarios

### **PASO 2: Base de Datos**

#### 2.1 Crear la tabla principal
```sql
-- Ejemplo: tabla proveedores
CREATE TABLE proveedores (
    id int(11) NOT NULL AUTO_INCREMENT,
    nombre varchar(100) NOT NULL,
    contacto varchar(100),
    telefono varchar(15),
    email varchar(100),
    direccion text,
    activo tinyint(4) DEFAULT 1,
    fecha_creacion timestamp DEFAULT CURRENT_TIMESTAMP,
    fecha_actualizacion timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    KEY idx_nombre (nombre),
    KEY idx_activo (activo)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

#### 2.2 Agregar el permiso al sistema
```sql
-- Agregar permiso
INSERT INTO permisos (nombre, descripcion, modulo, accion, activo)
VALUES ('proveedores_ver', 'Acceso a Proveedores', 'proveedores', 'ver', 1);

-- Asignar a roles (ejemplo: solo admin)
INSERT INTO rol_permisos (rol_id, permiso_id)
SELECT r.id, p.id
FROM roles r, permisos p
WHERE r.nombre = 'Administrador' AND p.nombre = 'proveedores_ver';
```

### **PASO 3: Backend (API PHP)**

#### 3.1 Crear archivo `Proveedores.php`
```php
<?php
// Prevenir acceso directo
if (!defined('BASEPATH')) exit('No direct script access allowed');

switch ($tipo) {
    case 'getProveedores':
        try {
            $query = "SELECT id, nombre, contacto, telefono, email, direccion, activo,
                            fecha_creacion, fecha_actualizacion
                     FROM proveedores
                     ORDER BY nombre ASC";

            $result = $conn->query($query);
            if (!$result) {
                sendResponse(false, "Error en consulta: " . $conn->error);
                break;
            }

            $proveedores = [];
            while ($row = $result->fetch_assoc()) {
                $row['activo'] = (bool)$row['activo'];
                $proveedores[] = $row;
            }

            sendResponse(true, "Proveedores obtenidos correctamente", $proveedores);
        } catch (Exception $e) {
            error_log("Error al obtener proveedores: " . $e->getMessage());
            sendResponse(false, "Error al obtener proveedores");
        }
        break;

    case 'crearProveedor':
        try {
            $nombre = isset($datapost['nombre']) ? trim($datapost['nombre']) : '';
            $contacto = isset($datapost['contacto']) ? trim($datapost['contacto']) : '';
            $telefono = isset($datapost['telefono']) ? trim($datapost['telefono']) : '';
            $email = isset($datapost['email']) ? trim($datapost['email']) : '';
            $direccion = isset($datapost['direccion']) ? trim($datapost['direccion']) : '';
            $activo = isset($datapost['activo']) ? intval($datapost['activo']) : 1;

            if (empty($nombre)) {
                sendResponse(false, "El nombre del proveedor es requerido");
                break;
            }

            $query = "INSERT INTO proveedores (nombre, contacto, telefono, email, direccion, activo)
                     VALUES (?, ?, ?, ?, ?, ?)";
            $stmt = $conn->prepare($query);
            $stmt->bind_param("sssssi", $nombre, $contacto, $telefono, $email, $direccion, $activo);

            if ($stmt->execute()) {
                sendResponse(true, "Proveedor creado correctamente", ['id' => $conn->insert_id]);
            } else {
                sendResponse(false, "Error al crear proveedor");
            }
        } catch (Exception $e) {
            error_log("Error al crear proveedor: " . $e->getMessage());
            sendResponse(false, "Error al crear proveedor");
        }
        break;

    case 'actualizarProveedor':
        try {
            $id = isset($datapost['id']) ? intval($datapost['id']) : 0;
            $nombre = isset($datapost['nombre']) ? trim($datapost['nombre']) : '';
            $contacto = isset($datapost['contacto']) ? trim($datapost['contacto']) : '';
            $telefono = isset($datapost['telefono']) ? trim($datapost['telefono']) : '';
            $email = isset($datapost['email']) ? trim($datapost['email']) : '';
            $direccion = isset($datapost['direccion']) ? trim($datapost['direccion']) : '';
            $activo = isset($datapost['activo']) ? intval($datapost['activo']) : 1;

            if ($id <= 0 || empty($nombre)) {
                sendResponse(false, "ID y nombre son requeridos");
                break;
            }

            $query = "UPDATE proveedores SET nombre = ?, contacto = ?, telefono = ?,
                     email = ?, direccion = ?, activo = ?, fecha_actualizacion = CURRENT_TIMESTAMP
                     WHERE id = ?";
            $stmt = $conn->prepare($query);
            $stmt->bind_param("sssssii", $nombre, $contacto, $telefono, $email, $direccion, $activo, $id);

            if ($stmt->execute()) {
                sendResponse(true, "Proveedor actualizado correctamente");
            } else {
                sendResponse(false, "Error al actualizar proveedor");
            }
        } catch (Exception $e) {
            error_log("Error al actualizar proveedor: " . $e->getMessage());
            sendResponse(false, "Error al actualizar proveedor");
        }
        break;

    case 'eliminarProveedor':
        try {
            $id = isset($datapost['id']) ? intval($datapost['id']) : 0;

            if ($id <= 0) {
                sendResponse(false, "ID del proveedor requerido");
                break;
            }

            $query = "UPDATE proveedores SET activo = 0 WHERE id = ?";
            $stmt = $conn->prepare($query);
            $stmt->bind_param("i", $id);

            if ($stmt->execute()) {
                sendResponse(true, "Proveedor eliminado correctamente");
            } else {
                sendResponse(false, "Error al eliminar proveedor");
            }
        } catch (Exception $e) {
            error_log("Error al eliminar proveedor: " . $e->getMessage());
            sendResponse(false, "Error al eliminar proveedor");
        }
        break;

    default:
        sendResponse(false, "Operación no válida para proveedores");
        break;
}
?>
```

#### 3.2 Agregar rutas en `api.php`
```php
// En el switch principal de api.php
case 'getProveedores':
case 'crearProveedor':
case 'actualizarProveedor':
case 'eliminarProveedor':
    include('./Proveedores.php');
    break;
```

#### 3.3 Agregar métodos en `apiService.js`
```javascript
// En apiService.js
async getProveedores() {
    return await this.request('POST', { tipo: 'getProveedores' });
}

async crearProveedor(proveedorData) {
    return await this.request('POST', {
        tipo: 'crearProveedor',
        ...proveedorData
    });
}

async actualizarProveedor(proveedorData) {
    return await this.request('POST', {
        tipo: 'actualizarProveedor',
        ...proveedorData
    });
}

async eliminarProveedor(id) {
    return await this.request('POST', {
        tipo: 'eliminarProveedor',
        id
    });
}
```

### **PASO 4: Frontend (React)**

#### 4.1 Crear `ProveedoresPage.js`
```jsx
import React, { useState, useEffect } from 'react';
import { Plus, Search, Edit, Trash2, Eye, Truck } from 'lucide-react';
import { useApp } from '../../context/AppContext';
import Button from '../../components/ui/Button';
import Input from '../../components/forms/Input';
import Modal from '../../components/ui/Modal';
import ProveedorForm from './ProveedorForm';
import apiService from '../../services/apiService';
import './ProveedoresPage.css';

const ProveedoresPage = () => {
    const { loading, setLoading, setError, clearError } = useApp();

    const [proveedores, setProveedores] = useState([]);
    const [filteredProveedores, setFilteredProveedores] = useState([]);
    const [searchQuery, setSearchQuery] = useState('');
    const [showCreateModal, setShowCreateModal] = useState(false);
    const [showEditModal, setShowEditModal] = useState(false);
    const [selectedProveedor, setSelectedProveedor] = useState(null);

    useEffect(() => {
        loadProveedores();
    }, []);

    useEffect(() => {
        filterProveedores();
    }, [proveedores, searchQuery]);

    const loadProveedores = async () => {
        try {
            setLoading(true);
            clearError();

            const result = await apiService.getProveedores();

            if (result.success) {
                setProveedores(result.data);
            } else {
                setError(result.message);
            }
        } catch (error) {
            setError('Error al cargar proveedores');
        } finally {
            setLoading(false);
        }
    };

    const filterProveedores = () => {
        if (!searchQuery.trim()) {
            setFilteredProveedores(proveedores);
            return;
        }

        const query = searchQuery.toLowerCase();
        const filtered = proveedores.filter(proveedor =>
            proveedor.nombre.toLowerCase().includes(query) ||
            proveedor.contacto?.toLowerCase().includes(query) ||
            proveedor.email?.toLowerCase().includes(query)
        );

        setFilteredProveedores(filtered);
    };

    const handleCreateProveedor = async (proveedorData) => {
        try {
            const result = await apiService.crearProveedor(proveedorData);

            if (result.success) {
                setShowCreateModal(false);
                loadProveedores();
            } else {
                setError(result.message);
            }
        } catch (error) {
            setError('Error al crear proveedor');
        }
    };

    const handleEditProveedor = async (proveedorData) => {
        try {
            const result = await apiService.actualizarProveedor({
                id: selectedProveedor.id,
                ...proveedorData
            });

            if (result.success) {
                setShowEditModal(false);
                setSelectedProveedor(null);
                loadProveedores();
            } else {
                setError(result.message);
            }
        } catch (error) {
            setError('Error al actualizar proveedor');
        }
    };

    const handleDeleteProveedor = async (id, nombre) => {
        if (!window.confirm(`¿Estás seguro de que quieres eliminar al proveedor "${nombre}"?`)) {
            return;
        }

        try {
            const result = await apiService.eliminarProveedor(id);

            if (result.success) {
                loadProveedores();
            } else {
                setError(result.message);
            }
        } catch (error) {
            setError('Error al eliminar proveedor');
        }
    };

    return (
        <div className="proveedores-page">
            {/* Header */}
            <div className="proveedores-header">
                <div className="header-content">
                    <h1 className="page-title">
                        <Truck size={24} />
                        Gestión de Proveedores
                    </h1>
                    <p className="page-description">
                        Administra los proveedores y sus datos de contacto
                    </p>
                </div>

                <div className="header-actions">
                    <Button
                        onClick={() => setShowCreateModal(true)}
                        className="create-proveedor-btn"
                        disabled={loading}
                    >
                        <Plus size={18} />
                        Nuevo Proveedor
                    </Button>
                </div>
            </div>

            {/* Filtros y búsqueda */}
            <div className="proveedores-filters">
                <div className="search-container">
                    <Input
                        placeholder="Buscar por nombre, contacto o email..."
                        value={searchQuery}
                        onChange={(e) => setSearchQuery(e.target.value)}
                        icon={<Search size={20} />}
                        className="search-input"
                    />
                </div>

                <div className="filters-summary">
                    <span className="proveedores-count">
                        {filteredProveedores.length} de {proveedores.length} proveedores
                    </span>
                </div>
            </div>

            {/* Tabla de proveedores */}
            <div className="proveedores-table-container">
                {loading && (
                    <div className="loading-state">
                        <div className="loading-spinner"></div>
                        <p>Cargando proveedores...</p>
                    </div>
                )}

                {!loading && filteredProveedores.length === 0 && (
                    <div className="empty-state">
                        <Truck size={48} />
                        <h3>No se encontraron proveedores</h3>
                        <p>
                            {searchQuery
                                ? 'No hay proveedores que coincidan con tu búsqueda'
                                : 'No hay proveedores registrados en el sistema'
                            }
                        </p>
                        {!searchQuery && (
                            <Button
                                onClick={() => setShowCreateModal(true)}
                                className="empty-action-btn"
                            >
                                <Plus size={18} />
                                Crear primer proveedor
                            </Button>
                        )}
                    </div>
                )}

                {!loading && filteredProveedores.length > 0 && (
                    <table className="proveedores-table-compact">
                        <thead>
                            <tr>
                                <th width="25%">Proveedor</th>
                                <th width="30%">Contacto</th>
                                <th width="25%">Información</th>
                                <th width="10%">Estado</th>
                                <th width="10%">Acciones</th>
                            </tr>
                        </thead>
                        <tbody>
                            {filteredProveedores.map((proveedor) => (
                                <tr key={proveedor.id} className={proveedor.activo ? '' : 'inactive-row'}>
                                    <td>
                                        <div className="proveedor-compact">
                                            <div className="proveedor-icon">
                                                <Truck size={16} />
                                            </div>
                                            <div className="proveedor-info">
                                                <div className="proveedor-name">{proveedor.nombre}</div>
                                                <div className="proveedor-id">ID: {proveedor.id}</div>
                                            </div>
                                        </div>
                                    </td>

                                    <td>
                                        <div className="contact-info">
                                            <div className="contact-name">{proveedor.contacto || 'Sin contacto'}</div>
                                            {proveedor.telefono && (
                                                <div className="contact-phone">{proveedor.telefono}</div>
                                            )}
                                        </div>
                                    </td>

                                    <td>
                                        <div className="info-compact">
                                            {proveedor.email && (
                                                <div className="info-email">{proveedor.email}</div>
                                            )}
                                            {proveedor.direccion && (
                                                <div className="info-address">{proveedor.direccion}</div>
                                            )}
                                        </div>
                                    </td>

                                    <td>
                                        <span className={`status-dot ${proveedor.activo ? 'active' : 'inactive'}`}>
                                            {proveedor.activo ? '● Activo' : '● Inactivo'}
                                        </span>
                                    </td>

                                    <td>
                                        <div className="actions-compact">
                                            <button
                                                className="action-btn-compact"
                                                onClick={() => {
                                                    setSelectedProveedor(proveedor);
                                                    setShowEditModal(true);
                                                }}
                                                title="Editar"
                                            >
                                                <Edit size={14} />
                                            </button>

                                            <button
                                                className="action-btn-compact delete"
                                                onClick={() => handleDeleteProveedor(proveedor.id, proveedor.nombre)}
                                                title="Eliminar"
                                            >
                                                <Trash2 size={14} />
                                            </button>
                                        </div>
                                    </td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                )}
            </div>

            {/* Modales */}
            {showCreateModal && (
                <Modal
                    title="Crear Nuevo Proveedor"
                    onClose={() => setShowCreateModal(false)}
                    size="large"
                >
                    <ProveedorForm
                        onSubmit={handleCreateProveedor}
                        onCancel={() => setShowCreateModal(false)}
                        isLoading={loading}
                    />
                </Modal>
            )}

            {showEditModal && selectedProveedor && (
                <Modal
                    title="Editar Proveedor"
                    onClose={() => {
                        setShowEditModal(false);
                        setSelectedProveedor(null);
                    }}
                    size="large"
                >
                    <ProveedorForm
                        proveedor={selectedProveedor}
                        onSubmit={handleEditProveedor}
                        onCancel={() => {
                            setShowEditModal(false);
                            setSelectedProveedor(null);
                        }}
                        isLoading={loading}
                        isEdit={true}
                    />
                </Modal>
            )}
        </div>
    );
};

export default ProveedoresPage;
```

#### 4.2 Crear `ProveedorForm.js`
```jsx
import React, { useState, useEffect } from 'react';
import { Truck, User, Mail, Phone, MapPin } from 'lucide-react';
import Input from '../../components/forms/Input';
import Button from '../../components/ui/Button';
import { isValidEmail } from '../../utils/validators';

const ProveedorForm = ({ proveedor, onSubmit, onCancel, isLoading, isEdit = false }) => {
    const [formData, setFormData] = useState({
        nombre: '',
        contacto: '',
        telefono: '',
        email: '',
        direccion: '',
        activo: 1
    });

    const [errors, setErrors] = useState({});

    useEffect(() => {
        if (isEdit && proveedor) {
            setFormData({
                nombre: proveedor.nombre || '',
                contacto: proveedor.contacto || '',
                telefono: proveedor.telefono || '',
                email: proveedor.email || '',
                direccion: proveedor.direccion || '',
                activo: proveedor.activo ? 1 : 0
            });
        }
    }, [isEdit, proveedor]);

    const handleInputChange = (field, value) => {
        setFormData(prev => ({
            ...prev,
            [field]: value
        }));

        // Limpiar error del campo
        if (errors[field]) {
            setErrors(prev => ({
                ...prev,
                [field]: ''
            }));
        }
    };

    const validateForm = () => {
        const newErrors = {};

        if (!formData.nombre.trim()) {
            newErrors.nombre = 'El nombre del proveedor es requerido';
        }

        if (formData.email && !isValidEmail(formData.email)) {
            newErrors.email = 'El email no es válido';
        }

        setErrors(newErrors);
        return Object.keys(newErrors).length === 0;
    };

    const handleSubmit = (e) => {
        e.preventDefault();

        if (!validateForm()) {
            return;
        }

        const dataToSubmit = {
            nombre: formData.nombre.trim(),
            contacto: formData.contacto.trim(),
            telefono: formData.telefono.trim(),
            email: formData.email.trim(),
            direccion: formData.direccion.trim(),
            activo: parseInt(formData.activo)
        };

        onSubmit(dataToSubmit);
    };

    return (
        <form onSubmit={handleSubmit} className="proveedor-form">
            <div className="form-container">
                {/* Columna Izquierda */}
                <div className="form-column">
                    <h3 className="column-title">
                        <Truck size={18} />
                        Información del Proveedor
                    </h3>

                    <div className="form-group">
                        <Input
                            label="Nombre del Proveedor"
                            type="text"
                            value={formData.nombre}
                            onChange={(e) => handleInputChange('nombre', e.target.value)}
                            error={errors.nombre}
                            icon={<Truck size={18} />}
                            placeholder="Nombre de la empresa"
                            required
                            disabled={isLoading}
                        />
                    </div>

                    <div className="form-group">
                        <Input
                            label="Persona de Contacto"
                            type="text"
                            value={formData.contacto}
                            onChange={(e) => handleInputChange('contacto', e.target.value)}
                            error={errors.contacto}
                            icon={<User size={18} />}
                            placeholder="Nombre del contacto"
                            disabled={isLoading}
                        />
                    </div>

                    <div className="form-group">
                        <Input
                            label="Teléfono"
                            type="tel"
                            value={formData.telefono}
                            onChange={(e) => handleInputChange('telefono', e.target.value)}
                            error={errors.telefono}
                            icon={<Phone size={18} />}
                            placeholder="555-123-4567"
                            disabled={isLoading}
                        />
                    </div>
                </div>

                {/* Columna Derecha */}
                <div className="form-column">
                    <h3 className="column-title">
                        <Mail size={18} />
                        Información de Contacto
                    </h3>

                    <div className="form-group">
                        <Input
                            label="Email"
                            type="email"
                            value={formData.email}
                            onChange={(e) => handleInputChange('email', e.target.value)}
                            error={errors.email}
                            icon={<Mail size={18} />}
                            placeholder="correo@proveedor.com"
                            disabled={isLoading}
                        />
                    </div>

                    <div className="form-group">
                        <label className="form-label">Dirección</label>
                        <textarea
                            value={formData.direccion}
                            onChange={(e) => handleInputChange('direccion', e.target.value)}
                            className="form-textarea"
                            placeholder="Dirección completa del proveedor"
                            rows={4}
                            disabled={isLoading}
                        />
                    </div>

                    {isEdit && (
                        <div className="form-group">
                            <label className="form-label">Estado</label>
                            <select
                                value={formData.activo}
                                onChange={(e) => handleInputChange('activo', e.target.value)}
                                className="form-select"
                                disabled={isLoading}
                            >
                                <option value={1}>✅ Activo</option>
                                <option value={0}>❌ Inactivo</option>
                            </select>
                        </div>
                    )}
                </div>
            </div>

            {/* Botones de acción */}
            <div className="form-actions">
                <Button
                    type="button"
                    variant="secondary"
                    onClick={onCancel}
                    disabled={isLoading}
                >
                    Cancelar
                </Button>

                <Button
                    type="submit"
                    loading={isLoading}
                    disabled={isLoading}
                >
                    {isEdit ? 'Actualizar Proveedor' : 'Crear Proveedor'}
                </Button>
            </div>
        </form>
    );
};

export default ProveedorForm;
```

#### 4.3 Crear `ProveedoresPage.css`
```css
/* Usar como base RolesPage.css y adaptar nombres de clases */
.proveedores-page {
  /* Copiar estilos de roles-page y adaptar */
}

.proveedores-header {
  /* Copiar estilos de roles-header */
}

.proveedores-table-compact {
  /* Copiar estilos de roles-table-compact */
}

/* Adaptar resto de estilos cambiando "roles" por "proveedores" */
```

#### **CSS Requerido para Inputs Numéricos:**
```css
/* Inputs numéricos sin flechas y alineación derecha */
.[modulo]-form input[type="number"] {
  text-align: right;
}

/* Ocultar flechas en Chrome, Safari, Edge, Opera */
.[modulo]-form input[type="number"]::-webkit-outer-spin-button,
.[modulo]-form input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* Ocultar flechas en Firefox */
.[modulo]-form input[type="number"] {
  -moz-appearance: textfield;
}
```

#### **Alineación de Tablas - Headers y Contenido:**
```css
/* Reglas globales aplicadas en globals.css */
/* Headers y contenido por defecto a la izquierda */
table th {
  text-align: left;
}

table td {
  text-align: left;
}

/* Headers y contenido de columnas numéricas a la derecha */
th[data-type="numeric"] {
  text-align: right;
}

td[data-type="numeric"],
td.numeric-column {
  text-align: right;
}

/* Headers y contenido de columnas de acciones centrados */
th[data-type="actions"] {
  text-align: center;
}

td[data-type="actions"],
td.actions-column,
td:has(.actions),
td:has(.actions-compact) {
  text-align: center;
}
```

#### **Estilos de Botones de Acción en Tablas:**
```css
/* Contenedor de botones de acción */
.actions-compact {
  display: flex;
  gap: 0.25rem;
  justify-content: center;
}

/* Botón de acción compacto */
.action-btn-compact {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #d1d5db;
  background: #ffffff;
  border-radius: 4px;
  color: #6b7280;
  cursor: pointer;
  transition: all 0.15s ease;
}

/* Efectos hover para botones de acción */
.action-btn-compact:hover {
  background: #f3f4f6;
  color: #1f2937;
  border-color: #9ca3af;
  transform: translateY(-1px);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Efecto hover específico para botón eliminar */
.action-btn-compact.delete:hover {
  background: #ef4444;
  color: white;
  border-color: #ef4444;
  transform: translateY(-1px);
  box-shadow: 0 2px 4px rgba(239, 68, 68, 0.3);
}
```

#### **Estilos de Estado Activo/Inactivo:**
```css
/* Status dots para estados activo/inactivo */
.status-dot {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.85rem;
  font-weight: 500;
}

.status-dot.active {
  color: #16a34a !important;
}

.status-dot.inactive {
  color: var(--error-color);
}
```

#### **Uso en JSX para Tablas:**
```jsx
<table className="tabla-modulo">
    <thead>
        <tr>
            <th>Nombre</th>                    {/* Izquierda por defecto */}
            <th>Descripción</th>               {/* Izquierda por defecto */}
            <th data-type="numeric">Precio</th> {/* Derecha - numérica */}
            <th data-type="numeric">Stock</th>  {/* Derecha - numérica */}
            <th data-type="actions">Acciones</th> {/* Centro - acciones */}
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Producto A</td>                 {/* Izquierda por defecto */}
            <td>Descripción del producto</td>   {/* Izquierda por defecto */}
            <td className="numeric-column">$25.99</td> {/* Derecha - numérica */}
            <td className="numeric-column">150</td>    {/* Derecha - numérica */}
            <td>                               {/* Centro - acciones */}
                <div className="actions-compact">
                    <button
                        className="action-btn-compact"
                        onClick={() => handleEdit(item.id)}
                        title="Editar"
                    >
                        <Edit size={14} />
                    </button>
                    <button
                        className="action-btn-compact delete"
                        onClick={() => handleDelete(item.id)}
                        title="Eliminar"
                    >
                        <Trash2 size={14} />
                    </button>
                </div>
            </td>
        </tr>
    </tbody>
</table>
```

#### **Ejemplo de Uso de Status Dots:**
```jsx
{/* En la tabla, columna de estado */}
<th>Estado</th>                     {/* Header de estado */}

{/* En el cuerpo de la tabla */}
<td>
    <span className={`status-dot ${item.activo ? 'active' : 'inactive'}`}>
        {item.activo ? '● Activo' : '● Inactivo'}
    </span>
</td>

{/* Variaciones para diferentes tipos */}
<span className="status-dot active">● Activa</span>     {/* Para categorías */}
<span className="status-dot inactive">● Inactivo</span>  {/* Para cualquier entidad */}
<span className="status-dot active">● Disponible</span>  {/* Para productos */}
<span className="status-dot inactive">● Agotado</span>   {/* Para stock */}
```

> **📝 Nota**: Los estilos globales ya están aplicados en `globals.css`, pero es recomendable incluir estos estilos específicos para mayor compatibilidad y consistencia visual en formularios y tablas.

### **PASO 5: Integración con Sistema de Permisos**

#### 5.1 Agregar al Sidebar.js
```javascript
// En allMenuItems array
{
    id: 'proveedores',
    label: 'Proveedores',
    icon: Truck,
    permission: 'proveedores_ver',
    checkPermission: () => hasPermission('proveedores_ver')
}
```

#### 5.2 Agregar al hook usePermissions
```javascript
const canManageProveedores = () => {
    return hasPermission('proveedores_ver');
};

// En el return
return {
    // ... otros métodos
    canManageProveedores
};
```

#### 5.3 Agregar ruta en el router principal
```javascript
// En el componente principal o router
case 'proveedores':
    return <ProveedoresPage />;
```

---

## 🔧 Convenciones y Patrones

### **Nombres de Archivos**
- **PascalCase** para componentes: `ProveedoresPage.js`
- **camelCase** para funciones: `getProveedores()`
- **snake_case** para base de datos: `proveedores_ver`

### **Estructura de Estados**
```javascript
const [items, setItems] = useState([]);
const [filteredItems, setFilteredItems] = useState([]);
const [searchQuery, setSearchQuery] = useState('');
const [showCreateModal, setShowCreateModal] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const [selectedItem, setSelectedItem] = useState(null);
```

### **Funciones Estándar**
- `load{Items}()` - Cargar datos de la API
- `filter{Items}()` - Filtrar por búsqueda
- `handleCreate{Item}()` - Crear nuevo registro
- `handleEdit{Item}()` - Editar registro
- `handleDelete{Item}()` - Eliminar registro

### **Estilos CSS**
- Usar variables CSS definidas en `:root`
- Seguir estructura de grid para formularios
- Mantener responsive design
- Usar clases BEM cuando sea apropiado

---

## 📋 Checklist de Verificación

### **Backend ✅**
- [ ] Tabla creada en base de datos
- [ ] Permiso agregado a tabla `permisos`
- [ ] Permiso asignado a roles apropiados
- [ ] Archivo PHP creado con endpoints CRUD
- [ ] Rutas agregadas en `api.php`
- [ ] Métodos agregados en `apiService.js`

### **Frontend ✅**
- [ ] Carpeta del módulo creada
- [ ] Componente principal creado (`{Modulo}Page.js`)
- [ ] Formulario creado (`{Modulo}Form.js`)
- [ ] Estilos CSS creados
- [ ] Integración con useApp hook
- [ ] Manejo de loading y errores

### **Integración ✅**
- [ ] Agregado al Sidebar con permisos
- [ ] Función agregada en usePermissions
- [ ] Ruta agregada en router
- [ ] Pruebas con diferentes roles

---

## 🧪 Testing

### **Pruebas Básicas**
1. **CRUD Completo**
   - Crear nuevo registro
   - Listar registros
   - Editar registro existente
   - Eliminar registro

2. **Filtrado y Búsqueda**
   - Buscar por diferentes campos
   - Filtros vacíos
   - Sin resultados

3. **Permisos**
   - Usuario con permiso ve el módulo
   - Usuario sin permiso no ve el módulo
   - API rechaza requests sin permisos

4. **Validaciones**
   - Campos requeridos
   - Formatos de email/teléfono
   - Longitudes máximas

### **Comandos de Testing**
```bash
# Testing de API
curl -X POST "http://localhost/api_pos/reportes/funciones_php/api.php" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer [token]" \
-d '{"tipo":"getProveedores"}'

# Testing de permisos
curl -X POST "http://localhost/api_pos/reportes/funciones_php/api.php" \
-H "Content-Type: application/json" \
-d '{"tipo":"login","usuario":"cajero","password":"password"}'
```

---

## 🔄 Mantenimiento

### **Actualizaciones Futuras**
- Seguir misma estructura para consistencia
- Actualizar documentación cuando agregues campos
- Mantener permisos simples (`_ver` únicamente)
- Usar siempre prepared statements en SQL
- Validar inputs tanto en frontend como backend

### **Buenas Prácticas**
- **Backup** antes de cambios en BD
- **Comentarios** en código complejo
- **Logs** para debugging
- **Validación** en ambos lados
- **Responsive design** siempre
- **Accesibilidad** básica

### **Estilos de Botones en Modales**
Para mantener consistencia visual en todos los formularios de modales:

#### **CSS Requerido para Botones 50/50:**
```css
/* Botones de formularios específicos para [módulo] */
.[modulo]-form .form-actions {
  display: flex !important;
  width: 100% !important;
  padding: 1.5rem 0 0 0;
  margin-top: 1rem;
  border-top: 1px solid var(--border-light);
  flex-direction: row !important;
  gap: 1rem;
}

/* Cada botón ocupa exactamente la mitad del ancho */
.[modulo]-form .form-actions button {
  flex: 1 !important;
  width: calc(50% - 0.5rem) !important;
  padding: 8px 16px !important;
  font-size: 14px !important;
  font-weight: 500 !important;
  height: 36px !important;
  border-radius: 6px !important;
}
```

#### **Estructura HTML de Botones:**
```jsx
<div className="form-actions">
    <Button
        type="button"
        variant="secondary"
        onClick={onCancel}
        disabled={isLoading}
    >
        Cancelar
    </Button>

    <Button
        type="submit"
        loading={isLoading}
        disabled={isLoading}
    >
        {isEdit ? 'Actualizar [Item]' : 'Crear [Item]'}
    </Button>
</div>
```

#### **Reglas de Distribución:**
- **Botón Cancelar**: Lado izquierdo (50% del ancho)
- **Botón Principal**: Lado derecho (50% del ancho)
- **Gap**: 1rem de separación entre botones
- **Responsive**: En mobile se apilan verticalmente

---

## 📞 Soporte

Para dudas sobre esta guía:

1. Revisar módulos existentes como referencia
2. Verificar convenciones en código actual
3. Consultar documentación del sistema de roles
4. Probar cada paso incrementalmente

---

*Guía creada para el sistema POS Abarrotes - Versión 1.0*
*Última actualización: 2025-09-22*