feat: facturación primer pago, fixes SAT/MP, autocompletado RFCs/conceptos

Backend:
- Notificación email al admin cuando llega primer pago aprobado (sin factura auto)
- Endpoints GET /pagos-sin-factura y POST /emitir-factura-pago para admin global
- Fix vinculación org Facturapi Horux 360 (69f23a5a242e0af47a41fa0d)
- Fix webhook MP: validación defensiva de x-signature header
- Fix autocompleto RFCs: eliminado filtro por contribuyenteId
- Fix autocompleto conceptos: eliminado filtro por contribuyenteId
- SAT fixes: anti-bot CSF scraper, request reuse, date range fix, stale job thresholds
- SAT sync request reuse across jobs para evitar agotar cuota diaria
- Typo fix MP_ACCESS_TOKEN en .env
- Trial invitations system backend

Frontend:
- Nueva página /admin/facturas-pendientes con tabla y emisión manual
- Métrica 'Facturas pendientes' en /clientes (clickable)
- Navegación onboarding FIEL/CSD corregida
- Sidebar themes sincronizados
- Fix SAT portal migration scraper (NetIQ)
- Trial invitation acceptance pages
This commit is contained in:
Horux Dev
2026-05-09 21:56:42 +00:00
parent b00b677c54
commit 9f11a0ba39
70 changed files with 2801 additions and 609 deletions

View File

@@ -14,7 +14,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '
import Link from 'next/link';
import { cn } from '@horux/shared-ui';
import { isDespachoTenant } from '@horux/shared';
import type { Role } from '@horux/shared';
import type { Role, UserInvite } from '@horux/shared';
// ── Horux360 legacy roles ─────────────────────────────────────────────────────
const legacyRoleLabels: Record<string, { label: string; icon: React.ElementType; color: string }> = {
@@ -83,10 +83,10 @@ export default function UsuariosPage() {
const isAdmin = currentUser?.role === 'owner' || currentUser?.role === 'cfo';
const [showInvite, setShowInvite] = useState(false);
const [inviteForm, setInviteForm] = useState<{ email: string; nombre: string; role: Role; supervisorUserId?: string }>({
const [inviteForm, setInviteForm] = useState<{ email: string; nombre: string; role: UserInvite['role']; supervisorUserId?: string }>({
email: '',
nombre: '',
role: defaultInviteRole as Role,
role: defaultInviteRole as UserInvite['role'],
});
const [selectedRfcIds, setSelectedRfcIds] = useState<string[]>([]);
@@ -183,7 +183,7 @@ export default function UsuariosPage() {
);
}
setShowInvite(false);
setInviteForm({ email: '', nombre: '', role: defaultInviteRole as Role, supervisorUserId: undefined });
setInviteForm({ email: '', nombre: '', role: defaultInviteRole as UserInvite['role'], supervisorUserId: undefined });
setSelectedRfcIds([]);
} catch (error: any) {
alert(error.response?.data?.message || 'Error al invitar usuario');
@@ -211,11 +211,11 @@ export default function UsuariosPage() {
</div>
<div className="flex items-center gap-2">
{isAdmin && isDespacho && (
<Link href="/carteras">
<Button variant="outline" className="flex items-center gap-2">
<Button variant="outline" className="flex items-center gap-2" asChild>
<Link href="/carteras">
<FolderOpen className="h-4 w-4" /> Gestionar Carteras
</Button>
</Link>
</Link>
</Button>
)}
{isAdmin && (
<Button onClick={() => setShowInvite(true)}>
@@ -263,13 +263,13 @@ export default function UsuariosPage() {
<Label htmlFor="role">Rol</Label>
<Select
value={inviteForm.role}
onValueChange={(v) => { setInviteForm({ ...inviteForm, role: v as Role, supervisorUserId: undefined }); if (v !== 'cliente') setSelectedRfcIds([]); }}
onValueChange={(v) => { setInviteForm({ ...inviteForm, role: v as UserInvite['role'], supervisorUserId: undefined }); if (v !== 'cliente') setSelectedRfcIds([]); }}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{inviteRoles.map(r => (
{inviteRoles.map((r: { value: string; label: string; description?: string }) => (
<SelectItem key={r.value} value={r.value}>
<div className="flex flex-col">
<span>{r.label}</span>